从 DLL 中的类调用方法而不暴露类

Rig*_*let 2 c++ dll dllexport

我在 DLL 中有一个类,它有一个我想从外部调用但不公开类本身的方法。假设我有以下课程:

// MyClass.h
class MyClass
{
public:
    // ...
    void SetNumber(int x);
private:
    int _number;
};

// MyClass.cpp
// ...
MyClass::SetNumber(int x)
{ 
    _number = x;
}
Run Code Online (Sandbox Code Playgroud)

我想创建 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);
}
Run Code Online (Sandbox Code Playgroud)

现在我有两个想法,我不确定这是否是一个好方法。

1)使用单例创建一个私有静态实例,MyClass并在我通过调用导出的每个函数中使用它MyClass().Instance().SetNumber(x)。外部使用静态实例是否安全?

2)在链接时创建一个线程,并让该线程响应我的每个导出函数将创建并推送到公共队列中的事件。这听起来像是一个重大的黑客攻击。

有什么建议?

Joh*_*ell 5

您有多种选择。我将在此处提供 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);
Run Code Online (Sandbox Code Playgroud)

实现如下所示:

#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);
}
Run Code Online (Sandbox Code Playgroud)

构建 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;
}
Run Code Online (Sandbox Code Playgroud)

这是让 DLL 客户端控制公开某些功能的实例的生命周期的最简单方法。

但是,通过将FlubberHandle管理“包装”在适当的 RAII 类中,为您的 DLL 客户端提供更丰富的接口并不需要太多努力。如果“隐藏”MyClass类公开了许多其他公共事物,您仍然可以选择仅公开您在导出的自由函数(CreateFlubber,DestroyFlubberSetFlubberNumber)和 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); }
};
Run Code Online (Sandbox Code Playgroud)

使用 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;
}
Run Code Online (Sandbox Code Playgroud)

最后一种方法是 Stefanus DuToit 创造的“Hourglass Interface”(他在 CppCon 2014 上的演讲“Hourglass Interfaces for C++ APIs”可在https://www.youtube.com/watch?v=PVYdHDm0q6Y在线获得),它只是想法你有一个库,在底部有一个“胖”/完整/丰富的 C++ 实现。您可以通过一个薄的 C 函数层将其功能公开给库客户端。最重要的是,您还可以通过将 C 函数包装在适当的(通常是 RAII)包装类中来为公开的功能分发丰富的 C++ 接口。