自动导出动态链接库接口在C++编程中绝对是一件烦人的事情,因为你不得不大量的重复以下几个步骤:
1.加载动态链接库
2.定义导出函数指针定义
3.定义导出函数指针变量
4.从动态链接库中导出函数
5.编写导出函数的封装函数
6.卸载动态链接库
有很多人试图将这个过程自动化,然而全部折戟沉沙。我认为终极原因在于动态链接库在导出函数的时候没有包含参数信息,而在使用的时候不得不将导出函数进行强制转型。
在C#中导出动态链接库的函数相比C++来说要简单的多:
1. 使用DllImportAttribute属性规定导出函数的文件路径、导出点、堆栈调用方式等
2.定义导出函数的C#函数原型
说简单是代码简单,不是实现过程简单。从表面上看,C#的这两部实现了C++的那6步,从原理上来看,C#仍然需要C++的那6步。C#的编译器把2、3、4、5的步骤封装了,在C#代码编译成中间语言的时候进行了展开。C#在定义导出函数的函数原型的时候需要做一件事,就是判断C#参数类型和C++参数类型的对应关系,为什么要特别注意这一点呢?就是因为C#在展开的时候需要准确的C++函数定义,否则调用过程中会出现错误。
Java中有一个能够调用本地动态链接库的组件——JNative,虽然我没有看过它具体是怎么实现的,但是从我了解到的只字片语中推断JNative参考了C#的实现过程并原模原样的复刻了一份——因为JNative在实现的时候仍然需要判断Java参数类型和C++参数类型的对应关系。
C#的实现方式和Java的实现方式在本质上和C++没有区别,但是在编码过程中要简单许多,因为C#和Java的编译器在重复代码的处理上比C++做了更多的工作。C++当然也能处理重复代码,只不过不是通过编译器,而是通过宏。
我按照C#的思路实作了C++自动导出函数的简单版本。
#pragma once
#include <WTypes.h>
#include <list>
using namespace std;
class DllImportAttribute
{
public:
// 加载动态链接库
virtual BOOL Init() = 0;
// 卸载动态链接库
virtual BOOL Uninit() = 0;
};
// 加载所有库
BOOL DllImportInit();
// 卸载所有库
BOOL DllImportUninit();
// 动态库列表
extern list<DllImportAttribute*> gDllImportList;
#define DLLIMPORTCLASSBEGIN(CLASS, DLLPATH) class CLASS; \
extern CLASS g##CLASS##Dll; \
class CLASS : public DllImportAttribute \
{ \
protected: \
HMODULE m_hModule; \
public: \
CLASS() : m_hModule(NULL) { \
gDllImportList.push_back(this); \
} \
virtual BOOL Init() { \
m_hModule = LoadLibraryA(DLLPATH); \
return (m_hModule != NULL); \
} \
virtual BOOL Uninit() { \
return FreeLibrary(m_hModule); \
} \
#define FUNCTIONENTRY(ENTRYTYPE, ENTRYPOINT) protected: \
typedef ENTRYTYPE; \
ENTRYPOINT ENTRYPOINT##Ptr; \
public: \
ENTRYPOINT ENTRYPOINT##Func() { \
ENTRYPOINT##Ptr = (ENTRYPOINT)GetProcAddress(m_hModule, "##ENTRYPOINT##"); \
return ENTRYPOINT##Ptr; \
} \
#define DLLIMPORTCLASSEND() };
#define DLLIMPORTCLASSIMPLEMENT(CLASS) CLASS g##CLASS##Dll;
#define DLLIMPORTCALL(CLASS, ENTRYPOINT) g##CLASS##Dll.ENTRYPOINT##Func()
#include "StdAfx.h"
#include "DllImportAttribute.h"
list<DllImportAttribute*> gDllImportList;
// 加载所有库
BOOL DllImportInit()
{
BOOL bSuccess = TRUE;
for (auto iter = gDllImportList.begin(); iter != gDllImportList.end(); iter++)
{
bSuccess &= (*iter)->Init();
}
return bSuccess;
}
// 卸载所有库
BOOL DllImportUninit()
{
BOOL bSuccess = TRUE;
for (auto iter = gDllImportList.begin(); iter != gDllImportList.end(); iter++)
{
bSuccess &= (*iter)->Uninit();
}
return bSuccess;
}
上面的两个文件是实现DLL自动导出函数的基础框架,直接引用到工程项目中。
使用上面的框架实现一个DLL函数导出的过程(以 MessageBoxA 为例 )如下:
#pragma once
#include "DllImportAttribute.h"
DLLIMPORTCLASSBEGIN(User32, "User32.dll")
FUNCTIONENTRY(int (WINAPI *MessageBoxA) (HWND, LPCSTR, LPCSTR, UINT), MessageBoxA)
DLLIMPORTCLASSEND()
#include "stdafx.h"
#include "User32.h"
DLLIMPORTCLASSIMPLEMENT(User32)
看起来是不是简单多了。客户端使用导出函数的例子:
DllImportInit();
DLLIMPORTCALL(User32, MessageBoxA)(NULL, "ad", "ad", MB_OK);
DllImportUninit();
来源:oschina
链接:https://my.oschina.net/u/235301/blog/170268