2009-01-31 83 views
4

我有一個外部庫中的函數,我不能用下面的簽名更改:傳遞一個合格的非靜態成員函數的函數指針

void registerResizeCallback(void (*)(int, int)) 

我想在成員函數傳遞作爲回調,因爲我的回調需要修改實例變量。

顯然,這是不可能用一個簡單的:

registerResizeCallback(&Window::Resize); 

,所以我真的不知道如何解決這個問題。

+1

在C中沒有什麼是不可能的。你的問題引起了我的興趣,所以我寫了一個關於解決它的黑客方式的博客:http://nothingintoinsight.blogspot.com/2009/02/how-to-hack-closures-in-your-c-code-or。 html – user51568 2009-02-01 20:14:43

回答

9

由於Igor Oks indicates,你不能這樣做。這個問題的其餘部分不是解決您的問題的答案,而是討論如何使用正確設計的回調API(看起來您使用的不是)。

大多數精心設計的回調接口允許您提供「void *」或其他方式來獲取回調中的上下文。在C++中使用這種方法的一個常見方法是在上下文參數中傳遞一個對象指針,然後回調函數可以將它轉換回對象指針並調用成員方法來完成真正的工作。你使用的回調API太糟糕了,不能提供上下文數據。

嚴格地說,回調必須是extern "C",但使用靜態成員方法進行回調很常見,我認爲在實踐中從來沒有問題。 (這是假定回調API是一個C接口,這是迄今爲止最常見的)。

一個例子:

// callback API declaration's 

extern "C" { 
    typedef unsigned int callback_handle_t; 

    typedef void (*callback_fcn_t)(void* context, int data1, int data2); 

    callback_handle_t RegisterCallback(callback_fcn_t, void* context); 
    void UnregisterCallback(callback_handle_t); 
} 

// ---------------------------------- 

// prototype for wrapper function that will receive the callback and 
// transform it into a method call 

extern "C" 
static void doWorkWrapper(void* context, int data1, int data2); 


// the class that does the real work 

class worker { 
public: 
    worker() { 
     hCallback = RegisterCallback(doWorkWrapper, this); 
    } 

    ~worker() { 
     UnregisterCallback(hCallback); 
    } 

    void doWork(int data1, int data2) { 
     // ... 
    }; 

private: 
    callback_handle_t hCallback; 
}; 

// the wrapper that transforms the callback into a method call 
extern "C" 
static void doWorkWrapper(void* context, int data1, int data2) 
{ 
    worker* pWorker = static_cast<worker*>(context); 

    pWorker->doWork(data1, data2); 
} 
1

擴大邁克爾伯爾的建議,你將不得不找出一個非成員函數如何獲得對正在修改的對象的實例訪問。一個常用的方法是利用靜態全局範圍在C:

// Top of your .c module: 
static Window *gMyWindow; 

// The declaration 
extern "C" { 
    void* my_callback(int, int); 
} 

// Later, set it just before handing off the callback 
void somefunc() { 
    ... 
    gMyWindow = &windowObjectRef; 
    registerResizeCallback(my_callback); 
    windowObjectRef.SomeOtherWindowCallCausingCallbackInvoke(); 
    ... 
} 

// The callback in the same .c module as the global 
void my_callback(int x, int y) { 
    Window *object = gMyWindow; 
    object->Resize(x, y); 
} 

我還沒有編譯/運行上面的代碼,所以有可能在細節上的調整,但希望這個概念是明確的:回調必須是C和C++之間的橋樑,然後問題是如何讓對象「進入」回調成爲成員函數調用的實例。

在您的環境中可能還有其他原因,爲什麼上面的全局示例不起作用,那麼您的任務是找出除了全局變量之外的其他機制將允許您根據您的情況將對象傳入回調。