dll的def文件与__declspec(dllexport)导出函数方式比较html
【__declspec(dllexport) 方式】
首先对C和C++编译(extern "C")与调用约定(__cdecl、__stdcall、__fastcall)进行组合测试:
【C++编译】
__declspec(dllexport) int add(int, int);windows
__declspec(dllexport) int __cdecl add(int, int);app
__declspec(dllexport) int __stdcall add(int, int);函数
__declspec(dllexport) int __fastcall add(int, int);工具
对于C++编译器的函数名修饰规则:无论__cdecl, __fastcall仍是__stdcall调用方式,函数修饰名都是以"?"开始,后面是函数在名字,再后面是函数返回类型和参数类型按照代号拼出的参数表。对于__stdcall方式,参数表的开始标示是"@@YG”,对于__cdecl方式则是"@@YA”,对于__fastcall方式则是"@@YI”.
参数表后以"@Z”标示整个名字的结束,若是该函数无参数,则以"Z”标识结束。
更详细的dll基础知识请参考:
http://hi.baidu.com/luosiyong/blog/item/92bbdcfe860435375c600812.html
更深刻的C++函数名修饰编码规则请参考:
http://hi.baidu.com/wanggang2008/blog/item/cd43e60756021bc07a89470a.html测试
【C编译】
extern "C" __declspec(dllexport) int add(int, int);编码
extern "C" __declspec(dllexport) int __cdecl add(int, int);spa
extern "C" __declspec(dllexport) int __stdcall add(int, int);设计
extern "C" __declspec(dllexport) int __fastcall add(int, int);htm
若是建立dll和可执行文件都是使用的VC,那用__declspec(dllexport)足够解决问题。可是若是建立出来的dll要和别的厂商的工具包构建的可执行文件连接,那就有一些额外的问题来了。
在开发dll的时候,通常不让编译器改变函数名,因此一般是以C方式编译,即加入了extern "C"说明。可是看上面的组合测试结果,__stdcall和__fastcall编译出来的函数名仍是和原始的函数名不一样。就拿__stdcall来讲,它以C编译导出的时候,会在函数前面加入下划线,并在函数后面加入@和参数总大小的字节数。
或许如今你就想,__cdecl不就是没有改变名称的方式吗,而且默认也是__cdecl调用约定,的确,咱们本身写的dll几乎均可以使用__cdecl方式。可是,Windows中最广泛的调用方式都是__stdcall(好比CALLBACK、 WINAPI),一些经常使用的定义以下:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
如今假如用VC的__stdcall方式开发的一个dll,里面包含了上面那样的add函数,若是要在VB中使用,VB的程序员须要以下声明:
Declare Function add Lib "win.dll" Alias"_add@8"() As Integer
注意他须要写的名称是 "_add@8",而不是简单的"add",不然就会出现函数未定义的连接错误。
【备注】
__declspec(dllexport)的位置:
To export functions, the __declspec(dllexport) keyword must appear to the left of the calling-convention keyword, if a keyword is specified.
For example:
__declspec(dllexport) void __cdecl Function1(void);
To export all of the public data members and member functions in a class, the keyword must appear to the left of the class name as follows:
class __declspec(dllexport) CExampleExport : public CObject
{ class definition };
Reference:
1.
http://msdn.microsoft.com/en-us/library/d91k01sh(VS.80).aspx
2.
http://msdn.microsoft.com/en-us/library/a90k134d(VS.80).aspx
【def文件导出方式】
首先了解一下 使用def文件从dll导出:
http://msdn.microsoft.com/zh-cn/library/d91k01sh(v=VS.80).aspx
具体到测试实例,咱们的def文件内容以下:
LIBRARY "win"
EXPORTS
add @1
其中LIBRARY指定dll的模块名称,即dll名字,EXPORTS后的每一行指定一个导出函数名字,这个名字和头文件中的声明一致,后面能够跟@序号指定该函数的序号(这个是可选的,后面按序号导入函数的时候再详细说)。
而后再测试一下__stdcall和__fastcall是否会对导出函数更名,测试结果以下,均未更名:
extern "C" int __stdcall add();
extern "C" int __fastcall add();
另一种方案是在代码中给连接器指定导出函数名字:
extern "C" __declspec(dllexport) int __fastcall add(int a, int b);
#pragma comment(linker, "/export:add=@add@8")
这里告诉连接器,导出一个函数名为add的函数,函数入口点和@add@8相同
这样,咱们既可使用add,也可使用@add@8了。
__stdcall方式和这相似,为add=_add@8。
【按序号而不是按名称从dll导出函数】
http://msdn.microsoft.com/zh-cn/library/e7tsx612%28VS.80%29.aspx
def文件定义以下:
LIBRARY "win"
EXPORTS
add @1 NONAME
函数名称和Hint都不见了。
这样也能够用来隐藏dll中一些重要函数。
隐藏了函数名称,在应用程序中使用序号来导入函数:
#include <windows.h>
#include <stdio.h>
int main()
{
typedef int (* AddFunc)(int, int);
HMODULE hModule = LoadLibrary("dll.dll");
AddFunc add = (AddFunc)GetProcAddress(hModule, MAKEINTRESOURCE(1)); //注意这里序号的指定方式
printf("%d\n", add(1, 2));
return 0;
}
【备注】
def文件和__declspec(dllexport)方式优缺点对比:
1、__declspec(dllexport)
若要输出类的全部成员:数据or函数,__declspec(dllexport)要放在类名左边声明:
class __declspec(dllexport) Class1{}
若是类没有数据成员,__declspec(dllexport)放在class关键字前声明就会被编译器忽略,就没有lib生成,以下:
__declspec(dllexport) class Class1{}
使用 .DEF 文件的优缺点(zz)
在 .def 文件中导出函数使您得以控制导出序号。当将附加的导出函数添加到 DLL 时,能够给它们分配更高的序号值(高于任何其余导出函数)。当您进行此操做时,使用隐式连接的应用程序没必要与包含新函数的新导入库从新连接。这很是重要,例如,在设计将由许多应用程序使用的第三方DLL 时。能够经过添加附加功能不断地加强 DLL,同时确保现有应用程序继续正常使用新的 DLL。MFC DLL 是使用 .def 文件生成的。