总结一下DLL声明的一些问题

有关DLL的问题现在资料很多,但是很多人写DLL时经常出现调用程序无法找到相关的导出函数的问题,这里主要的原因是DLL在声明时出的问题。
在这里主要有两个问题,一个是调用约定的问题,一个是函数名修饰的问题,而这两个问题又是相互影响的。
一:声明为:extern "C" int __declspec(dllexport)add(int x, int y);
这种声明是强制用C语言方式进行修饰,且用C的默认约定,即__cdecl方式。这种方式编译产生的DLL中有一个导出函数:add,不加任何修饰。
二:声明为:extern "C" int __declspec(dllexport) __stdcall add(int x, int y);
这种声明是强制用C语言方式进行修饰,且用stdcall约定,这种方式编译产生的DLL中有一个导出函数:_add@8,即前面有“_”,后面加了参数长。
三:声明为:int __declspec(dllexport) __stdcall add(int x, int y);
这种声明不强制用C语言方式进行修饰,但是用stdcall约定,这种方式编译产生的DLL中有一个导出函数:?add@@YGHHH@Z。这个名字很怪,后面的不好理解。
四:声明为:int __declspec(dllexport) __cdecl add(int x, int y);
这种声明是不强制用C语言修饰,且用cdecl约定,这种方式编译产生的DLL中有一个导出函数:?add@@YAHHH@Z,注意看,和第三种方有一点不同。

实验一:显式调用方式调用DLL中的add函数。
#include <stdio.h>
#include <windows.h>
typedef  int(_stdcall *lpAddFun)(int, int); //宏定义函数指针类型
int main(int argc, char *argv[])
{
HINSTANCE hDll; //DLL句柄
lpAddFun addFun; //函数指针
hDll = LoadLibrary("1.dll");
if (hDll != NULL)
{
addFun = (lpAddFun)GetProcAddress(hDll, "add");
if (addFun != NULL)
{
int result = addFun(2, 3);
printf("%d", result);
}
else
printf("No Function");
}
else
printf("NO DLL");
FreeLibrary(hDll);
return 0;
}
方式一:调用成功。另外三种方式全部出错
实验二:隐式调用DLL中的add函数
#include <stdio.h>
#include <windows.h>
#pragma comment(lib,"1.lib")
extern "C" int __declspec(dllimport) add(int x, int y);//声明方式随着DLL中的声明方式改变
int main(int argc, char *argv[])
{
int result = add(2, 3);
printf("%d", result);
return 0;
}
方式一:调用成功。另外发现一个奇怪现象:在调用程序中
声明函数时extern "C" int __declspec(dllimport) add(int x, int y);
写作:extern "C" int __declspec(dllecprot) add(int x, int y);同样成功,将__declspec(…)去掉也同样成功。换句话说,在调用DLL的程序中,导入是没有必要加的。
方式二:调用成功。同样出现上面导入标识可以不加的现象。
方式三:调用成功,同样也出现上面导入标识可以不加的现象。
方式四:调用成功,同样也出现上面导入标识可以不加的现象。
总结:对于DLL导出函数声明的四种写法,在动态调用时,
声明成这样:extern "C" int __declspec(dllimport) add(int x, int y);是最好的,其它声明方式调用都没有成功。但是众所周知,windows默认的调用约定是stdcall方式,如果想别的语言能用DLL的话,最好是将调用约定写成stdcall方式,但是这种方式又不能动态调用。
在隐式调用时,四种声明方式都是可以的,只要调用者的声明方式和DLL声明时的方式一致即可。另外,在调用程序中对于导入的声明是可以去掉的,大量书籍中关于导入、导出的问题都是利用宏来处理的,如:在头文件中写作:
#ifdef DLL_FILE
extern "C" int __declspec(dllexport) add(int x, int y);
#else
extern "C" int __declspec(dlleximport) add(int x, int y);
这样这个头文件既可以用在DLL工程中,又可以用在调用程序中,但是经过实验发现,这个根本就没有必要,在调用者程序中不管是写作__declspce(dllexport)还是写作__declspec(dllimport)或者不写都能成功调用。
关于DEF文件
在DLL工程中引用DEF文件,内容如下:
LIBRARY 1
EXPORTS
add @ 1
通过depends查看导出函数全是add,但是隐式方式调用时,还是要求调用者的声明方式和DLL中声明方式相同。
对于动态调用实验结果:
方式一:成功。方式二:不成功,但是将函数指针改为typedef int(_stdcall *lpAddFun)(int, int);成功,即调用者要声明约定方式与DLL中声明的调用约定方式相同,否则报错。
方式三:同方式二,同样要将函数指针改为typedef int(_stdcall *lpAddFun)(int, int);才成功完成调用。
方式四:成功。
总结:通过DEF文件来导出函数,调用者同样也要声明相同的调用约定,即_stdcall或是_cdecl必须要相同,其中_cdecl是C语言默认方式。

你可能感兴趣的:(总结一下DLL声明的一些问题)