现在的位置: 首页 > 综合 > 正文

DLL编程总结

2018年05月11日 ⁄ 综合 ⁄ 共 4234字 ⁄ 字号 评论关闭

1)创建DLL

创建DLL的时候,头文件里在输出变量,函数,类之前使用__declspec(dllexport)修饰符号。当VC编译器看到变量,函数或者类之前的这个修改符的时候,它就将某些附加信息嵌入产生的.obj文件中。当链接DLL的所有.obj文件时,链接程序要查找关于输出变量,函数或类的信息,并自动生成一个.lib文件,它包含一个DLL输出的符号列表。如果要链接引用该DLL的输出符号的任何可执行模块,该.lib文件是必不可少的。(DLL工程需要定义MYMATHLIB_EXPORTS宏)

MyMathLib.h

#ifdef MYMATHLIB_EXPORTS
#define MYMATHLIB_API __declspec(dllexport)
#else
#define MYMATHLIB_API __declspec(dllimport)
#endif

int MYMATHLIB_API Add(int lhs, int rhs);

MyMathLib.cpp

#include "MyMathLib.h"

int MYMATHLIB_API Add(int lhs, int rhs)
{
	return lhs + rhs;
}

除了创建.lib文件外,链接程序还要将一个输出符号表嵌入产生的DLL文件。可以使用dumpbin程序察看dll的输出节。

>dumpbin -exports MyMathLib.dll
Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation. All rights reserved.


Dump of file MyMathLib.dll

File Type: DLL

Section contains the following exports for MyMathLib.dll

00000000 characteristics
4E258F37 time date stamp Tue Jul 19 22:05:43 2011
0.00 version
1 ordinal base
1 number of functions
1 number of names

ordinal hint RVA name

1 0 0001106E ?Add@@YAHHH@Z = @ILT+105(?Add@@YAHHH@Z)

Summary

1000 .data
1000 .idata
2000 .rdata
1000 .reloc
1000 .rsrc
4000 .text
10000 .textbss

2)创建可执行模块

可执行模块引用DLL的头文件,使用__declspec(dllimport)符号进行定义。当编器看到修改变量,函数或类的__declspec(dllimport)时,它知道这个符号是从某个DLL模
块输入的。创建产生的可执行模块的链接程序必须确定哪些DLL包含代码引用的所有输入符号。因此你必须将DLL的.lib文件传递给链接程序。

实际上,当输入一个符号时,不必使用关键字__declspec(dllimport),只要使用标准的C关键字extern即可。但是如果编译器预先知道你引用的符号将从一个DLL的.lib文件输入,那么编译器就能够生成运行效率稍高的代码。因此建议你尽量将__declspec(dllimport)关键字用于输入函数和符号。

MyEXE.cpp

#include <iostream>
#include "MyMathLib.h"

#pragma comment(lib, "MyMathLib.lib") 

int _tmain(int argc, _TCHAR* argv[])
{
	int ret = Add(1, 2);
	std::cout << ret << std::endl;
	return 0;
}

当链接程序进行输入符号的转换时,它就将一个称为输入节的特殊的节嵌入产生的可执行模块。输入节列出了该模块需要的DLL模块以及由每个DLL模块引用的符号。
可以使用dumpbin程序察看模块的输入节。

Dump of file MyEXE.exe

File Type: EXECUTABLE IMAGE

  Section contains the following imports:

    MSVCP100D.dll
                ......
    MyMathLib.dll
                4183BC Import Address Table
                4181F8 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                    0 ?Add@@YAHHH@Z

    MSVCR100D.dll
                ......
    KERNEL32.dll
                ......

3)调用约定

__stdcall
Pascal方式清理C方式压栈,通常用于Win32 Api中,.参数从右向左压入堆栈,.函数被调用者自己在退出时清空堆栈。

__cdecl
C调用约定The C default calling convention)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数vararg的函数(如printf)只能使用该调用约定)。它是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用__stdcall函数的大。

C编译时函数名修饰约定规则:
__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。
__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。

C++编译时函数名修饰约定规则:
__stdcall调用约定:
  1、以“?”标识函数名的开始,后跟函数名;
  2、函数名后面以“@@YG”标识参数表的开始,后跟参数表;
  3、参数表以代号表示:
  X--void ,D--char,E--unsigned char,F--short,H--int,I--unsigned int,J--long,K--unsigned long,M--float,N--double,_N--bool,....

  PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;
  4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;
  5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。
  其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,
__cdecl调用约定:规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。

例如int Add(int lhs, int rhs) =〉
?Add@@YAHHH@Z

可以使用extern "C" 防止VC编译器修改函数名字。

#ifdef  __cplusplus
extern "C" {
#endif
int MYMATHLIB_API Add(int lhs, int rhs);
#ifdef  __cplusplus
}
#endif

重新生成MyMathLib.dll后,使用dumpbin察看输出节:

   ordinal hint RVA      name

         1    0 000110AF Add = @ILT+170(_Add)

 

如果加上__stdcall修饰符,

#ifdef MYMATHLIB_EXPORTS
#define MYMATHLIB_API  __declspec(dllexport) __stdcall
#else
#define MYMATHLIB_API  __declspec(dllimport) __stdcall
#endif


#ifdef  __cplusplus
extern "C" {
#endif
int MYMATHLIB_API Add(int lhs, int rhs);
#ifdef  __cplusplus
}
#endif

重新生成MyMathLib.dll后,使用dumpbin察看输出节:

    ordinal hint RVA      name

          1    0 00011087 _Add@8 = @ILT+130(_Add@8)

 

一般使用extern "C" 实现C++与C及其它语言的混合编程。
如果在C++中引用C语言中的函数和变量,在包含C语言头文件时,或者C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。

在C中引用C++语言中的函数和变量时,比如回调函数,C++的头文件需添加extern "C"。

4).def文件

模块定义 (.def) 文件是包含一个或多个描述 DLL 各种属性的 Module 语句的文本文件。如果不使用 __declspec(dllexport) 关键字导出 DLL 的函数,则 DLL 需要 .def 文件。
.def 文件必须至少包含下列模块定义语句:
文件中的第一个语句必须是 LIBRARY 语句。此语句将 .def 文件标识为属于 DLL。LIBRARY 语句的后面是 DLL 的名称。链接器将此名称放到 DLL 的导入库中。
EXPORTS 语句列出名称,可能的话还会列出 DLL 导出函数的序号值。通过在函数名的后面加上 @ 符和一个数字,给函数分配序号值。

MyMathLib.h

int Add(int lhs, int rhs);

MyMathLib.cpp

#include "MyMathLib.h"

int Add(int lhs, int rhs)
{
	return lhs + rhs;
}

MyMathLib.def

LIBRARY MyMathLib
EXPORTS
	Add		@100

重新生成MyMathLib.dll后,使用dumpbin察看输出节:

   ordinal hint RVA      name

       100    0 0001106E Add = @ILT+105(?Add@@YAHHH@Z)

当生成 DLL 时,链接器使用 .def 文件创建导出 (.exp) 文件和导入库 (.lib) 文件。然后,链接器使用导出文件生成 DLL 文件。隐式链接到 DLL 的可执行文件在生成时链接到导入库。可以使用dumpbin程序察看可执行文件的输入节。可以看到现在是按序号进行连接了。

    MyMathLib.dll
                4183BC Import Address Table
                4181F8 Import Name Table
                     0 time date stamp
                     0 Index of first forwarder reference

                      Ordinal   100

 

抱歉!评论已关闭.