VC自动加载动态库的一点想法

本文探讨了C++动态库加载的两种方式,并对比了C++与C#的动态库调用流程。针对C++加载动态库的繁琐过程,作者分享了一种通过宏实现自动加载的方法,包括DLLIMPORTCLASSBEGIN和DLLIMPORTCLASSEND等宏的使用。同时,文章指出这种方法在接口不存在或获取失败时可能导致崩溃,并提出了利用C++构造与析构函数进行优化的建议,以确保动态库加载的健壮性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态库的加载方式有静态加载和动态加载。动态加载有个好处就是只要接口没变,重新编译之后,调用者不用重新编译。

之前在某网站看到有个大神说c++作为一门面向对象语言,其使用便捷性远远不如C#以及java,还编写了一个自动调用动态库的过程。确实c++对象封装上面不如C#和Java,也不如他们使用便捷,但是c++的功能非常强大的,特别是其强大的指针功能(这是从C语言继承来的)。尽管指针在使用时容易造成内存问题,但我们不能否认指针功能的强大,尤其是万能指针(void*),基本上所有指针都可转化为(void*)。


c++在动态库加载处理上确实有点麻烦,下面是c++和c#调用动态库流程的比较:

c++
c#
(1)、定义动态库句柄:HINSTANCE hModule;
定义函数指针接收接口地址:
typedef int (WINAPI* Sendmessage)(
            IntPtr hWnd,
            int Msg,
            int wParam,
            ref lParam lparam);
Sendmessage sendPtr;
(2)、加载动态库:hModule = LoadLibrary("User32.dll")
(3)、获取接口地址:
sendPtr= (Sendmessage)GetProcAddress(hModule,"SendMessage")
(4)、调用接口
(5)、释放动态库:FreeLibray(hModule)
(1)、加载动态库:
[DllImport("User32.dll", EntryPoint = "SendMessage")]
(2)、接口声明
private static extern int SendMessage(
            IntPtr hWnd,
            int Msg,
            int wParam,
            ref lParam lparam);
(3)、调用接口










从表中可以看出C++动态加载动态库确实有点繁琐,它不像c#那样将动态库加载、获取接口地址、释放动态库过程封装起来自动实现,无需开发者去实现。所以大神用下面的C宏实现自动化动态库加载,感受一下,应该容易看懂:

#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 = LoadLibrary(DLLPATH);				\
		return (m_hModule != NULL);						\
	}													\
	virtual BOOL UnInit()								\
	{													\
		return FreeLibrary(m_hModule);					\
	}													\

#define FUNCTIONENTRY(ENTRYTYPE,ENTRYPOINT) public:		\
	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()


list<DllImportAttribute*> gDllImportList;
//加载动态库
BOOL DllImportInit()
{
	BOOL bSuccess = TRUE;
	for (list<DllImportAttribute*>::iterator iter = gDllImportList.begin(); iter != gDllImportList.end(); iter++)
	{
		bSuccess &= (*iter)->Init();
	}
	return bSuccess;
}

//卸载动态库
BOOL DllImportUnInit()
{
	BOOL bSuccess = TRUE;
	for (list<DllImportAttribute*>::iterator iter = gDllImportList.begin(); iter != gDllImportList.end(); iter++)
	{
		bSuccess &= (*iter)->UnInit();
	}
	return bSuccess;
}

调用方式如下:

//类定义
DLLIMPORTCLASSBEGIN(User32,"User32.dll")
FUNCTIONENTRY(int (WINAPI *MessageBoxA)(HWND,LPCTSTR,LPCTSTR,UINT),MessageBoxA)
DLLIMPORTCLASSEND()

int main(int argc, char* argv[])
{
	//对象声明
	DLLIMPORTCLASSIMPLEMENT(User32)

	//调用
	DllImportInit();
	DLLIMPORTCALL(User32,MessageBoxA)(NULL,"Message","Hips",MB_OK);
	DllImportUnInit();
	return 0;
}

分析:

