⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 19.6 解决名字改编问题.txt

📁 网上第一本以TXT格式的VC++深入详解孙鑫的书.全文全以TXT格式,并每一章节都分了目录,清晰易读
💻 TXT
字号:
19.6 解决名字改编问题
前面已经提到, C++编译器在生成 DLL时,会对导出的函数进行名字改编,并且不同的编译器使用的改编规则不一样,因此改编后的名字是不一样的。这样,如果利用不同的编译器分别生成 DLL和访问该 DLL的客户端程序的话,后者在访问该 DLL的导出函数时就会出现问题。例如,如果用 C++语言编写了一个 DLL,那么用 C语言编写的客户端程序访问该 DLL中的函数时就会出现问题。因为后者将使用函数原始名称来调用 DLL 
中的函数,而 C++编译器己经对该名称进行了改编,所以 C语言编写的客户端程序就找
不到所需的 DLL导出函数。对上述 DIl1.dIl和 DllTest.exe程序来说,因为采用的是同一
种 C++编译器,后者知道该 DLL中导出函数改编后的名称,所以调用时没有出现问题。

鉴于以上原因,我们希望动态链接库文件在编译时,导出函数的名称不要发生改变。为了实现这一目的,在定义导出函数时,需要加上限定符: extern "c" 0注意双引号中的 "C"字母一定要大写。仍以 Dll1和 DllTest为例,首先打开 Dll1工程,找到 Dll l. cpp文件中定义 DLL1 API宏的代码,在其中添加限定符 : extem "c"。同样,在 Dll l. h文件中找到定义 DLLLAPI宏的代码,添加限定符: extem "c"。然后将 Dll l. h头文件中 Point类的定义代码,以及 Dll l. cpp源文件中 Point类成员函数的实现代码都注释起来(下面将解释原因)。这时, Dll l. h文件和 Dll l. cpp文件的代码分别如例 19-15和例 19-16所示。 
例 19-15 

#ifdef DLL1_API 
#else 
#define DLL1_API extern "C" _declspec(dllirnport) 
#endif 
DLL1_API int add(int a , int b); 
DLL1_API int subtract(int a , int b); 
I *class Point 
public : vo工 d DLL1_API output(工 nt x , int y) ; , void test() ; 
} ; * 1 
例19-16 

#define DLL1_API extern "C" _declspec(dllexport) 

#include "Dll1.h" 
#include <windows .h> 
#include <stdio. h> 
int add(int a , int b) 

return a+b ; 

int subtract(int a , int b) 
return a-b; 

/1* 

void point : : output(int x , int y) 
HWND hwnd=GetForegroundWindow(); 
HDC hdc=GetDC(hwnd); 
char buf [20] ; 
memset(buf , 0 , 20) ; 
sprintf(buf , "x=毛d, y=屯d", x, y); 
TextOut(hdc , O, O, buf , strlen(buf)) ; 
ReleaseDC(hwnd, hdc) ; 

void Point : : test() { } 
*/ 
再次利用 Build命令生成 Dll l. dll文件,并用 dumpbin命令的 exports选项查看该动态链接库的导出信息,结果如图 19.17所示,可以发现这时该 DLL导出的 add和 subtract这两个函数的名字没有发生改变。 
. 
图 1 9. 1 7 Dll l. dll的导出信息(五 )

然后,将最新的 Dll 1.1 ib和 Dll l. dll文件复制到 DlITes t工程所在目录下。接着,在 VC++开发环境中打开 DllTes t工程,并将该程序中调用 Point类的代码注释起来,之后在 VC++开发环境下运行该程序,并分别单击【 Add】和【 Subtract】按钮,将会发现这时客户端程序是可以访问 Dlll中的导出函数的。
利用限定符: extern "C"可以解决 C++和 C语言之间相互调用时函数命名的问题。但是这种方法有一个缺点,就是不能用于导出一个类的成员函数,只能用于导出全局函数这种情况。这就是为什么上面我们要将 Point类的代码注释的原因了。
另外,如果导出函数的调用约定发生了改变,那么即使使用了限定符 : extern '"C",该函数的名字仍会发生改编。为了说明这种情况,再次在 VC++开发环境中再次打开 Dll1工程,并修改 Dll l. h文件中 add函数和 s ubtrac t函数的声明代码,使这两个函数采用标准调用约定,即在声明这些函数时添加 stdcall关键字,结果如例 1 9-17所示。
" ‘ I 721 
第 19

