討論區快速選單
知識庫快速選單
討論區最近新進100則主題 程式設計俱樂部Facebook粉絲團
[ 回上頁 ] [ 討論區發言規則 ]
callback的static問題
更改我的閱讀文章字型大小
作者 : frog(青蛙)
[ 貼文 60 | 人氣 5752 | 評價 40 | 評價/貼文 0.67 | 送出評價 11 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/7/3 下午 08:09:39
Hi,各位前輩
    用callback方式, 在stdt2class呼叫stdtMdfy, 執行到stdtMdfy的OnConfClick, 要回call stdt2class的stdtMdfyFinish(), 但stdtMdfyFinish()是static, 而函式中又用到no-static的OnQry, 所以編譯不過, 但stdtMdfyFinish()不設static也編譯不過
    己爬文許久, 做到這邊真的找不到解決方式了, 請各位前輩指點修正, 或有其他更好的方式可以達成這功能


stdtMdfy.h
    class stdtMdfy: public wxDialog{
     typedef void (*mdfyFinisk)(); // 宣告回呼函式的原型;
     mdfyFinisk pMdfyFinisk= NULL; ; // 宣告一個回呼函式指標;
public:
     void SetCallBack(mdfyFinisk fun) { pMdfyFinisk= fun; } // 註冊回呼函式;
     private:
     void OnConfClick(wxCommandEvent& event);
    }
stdtMdfy.cpp
    void stdtMdfy::OnConfClick(wxCommandEvent& event){
     (*pMdfyFinisk)();
    }

stdt2class.h
    class stdt2class: public wxDialog{
public:
     private:
     void OnQry(wxCommandEvent& event);
     static void stdtMdfyFinish();
    }
stdt2class.cpp
    stdtMdfy* stdtMdfy2ClassF;
    void stdt2class::OnQry(wxCommandEvent& event){
    }
    stdt2class::stdt2class(wxWindow* parent,wxWindowID id,const wxPoint& pos,const wxSize& size){
     stdtMdfy2ClassF = new stdtMdfy(this, wxID_ANY);
stdtMdfy2ClassF->SetCallBack(stdtMdfyFinish);
    }
    void stdt2class::stdtMdfyFinish(){
     wxCommandEvent event = wxCommandEvent(wxEVT_COMMAND_BUTTON_CLICKED, ID_btnQry);
OnQry(event);
    }
作者 : ice_emissary(燃燒的大地) 貼文超過200則
[ 貼文 357 | 人氣 0 | 評價 1730 | 評價/貼文 4.85 | 送出評價 16 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/7/4 下午 12:57:20
從原理說起…

# 第一部份:C++ 回呼

C++ 程式通常喜歡寫成員函式,當需要回呼時通常會要求傳入一個抽象類別物件,而使用者的實際回應通常被實做在該類別的衍生類,像是:

// 假設這是由某程式庫定義的回呼介面。
class EventCallbacks
{
public:
    virtual int SomeEvent(int args) = 0;
    virtual int AnotherEvent(int args) = 0;
};

// 以及該程式庫的回呼註冊函式。
void RegisterEvents(EventCallbacks *event);

// 然後使用者寫下真正的回呼工作。
class UsersAction : public EventCallbacks
{
public:
    virtual int SomeEvent(int args)
    {
     // Works...
     return 0;
    }

    virtual int AnotherEvent(int args)
    {
     // Works...
     return 0;
    }
};

// 並且使用者在某處註冊了類別。
int main(void)
{
    // ...
    UsersAction action;
    RegisterEvents(&action);
    // ...
    return 0;
}

# 第二部份:C 回呼

如果該程式庫是用 C 寫成的,我們都知道 C 語法上沒有類別這種東西,於是 C 程式庫通常會需要傳入函式指標,像這樣:

// 假設這是由某程式庫定義的回呼介面。
typedef int(*SomeEvent)(void *userarg, int args); // 註:那個 userarg 是重要的設計,後面會說明。
typedef int(*AnotherEvent)(void *userarg, int args); //

// 以及該程式庫的回呼註冊函式。
void RegisterSomeEvent(void *userarg, SomeEvent *event);
void RegisterAnotherEvent(void *userarg, AnotherEvent *event);

// 然後使用者寫下真正的回呼工作。
// (眼尖的讀者會發現這裡函式第一個參數的型態和前面宣告的不太一樣,
// 雖然後面會說明,但我想悟性較高的讀者看到參數名稱叫作 self 大概就知道怎麼回事了!)
int SomeEvent(struct UserObject *self, int args)
{
    // Works...
    return 0;
}

int AnotherEvent(struct UserObject *self, int args)
{
    // Works...
    return 0;
}

// 並且使用者在某處註冊了類別。
int main(void)
{
    // ...
    struct UserObject action;
    RegisterSomeEvent(&action, (int(*)(void*,int)) SomeEvent);
    RegisterAnotherEvent(&action, (int(*)(void*,int)) AnotherEvent);
    // ...
    return 0;
}

# 第三部份:C 回呼與物件導向

雖然 C 語言在語法上沒有類別,但不代表 C 語言程式人員就不渴望物件導向式程式設計。
知曉 C++ 底層結構原理的人應該知道,C++ 成員函式其實隱含一個表面上看不見的參數,第一個參數就是物件自己,也就是 this 指標。
透過這個機制,C++ 成員函式才能夠自由的取用成員變數。
既然如此,那 C 語言程式師當然也可以自己造一個 this 指標以及成員函式,不過就是變成顯示調用而已。
這就解釋了前面的 SomeEvent 和 AnotherEvent 函式的第一個 self 變數的作用,
這裡的 self 和 this 意義相同、用途一樣,取另一個名字只是為了不要與 C++ 關鍵字重名。

在回呼過程中,為了要讓這個設計能夠作用,回呼機制顯然必須要做些設計。
於是,回呼函式的原型被加入一個 userarg 參數,其意義就是由使用者定義的 self 指標;
由於程式庫無法預先知道使用者自行定義的類別到底長相為何?索性就以 void* 代表了!
然後在註冊回呼函式的時候,就會要求使用者傳入一個參數(userarg),這個參數的實際意義由使用者自己定義,
回呼機制只負責在呼叫回呼函式時,將該參數直接傳給函式的 userarg 參數。
這樣,使用者只要在註冊時傳入類別的指標,就可以在回呼函式中自由的取用 self 指標了。
除了一些不懂物件導向的 C 程式設計師會不知道這個 userarg 是幹什麼用的,甚至索性傳入 NULL 而忽略它的存在!

這制度看起來不錯,但有些人可能會有疑問:這個回呼機制需要仰賴程式庫的特別設計,但若那個程式庫不是我寫的,不就沒輒了?
理論上是這樣沒錯,但好在這個機制已經被大量的運用在各種回呼情境上,
目前主流的各種程式庫、特別是開源的程式庫幾乎都可以見到這種設計,例如 pthread。
只有在面對由不懂物件導向的人所設計出的程式庫時才需煩惱這個問題,這時除了用全域變數傳遞資料外別無他法!

# 第四部份:C++ 使用 C style 回呼

由於許多好用的程式庫都提供甚至只提供 C 介面,因此 C++ 使用者很容易會遇到需要與 C 的回呼機制打交道的情境,
而我想這也是樓主所遭遇問題的最初原因。
簡單的方法是照著 C 語言的方法建立全域函式,然後傳入程式庫供其呼叫。
當然 C++ 使用者通常不喜歡這樣,因為 C++ 使用者比較喜歡寫成員函式,
然而 C 語言又不支援 this call convention,而無法回呼成員函式。

這時,我們就會把類別中的某個函式宣告為 static,也就是靜態函式,
其實它就是傳統的 C 全域函式,因此不能夠取用類別變數或呼叫其他成員函式。
但若你讀懂了前面的說明,就會知道該如何在這種情況下變通!
我們只要把 this 指標透過 userarg 傳給這個靜態類別函式,這樣又可以在這個函式裡取用其他成員了!
把上面的範例依照這種情境修改如下:

// 假設這是由某程式庫定義的回呼介面。
typedef int(*SomeEvent)(void *userarg, int args); // 註:那個 userarg 是重要的設計,後面會說明。
typedef int(*AnotherEvent)(void *userarg, int args); //

// 以及該程式庫的回呼註冊函式。
void RegisterSomeEvent(void *userarg, SomeEvent *event);
void RegisterAnotherEvent(void *userarg, AnotherEvent *event);

// 然後使用者寫下真正的回呼工作。
class UsersAction
{
public:
    static int SomeEvent(UsersAction *self, int args)
    {
     // Works...
     return 0;
    }

    static int AnotherEvent(UsersAction *self, int args)
    {
     // Works...
     return 0;
    }
};

// 並且使用者在某處註冊了類別。
int main(void)
{
    // ...
    UsersAction action;
    RegisterSomeEvent(&action, (int(*)(void*,int)) action.SomeEvent);
    RegisterAnotherEvent(&action, (int(*)(void*,int)) action.AnotherEvent);
    // ...
    return 0;
}

就這樣,搞定!
不過有些人可能會覺得在 C++ 的回呼函式中還要不斷使用 self 指標而感到彆扭的話,其實可以再多家一層包裝,
在靜態回呼函式中呼叫自己的成員響應函式即可:

class UsersAction
{
public:
    // 這函式用來註冊。
    static int SomeEventRegistered(UsersAction *self, int args)
    {
     return self->SomeEvent(args);
    }

    // 這函式用來註冊。
    static int AnotherEventRegistered(UsersAction *self, int args)
    {
     return self->AnotherEvent(args);
    }

public:
    // 這才是真正的響應函式。
    virtual int SomeEvent(int args)
    {
     // Works...
     return 0;
    }

    // 這才是真正的響應函式。
    virtual int AnotherEvent(int args)
    {
     // Works...
     return 0;
    }
};
作者 : ma_hty(白老鼠(Gary))討論區板主 OpenGL卓越專家DirectX優秀好手C++頂尖高手貼文超過2000則人氣指數超過70000點
[ 貼文 2161 | 人氣 89850 | 評價 10090 | 評價/貼文 4.67 | 送出評價 78 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/7/4 下午 01:24:07
static member function 即是 收藏在 class定義之內的global function, 從 global function 的角度考慮, 當然不可以沒object去呼叫 member function 呀.

最簡易的修補方法, 就是多初始一個 wxDialog 的 global variable , 特地給 stdtMdfyFinish() 使用.

多出一個 global 變數 , 感覺不整齊的話, 你可以考慮在你的 class 之下, 加入 static 變數. static member variable 跟 static member function 的安排類似, 即是 收藏在 class定義之內的global variable .

^^" 其實何苦要迫自已把東西都包裝到 class定義之內 呢? global functions 和 global variables 也可以管理好 + 也可以好管理 的.
作者 : igbt(IGBT)
[ 貼文 7 | 人氣 0 | 評價 10 | 評價/貼文 1.43 | 送出評價 0 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/7/4 下午 03:22:58
請參考修改後的code, 應該ok 因為我常常在用
主要是this pointer觀念的運用
不過要注意stdt2class的life cycle
傳入註冊的this pointer是否一直有效

//stdtMdfy.h
class stdtMdfy: public wxDialog
{
    typedef void (*mdfyFinisk)(void* thisT); // 宣告回呼函式的原型;
    mdfyFinisk pMdfyFinisk= NULL; ; // 宣告一個回呼函式指標;
void *pthis;
public:
    void SetCallBack(mdfyFinisk fun, void* thisT)
    {
     pMdfyFinisk= fun; // 註冊回呼函式;
pthis = thisT;
    }
private:
    void OnConfClick(wxCommandEvent& event);
}
//stdtMdfy.cpp
void stdtMdfy::OnConfClick(wxCommandEvent& event)
{
    (*pMdfyFinisk)(pthis);
}

//stdt2class.h
class stdt2class: public wxDialog
{
public:
private:
    void OnQry(wxCommandEvent& event);
    static void stdtMdfyFinish(void *pthis);
}
//stdt2class.cpp
stdtMdfy* stdtMdfy2ClassF;
void stdt2class::OnQry(wxCommandEvent& event)
{
}
stdt2class::stdt2class(wxWindow* parent,wxWindowID id,const wxPoint& pos,const wxSize& size)
{
    stdtMdfy2ClassF = new stdtMdfy(this, wxID_ANY);
    stdtMdfy2ClassF->SetCallBack(stdtMdfyFinish, this);
}
void stdt2class::stdtMdfyFinish(void *p)
{
stdt2class *pthis = (stdt2class *)p;
    wxCommandEvent event = wxCommandEvent(wxEVT_COMMAND_BUTTON_CLICKED, ID_btnQry);
    pthis->OnQry(event);
}
作者 : frog(青蛙)
[ 貼文 60 | 人氣 5752 | 評價 40 | 評價/貼文 0.67 | 送出評價 11 次 ] 
[ 給個讚 ]  [ 給個讚 ]  [ 回應本文 ]  [ 發表新文 ]  [ 回上頁 ] [ 回討論區列表 ] [ 回知識入口 ]
2017/7/7 下午 06:28:27
Hi,
    感謝三位前輩的指點, 說真的理論看不懂, 都是用實作的方式學習
    雖看不懂前面二位前輩的理論, 但用這些資訊, 再去找其他資料, 終於有弄出來要的功能, 但煩瑣些
    igbt所提供的就完全了解, 一直卡關, 就在如何給this這個資料
    下面是我誤打誤撞作出來的, 也許也可參考
    不論如何,都感謝前輩們的指點


stdtMdfy.h
    #include "stdt2class.h"
    class stdtMdfy: public wxDialog{
public:
     void setObject(void* pObject);
     private:
     void* mObject;
    };
stdtMdfy.cpp
    void stdtMdfy::OnConfClick(wxCommandEvent& event){
     stdt2class::stdtMdfyFinish(mObject);
    }
    //給要回去物件的指標
    void stdtMdfy::setObject(void* pObject){
     mObject = pObject;
    }

stdt2class.h
    class stdt2class: public wxDialog{
     public:
     static void stdtMdfyFinish(void* pObject);
     private:
     void OnQry(wxCommandEvent& event);
    }
stdt2class.cpp
    stdtMdfy2ClassF = new stdtMdfy(this, wxID_ANY);
    stdtMdfy2ClassF->setObject(this); //將指標傳出

    void stdt2class::stdtMdfyFinish(void* pObject){
     stdt2class* pclass = (stdt2class*)pObject;
wxCommandEvent event1 = wxCommandEvent(wxEVT_COMMAND_BUTTON_CLICKED, ID_btnQry);
pClass->OnQry(event1);
    }
 板主 : simula
 > C++ - 討論區
 - 最近熱門問答精華集
 - 全部歷史問答精華集
 - C++ - 知識庫
  ■ 全站最新Post列表
  ■ 我的文章收藏
  ■ 我最愛的作者
  ■ 全站文章收藏排行榜
  ■ 全站最愛作者排行榜
  ■  月熱門主題
  ■  季熱門主題
  ■  熱門主題Top 20
  ■  本區Post排行榜
  ■  本區評價排行榜
  ■  全站專家名人榜
  ■  全站Post排行榜
  ■  全站評價排行榜
  ■  全站人氣排行榜
 請輸入關鍵字 
  開始搜尋
 
Top 10
評價排行
C++
1 Raymond 13050 
2 青衫 4760 
3 simula 4690 
4 coco 4030 
5 白老鼠(Gary) 3670 
6 ozzy 2540 
7 Ben 2250 
8 Anderson 1960 
9 windblown 1650 
10 Kenny 1560 
C++
  專家等級 評價  
  一代宗師 10000  
  曠世奇才 5000  
  頂尖高手 3000  
  卓越專家 1500  
  優秀好手 750  
Microsoft Internet Explorer 6.0. Screen 1024x768 pixel. High Color (16 bit).
2000-2018 程式設計俱樂部 http://www.programmer-club.com.tw/
0.15625