按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!
int Max(int a; int b)
{
if(a》=b)
return a;
else
return b;
}
int Min(int a; int b)
{
if(a》=b)
return b;
else
return a;
}
该动态链接库编译成功后,打开“MyDll ”工程中的“debug ”目录,可以看到 MyDll。dll 、
MyDll。lib 两个文件。LIB 文件中包含 DLL 文件名和 DLL 文件中的函数名等,该文件只是对
应 DLL 文件的“映像文件 ”,比 DLL 文件中 LIB 文件的长度小,在进行隐式链接 DLL 时要
用到它。在 MyDll。h 中有关键字〃extern C〃,它可以使其他编程语言访问所编写的 DLL 中的
函数。
2 .用 DEF 文件创建工程
为了用 DEF 文件创建 DLL ,请先删除上个例子创建的工程中的 MyDll。h 文件,保留
MyDll。cpp 并在该文件中删除#include MyDll。h 语句,同时加入一个文本文件,命名为
MyDll。def ,再添加如下代码:
LIBRARY MyDll
·260 ·
…………………………………………………………Page 272……………………………………………………………
第 10 章 动态链接库
EXPORTS
Max
Min
其中 LIBRARY 语句说明该 DEF 文件属于相应的 DLL ,可以在 EXPORTS 语句下列出要
导出的函数名称。如果在 DEF 文件中的导出函数后加@n,如 Max@1 和 Min@2 ,表示要导
出的函数顺序号,在进行显式连时可以用到它。该 DLL 编译成功后,打开工程中的 Debug
目录,同样也会看到MyDll。dll 和 MyDll。lib 文件。
10。4。2 MFC AppWizard'dll'方式生成常规/扩展 DLL
在 MFC AppWizard'dll'下生成 DLL 文件有 3 种方式,在创建 DLL 时,要根据实际情况
选择创建 DLL 的方式。一种是常规 DLL 静态链接到 MFC ,另一种是常规 DLL 动态链接到
MFC 。前者使用的是 MFC 的静态链接库,生成的 DLL 文件长度大,一般不使用这种方式;
后者使用 MFC 的动态链接库,生成的 DLL 文件长度小。动态链接到 MFC 的规则 DLL 所有
输出的函数应该以如下语句开始:
AFX_MANAGE_STATE(AfxGetStaticModuleState()) //此语句用来正确地切换 MFC 模块状态
最后一种是 MFC 扩展 DLL ,这种 DLL 特点是用来建立 MFC 的派生类,DLL 只被用
MFC 类库所编写的应用程序所调用。前面已经介绍过,Extension DLLs 和 Regular DLLs 不
一样,它没有一个从 CWinApp 继承而来的类的对象,编译器默认了一个 DLL 入口函数
DLLMain()作为对 DLL 的初始化,可以在此函数中实现初始化,代码如下:
BOOL WINAPI APIENTRY DLLMain(HINSTANCE hinstDll ,DWORD reason ,LPVOID flmpload)
{
switch(reason)
{
……………//初始化代码;
}
return true;
}
参数 hinstDll 表示存放 DLL 的句柄,参数 reason 指明调用函数的原因 。对于隐式链接是
一个非零值,对于显式链接值是零。
在 MFC 下建立 DLL 文件,会自动生成 def 文件框架,其他与建立传统的 Non…MFC DLL
没有什么区别,只要在相应的头文件写入关键字_declspec(dllexport) 函数类型和函数名等,或
在生成的 def 文件中 EXPORTS 下输入函数名就可以了。需要注意的是在向其他开发人员分
发 MFC 扩展 DLL 时,不要忘记提供描述 DLL 中类的头文件以及相应的 LIB 文件和 DLL 本
身,此后开发人员就能充分利用开发的扩展 DLL 了。
10。4。3 导出函数调用约定
关于动态链接库输出函数的约定有调用约定和名字修饰约定两种。调用约定决定着函数
参数传送时入栈和出栈的顺序,以及编译器用来识别函数名字的修饰约定。名字修饰约定随
·261 ·
…………………………………………………………Page 273……………………………………………………………
Visual C++ 6。0 程序设计从入门到精通
调用约定和编译种类(C 或 C++ )的不同而变化。为了让不同的编程语言共享动态链接库带
来的方便,函数输出时必须使用正确的调用约定,并且最好不带有任何由编译器生成的名字
修饰。下面详细介绍实现这些需求的方法。
1.调用约定
Visual C++ 6。0 支持的函数调用约定有多种,在这里主要介绍_stdcall 调用约定、C 调用约
定和_fastcall 调用约定。
_stdcall 调用约定相当于 16 位动态库中经常使用的 PASCAL 调用约定。在 32 位的 Visual
C++ 6。0 中 PASCAL 调用约定不再被支持(实际上它已被定义为_stdcall 。除了_pascal 外,
_fortran 和_syscall 也不被支持),取而代之的是_stdcall 调用约定。两者实质上是一致的,即
函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的
是函数名的修饰部分(具体参见下一节介绍 )。
C 调用约定(即用_cdecl 关键字说明)和_stdcall 调用约定有所不同,虽然在参数传送方
面是一样的,但 C 调用约定对于传送参数的内存栈却是由调用者来维护的(也正因为如此,
实现可变参数的函数只能使用该调用约定),另外,在函数名修饰约定方面也有所不同。
_fastcall 调用约定的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用
ECX 和 EDX 传送前两个双字或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的
函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。
关键字_stdcall 、_cdecl 和_fastcall 可以直接加在要输出的函数前,也可以在编译环境中选
择“Setting。。。|C/C++|Code Generation”菜单命令,在如图 10…3 所示的对话框中设置编译环境。
当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。
它们对应的命令行参数分别为/Gz 、/Gd 和/Gr 。默认状态为/Gd ,即_cdecl 。
图 10…3 编译环境设置
如果要完全模仿 PASCAL 调用约定首先必须使用_stdcall 调用约定,至于函数名修饰约
定,可以通过其他方法模仿。另外需要注意的是 Windows。h 支持 WINAPI 宏,该宏可以将输
出函数翻译成适当的调用约定,在 WIN32 中,它被定义为_stdcall 。
2 .函数名修饰约定
函数名修饰约定随编译种类和调用约定的不同而不同,下面分别说明。对于 C 编译,
·262 ·
…………………………………………………………Page 274……………………………………………………………
第 10 章 动态链接库
_stdcall 调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@ ”符号和其参数的
字节数,格式为_functionname@number 。_cdecl 调用约定仅在输出函数名前加上一个下划线
前缀,格式为_functionname 。_fastcall 调用约定在输出函数名前加上一个“@ ”符号,后面
也是一个“@ ”符号和其参数的字节数,格式为@functionname@number 。它们均不改变输出
函数名中字符的大小写,这和 PASCAL 调用约定不同,PASCAL 约定输出的函数名无任何修
饰且全部大写 。本例将给出一种完全模仿PASCAL 调用约定的方法,在 DEF 文件的 EXPORTS
段通过别名来实现。例如:
int __stdcall MyFunc (int a;double b);
void __stdcall InitCode (void);
在 DEF 文件中:
EXPORTS
MYFUNC=_MyFunc@12
INITCODE=_InitCode@0
Visual C++编译输出的函数名修饰较为复杂,编译时 stdcall 调用约定规则如下。
o 以“? ”标识函数名的开始,后跟函数名。
o 函数名后面以“@@YG”标识参数表的开始,后跟参数表。
o 参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型。
o 参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z ”标识结束。
其格式为“?functionname@@YG*****@Z ”或“?functionname@@YG*XZ ”,例如:
int Test1(char *var1;unsigned long)………… “?Test1@@YGHPADK@Z ”,void Test2()…………
“?Test2@@YGXXZ ”。
cdecl 调用约定规则同上面的 stdcall 调用约定类似,只是参数表的开始标识由上面的
“
@@YG”变为“@@YA”。
fastcall 调用约定规则同上面的 stdcall 调用约定,只是参数表的开始标识由上面的
@@YG”变为“@@YI”。
“
3 .得到没有修饰的函数名
Visual C++ 输 出 函 数 时 使 用 _declspec(dllexport) , 而 不 再 用 _export 修 饰 字 。
_declspec(dllexport)在 C 调用约定、C 编译情况下可以去掉输出函数名的下划线前缀。extern
〃C〃使得在 C++ 中使用 C 编译方式成为可能,在一个 C++文件中,用 extern 〃C〃来指明该函数
使用 C 编译方式。例如,在一个 C++文件中,有如下函数:
extern 〃C〃 {void __declspec(dllexport) __cdecl Test(int var);}