关于定义类成员导出函数需要注意的一些问题

本文介绍如何通过定义.def文件来自定义DLL中的导出函数名称,避免使用GetProcAddress时出现奇怪的字符串。同时,讨论了类导出函数动态调用时this指针的处理方法及成员变量封装的重要性。

1:如果是想自己定义的导出函数在IDA中看起来和你的函数名称一样的话应该在项目中定义相应的xxxxxx.def文件。

在def文件中定义对应的C++函数名如如下:

LIBRARY
EXPORTS
MyWSASend @1
MyWSAIoctl @2
Mysetsockopt @3
Myrecv @4
Myconnect @5
SetHookFlag @6

这样在编译出来的dll中就可以用GetProcAddress填写对应的导出函数名,而不需要加一些奇怪的字符串。

2:类导出函数的动态调用需要注意一个问题就是第一个参数是类的this指针,但是我们在外部调用的时候往往无法获得这个类的this指针。所以这里我们就传入一个nullptr否则会出现堆栈不平,程序崩溃。

 pfn_WSASend pfn_func = (pfn_WSASend)MyWSASend;
 rv = pfn_func(nullptr, socket_, &write_buffer, 1, &num, 0,
 &core_->write_overlapped_, NULL);


3:在类的实现上面也有一点小的变化就是所以涉及到类成员变量的引用,都必须经过一次封装。否则在动态调用函数内部用到成员变量会崩溃。


XbNetApiProtect g_netProtect; 初始化


private:
IUnderlyingApi * GetApiWSASend(void);
IUnderlyingApi * GetApiWSAIoctl(void);
IUnderlyingApi * GetApisetsockopt(void);
IUnderlyingApi * GetApirecv(void);
IUnderlyingApi * GetApiconnect(void);
bool GetHookFlag(void);
void SetRealHookFlag(bool valuebool);


private:
IUnderlyingApi * ApiWSASend;
IUnderlyingApi * ApiWSAIoctl;
IUnderlyingApi * Apisetsockopt;
IUnderlyingApi * Apirecv;
IUnderlyingApi * Apiconnect;
bool is_protect_;


bool XbNetApiProtect::GetHookFlag(void) <<<封装的取类成员变量的函数
{
return is_protect_;
}


int __stdcall XbNetApiProtect::MyWSASend(SOCKET s, _In_reads_(dwBufferCount) LPWSABUF lpBuffers, _In_ DWORD dwBufferCount, _Out_opt_ LPDWORD lpNumberOfBytesSent, _In_ DWORD dwFlags, _Inout_opt_ LPWSAOVERLAPPED lpOverlapped, _In_opt_ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine)
{
int retvalue;
if (!g_netProtect.GetHookFlag()) <<<比如如果这里直接写is_protect_其实在调用this->is_protect_由于this指针传过来的时候是nullptr,所以必须经过封装
{
return WSASend(s, lpBuffers, dwBufferCount, lpNumberOfBytesSent, dwFlags, lpOverlapped, lpCompletionRoutine);
}


int params[] = { s, (int)lpBuffers, dwBufferCount, (int)lpNumberOfBytesSent, dwFlags, (int)lpOverlapped, (int)lpCompletionRoutine };
retvalue = (int)(g_netProtect.GetApiWSASend())->Invoke((void **)&params);
return retvalue;
}


