导航

dll导出函数的两种方式的比较

Posted on 2018-09-04 16:26  talenth  阅读(9230)  评论(0编辑  收藏  举报

最初的网页链接已经挂了, 在此贴一个中间的转载链接

https://blog.csdn.net/zhazhiqiang/article/details/51577523

一 概要

  vs中导出 dll的方法有两种: 一种是使用__declspec(dllexport), 另一种就是使用.def文件方式,

  这两种方式的最主要区别是在导出函数的名字上, 其次还有一些操作的灵活性上以及功能的强弱

二 一个具体的例子

1 __declspec(dllexport) 方式
首先对C和C++编译(extern "C")与调用约定(__cdecl、__stdcall、__fastcall)进行组合测试:
<1> 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”标识结束。 

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

<3> 备注
__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  };

2 def文件导出方式(项目中添加一个def后缀的文件, 然后在项目属性->连接器->所有选项->模块定义文件)

具体到测试实例,我们的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;
}


三 总结:

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