VS2005,单解决方案,2个项目:DLLTEST,MAIN2。
【文件/新建/项目】-> Visual C++ -> Win32控制台应用程序,输入项目名称,在“应用程序设置”中,"应用程序类型"选择“DLL",“附加选项”:“空项目”。
vs2005自动生成一个解决方案,此时右击【解决方案管理器】中的解决方案,再添加一个WIN32控制台应用程序:MAIN2。
动态调用DLL,只需要DLL文件,(静态链接还必须要有.lib文件才能编译,并且要设置MAIN1依赖于DllTest),当编译生成可执行文件后,只需要DLL文件。)
静态链接DLL时,若没有.lib文件或没有设置MAIN1依赖于DllTest时,会有链接错误:
1>正在编译...
1>MAIN1.cpp
1>正在链接...
1>MAIN1.obj : error LNK2001: 无法解析的外部符号 "int __cdecl TheFunction(void)" (?TheFunction@@YAHXZ)
动态链接DLL可以不依赖于任何DLL项目,只需要DLL文件即可。
动态调用DLL,可以对DLL文件无法打开时做出额外响应(静态调用不行),
动态调用DLL,首先需要调用LoadLibrary(),获得DLL的句柄,然后再调用GetProcAddress()函数获得输出函数的地址。
在《简单的WIN32静态调用DLL多项目实现》中提到的在DLL中对于输出函数的声明:
_declspec ( dllexport ) BOOL TheFunction();
此时编译能够通过,程序也能够执行,但是GetProcAddress()函数却找不到输出函数的地址,也就是无法调用DLL中的函数。
对此,请看下面摘取《Windows核心编程(第五版)》P516中的一段话:
“读者还是会注意到MYLIBAPI符号包含了extern "C"修饰符。只有在编写C++代码的时候,才应该使用这个修饰符,在编写C代码的时候不应该使用该修饰符。C++编译器通常会对函数名和变量名进行改编(mangle),这在链接的时候会导致严重的问题。 ... 当连接器试图连接可执行文件的时候,会发现可执行文件引用了一个不存在的符号并报错。extern "C"用来告诉编译器不要对变量名或函数名进行改编,这样用C、C++或任何编程语言编写的可执行模块都可以访问该变量或函数。”
C++编译器通常会对函数名和变量名进行改编(mangle),这里的原因主要是针对C++所支持的函数重载。
此时在DLL项目文件中把声明改为:
extern "C" _declspec ( dllexport ) BOOL TheFunction();
程序编译通过且执行成功。
在动态调用DLL中添加extern "C"的原因是调用程序找不到DLL中的函数地址(因为在编译时进行了改编),改编成了什么?可以在相应的.lib文件中看到,用记事本打开.lib文件,会找到类似一下的代码:
__NULL_IMPORT_DESCRIPTOR DllTest_NULL_THUNK_DATA ?TheFunction@@YAHXZ __imp_?TheFunction@@YAHXZ
其中的?TheFunction@@YAHXZ既是改编后的真实输出函数TheFunction的名字。若不使用extern "C"修饰符,直接在GetProcAddress()函数中使用"?TheFunction@@YAHXZ"也可以。不过还是使用extern "C"方便一些。这也可以说明在静态调用DLL中,程序在连接.lib文件时自动匹配调用程序调用的DLL中的函数,所以可以不使用extern "C"在静态调用DLL的程序中。
对于静态调用DLL,在调用程序中可以不使用extern "C" _declspec(dllimport)修饰符,“但是,如果编译器能够提前知道我们所引用的符号是从一个DLL的.lib文件中导入的,那么它将能够产生略微高效的代码。有鉴于此,我建议读者在导入函数和数据符号的时候使用__declspec(dllimport)关键字。如果调用的是标准的Windows函数,那么Microsoft已经替我们准备好了。”(《Windows核心编程(第五版)》P520)
对于动态调用DLL,在调用程序中可以不使用extern "C" _declspec(dllimport)修饰符。“Because the program uses run-time dynamic linking, it is not necessary to link the module with an import library for the DLL.”(MSDN中在解释GetProcAddress()函数时的例子“Using Run-Time Dynamic Linking”)。
在调用程序中若不使用extern "C" _declspec(dllimport)修饰符(此时不清楚是哪里的函数了),用户定义了和DLL输出完全相同(返回值,函数名,参数及参数类型)的函数,此时编译时会出错:
1>正在编译...
1>MAIN1.cpp
1>./MAIN1.cpp(60) : error C2556: “DWORD TheFunction(void)”: 重载函数与“BOOL TheFunction(void)”只是在返回类型上不同
1> ./MAIN1.cpp(16) : 参见“TheFunction”的声明
1>./MAIN1.cpp(60) : error C2371: “TheFunction”: 重定义;不同的基类型
1> ./MAIN1.cpp(16) : 参见“TheFunction”的声明
1>./MAIN1.cpp(67) : error C3861: “TheFunction”: 找不到标识符
看来还是效仿《Windows核心编程(第五版)》P514的做法,将extern "C" _declspec(dllimport)修饰符添加到调用程序中去。