1. 程式人生 > >如何讓C++類的成員函式作為回撥函式

如何讓C++類的成員函式作為回撥函式

Element Implementation
Argument-passing order Right to left.
Argument-passing convention By value, unless a pointer or reference type is passed.
Stack-maintenance responsibility Called function pops its own arguments from the stack.
Name-decoration convention An underscore () is prefixed to the name. The name is followed by the at sign (@) followed by the number of bytes (in decimal) in the argument list. Therefore, the function declared as int func( int a, double b ) is decorated as follows: _func
@12
Case-translation convention None

其中心思想是,_stdcall修飾的函式,引數從右至左依次壓入堆疊,被呼叫者(callee)負責平衡堆疊(clean also called ‘stack unwinding handling’)。

下面來看看類的成員函式有怎樣的特點。在VC++中,所有類的成員函式在定義的時候都被隱式(implicit)定義為__thiscall引數傳遞方式。在MSDN 中對__thiscall做了如下定義:The _thiscall calling convention is used on member functions and is the default calling convention used by C++ member functions that do not use variable arguments. Under _thiscall, the callee cleans the stack, which is impossible for vararg functions. Arguments are pushed on the stack from right to left, with the this pointer being passed via register ECX, and not on the stack, on the x86 architecture.

其中心思想是,_thiscall 修飾的函式引數從右至左依次壓入堆疊,被呼叫者負責平衡堆疊。之後是與C語言所有引數傳遞方式均不相同的一點:成員函式所在類的this指標被存入ecx暫存器(這個特性只針對Intel x86架構)。

對比之後,我們發現類成員函式不能作為回撥函式的主要原因在於類成員函式使用__thiscal引數傳遞方式,因此需要呼叫者(caller)通過ecx暫存器提供類物件的指標。而回調函式使用__stdcall引數傳遞方式,不具備這個特點。

如何讓類成員函式成為回撥函式

根據第一節對回撥函式與類成員函式各自特點的分析。不難發現,只要能想辦法在類成員函式被呼叫之前設定好ecx暫存器,就能在_stdcall呼叫的基礎上模擬出一個完好的_thiscall呼叫。

如何提前設定ecx暫存器呢?我們知道函式呼叫實際是通過彙編指令(oprand)’call 函式地址’完成的。因此我們可以提供一箇中間函式。當回調發生時,先呼叫中間函式,再在中間函式執行過程中設定ecx暫存器,當ecx設定好後jmp到類 成員函式去(注意:這裡是jmp不是call)。當執行到類的成員函式時,函式上下文(function context)就和_thiscall所產生的完全一樣了。

如何製作這個中間函式呢?普通的函式是不行的。主要因為在vc++ debug版本的程式碼中要使用ecx暫存器做堆疊溢位檢測(stack overflow detect),即使是空函式都是如此。其次由於存在棧框(stack frame)效率也不高。

這時就需要使用thunk來達到我們的目的。所謂thunk就是程式自己生成並執行的一小段彙編程式碼。下面通過程式碼來理解thunk。


