从 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 东西,你仍然可以选择只暴露你在导出的自由函数中选择的东西(CreateFlubberDestroyFlubberSetFlubberNumber) 和 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++ 接口分发给公开的功能。