2015-10-14 64 views
2

我有一個DLL中的類有一個方法,我想調用外部但不暴露類本身。可以說我有以下類:從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)使用一個單例,我創建一個MyClass的私有靜態實例,並在通過調用如MyClass().Instance().SetNumber(x)的調用導出的每個函數中使用它。外部使用的靜態實例是否安全?

2)鏈接時創建一個線程,讓線程響應每個我導出的函數創建並推入公共隊列的事件。這聽起來像是一個重大的黑客攻擊。

有什麼建議嗎?

+0

第三個選項是不透明接口;例如一個函數返回類的一個新實例強制轉換爲'HANDLE',並在DLL中使用'HANDLE'作爲參數並通過它調用類方法的幫助器函數(顯然還有一個函數釋放了'HANDLE' )。 –

+1

作爲一個單身人士是一個階級的固有屬性。它不取決於你是否想用特定的技術公開它的方法。如果它本質上是一個單身人士,那麼繼續將它編程爲一個單身人士。至於你的第二個想法,它基本上是「讓我們以一種非常模糊和迂迴的方式來創建一個單身人士」。 –

回答

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客戶端的最簡單方法控制暴露某些功能的實例的生命週期。

但是,通過將FlubberHandle管理「包裝回」到正確的RAII類中,爲您的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迪圖瓦創造「沙漏接口」(他的演講「沙漏接口,用於C++的API」在CppCon 2014可用在線在https://www.youtube.com/watch?v=PVYdHDm0q6Y),而且很簡單你有一個在底部有一個「胖」/完整/豐富的C++實現的庫。您可以通過精簡的C函數層將其功能展示給庫客戶端。最重要的是,您還可以通過將C函數包裝在適當的(通常是RAII)包裝類中,將豐富的C++接口分發到您公開的功能。

+0

RAII類很漂亮,但是這個DLL的主要用例是在Python中使用,我聽說將類導出到其他語言並不明智,除非您可以確認設置始終完全相同。我期待消費者不得不使用初始化和清理函數...... –

+1

如果您希望庫中有1個全局實例_internally_並始終使用該實例,而不是讓客戶端創建並銷燬不透明的句柄類型,就像我的例子那樣,那麼你可以在庫的'init'函數中創建它,並在庫'deinit'函數中銷燬它,然後在沒有客戶端看到它的情況下使用該實例_side_暴露的「C」函數。 –

0

如果你不想公開這個類本身,一個簡單的解決方案是使用一個包裝函數,採用Jonathan Potter建議的不透明void *指針。如果你希望它是從C代碼調用,或者如果您需要未重整符號,你可以optionnally 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預編譯器翻譯的方法調用...)