VS2019 VC++ 静态库的开发与使用

前言

这篇文章应该写在这个系列里面的:VS2017的C++开发心得(九)DLL动态链接——多项目开发

但当时没写,只写了动态链接的相关内容,是因为我认为静态链接相比较动态链接会简单得多,所以干脆就略过了。既然有人提问了,那我就简单的开发流程和主要遇到的问题来讲讲静态库的开发。

首先总体来看,静态库和动态库的区别在于:

1. 静态库只出现在项目的编译生成的链接期,而动态库则是程序运行时加载使用的;

2. 静态库只有一个.lib文件,而动态库除了一个.dll文件以外一般还有个对应的.lib文件;

3. 静态库最后会内嵌到输出的.exe或.dll中,而动态库是外挂使用;

4. 静态库是没有链接器的,因为静态库不需要链接,所以你即便有些符号没有找到实现也不会报错。

VS2019创建静态库

1. 首先创建一个控制台程序StaticLibTest,过程省略,我之前的文章有很多介绍。

2. 添加静态库项目,StaticLib1:

到这里两个项目创建起来了,但是两个项目间是毫无联系的,下面用链接器让StaticLibTest使用StaticLib1生成的静态库。

1. 在StaticLibTest中的链接器中添加StaticLib1的生成路径:

这个路径实际上就是整个解决方案的生成路径,.exe也生成在这里。我这里就没有做lib和bin分开的项目管理了。

2. 在输入中加入StaticLib1.lib

3. 在StaticLibTest.cpp中加入fnStaticLib1的调用,这个函数是VS自动生成的,在StaticLib1.cpp下面,根据你的项目名不同,这个函数名也不一样:

4. 添加项目依赖关系:

这里是为两个项目在生成的时候绑定了依赖关系,StaticLibTest在生成前一定会检查StaticLib1是不是最新的生成。很多人出现的问题大多数都是由于静态库没有生成更新导致的,这是很愚蠢的问题。

5. 直接调试运行(先生成StaticLib1.lib再生成StaticLibTest.exe):

这样就链接好了,非常简单。

静态库的理解

为了大家更好的理解静态库,我这里做个等效测试。

1. 在控制台项目中添加一个a.cpp文件,内容如下:

//a.cpp//
const char* filename = "a.cpp";
//const char* filename = __FILE__;
int codeline = 2;
//int codeline = __LINE__;

const char* GetFunctionName()
{
  return "GetFunctionName"; 
  //return __FUNCTION__;
}

注释掉的代码后面再解释,这是编译器为我们提供的方便。

2. 在StaticLibTest.cpp中添加这三个变量函数的调试打印:

StaticLibTest.cpp/
#include <iostream>
void fnStaticLib1();
extern const char* filename;
//const int codeline = 2;
extern int codeline;

const char* GetFunctionName();
int main()
{
    std::cout << "Hello World!\n";
    fnStaticLib1();
    std::cout << filename << std::endl;
    std::cout << codeline << std::endl;
    std::cout << GetFunctionName() << std::endl;
}

注意这里我没有用const int,因为它不参与链接,只在当前文件可见。为什么const char*可以?读者可以思考下。

3. 打印结果是:

4. 我把a.cpp直接添加到StaticLib1中,而在StaticLibTest中移出掉。即这个文件编译在静态库中,不在控制台项目中编译,如下:

5. 拖过来以后会有这个错误提示,“PCH 警告: 找不到合适的头停止点位置。未生成 IntelliSense PCH 文件”:

这个是由于VS默认为静态库项目打开了PCH预编译头,即pch.h和pch.cpp。这里不具体介绍预编译头用法,简单说就是会提高编译效率。但是,预编译头也要求每个项目下的.cpp都需要include"pch.h"。如果没有这么做,会有以下的编译错误:

fatal error C1010: 在查找预编译头时遇到意外的文件结尾。是否忘记了向源中添加“#include "pch.h"”?

而且,这里还有恶心的一点,必须是#include "pch.h".  #include "../pch.h"这个也是不行的。这个强制要求我感觉应该算VS的bug,暂时没能理解为啥VS这么做。

