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

原文:http://hi.baidu.com/qinpc/blog/item/a1e7d262e9f2d5d8e7113a96.html

 

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

【__declspec(dllexport) 方式】
首先对C和C++编译(extern "C")与调用约定(__cdecl、__stdcall、__fastcall)进行组合测试:
【C++编译】
__declspec(dllexport) int add(int, int);



__declspec(dllexport) int __cdecl add(int, int);



__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);



extern "C" __declspec(dllexport) int __stdcall add(int, int);



extern "C" __declspec(dllexport) int __fastcall add(int, int);

如果创建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)方式优缺点对比:

一、__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++ 函数的修饰名可能会发生变化。

二、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 文件中的导出名相匹配。 

你可能感兴趣的:(dll的def文件与__declspec(dllexport)导出函数方式比较)