原文: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)
若要输出类的所有成员:数据or函数,__declspec(dllexport)要放在类名左边声明:
class __declspec(dllexport) Class1{}
如果类没有数据成员,__declspec(dllexport)放在class关键字前声明就会被编译器忽略,就没有lib生成,如下:
__declspec(dllexport) class Class1{}
使用 __declspec(dllexport) 非常方便,因为不必考虑维护 .def 文件和获取导出函数的修饰名。例如,如果您设计的 DLL 供自己控制的应用程序使用,则此方法很适用。如果通过新的导出函数重新生成 DLL,还必须重新生成应用程序,因为如果使用不同版本的编译器进行重新编译,则导出的 C++ 函数的修饰名可能会发生变化。
二、def文件
def文件中PRIVTATE的作用
使用 .DEF 文件的优缺点(zz)
在 .def 文件中导出函数使您得以控制导出序号。当将附加的导出函数添加到 DLL 时,可以给它们分配更高的序号值(高于任何其他导出函数)。当您进行此操作时,使用隐式链接的应用程序不必与包含新函数的新导入库重新链接。这非常重要,例如,在设计将由许多应用程序使用的第三方DLL 时。可以通过添加附加功能不断地增强 DLL,同时确保现有应用程序继续正常使用新的 DLL。MFC DLL 是使用 .def 文件生成的。