所以a.cpp的文件要加上include "pch.h",然后在StaticLib1的项目属性 C++/C的附加包含路径下加入:"../StaticLib1":

或者你也可以简单的去除pch,以便测试:

6. 再次调试运行,结果完全一样:

以上的操作是为了让你理解,静态链接库的内容就像是把我这个项目内的某些文件到外面先编译好,实际上在使用的时候,就像是在使用同一个项目下的cpp文件一样。

这也是为什么我会说静态库比动态库简单得多。

静态库的问题

我这里静态库的代码都看得到,所以会觉得用起来很简单。一般情况下,都是一些头文件加上一个静态库的使用情景。

头文件决定了静态库的链接符号名,链接器会去.lib里找这些符号。但是,要是你自己脱离对方提供的头文件来调用,或者头文件写的有问题,那往往容易出现无法解析的外部符号的链接错误。

比如,我去掉const char*的const修饰:

这时就会有以下的链接错误:

StaticLibTest.obj : error LNK2001: 无法解析的外部符号 "char * filename" (?filename@@3PEADEA)

遇到这种链接错误就根据这个符号名去对应.lib的符号,看看是否是完全一样的。

比如这里,StaticLibTest是调用端。调用端使用的符号是?filename@@3PEADEA。那需要到被调用端的StaticLib1.lib里去查找下,是否有这样的符号。如果没有,那么正确的符号名是什么?

查找符号名也是使用DUMPBIN这个VS工具。

使用DUMPBIN查看符号表

这里再说一次DUMPBIN的使用方式,因为之前有人打开控制台问我为啥没有。这个工具是在VS的环境下,不在系统环境,所以需要通过VS的命令行工具来打开。有两种方式:

1. 在VS的菜单中打开:

2. 在开始菜单,VS2019的文件夹中打开:

打开以后cd到.lib的生成目录:

1. 先点击地址栏,复制文件夹路径:

2. 然后就是 cd 切换文件夹的操作:

注意如果不在一个硬盘是不能用cd的. cd的路径是同一个硬盘下才行。如果你的项目文件夹在D盘下,那么先输入D:,切换到D盘,再进行cd操作。

3. 输入DUMPBIN /LINKERMEMBER StaticLib1.lib

这八个public symbols,就是你可以在你的调用项目里通过链接符号而使用的变量或者函数。这里看到被调用端的正确符号是:

?filename@@3PEBDEB,而调用端去掉const以后的符号名是?filename@@3PEADEA,是不一样的。这时候你需要,仔细核对了。你也可以自己根据符号名推理出是const char*,怎么推理需要看下C++ name mangling的相关知识,比如:https://en.wikipedia.org/wiki/Name_mangling 

VS2019 dumpbin查看DLL的导出函数

最后补充两个DUMPBIN的小窍门。如果是比较大的lib的话,有可能会把控制台溢出,导致一部分内容被截断,可以通过以下两个方法来实现查找符号:

1. 写入到一个txt文件来查找:

DUMPBIN /LINKERMEMBER StaticLib1.lib > staticlib1.txt

> *.txt这就是把左边的控制台输出导出到右边的这个.txt文件中,非常实用的命令。

2. 使用findstr来直接查找对应的符号名

DUMPBIN /LINKERMEMBER StaticLib1.lib | findstr filename

| findstr filename 这个就是对左边的控制台输出进行findstr的筛选,对应Linux下的 |grep. 

调试技巧文件名,函数名,行号

看看上面的一段代码:

const char* filename = __FILE__;
int codeline = __LINE__;
const char* GetFunctionName()
{
  return __FUNCTION__;
}

__FILE__,__LINE__,__FUNCTION__这三个保留字是编译器保留的,可以在编译的时候进行替换,分别对应文件名和行号,以及函数名。

所以一般会用它们来帮助我们调试输出的时候带上当前的代码位置,比如:

这时候控制台的输出为:

可以思考下,我为啥不用LogLine的函数,而用一个宏定义函数。

这三个保留字还可以和宏定义中的#和##组合出非常有用的调试打印方法。

猜你喜欢

转载自blog.csdn.net/luoyu510183/article/details/106026541