使用LoadLibrary调用DLL中输出的class(转载)

来源:互联网 发布:拍照英语翻译软件下载 编辑:程序博客网 时间:2024/04/30 11:54

使用LoadLibrary调用DLL中输出的class

Anup. V.

李成竹 

原文地址:http://www.codeproject.com/dll/classesexportedusingLL.asp

·                     Demo下载- 21.4 Kb

·                     源码下载 - 5.88 Kb

引言

我见过相当多的用来说明在程序中如何使用从DLL中输出的class的代码,但这些方法都是通过隐式链接完成的。回忆一下DLL的概念,有两种方法可以使用DLL中输出的函数:一是在程序代码中简单地引用DLL中符号,这使得加载器在程序启动时隐式地加载(链接)所需的DLL,这就是众所周知的“隐式链接”。

第二种方法就是在程序运行过程中显式地加载所需的DLL(使用LoadLibrary())并且显式地链接到需要的输出符号。换句话说,如果程序要调用DLL中的一个函数,可以显式地加载一个DLL到她的进程地址空间,然后获得函数在DLL中的虚拟内存地址,并利用这个地址来调用函数。这种方法的优美之处就在于所有的工作都是在程序运行过程中完成的,并且程序可以从进程地址空间中卸载不再需要的DLL。这种方法就是“显式链接”。

背景

前面窝已经介绍了函数的调用方法,但是怎么使用输出类呢?对于隐式链接的DLL,调用类和调用函数没有什么区别;而在一般情况下,想要显示加载DLL并使用其中的类是不可能的。但是我写这篇文章并不是为了告诉你为什么这不可能,而是要告诉你如何来实现它。对了!就是使用LoadLibrary()

在继续下文之前我想告诉你,以下的代码很粗糙,如果你准备将其用于你的项目中,请先征得你老板的同意。但是这些代码不仅用于让你加深理解,在实在没有办法的情况下也不失为一种极端的解决方法。

代码

在示例代码中,我创建了一个名为Calc.DLL计算器DLL,并在一个名为UserOfcalc的命令行程序中使用其提供的计算功能。

// Calc.DLL包含了一个名为CCalc的输出类

// 它包含3个方法,分别是 AddSubGetLastFunc()

 

// CALC.H – CCalc类声明

 

#include <tchar.h>

 

#ifdef CALC_EXPORTS

#define CALC_API __declspec (dllexport)

#else

#define CALC_API __declspec (dllimport)

#endif

 

#define SOME_INSTN_BUF        260

 

class CALC_API CCalc

{

private:

TCHAR m_szLastUsedFunc[SOME_INSTN_BUF];

 

public:

    CCalc ();

 

    int Add (int i, int j);

    int Sub (int i, int j);

    TCHAR* GetLastUsedFunc ();

 

};

DLL的实现部分在文件Calc.cpp中:

#include "Calc.h"

#include <windows.h>

 

BOOL APIENTRY DllMain (HANDLE, DWORD, LPVOID)

{

    return TRUE;

}

 

// 构造函数,初始化m_szLastFuncCalled数组

CCalc::CCalc ()

{

 

    memset (m_szLastUsedFunc, 0, sizeof (m_szLastUsedFunc));

    strcpy (m_szLastUsedFunc, "No function used yet");

}

 

 

int CCalc::Add (int i, int j)

{

    strcpy (m_szLastUsedFunc, "Add used");

    return (i + j);

}

 

int CCalc::Sub (int i, int j)

{

    strcpy (m_szLastUsedFunc, "Sub used");

    return (i - j);

}

现在,通过以下步骤可以显式地加载DLL并使用Calc类中提供的函数:

  1. 第一步是使用LoadLibraryCalc.DLL加载到你的程序中。

HMODULE hMod = LoadLibrary ("Calc.dll");

if (NULL == hMod)

{

    printf ("LoadLibrary failed/n");

    return 1;

}

  1. 因为你有Calc.DLL的头文件,所以下一步就是分配一个与类大小匹配的内存块,然后调用构造函数代码。

CCalc *pCCalc = (CCalc *) malloc (sizeof (CCalc));

if (NULL == pCCalc)

{

    printf ("memory allocation failed/n");

    return 1;

}

但是在C++中我们为什么要使用malloc而不用new呢?这是因为new操作符会调用CCalc's的默认构造函数,而我们根本访问不到它。记住,我们必须要动态地加载DLL,因此在build时没有定义CCalc类的构造函数。

因此,我们仅仅获得了一块与CCalc类大小相等的未初始化的内存。

  1. 如果你使用Dumpbin.exe(位于Microsoft Visual Studio/VC98/Bin文件夹下)来查看输出函数,你会看到DLL的一个输出函数列表(我已经使用一个DEF文件修复了函数名)。如下图所示:

列表包含了函数Add, Sub, GetLastUsedFunc和构造函数的虚拟内存地址。

前面我们已经获得了一块内存,现在必须调用构造函数对其进行初始化,所以我们要获取构造函数在DLL中的相对虚拟地址(RVA)。

PCTOR pCtor = (PCTOR) GetProcAddress (hMod, "CCalc");

if (NULL == pCtor)

{

    printf ("GetProcAddress failed/n");

    return 1;

}

PCTOR是一个在UserOfCalc.cpp中定义的函数指针,其定义代码如下:

typedef void (WINAPI * PCTOR) ();

  1. 现在有了构造函数的地址,接下来就应该要调用它来初始化前面用malloc分配的那块内存。但怎样才能使一个对象和这个构造函数关联起来呢?

如果你还记得,当任何成员函数(包括构造函数)被调用时,对象的地址会自动传递到被调用函数,而且这个地址存储在栈中。在基于Intel的机器上,这个对象地址通过ECX寄存器被压入栈顶,所以当你创建一个类并调用其成员函数时,ECX寄存器包含‘this’指针。下面的截图会使问题清晰一点。

你应该注意到汇编窗口中当前执行指令

LEA ECX, [EBP-4]

完成时, ECX和‘&bmw’的值是相同的。在不同的处理器架构的机器上,使用的寄存器可能不是ECX,这里只是举例说明。

  1. 回到我们的Calc.dll,已经有了内存块(以后的对象)的地址,现在用内嵌汇编语句将这个地址拷贝到ECX寄存器:

__asm { MOV ECX, pCCalc }

  1. 现在我们已经获得了构造函数的地址,只须要:

pCtor ();

  1. 当你的函数指针pCtor()DLL中返回时,它已经完成了该对象的初始化。
  2. 要调用Calc类的其它任何成员函数,只须再次拷贝pCalcECX并获取输出函数载进程中的地址,然后直接调用即可。你观察任何简单类的反汇编代码都会发现,在每次成员函数调用之前总有一个汇编指令将‘this’拷贝到ECX。这就是我们前面所做的事。

后序

如果你单步运行源代码,你头脑中的概念将更加清晰。要了解DLLs的工作机制,请参看《Programming Applications for Microsoft Windows》或《Advanced Windows NT》,都是Jeffrey Richter的著作。

最后,这是我第一次写作,如果你喜欢这篇文章或者它对你有所帮助的话我会非常高兴。如果你有任何建议或批评,请发至我的邮箱anupshubha@yahoo.com。(译者邮箱:jdcb2001@163.com


 
原创粉丝点击