VS下动态库dll的显式调用(动态调用)

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







你可能感兴趣的:(C++)