在创建一个DLL的时候,事实上在创建一组可供一个执行模块(或者其他DLL)调用的模块。一个dll可以到处变量,函数或者C++类,来供其他模块的使用。
另外,在创建DLL的时候,应该首先创建一个头文件来包含想要导出的变量和函数。这个头文件还必须定义导出的函数或者变量所用到的任何符号和数据结构。DLL的所有源文件,都应该包括这个头文件。另外,我们必须分发这个头文件。这样任何可能需要导入这些函数或者变量的源文件都可以包含该头文件。让DLL的构建者和可执行模块的构建者使用同一个头文件可以使维护更加容易。
下面用例子说明:
/********************
MyLib.h
********************/
#ifdef MYLIBAPI
//MYLIBAPI should be defined in all of the DLL's source.
//code modules before this header is included.
//All functions/variables are being exported.
#else
//This header file is included by an exe source coude moudle.
#define MYLIBAPI extern "C" _declspec(dllimport)
#endif
//define and data structuree and symbols hear
//define exported variables here.
MYLIBAPI int g_nResult;
MYLIBAPI int add(int nleft,int nright);
在dll的源文件中,都应该包含这个头文件:
/****************
MyLibFile1.cpp
/****************/
//include the standard windows and C-Runtime header file here.
#include <Windows.h>
//this dll source code file exports functions and varibales
#define MYLIBAPI extern "C" _declspec(dllexport)
//include teh exported data structures,symbols,functions,and varibales.
int g_nResult;
int Add(int nLeft,int nRight)
{
g_nResult = nLeft + nRight;
return (g_nResult);
}
/////////////////end of the file ///////////////////////////////
在编译前面的DLL源文件时候,MYLIBAPI在包含MyLib.h头文件之前,被定义为_declspec(dll export).如果编译器看到一个变量,函数,或者类,使用_declspec(dllexport)修饰的,那么它就知道应该在生成DLL的模块中导出该变量、函数或者C++类,注意对那些需要被导出的变量和函数,我们必须在头文件中的变量和函数定义前面加上MYLIBAPI标识符。
另外要注意,在源文件中,不必在要被导出的变量函数前面加上MYLIBAPI 标识符,这里不需要MYLIBAPI 标识符的原因在于编译器在解析头文件的时候,会记住导出哪些变量或者函数。
我们还会注意到,在MYLIBAPI 符号包含有个extern C修饰符,只有在编写C++代码时候,才会使用这个修饰符,在编写C代码的时候,不应该使用该修饰符。C++编译器通常会对函数名和变量进行改编,这在链接的时候,会导致严重的问题。举个例子,假设一个DLL是用C++来写的,而可执行文件使用C写的。在构建DLL的时候,编译器会对函数名进行改编,但在构建可执行文件的时候,编译器不会对函数名进行改编。当连接器试图链接可执行文件时,会发现可执行文件引用了一个不存在的符号并报错。“extern C”告诉编译器,不要对变量名和函数名进行改编,这样用C++,C或者任何编程语言编写的可执行模块都可以访问该变量或者函数。
现在看到,dll的源文件应该如何使用这个头文件,那么可执行文件的源文件又该如何?可执行文件不应该在包含这个头文件之前定义MYLIBAPI.由于MYLIBAPI未定义,因此头文件会将MYLIBAPI定义为_declspec(dllimport).这样编译器就知道该可执行文件的源文件要从DLL模块中导入一些变量和函数。
当microsoft的C++编译器看到用这个修饰符_declspec(dllexport)修饰的变量,类,函数原型,C++类的时候,会在生成的obj文件中嵌入一些额外的信息。当连接器在链接dll的所有obj文件时,会解析这些信息。
在连接dll的时候,连接器会检测这些与导出的变量,函数,或者类有段的嵌入信息,并生成相应的lib文件。这个lib文件列出了该dll的导出符号,在链接任何可执行模块的时候,只要可执行模块引用了该dll导出的符号,那么这个lib文件当然是必须的。除了创建这个lib的文件外,连接器还会在生成的dll文件中嵌入一个导出符号表。这个导出段会列出了导出的变量、函数和类的符号名。连接器还会保存相对的虚拟地址,表示每个符号可以再dll模块中的何处找到。
我们可以通过微软vs10提供的工具,dumpbin.exe,打开kernel32.dll。下面仅仅列出部分:
Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file C:\Users\Administrator\Desktop\kernel32.dll
File Type: DLL
Section contains the following exports for KERNEL32.dll
00000000 characteristics
4B1E0840 time date stamp Tue Dec 08 16:03:12 2009
0.00 version
1 ordinal base
1361 number of functions
1361 number of names
ordinal hint RVA name
3 0 AcquireSRWLockExclusive (forwarded to NTDLL.RtlAcquireSRWLockExclusive)
4 1 AcquireSRWLockShared (forwarded to NTDLL.RtlAcquireSRWLockShared)
5 2 000490ED ActivateActCtx
6 3 00039EB8 AddAtomA
7 4 00045D2D AddAtomW
8 5 000AAC19 AddConsoleAliasA
9 6 000AABAF AddConsoleAliasW
10 7 0008F914 AddIntegrityLabelToBoundaryDescriptor
11 8 000855CB AddLocalAlternateComputerNameA
12 9 000854E0 AddLocalAlternateComputerNameW
13 A 0003C0E1 AddRefActCtx
14 B 00038950 AddSIDToBoundaryDescriptor
15 C 0008BFCA AddSecureMemoryCacheCallback
16 D AddVectoredContinueHandler (forwarded to NTDLL.RtlAddVectoredContinueHandler)
17 E AddVectoredExceptionHandler (forwarded to NTDLL.RtlAddVectoredExceptionHandler)
……
……
从中可以 系统看到,符号是按照顺序排列的,RVA这一列中的数值表示一个偏移量,导出的符号位于DLL文件映像的位置。Ordinal这一列是为了和16位版本的操作系统源代码表示兼容而设置的。hint这列是为了提高系统系能而设置的。
windows函数连接到可执行文件或者dll文件时,mic希望我们通过符号名来链接。如果使用序号,我们将面临应用程序无法再新版本的os中运行。