我在DLL中有一个类,它有一个我想在外部调用但不暴露类本身的方法。可以说我有以下课程:
// MyClass.h
class MyClass
{
public:
// ...
void SetNumber(int x);
private:
int _number;
};
// MyClass.cpp
// ...
MyClass::SetNumber(int x)
{
_number = x;
}
我想创建一个MyClass实例,并在DLL的整个生命周期中使用它。
// main.cpp
#define EXTERN extern "C" __declspec( dllexport )
int APIENTRY WinMain(/* ... */)
{
MyClass * myclass = new MyClass(); // I need to use this instance
// everytime in the exported SetNumber function.
return 0;
}
void EXTERN SetNumber(int x)
{
// Get myclass pointer
myclass.SetNumber(x);
}
现在我有两个想法,我不确定两者是否是一个好方法。
1)使用Singleton创建MyClass
的私有静态实例,并在我通过MyClass().Instance().SetNumber(x)
等调用导出的每个函数中使用它。静态实例是否可以免受外部使用?
2)链接时创建一个线程,并让线程响应我的每个导出函数将创建并推入公共队列的事件。这听起来像是一个重大的黑客。
有什么建议吗?
答案 0 :(得分:4)
您可以选择几种方式。我将在这里提供2个选项,其中DLL的客户端可以完全控制它使用的MyClass
实例的生命周期,尽管客户端不知道它实际上是MyClass
实例。
假设您公开的DLL暴露功能是 Flubber 。第一个选项是您使用包含以下内容的公共头文件Flubber.h传递DLL:
#ifdef FLUBBER_EXPORTS
#define FLUBBER_API __declspec(dllexport)
#else
#define FLUBBER_API __declspec(dllimport)
#endif
typedef struct FlubberHandle_* FlubberHandle;
FLUBBER_API FlubberHandle CreateFlubber();
FLUBBER_API void DestroyFlubber(FlubberHandle flubber);
FLUBBER_API void SetFlubberNumber(FlubberHandle flubber, int number);
实现如下:
#include "Flubber.h"
class MyClass
{
public:
void SetNumber(int x){ _number = x;}
private:
int _number;
};
struct FlubberHandle_
{
MyClass impl;
};
FLUBBER_API FlubberHandle CreateFlubber()
{
return new FlubberHandle_;
}
FLUBBER_API void DestroyFlubber(FlubberHandle flubber)
{
delete flubber;
}
FLUBBER_API void SetFlubberNumber(FlubberHandle flubber, int number)
{
flubber->impl.SetNumber(number);
}
构建DLL时,请定义FLUBBER_EXPORTS
宏。当客户端使用DLL时,它不能这样做,只是引用DLL导入库(Flubber.lib)并包含Flubber.h头文件:
#include <Flubber.h>
int main()
{
FlubberHandle flubberHandle = CreateFlubber();
... // Do lots of other stuff before setting the number
SetFlubberNumber(flubberHandle, 4711);
... // Do even more stuff and eventually clean up the flubber
DestroyFlubber(flubberHandle);
return 0;
}
这是让DLL客户端控制暴露某些功能的实例的生命周期的最简单方法。
但是,通过在适当的RAII类中“回退”FlubberHandle
管理,为您的DLL客户端提供更丰富的接口并不需要花费太多精力。如果“隐藏”MyClass
类公开了许多其他公开内容,您仍然可以选择仅在导出的免费函数中公开您选择的内容(CreateFlubber
,DestroyFlubber
和SetFlubberNumber
)和RAII课程:
#ifdef FLUBBER_EXPORTS
#define FLUBBER_API __declspec(dllexport)
#else
#define FLUBBER_API __declspec(dllimport)
#endif
typedef struct FlubberHandle_* FlubberHandle;
FLUBBER_API FlubberHandle CreateFlubber();
FLUBBER_API void DestroyFlubber(FlubberHandle flubber);
FLUBBER_API void SetFlubberNumber(FlubberHandle flubber, int number);
class Flubber final
{
FlubberHandle m_flubber;
public:
Flubber() : m_flubber(CreateFlubber()) {}
Flubber(const Flubber&) = delete;
Flubber& operator=(const Flubber&) = delete;
~Flubber() { DestroyFlubber(m_flubber); }
void SetNumber(int number) { SetFlubberNumber(m_flubber, number); }
};
使用RAII类,客户端对DLL及其API的体验将得到很大改善:
#include "stdafx.h"
#include <Flubber.h>
int main()
{
Flubber flubber;
... // Do lots of other stuff before setting the number
flubber.SetNumber(4711);
return 0;
}
最后一种方法是由Stefanus DuToit创造的“Hourglass Interface”(他在CppCon 2014上的“C ++ API的Hourglass Interfaces”演示文稿可以在https://www.youtube.com/watch?v=PVYdHDm0q6Y在线获得),而这只是你想拥有一个库的想法底部的“胖”/完整/丰富的C ++实现。您通过瘦C函数层向库客户端公开其功能。最重要的是,您还可以通过将C函数包装在适当的(通常是RAII)包装类中来为您的公开功能分发丰富的C ++接口。
答案 1 :(得分:0)
如果你不想暴露类本身,一个简单的解决方案是使用一个包装函数,它采用Jonathan Potter建议的不透明void *
指针。如果您希望从C代码调用它,或者您需要一个未编码的符号,您可以使用C链接选择性地声明它:
void setNumber (void *obj, int x) {
static_cast<MyClass *>(obj)->SetNumber(x);
}
或:
extern "C" void setNumber (void *obj, int x) {
static_cast<MyClass *>(obj)->SetNumber(x);
}
(顺便说一句,这是第一个C编译器只是C预编译器翻译方法调用的方式......)