2010-11-20 33 views
1

我必須在庫API中設置一個函數指針,以便在需要執行特定操作時調用該函數。使用指針指向成員函數的困境

int (*send_processor)(char*,int); 
int setSendFunctor(int (*process_func)(char*,int)) 
    { 
     send_processor = process_func; 
    } 

以我主要來源,我有定義爲一個類的成員函數這一功能,

int thisclass::thisfunction(char* buf,int a){//code} 

thisfunction取決於其他功能thisclass。所以我不能把它從課堂上分離出來。

我無法設置指向setSendFunctor中成員函數的指針,因爲在API內部,它不會將它分配給成員函數的指針,而是分配給常規函數指針send_processor。

由於該類不是派生類,因此我無法使用虛函數。

處理這個問題的最佳方法是什麼?

+0

感謝您的回覆。對於這個問題,我無法使用Boost或任何其他第三方庫。 – SkypeMeSM 2010-11-20 20:38:06

+0

你可以改變你使用的庫嗎? – Stewart 2010-11-20 20:47:17

+0

無論如何,boost庫僅在這個特定情況下提供有限的幫助。提到它的答案誤解了你的問題。無論如何,那些可能以任何方式提供幫助的部分都是C++ TR1的一部分,您的編譯器可能會支持這一點。這是標準的一部分。 – Omnifarious 2010-11-20 21:44:36

回答

3

我通常處理這個問題的方法是在我的類中創建一個靜態函數,它將一個明確的this指針作爲參數,然後簡單地調用成員函數。有時候,靜態函數需要緊跟C庫期望的回調類型簽名,在這種情況下,我會將這種情況下常見的void *轉換爲我真正想要的this指針,然後調用成員函數。

如果API沒有設備給它一個void *,然後將返回給您的回調,該API已損壞,需要修復。如果你不能解決它,有選擇涉及全球(或他們的小表弟static)變量。但他們很醜。

我曾經創建過一個相當黑客的模板系統,用於創建這些類型的變量,並將靜態函數綁定到它們,然後您可以將它們傳遞給具有此類API的東西。但我不得不去追捕它,我並不確信它爲一個普通的舊的全球提供了任何好處。

基於這種想法,而無需使用模板會看起來像這樣的解決方案:

class InstanceBinder1 { 
    public: 
    static initialize(thisClass *instance, int (thisClass::*processor)(char *buf, int a)) { 
     instance_ = instance; 
     processor_ = processor; 
    } 

    static int processor(char *instance, int a) { 
     instance_->*processor_(buf, a); 
    } 

    private: 
    static thisClass *instance_; 
    static int (thisClass::*processor_)(char *buf, int a); 
}; 

然後,你可以在&InstanceBinder1::processor傳遞給C庫。

您需要爲每個需要C庫調用的單獨的thisClass實例創建一個此類新類。這意味着這些實例的數量將在編譯時確定,並且沒有辦法解決。

2

不幸的是,沒有辦法像這樣將一個指向成員的指針傳遞給C API,因爲C api不知道如何處理這個指針。如果你不能改變圖書館,你會陷入困境。

如果API提供了一種通過庫傳遞不透明「上下文」指針的方法(就像在Windows中使用CreateThread一樣,其中參數被OS視爲傳遞的指針大小的數字),那麼你應該使用一個靜態成員函數,並使用這個上下文參數來傳遞你的指針。在靜態成員函數中,將上下文參數轉換回指針並通過它調用成員函數。如果圖書館可以由您更改,或者您可以讓某人更改它,並且它不提供此功能,您應該添加它,因爲這是橋接對象與C之間的最佳方式。

不幸的是,您的API似乎沒有提供這樣做的方法。如果您的應用程序是單線程的,並且可以保證同時沒有重入或多個庫的用戶,那麼您可能能夠將該指針存儲在全局或類靜態成員中,然後您可以做到這一點,並再次使用靜態成員函數作爲回調並通過參數調用。

第三種方法,絕對的最後手段,可能會產生「咚」的對象,這是實際上生成可執行代碼來配置this指針,並跳轉到正確的成員函數的小物件。在具有DEP和現代事物的現代系統上,這是一個很難解決的問題,而這種方法很可能不值得這樣麻煩。 ATL庫在Windows上執行此操作,但這很困難,多年來已經導致它們出現很多安全問題和更新問題。

+0

請注意,我假設你不能更改庫。如果你可以改變圖書館,你應該這樣做。 – Stewart 2010-11-20 20:39:17

+0

您的評論假定您無法編輯答案。如果你可以編輯答案,你應該這樣做。 *咧*當然,我也可以編輯答案,但... – Omnifarious 2010-11-20 21:40:24

+0

@Omnifarious - 好點,做得好:)。編輯作出回答。謝謝你提醒我。 – Stewart 2010-11-21 18:44:10

1

最簡單的方法是創建一個函數(「調用者」和「設置者」)來設置全局變量的值。您可以使用setter在程序中的某個點設置全局值,以便構建要調用成員函數的對象。調用者將被傳遞給setSendFunctor,並在調用時將該調用轉發給全局變量的值。

我故意沒有談到高於全球的類型;可以是任何你可能希望它是(普通的舊函數指針,一個成員函數指針,從升壓或其他一些圖書館,或其他任何可方便仿函數)。

不幸的是,我不認爲有沒有辦法做到這一點沒有全球性,因爲C API相當有限。

+0

正如我在我的回覆中指出的那樣,如果絕對沒有機會再次調用API或通過多個線程同時調用API,這將是安全的。如果這兩件事情是真實的並且永遠是真的,那麼這是一個很好的解決方案。如果不是,這個解決方案會給你帶來很多問題。 – Stewart 2010-11-20 20:48:30

+0

沒有什麼能阻止你實現你在調用者和設置者中選擇的任何線程同步機制。 – Jon 2010-11-21 02:31:41

0

我不確切知道char *和int用於什麼。如果它們是由C API生成的,那麼就沒有辦法傳遞任何「上下文」信息。

如果其中一個引用了你傳入的東西並被回叫給你,那麼你可以將它們映射回原來的「this」或一個包含「this」的結構以及其他一些char *或int 。

1

如果一切都失敗了,並且您的庫只能使用沒有客戶端定義數據的函數指針,並且您想要在多個對象上獲得回調,並且回調的生命週期重疊,那麼您可以使用蹦牀。

我通常這樣做的方式(實際上,我只需要做幾次,所以它通常不夠常見,因爲大多數圖書館都足夠理智地通過一些當你設置回調void *的用戶數據)是編譯一個硬編碼指針的函數在它:步進到它並獲得

int send_processor_trampoline (char* buf, int a) 
{ 
    return ((thisclass*) 0x12345678) -> thisfunction (buf, a); 
} 

然後,您可以檢查機器代碼此功能(在Windows函數指針的值,然後檢查該位置的內存)。通常情況下,您會有幾十個字節,其中硬編碼指針的位置很明顯。如果不是,請更改指針值並區分它們。因此,對給定對象的成員函數的調用將具有相同的機器代碼,但硬編碼指針的字節將替換爲指向接收器對象的指針的字節。

若要在給定一個指向thisclass對象運行蹦牀,抓住一些可寫的,從OS(通常你在大塊得到這個可執行內存,所以創建一個管理器對象爲蹦牀,所以你不用爲4K每個20字節的函數),將這些字節複製到其中,將硬編碼指針的字節替換爲對象的指針。您現在有一個指向可以調用的自由函數的指針,該指針將調用特定對象上的成員函數。