例 19-17 
#ifdef OLLl API #else #define OLLl_API extern "C" _declspec(dllirnport) #endif 
OLLl_API int _stdcall add(int a , int b); OLLl_API int _stdcall subtract(int a , int b); 
同时,在 dll1.cpp源文件中,在 add函数和 subtract函数的定义前也需要添加_stdcall关键字,结果如例 19-18所示。 
19IJ 19-18 


#define DLL1_API extern "C" _declspec(dllexport) 

#include "dll1. h " 
#include <Windows .h> 
#include <stdio .h> 
int一stdcall add(int a , int b) 

return a+b; 
int 	_stdcall subtract(int a , int b) return a-b; 
如果没有添加 stdcall关键字,那么函数的调用约定就是 C调用约定。前面的内容已经提到,标准调用约定就是 WINAPI调用约定,也就是 pascal调用约定,这种约定方式与 C调用约定不一样。
利用 Build命令生成最新的 Dll l. dll,然后利用 Dumpbin命令的 exports选项查看该动态链接库的导出情况,结果如图 19.18所示。可以看到,这时导出函数名字仍然发生了变化,例如 add函数的名字变为: add@8,在 add这一函数名字前面加了一条下划线,后面添加一个"@"符号,接着是数字: 8,该数字表示 add函数的参数所占宇节数,因为 add函数具有两个 int类型的参数,所以其占用 8个字节。
也就是说,如果函数的调用约定发生了变化,即使在声明这些导出函数时使用了 extem "C"限定符,它们的名字仍然会发生改编。我们知道, C语言和 Oe1phi语言使用的调用约定是不一样的,后者使用的是 pascal调用约定,即标准调用约定 : stdcall。如果现在需要利用 C语言编写一个 OLL.然后由利用 Delphi编写的客户端程序访问的话,那么在导出函数时,应指定其使用标准的函数调用约定。但是,这仍会出现问题,因为这时函数名称会发生改编。这种情况下,可以通过一个称为模块定义文件 ( OEF )的方式来解决名字改编问题。
图 19.18 DlIl. dll的导出信息 (六〉

首先,我们利用 VC++再新建一个 Win32 Oynamic-Link: Library类型的工程,工程取名为: 0112,并在 AppWizard的第一个步选择 "An empty Oll project"选工页,即创建一个空的动态链接库工程。然后,为该工程添加一个 C++源文件 :Dll2.cpp,并与上面 0111例子一样,在其中编写一个完成加法运算的函数和一个完成减法运算的函数,结果代码如例 19-19所示。 

int 	add(int a , i nt b) return a+b ; 
int 	subtract(int a , int b) return a-b; 
接下来,为该工程添加一个模块定义文件,其后缀名为 : def。添加方法有多种,可以在资源管理器中,找到 dll2工程所在目录,然后在该目录下新建一个空的文本文件,并将该文件命名为: dll2.def。然后回到 VC++开发界面,将该文件添加到 dll2工程中,添加方法是:选择【工程t\Add To Projed/Files...】菜单项,然后利用弹出的"Insert Files into Project"对话框找到 dll2.def文件,并将其添加到 dll2工程中。然后,在 dll 2.def文件中,添加如例 19-20所示代码。 
例20 

LIBRARY dll2
EXPORTS 
add 
subtract 	‘ 
其中 LIBRARY语句用来指定动态链接库的内部名称。该名称与生成的动态链接库的
名称一定要匹配。这句代码并不是必须的。 EXPORTS语句的作用是表明DLL将要导出的函数,以及为这些导出函数指定的符号名。当链接器在链接时,会分析这个 DEF文件,当发现在EXPORTS语句下面有add和J subtract这两个符号名,并且它们与源文件中定义的 add和 subtract函数的名字是一样的时候,它就会以add和subtract这两个符号名导出相应的函数。如果将要导出的符号名和源文件中定义的函数名不一样,则可以按照下述语法指定导出函数: 
entryname=internalname 
其中等号左边的en町name项是导出的符号名,右边的intemalname项是 DLL中将要导出的函数的名字。关于EXPORTS语句的详细用法,读者可以查阅MSDN。
利用Build命令生成Dll2.dll,然后利用Dumpbin命令的exports选项查看该动态链接库的导出信息。结果如图 19.19所示,可以看到,这时输出的动态链接库导出函数名字是 add和 subtract,即按照Dll2.def文件中列出的名字输出的,没有发生名字改编。 
图 19.19 Dll2.dll的导出信息(一) 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -