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

时间:2015-10-14 05:52:57

标签: c++ dll dllexport

我在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)链接时创建一个线程,并让线程响应我的每个导出函数将创建并推入公共队列的事件。这听起来像是一个重大的黑客。

有什么建议吗?

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类公开了许多其他公开内容,您仍然可以选择仅在导出的免费函数中公开您选择的内容(CreateFlubberDestroyFlubberSetFlubberNumber )和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预编译器翻译方法调用的方式......)