dll的def文件与__declspec(dllexport)导出函数方式比较

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)

在 32 位编译器版本中,可使用 __declspec(dllexport) 关键字从 DLL 导出数据、函数、类或类成员函数。__declspec(dllexport) 在link时会将导出指令添加到obj文件中,所以不须要使用 .def 文件。固然,即便用了__declspec(dllexport)依然可使用*.def文件,由于不一样编译器对于类的成员函数的name mangling规则不一样,能够定义.def文件经过序号调用。为每一个dll写def显得很繁杂,目前def使用已经比较少了,更多的是使用__declspec(dllexport)在 源代码中定义dll的输出函数。

 

若要输出类的全部成员:数据or函数,__declspec(dllexport)要放在类名左边声明:
class __declspec(dllexport) Class1{}
若是类没有数据成员,__declspec(dllexport)放在class关键字前声明就会被编译器忽略,就没有lib生成,以下:
__declspec(dllexport) class Class1{}

使用 __declspec(dllexport) 的优缺点(zz)
使用 __declspec(dllexport) 很是方便,由于没必要考虑维护 .def 文件和获取导出函数的修饰名。例如,若是您设计的 DLL 供本身控制的应用程序使用,则此方法很适用。若是经过新的导出函数从新生成 DLL,还必须从新生成应用程序,由于若是使用不一样版本的编译器进行从新编译,则导出的 C++ 函数的修饰名可能会发生变化。
2、def文件
其实def文件的功能至关于extern “C” __declspec(dllexport)
def文件中PRIVTATE的做用
The optional keyword PRIVATE prevents entryname from being placed in the import library generated by LINK. It has no
effect on the export in the image also generated by LINK.用了PRIVATE,生成的lib里没有对应方法或者数据的entryname所以不能被客户隐式调用。

 

使用 .DEF 文件的优缺点(zz)
在 .def 文件中导出函数使您得以控制导出序号。当将附加的导出函数添加到 DLL 时,能够给它们分配更高的序号值(高于任何其余导出函数)。当您进行此操做时,使用隐式连接的应用程序没必要与包含新函数的新导入库从新连接。这很是重要,例如,在设计将由许多应用程序使用的第三方DLL 时。能够经过添加附加功能不断地加强 DLL,同时确保现有应用程序继续正常使用新的 DLL。MFC DLL 是使用 .def 文件生成的。

使用 .def 文件的另外一个优势是:可使用 NONAME 属性导出函数,该属性仅将序号放到 DLL 的导出表中。对具备大量导出函数的 DLL,使用NONAME 属性能够减少 DLL 文件的大小。有关编写模块定义语句的信息,请参见模块定义语句的规则。有关序号导出的更多信息,请参见按序号而不是按名称从 DLL 导出函数。
使用 .def 文件的主要缺点是:在 C++ 文件中导出函数时,必须将修饰名放到 .def 文件中,或者经过使用外部“C”用标准 C 连接定义导出函数,以免编译器进行名称修饰。若是须要将修饰名放到 .def 文件中,则能够经过使用 DUMPBIN 工具或 /MAP 连接器选项来获取修饰名。请注意,编译器产生的修饰名是编译器特定的。若是将 Visual C++ 编译器产生的修饰名放到 .def 文件中,则连接到 DLL 的应用程序必须也是用相同版本的 Visual C++ 生成的,这样调用应用程序中的修饰名才能与 DLL 的 .def 文件中的导出名相匹配。 
相关文章
相关标签/搜索