<think>我们正在讨论如何在C++中封装动态库,特别是如何导出类的成员函数。用户的问题中提到了几个引用,其中引用[1]说明了在Windows和Linux平台上导出类的基本方法;引用[2]展示了一个导出类到Lua的示例(但用户可能更关注C++本身的导出);引用[3]提到了一种利用继承和多态来导出类并隐藏实现细节的方法;引用[4]则介绍了通过工厂函数和隐藏实现类来封装动态库。结合用户的问题和之前的回答,用户的核心需求是:在C++中封装动态库,并导出类的成员函数,同时可能希望隐藏实现细节。 ###关键点: 1. **导出类的基本方法**:使用`__declspec(dllexport)`(Windows)或`__attribute__((visibility("default")))`(Linux)标记类或成员函数。 2. **隐藏实现细节**:可以通过接口类(抽象基类)和工厂函数来实现,这样动态库的使用者只需要知道接口,而不需要知道具体实现类的细节。###解决方案:####方法一:直接导出类(简单但暴露实现细节)这种方法适用于不需要隐藏实现细节的情况。 **步骤:** 1.在头文件中定义类,并使用导出标记。2.在源文件中实现类的成员函数。3.编译生成动态库。 **示例(Windows平台):**头文件 `MyClass.h`: ```cpp#ifdef MYLIB_EXPORTS#define MYLIB_API__declspec(dllexport)#else#defineMYLIB_API __declspec(dllimport)#endifclassMYLIB_API MyClass {public:MyClass();~MyClass();int add(int a, intb);voidsetValue(int v);int getValue()const;private:intvalue;}; ```源文件`MyClass.cpp`:```cpp#include"MyClass.h" MyClass::MyClass() :value(0){} MyClass::~MyClass() {}int MyClass::add(inta,int b) {return a+ b; }void MyClass::setValue(int v) {value =v;} intMyClass::getValue()const {return value; }``` **注意:**在编译动态库时,需要定义`MYLIB_EXPORTS`宏(通常在项目设置中指定)。这样,在编译动态库时,类`MyClass`会被标记为导出。使用该动态库的程序在包含头文件时,不需要定义`MYLIB_EXPORTS`,因此会导入该类。 ####方法二:使用接口类和工厂函数(隐藏实现)这种方法可以隐藏实现细节,提高封装性。**步骤:**1.定义一个抽象基类(接口),只包含纯虚函数。2.在动态库中实现一个派生类,实现接口。3.提供工厂函数来创建和销毁对象(工厂函数使用`extern"C"`以避免名称修饰)。**示例:**接口头文件 `IBase.h`(不需要导出标记,因为只有纯虚函数):```cppclassIBase{ public:virtual ~IBase() {}virtualint add(int a, intb)=0;virtualvoid setValue(intv)=0;virtualint getValue()const =0; };//工厂函数声明extern "C"{IBase* createBase();voiddestroyBase(IBase* obj); }``` 动态库实现文件 `BaseImpl.cpp`(在动态库项目中): ```cpp#include "IBase.h"//实现类classBaseImpl: publicIBase{ public:BaseImpl() :value(0){}~BaseImpl()override {}intadd(inta,int b) override{ returna +b;}void setValue(intv)override {value =v;}int getValue()const override{ returnvalue;} private:int value; };//工厂函数实现extern"C" {IBase*createBase() {try{return newBaseImpl();} catch(...) {returnnullptr;}}voiddestroyBase(IBase* obj) {deleteobj;}} ```**使用动态库:**```cpp#include"IBase.h" #include<iostream> intmain(){//显式加载动态库(或隐式链接)IBase* obj= createBase();if(obj) {std::cout<< "Add:" <<obj->add(3,4) <<std::endl;obj->setValue(10);std::cout<< "Value:" <<obj->getValue() <<std::endl;destroyBase(obj);}return0;} ```**优点:**-隐藏了实现类`BaseImpl`,用户只能看到接口`IBase`。-工厂函数使用`extern "C"`,确保函数名不会因编译器不同而被修饰,便于跨语言调用和显式加载(使用`LoadLibrary`和`GetProcAddress`或`dlopen`和`dlsym`)[^3][^4]。###跨平台考虑- **Windows**:使用`__declspec(dllexport)`和`__declspec(dllimport)`。 -**Linux/macOS**:使用`__attribute__((visibility("default")))`导出,并在编译时添加`-fvisibility=hidden`选项隐藏其他符号。###编译命令示例(Linux)```bash#编译动态库g++ -shared -fPIC -fvisibility=hidden -o libmylib.so BaseImpl.cpp#编译使用程序g++ -o testmain.cpp -L.-lmylib```###总结导出类的成员函数有两种主要方式:1.**直接导出类**:简单直接,但会暴露类的实现细节(包括私有成员变量和函数在头文件中可见)。2.**接口类+工厂函数**:隐藏实现细节,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值