#include "windows.h"
#include "stdio.h"
#include "stdlib.h"
#include "assert.h"
#include "stdafx.h"
//////////////////////////////////////////////////////////////////////////
// 回撥函式型別定義
typedef int (CALLBACK pfaCallBack)(int, long, char);
//////////////////////////////////////////////////////////////////////////
// thunk 結構定義
// 由於thunk 要被當作程式碼來執行,因此thunk 結構必須是位元組對齊的,這裡使用
// VC++ 的修飾符號#pragma pack(push, 1) 來定義一個位元組對齊的結構體
// 之後通過#pragma(pop) 恢復預設對齊模式
#pragma pack(push, 1)
struct Thunk
{
     BYTE op_movecx;
     DWORD_PTR val_ecx;
     BYTE op_call;
     DWORD_PTR val_address;
};
#pragma pack(pop)
//////////////////////////////////////////////////////////////////////////
// 一個類的定義,就這樣平靜的開始了
class Dummy {
// 一個成員變數
private:
     int m_id ;
// 定義一個thunk
private:
     Thunk m_thunk;
// 定義建構函式,在建構函式中設定m_id值
public:
     Dummy(int id):m_id(id)
     {
     }
//////////////////////////////////////////////////////////////////////////
// 定義一個回撥函式,另外他還是個類的成員函式呢
public:
     int memberCallback(int intVal, long longVal, char charVal)
     {
         // 做自己想做的事情
         printf("\nI am a member function of class Dummy""(Dummy::memberCallback),ID = %d.""\nI got the value 0x%08x 0x%08x \'%c\'"
             , m_id, intVal, longVal, charVal);
         return m_id;
     }
//////////////////////////////////////////////////////////////////////////
// 初始化thunk 的資料,這裡是關鍵
public:
     void InitThunk()
     {
         // 0xB9是‘mov ecx, 數值’的機器碼,xB9之後的個位元組(32位)指定了要
         // 給ecx的數值.
         m_thunk.op_movecx = 0xB9;
         // 填寫要給ecx的數值為this(類物件的指標)
         m_thunk.val_ecx = (DWORD_PTR)this;
         // 0xE9是‘jmp 相對地址’的機器碼。相對地址由xE9之後的個位元組(32位)
         // 給出。
         m_thunk.op_call = 0xE9;
         // 獲得Dummy::memberCallback的具體地址。關於成員函式與類物件的關係
         // 請參閱Stan Lippman 的<<Inside C++ Object Model>>
         // 用匯編獲得地址省去了用C++帶來的難看的語法
         DWORD_PTR off = 0;
        _asm
         {
                 mov eax, Dummy::memberCallback          
                 mov DWORD PTR [off], eax
         }
         // jmp後面是相對地址,因此要求出這個地址
         // 相對地址=成員函式地址-跳轉點下一指令地址
         // 正負號不要緊,jmp自己能根據正負判斷如何跳。
         m_thunk.val_address =
             off - ( (DWORD_PTR)(&m_thunk.val_address) + sizeof(DWORD_PTR) );
     }
//////////////////////////////////////////////////////////////////////////
// 返回thunk的地址給要回調他的函式。
// 那個函式還以為thunk是一個函式地址呢。根本不知道thunk是我們自己構造的
// 資料
public:
     pfaCallBack GetStaticEntry()
     {
         return (pfaCallBack)&m_thunk;
     }
};
//////////////////////////////////////////////////////////////////////////
// 一個呼叫回撥函式的函式
void Trigger(pfaCallBack callback)
{
     assert(callback);
     int intVal = 0x1234;
     int longVal = 0x5678ABCD;
     int charVal = 'D';
     // 函式內部
     int r;
     // 開始回撥
     r = callback(intVal, longVal, charVal);
     printf("\n Return value = %d\n", r);
}
//////////////////////////////////////////////////////////////////////////
// 傳說中的主函式。VC++工程裡生成的就叫_tmain不叫main。
int _tmain(int argc, _TCHAR argv[])
{
     //生成一個物件
     Dummy *dummy1 = new Dummy(9);
     //初始化thunk
     dummy1->InitThunk();
     //取得thunk地址
     pfaCallBack pCallback1 = dummy1->GetStaticEntry();
     //給需要回調函式的函式傳遞thunk
     Trigger(pCallback1);
     // 按任意鍵繼續...
     system("pause");
     return 0;
}

相關推薦

如何C++成員函式作為函式

Element Implementation Argument-passing order Right to left. Argument-passing convention By value, unless a pointer or reference type is passed. Stack-ma

C++中 執行緒函式為靜態函式成員函式作為函式

 執行緒函式為靜態函式:   執行緒控制函式和是不是靜態函式沒關係,靜態函式是在構造中分配的地址空間,只有在析構時才釋放也就是全域性的東西,不管執行緒是否執行,靜態函式的地址是不變的,並不在執行緒堆疊中static只是起了一個裝飾的作用,所以二者並沒有必然的關係   執行緒也是一種

C++的中使用成員函式作為函式

由於類有隱式的this指標,所以不能直接把類成員函式作為回撥函式使用。現用一例子來展示如何在類中使用類成員函式作為回撥函式。 此例子僅用於展示如何在類中使用類成員函式作為回撥函式 程式碼如下: #include "stdafx.h" #include

C++成員函式作為函式說起

在網路訊息處理中經常要用到回撥機制。 例如處理非同步網路操作的前攝器設計模式(Proactor),(可以參考 《C++ 網路程式設計 卷2》中關於ACE Proactor模式實現 )。 非同步的 Web 伺服器將這樣來利用前攝器模式:首先讓 Web 伺服器向 OS 發出非同步