DLLIMPORTCLASSBEGIN(User32,"User32.dll")
FUNCTIONENTRY(int (WINAPI *MessageBoxA)(HWND,LPCTSTR,LPCTSTR,UINT),MessageBoxA)
DLLIMPORTCLASSEND()

这段宏具体展开就比较清晰了,相当于定义了类User32,继承自DllImportAttribute,该类有两个公共方法:Init()加载动态库,UnInit()释放动态库。

然后定义函数指针类型:

typedef int (WINAPI *MessageBoxA)(HWND,LPCTSTR,LPCTSTR,UINT);

MessageBoxA MessageBoxAPtr;


然后宏DLLIMPORTCLASSIMPLEMENT(User32)定义了类User32的对象 gUser32DLL,构造的时候自动将对象放入 gDllImportList链表中,

DllImportInit()函数将链表中的动态库全部加载。

然后DLLIMPORTCALL(CLASS,ENTRYPOINT)宏获取接口地址然后调用接口。

DllImportUnInit()接口释放链表中的所有对象的动态库释放。


这个方法确实不错,C++也只能这样实现自动加载动态库,将这段代码放到头文件中,这样以后使用动态库时不必再去实现加载动态库流程。


但是有个问题,如果获取接口地址失败或者接口不存在,这样GetProcAddress会返回iNULL,这时这段代码直接就崩溃了。另外这段代码可以稍微改进一下,利用c++的构造与析构来实现动态库的加载与释放,这也是C++的一个特点,可以将内存释放放到析构函数里面,在调用之前检查一下接口是否获取成功。下面是具体实现:

#include <wtypes.h>
#include <list>

using namespace std;

#define DLLIMPORTCLASSBEGIN(CLASS,DLLPATH) class CLASS;	\
extern CLASS g##CLASS##DLL;								\
class CLASS 											\
{														\
protected:												\
	HMODULE m_hModule;									\
public:													\
	BOOL	bSuccess;									\
public:													\
	CLASS(): m_hModule(NULL)							\
	{													\
		m_hModule = LoadLibrary(DLLPATH);				\
		m_hModule == NULL ? bSuccess=FALSE:bSuccess=TRUE;	\
	}													\
	~CLASS()											\
	{													\
		if(m_hModule)									\
			FreeLibrary(m_hModule);						\
	}													\
	
#define FUNCTIONENTRY(ENTRYTYPE,ENTRYPOINT) public:		\
	typedef ENTRYTYPE;									\
	ENTRYPOINT ENTRYPOINT##Ptr;							\
public:													\
	ENTRYPOINT ENTRYPOINT##Func(){						\
               if(!bSuccess) return NULL;						\
		ENTRYPOINT##Ptr =								\
	(ENTRYPOINT)GetProcAddress(m_hModule,#ENTRYPOINT);	\
		return ENTRYPOINT##Ptr;							\
	}													\
	
#define DLLIMPORTCLASSEND() };

#define DLLIMPORTCLASSIMPLEMENT(CLASS) CLASS g##CLASS##DLL;	
#define DLLENTRYEXIST(CLASS,ENTRYPOINT) (g##CLASS##DLL.ENTRYPOINT##Func() == NULL?FALSE:TRUE)
#define DLLIMPORTCALL(CLASS,ENTRYPOINT)	g##CLASS##DLL.ENTRYPOINT##Ptr

调用的时候如下:

//类定义
DLLIMPORTCLASSBEGIN(User32,"User32.dll")
FUNCTIONENTRY(int (WINAPI *MessageBoxA)(HWND,LPCTSTR,LPCTSTR,UINT),MessageBoxA)
DLLIMPORTCLASSEND()

int main(int argc, char* argv[])
{
	//对象声明
	DLLIMPORTCLASSIMPLEMENT(User32)

	//调用
	if (DLLENTRYEXIST(User32,MessageBoxA))
	{
		DLLIMPORTCALL(User32,MessageBoxA)(NULL,"Message","Hips",MB_OK);
	}
	
     return 0;
}

这样DllImportInit()、DllImportUnInit()这两个方法完全被砍掉。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值