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

    有关DLL的问题很多,很多人写DLL时经常出现调用程序无法找到相关的导出函数的问题,其实主要的原因是DLL在声明时出的问题。 
在这里主要有两个问题,一个是调用约定的问题,一个是函数名修饰的问题,而这两个问题又是相互影响的。 
首先看下一下四种声明方式:
    1 声明为:extern "C" int __declspec(dllexport)add(int x, int y); 
这种声明是强制用C语言方式进行修饰,且用C的默认约定,即__cdecl方式。这种方式编译产生的DLL中有一个导出函数:add,不加任何修饰。 

    2 声明为:extern "C" int __declspec(dllexport) __stdcall add(int x, int y); 
这种声明是强制用C语言方式进行修饰,且用stdcall约定,这种方式编译产生的DLL中有一个导出函数:_add@8,即前面有“_”,后面加了参数长。
 
    3 声明为:int __declspec(dllexport) __stdcall add(int x, int y); 
这种声明不强制用C语言方式进行修饰,但是用stdcall约定,这种方式编译产生的DLL中有一个导出函数:?add@@YGHHH@Z。这个名字很怪,后面的不好理解。 

    4 声明为: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; 
} 
方式1调用成功,另外三种方式全部出错 


实验二:隐式调用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; 
} 
方式1 调用成功。另外发现一个奇怪现象:在调用程序中声明函数时,将extern "C" int __declspec(dllimport) add(int x, int y); 
写作extern "C" int __declspec(dllexpor) add(int x, int y);同样成功,将__declspec(…)去掉也同样成功。换句话说,在调用DLL的程序中,导入是没有必要加的。 
方式2 调用成功。同样出现上面导入标识可以不加的现象。 
方式3 调用成功,同样也出现上面导入标识可以不加的现象。 
方式4 调用成功,同样也出现上面导入标识可以不加的现象。 

总结:对于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中声明方式相同。 

对于动态调用实验结果: 
方式1 成功。方式2 不成功,但是将函数指针改为typedef int(_stdcall *lpAddFun)(int, int);成功,即调用者要声明约定方式与DLL中声明的调用约定方式相同,否则报错。 
方式3 同方式2,同样要将函数指针改为typedef int(_stdcall *lpAddFun)(int, int);才成功完成调用。 
方式4 成功。 
总结:通过DEF文件来导出函数,调用者同样也要声明相同的调用约定,即_stdcall或是_cdecl必须要相同,其中_cdecl是C语言默认方式。 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值