VS下动态库dll的显式调用
动态库的加载分两种形式:分为静态加载和动态加载。静态加载时,对应的头文件、DLL,和LIB缺一不可,并且生产的EXE没有找到DLL文件就会导致“应用程序初始化失败”。动态加载只需要dll,通过LoadLibrary()函数进行加载,但该方式对生成的dll的规范有一定的要求否则容易出错。
Dll的动态调用常规代码如下:
//#include
#include
#include
#include
//#define _AFxDLL
typedef int (*DLLfun)(int, int);
int main(){
DLLfun fun1;
HINSTANCE hdll;
hdll=LoadLibrary("Image Enhance.dll"); //加载dll
printf("hdll:%p\n",hdll);//打印dll地址
if(hdll==NULL){
FreeLibrary(hdll);//释放dll
}
fun1 = (DLLfun)GetProcAddress(hdll, "sharpen");//获取函数地址
printf("fun1:%p\n",fun1);
if(fun1==NULL){
FreeLibrary(hdll);
}
int r;
r = fun1(4,5);//函数地址
return 0;
}
注:函数名与函数指针可以查看http://blog.csdn.net/liangyanghui/article/details/77944193)
从上面的程序可以看出,dll动态调用主要用到三个函数LoadLibrary,GetProcAddress以及FreeLibrary。
1. LoadLibrary是用来加载dll的,格式为
HINSTANCE hdll;
hdll=LoadLibrary("Image Enhance.dll");
调用成功则返回函数地址,否则返回0或NULL。
(1)当其调用出错时,用GetLastError()得到返回值一般为126。使用LoadLibrary()动态加载dll失败的原因一般分两种:
1)路径不对(程序与dll要放在同一文件夹)
2) dll本身错误(依赖其他dll)
解决方法:
a. 将DLL与exe放于同一目录
b. Loadlibrary()与LoadlibraryEx()
c. DLL本身依赖使用depends.exe/Dependency Walker(depends)查看该DLL依赖哪些DLL
注:depends.exe工具下载地址:http://download.csdn.net/download/liangyanghui/9977026
“depends.exe 使用说明”:http://blog.csdn.net/scythe666/article/details/47165533
2. GetProcAddress()是用来获取函数地址的,调用格式
fun1 =(DLLfun)GetProcAddress(hdll, "sharpen");
具体看上面程序或者百度。通常若dll生成规范,可以直接运行成功。失败则返回0或NULL。
(1)当其调用出错时,用GetLastError()得到返回值一般为127,表示函数地址获取失败。原因基本上是因为函数名(如“sharpen”)在dll中不存在。当函数名没有拼写出错的时候,则是因为dll在生成过程中把函数按不同标准进行修改,如改成了“?? 0sharpen @@QAE@XZ”(通过depends.exe工具可以查看具体函数名),这时候调用参数改成下面的形式就可以调用成功。
fun1 = (DLLfun)GetProcAddress(hdll, "?? 0sharpen @@QAE@XZ ");
这种方式调用起来很不方便,每次都要去查看dll,所以我们最好在生成dll的时候就让函数名变得规范,下文会对dll生成中的注意点进行介绍。
3. FreeLibrary()是用来释放加载dll时占用的空间的,由于Loadlibrary()为对dll的显式加载(又叫动态加载),这种方式不会在用完dll后自动清理dll所占用的空间,所以我们要手动清除dll所占用的空间。否则会导致内存泄漏。调用格式如下:
FreeLibrary(hdll);
以上三个函数的具体使用还可以参考:
“动态载入DLL所需要的三个函数详解(LoadLibrary,GetProcAddress,FreeLibrary)”
http://www.cnblogs.com/westsoft/p/5936092.html
4.规范化生成dll的注意点
(1)头文件中定义__declspec(dllexport) 时,要加上extern "C",从而规范dll的输出符合C标准,否则容易生成带@之类的字符串。extern"C"使得在C++中使用C编译方式成为可能。在“C++”下定义“C”函数,需要加extern“C”关键词。用extern "C"来指明该函数使用C编译方式。加上extern “C”后,输出函数的形式为"sharpen",符合预期标准。格式如下:(其中IMG_EXPORTS为自己定义输出宏)
#define IMG_EXPORTS extern "C" __declspec(dllexport)
//或者
extern "C" _declspec(dllimport) int calculateLineNum(CString filePath);
但是该过程需要注意一点避免出错,例如下面生成dll的头文件代码中,函数输出应该写在“AAA”出现的位置,而不是“BBB”位置,在需要输出到dll的函数前加上IMG_EXPORTS,如下:
#ifdef IMG_API_EXPORTS
#define IMG_EXPORTS extern "C" __declspec(dllexport)
AAAAAAAAAAAAAAAAA
IMG_EXPORTS int fun1(int a,int b);
#else
#define IMG_EXPORTS __declspec(dllimport)
#endif
BBBBBBBBBBBBBBBBBB
注:使用微软专用的_declspec (dllexport)
cpp文件在编译为OBJ文件时要对函数进行重新命名,C语言会把函数name重新命名为_name,而C++会重新命名为_name@@decoration,extern "C"表示用C语言的格式将函数重命名,要输出整个的类,对类使用_declspec(_dllexpot);要输出类的成员函数,则对该函数使用_declspec(_dllexport)。
注:extern C的作用详解参考博客:http://blog.csdn.net/jiqiren007/article/details/5933599
(2)不加extern "C"的情况下,输出函数名容易变形,静态调用dll完全没问题,但是显式调用dll容易出错。除了extern "C",我们还可以添加的标准约定,如_stdcall等。
1)__stdcall调用约定
在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。
2)__cdecl
调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。
3)__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。
它们均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。
注:关于dll导出函数名的方式还可以参考以下博客:
dll导出函数名的那些事:http://blog.csdn.net/qq_16209077/article/details/51989114