S.l.e!ep.¢%

像打了激速一样,以四倍的速度运转,开心的工作
简单、开放、平等的公司文化;尊重个性、自由与个人价值;
posts - 1098, comments - 335, trackbacks - 0, articles - 1
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

[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


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理