成員函式作為函式(外一篇:友元函式

問題的提出  我們已知道類具有封裝和資訊隱藏的特性。只有類的成員函式才能訪問類的私有成員,程式中的其他函式是無法訪問私有成員的。非成員函式可以訪問類中的公有成員,但是如果將資料成員都定義為公有的,這又破壞了隱藏的特性。另外,應該看到在某些情況下,特別是在對某些成員函式多次呼叫時,由於引數傳遞,型別檢查和安全性

如何使 成員函式作為函式

如果試圖直接使用C++的成員函式作為回撥函式將發生錯誤,甚至編譯就不能通過。其錯誤是普通的C++成員函式都隱含了一個傳遞函式作為引數,亦即“this”指標,C++通過傳遞this指標給其成員函式從而實現程式函式可以訪問C++的資料成員。這也可以理解為什麼C++類的多個例項

如何實現成員函式作為函式

回撥函式(Callback   function)大量用於Windows的系統服務,通過它,程式設計師可以安裝裝置驅動程式和訊息過濾系統,以控制Windows的有效使用。許多程式設計師都發現,利用MFC或者其它的C++應用編寫回調函式是非常麻煩的,其根本原因是回撥函式是基於

QT中成員函式作為函式

    這裡主要實現的功能:需要設計一個外掛,把外掛內的資料通過函式指標引數的方式傳遞到另外一個類中,顯示出來,使用回撥函式的方式 http://blog.csdn.net/ksn13/article/details/40538083,程式碼的邏輯和上述網站的第三種方法一樣

C++成員函式作為函式的問題

1. 程式設計分兩類 一,應用程式設計和系統程式設計 系統程式設計就是編寫底層的庫, 應用程式設計就是利用已經編寫好的庫的介面來編寫某種具有某些功能的程式,即應用 所謂的庫,就是為了給應用提

函式作為函式發現的問題

最近在做一個專案用到圓剛的視訊採集卡,需要對其sdk進行二次開發,拿到幀進行處理。 按照要求,回撥函式是靜態函式,自己定義了回撥函式,然後把demo的程式碼粘過來,編譯,報錯!!顯示靜態函式不能非法引用變數。 無奈,諮詢技術,說”靜態函式只能呼叫靜態變數,而且想要在靜態函式

c++成員函式函式為啥要申明為static的

  眾所周知,C++的類成員函式不能像普通函式那樣用於回撥,因為每個成員函式都需要有一個物件例項去呼叫它。         通常情況下,要實現成員函式作為回撥函式,一種常用的方法就是把該成員函式設計為靜態成員函式,但這樣做有一個缺點,就是會破壞類的結構性,因為靜態成員

什麼是函式,如何定義和實現一個成員函式函式(轉)

C/C++中回撥函式初探    簡介   對於很多初學者來說,往往覺得回撥函式很神祕,很想知道回撥函式的工作原理。本文將要解釋什麼是回撥函式、它們有什麼好處、為什麼要使用它們等等問題,在開始之前,假設你已經熟知了函式指標。   什麼是回撥函式?   簡而言之,回撥函

如何實現成員函式函式

  如果試圖直接使用C++的成員函式作為回撥函式將發生錯誤,甚至編譯就不能通過。通過查詢資料發現,其錯誤是普通的C++成員函式都隱含了一個傳遞函式作為引數,亦即“this”指標,C++通過傳遞this指標給其成員函式從而實現程式函式可以訪問C++的資料成員。這也可以

關於 C#呼叫C庫Dll,有函式時,只執行一次函式就直接掛掉 的解決方法

錯誤         直接當機,如下圖:           錯誤原因        回撥函式宣告原因,跟堆疊有關係  

C 函式指標 函式

    http://www.cnblogs.com/chenyuming507950417/archive/2012/01/02/2310114.html  今天討論下C/C++中的回撥函式。     &

C#委託,事件與函式

using System;using System.Collections.Generic;using System.Text;namespace TestApp{    ///<summary>/// 委託    ///</summary>///<param name="s1

C#呼叫C/C++ DLL 引數傳遞和函式的總結

Int型傳入: Dll端: extern "C" __declspec(dllexport) int Add(int a, int b) {     return a+b; } C#端: [DllImport("aeClient2.0.dll", CallingCo

詳解C#委託,事件與函式

.Net程式設計中最經常用的元素,事件必然是其中之一。無論在ASP.NET還是WINFrom開發中,窗體載入(Load),繪製(Paint),初始化(Init)等等。“protected void Page_Load(object sender, EventArgs e)”這

c++函式/ROS函式

以下均是個人在實際耕碼的過程中遇到的問題和整理的結果,可能會有不對的地方,望各位指正與交流 ------------------------------------------------------------------我會有喵的--------------------

11月6日排序函式,匿名函式函式,遞迴函式, zip函式

##### 排序sort, sorted的區別: list.sort(func=None, key=None, reverse=False(or True)) 對於reverse這個bool型別引數,當reverse=False時:為正向排序;當reverse=True時:為方向排序。預設為Fal