[DLL - Beginers]使用DEF文件修复函数名——对《使用LoadLibrary调用从Dll中输出的class》的一点补充 收藏
使用DEF文件修复函数名
——对《使用LoadLibrary调用从Dll中输出的class》的一点补充
作者 李成竹
在《使用……》一文中,作者在“代码”的第三点提到了“使用一个DEF文件修复了函数名”,但是并没有讲解什么是DEF文件,也没有说明应该如何修复,可能会使某些初学者(包括我自己)感到疑惑。我也上网搜索了一下,讲解DEF文件作用以及详细使用方法的文章不多且比较零散,本文在此用一个简单例子简单阐述一下DEF文件一般的使用方法,以方便需要者查阅。
DEF文件的全称是Module-Definition File,即模块定义文件,是用来定义EXE和DLL文件的一种文件格式,以文本形式保存(可用记事本创建/编辑)。由于链接器为大多数模块定义声明提供了对应的命令行选项,所以一般的Win32程序并不需要.DEF文件。但是在编写DLL时,尤其是在编写C++的DLL时,(由于名称修饰)DEF文件还是有它的用武之地的。
※注:关于“名称修饰”在很多地方都有介绍,文中不作讲解。
DEF文件的主要内容是由一系列的声明(statement)组成,包括NAME、LIBRARY、DISCRIPTION、STACKSIZE、SECTIONS、EXPORTS、VERSION。
¨ NAME:指定输出文件的文件名,设置image基址
¨ LIBRARY(DLL):指定DLL的内部名称和加载时的基址
¨ DISCRIPTION:文件描述
¨ STACKSIZE:设置栈的大小
¨ SECTIONS:设置image文件的一个或多个段属性
¨ EXPORTS:定义输出列表
¨ VERSION:指定文件版本
其中最常用的是LIBRARY、EXPORTS和DISCRIPTION。
示例
1. 现在有一个已经编写好的类要用DLL输出,并通过函数名对DLL进行动态调用。其头文件和源文件如下:
Header File:
#ifdef LIBDLL_EXPORTS
#define LIBDLL_API __declspec(dllexport)
#else
#define LIBDLL_API __declspec(dllimport)
#endif
#include <iostream.h>
// This class is exported from the LibDll.dll
class LIBDLL_API CTest
{
int data;
public:
CTest();
void print();
};
Source File:
#include "stdafx.h"
#include "LibDll.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
CTest::CTest()
{
this->data = 0;
}
void CTest::print()
{
cout<<"The member function print() is from a DLL.\n";
}
从代码中可以看到,DLL中定义了一个CTest类,它有一个构造函数和一个成员函数print()。
2. 然后新建一个Win32 Console Application来调用DLL。
Header File:
#define LIBDLL_API __declspec(dllimport)
#include <iostream.h>
// This class is exported from the LibDll.dll
class LIBDLL_API CTest
{
int data;
public:
CTest();
void print();
};
Source File:
#include "stdafx.h"
#include <iostream.h>
#include <malloc.h>
#include <windows.h>
#include "LibDll.h"
typedef void (WINAPI *PCTOR)();
typedef void (*PPRINT)();
inline void CTest_print(HMODULE, CTest*);
inline void CTest_CTest(HMODULE, CTest*);
int main(int argc, char* argv[])
{
//加载DLL
HMODULE hmod = LoadLibrary("LibDll.dll");
if(hmod == NULL)
{
cout<<"Failed loading DLL.\n";
return 1;
}
//创建类对象
CTest* pCTest = (CTest*)malloc(sizeof(CTest));
//初始化CTest对象
CTest_CTest(hmod, pCTest);
//调用成员函数
CTest_print(hmod, pCTest);
FreeLibrary(hmod);
free(pCTest);
cout<<"Press [Enter] to exit.";
cin.peek();
return 0;
}//end main
void CTest_print(HMODULE hMod, CTest* pObj)
{
PPRINT pprint = (PPRINT)GetProcAddress(hMod, "print");
if(pprint == NULL)
{
cout<<"Function print() not found.\n";
}
else
{
__asm{ MOV ECX, pObj}
pprint();
}
}
void CTest_CTest(HMODULE hMod, CTest* pObj)
{
PCTOR pCtor = (PCTOR)GetProcAddress(hMod, "CTest");
if(pCtor == NULL)
{
cout<<"Function CTest() not found.\n";
}
else
{
__asm{ MOV ECX, pObj}
pCtor();
}
}
本来到这里就应该可以正常运行了,但是你会发现在执行CTest_CTest函数时会提示"Function CTest() not found."。为什么会找不到函数呢?那是因为C++编译器在生成DLL时对输出函数的名称进行来“修饰”,所以DLL中的函数名称已经不再是我们在代码中所写的函数名,这个时候就需要用DEF文件来进行“函数名称修复”。
3. 用Dumpbin的/EXPORTS参数打开LibDll.Dll,截图如下:
其中1和3就是CTest类的构造函数和print()函数的实际名称(吓人吧……)。然后我们在DLL的工程目录中新建一个“LibDll.def”文件,并在“工程->设置->Link”中添加参数(/def:".\libdll.def"),并编
辑DEF文件内容如下:
LIBRARY LibDll
EXPORTS
CTest = ??0CTest@@QAE@XZ
print = ?print@CTest@@QAEXXZ
相信你已经看出来了,这实际上是一个函数名映射。
再用Dumpbin打重新编译得到的DLL文件,截图如下:
图中的4和5就是修复后的函数名。
现在再运行第二步中的程序就可以成功地调用DLL里的函数了!
尾注
文中只使用了DEF文件的一小部分功能,详细资料请参见MSDN。
按照MSDN上的说法,有三种方法可以用来输出函数,按推荐顺序如下:
在源代码中使用__declspec(dllexport)关键字(调用工程需包含*.lib)
使用DEF文件中的EXPORTS声明(不需要*.lib,可实现动态调用)
在LINK命令中使用/EXPORT参数(效果和DEF文件相同)
具体应该使用哪种方法,还应该视用途由使用者自己决定。
作者水平有限,文中难免不当之处,欢迎大家指出和批评。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/jdcb2001/archive/2006/11/21/1401569.aspx