从 DLL 中的 Class 调用方法而不公开 Class
Call method from Class in DLL without exposing Class
我在 DLL 中有一个 class,它有一个我想在外部调用但不公开 class 本身的方法。假设我有以下 class:
// 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) 在链接时创建一个线程,并让线程响应我的每个导出函数将创建并推送到 public 队列中的事件。这听起来像是一个大黑客。
有什么建议吗?
如果您不想公开 class 本身,一个简单的解决方案是按照 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++ 编译器如何翻译方法调用...)
您有多种选择。我将在这里展示 2 个选项,其中 DLL 的客户端可以完全控制它使用的 MyClass
实例的生命周期,尽管客户端不知道它实际上是一个 MyClass
实例。
假设您的 DLL 公开功能 public 被称为 Flubber。第一个选项是您使用包含以下内容的 public 头文件 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 客户端控制公开特定功能的实例的生命周期的最简单方法。
然而,通过 "wrapping back" FlubberHandle
适当的 RAII class 管理为您的 DLL 客户端提供更丰富的接口并不需要太多努力。如果 "hidden" MyClass
class 暴露了许多其他 public 东西,你仍然可以选择只暴露你在导出的自由函数中选择的东西(CreateFlubber
,DestroyFlubber
和 SetFlubberNumber
) 和 RAII class:
#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 class,客户端对您的 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 上的演讲 "Hourglass Interfaces for C++ APIs" 可在 https://www.youtube.com/watch?v=PVYdHDm0q6Y 在线获取),这只是您拥有一个图书馆的想法底部有一个 "fat"/full/rich C++ 实现。您通过薄 C 函数层将其功能公开给库客户端。最重要的是,您还通过将 C 函数包装在适当的(通常是 RAII)包装器 class.
中,将丰富的 C++ 接口分发给公开的功能。
我在 DLL 中有一个 class,它有一个我想在外部调用但不公开 class 本身的方法。假设我有以下 class:
// 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) 在链接时创建一个线程,并让线程响应我的每个导出函数将创建并推送到 public 队列中的事件。这听起来像是一个大黑客。
有什么建议吗?
如果您不想公开 class 本身,一个简单的解决方案是按照 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++ 编译器如何翻译方法调用...)
您有多种选择。我将在这里展示 2 个选项,其中 DLL 的客户端可以完全控制它使用的 MyClass
实例的生命周期,尽管客户端不知道它实际上是一个 MyClass
实例。
假设您的 DLL 公开功能 public 被称为 Flubber。第一个选项是您使用包含以下内容的 public 头文件 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 客户端控制公开特定功能的实例的生命周期的最简单方法。
然而,通过 "wrapping back" FlubberHandle
适当的 RAII class 管理为您的 DLL 客户端提供更丰富的接口并不需要太多努力。如果 "hidden" MyClass
class 暴露了许多其他 public 东西,你仍然可以选择只暴露你在导出的自由函数中选择的东西(CreateFlubber
,DestroyFlubber
和 SetFlubberNumber
) 和 RAII class:
#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 class,客户端对您的 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 上的演讲 "Hourglass Interfaces for C++ APIs" 可在 https://www.youtube.com/watch?v=PVYdHDm0q6Y 在线获取),这只是您拥有一个图书馆的想法底部有一个 "fat"/full/rich C++ 实现。您通过薄 C 函数层将其功能公开给库客户端。最重要的是,您还通过将 C 函数包装在适当的(通常是 RAII)包装器 class.
中,将丰富的 C++ 接口分发给公开的功能。