• 全部
  • VC综合技术
  • 互联网技术
  • MFC AppLauncher
  • .NET 技术
  • 界面
  • 进程
  • 算法
  • 硬件/系统
  • 数据库
  • VC++技术资源

关于成员函数作为回调函数以及CreateWindowEx的问题

lifeforu 北京赛智科技有限公司 项目经理  2006-04-21 12:58:24
问题描述:
需要动态创建很多窗口,并且还需要保存创建的信息,并且事件处理还需要根据该窗口的原始信息.
例:
class ctrl{
string name,caption;
int id,type;}
class form{
string name,catpion;
HWND hwnd;
vector<ctrl> vecCtrls;
}
class mysdk{
form *pForm;
RegisterWindowClass()
{
WNDCLASS wndclass ;
................
wndclass.lpfnWndProc = GetStaticEntry();//WndProc ;//
.................
}
LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{........}
void showForm(void*)
{
createWindowEx(.....);
}
}
大致上就是这样,首先在网上找到一篇关于如何让类的成员函数作为回调函数 的文章,如下:
http://tb.donews.net/TrackBack.aspx?PostId=815153
,OK,wndclass.lpfnWndProc = GetStaticEntry();//这句即按该文章所描述的方法
,RegisterClass()执行正常,但在createWindowEx的时候出错了,try GetLastError()=0,这时候就不知道是什么原因了...

大家给分析分析
...全文
590 点赞 收藏 16
写回复
16 条回复
切换为时间正序
当前发帖距今超过3年,不再开放新的回复
发表回复
broccoli 2006-06-27
up
回复
lifeforu 2006-04-22
To pomelowu(羽战士)
以前没注意GWL_USERDATA这个参数,实在惭愧,要是知道可以这么保存的话也大可不必这么麻烦了.
谢谢,另pfaCallBack 的定义已经修改过,
typedef LRESULT (CALLBACK *pfaCallBack)(HWND , UINT , WPARAM , LPARAM);

To lights_joy(江陵浪子)
惭愧到没接触过把MFC中的CHandleMap剥出来或者自己做一个MAP(HWND -> VOID*),能否提供更多一些的资料学习一下?

回复
lifeforu 2006-04-21
你提前的太频繁了!!!
回复
lifeforu 2006-04-21
to: pomelowu(羽战士)

打不开网页。不过没看到你的回掉函数详细的定义,是static的么,符合调用归约么?
不过相信你参考的帖子都覆盖了这些问题,此外窗口创建不成功可能和你注册窗口有关,比如风格之类的设定等。可你提供的信息少了点。

我考虑了很久,回调函数必须用成员函数方可,因为我的WndProc函数确定需要访问成员变量,比如说在ON_CTRLCOLOR的时候要根据预定义的控件信息进行设置,以及ON_CLICK等消息均需要成员信息,参考的帖子都覆盖了这些问题(按该文章所描述我已经达到要求)

注册窗口我可以保证除了回调函数之外的正确性,因为使用C类型函数作为回调函数是可以运行的.
且CreateWindowEx的参数我也可以保证有效性,如果换成C类型函数是可以运行的.

按理说try(createwindowex)之后GetLastError应该可以得出错在什么地方了,可惜返回为0

我的成员函数:
声明之:
void InitThunk();
pfaCallBack GetStaticEntry();
BOOL RegisterWindowClass();
LRESULT WndProc(HWND , UINT , WPARAM , LPARAM);
void OnDestory(HWND hWnd);
void OnCreate(HWND hWnd , WPARAM wParam , LPARAM lParam);
HBRUSH OnCtlColor(HWND hWnd , HDC hdc , UINT nCtlColor);
BOOL OnEraseBkgnd(HWND hWnd , HDC hdc);

定义之:
BOOL SDKForm::RegisterWindowClass()
{
if(m_bRegisterClassName)
return TRUE;
WNDCLASS wndclass ;
HINSTANCE hInstance = (HINSTANCE)AfxGetMainWnd();
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = GetStaticEntry();//WndProc ;//
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;


if (!RegisterClass (&wndclass))
{
MessageBox (AfxGetMainWnd()->GetSafeHwnd()
, TEXT ("This program requires Windows NT!")
, szAppName , MB_ICONERROR) ;
return FALSE;
}
m_bRegisterClassName = TRUE;
return TRUE;
}


LRESULT SDKForm::WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdcStatic = (HDC)wParam;
HWND hwndStatic = (HWND)lParam;
CICDApp* pApp = (CICDApp*)AfxGetApp();

int idCtrl = (int) wParam;
LPNMHDR pnmh = (LPNMHDR) lParam;

switch(message)
{
case WM_ERASEBKGND:
return OnEraseBkgnd(hwnd , hdcStatic);
break;
case WM_CTLCOLORBTN:
case WM_CTLCOLORSTATIC:
return (LONG)OnCtlColor(hwndStatic , hdcStatic , message);
break;
case WM_CREATE:
OnCreate(hwnd , wParam , lParam);
break;
case WM_DESTROY:
OnDestory(hwnd);
break;
case WM_NOTIFY:
break;
case WM_COMMAND:
pApp->DoProjectEvent_Command(hwnd , message , wParam , lParam , m_pForm->GetFormName());
break;
default:
break;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}



void SDKForm::InitThunk()
{ m_thunk.op_movecx = 0xB9;
m_thunk.val_ecx = (DWORD_PTR)this;
m_thunk.op_call = 0xE9;
typedef LRESULT ( SDKForm::* pf )(HWND , UINT , WPARAM , LPARAM );
pf p = &SDKForm::WndProc;//此处按原文章无法编译,改为这样

DWORD_PTR off = 0;
_asm
{
mov eax, p
mov DWORD PTR [off], eax
}
m_thunk.val_address =
off - ( (DWORD_PTR)(&m_thunk.val_address) + sizeof(DWORD_PTR) );
}
回复
sad_4978 2006-04-21
开完会看看。
回复
lifeforu 2006-04-21
我也打不开这篇文章了,我还是先把文章贴上来吧:

为什么类(class)的成员函(member function)数不能作为回调函数(callback function)
首先来看看回调函数有怎样的特点。windows中,回调函都显式(explicit)使用CALLBACK修饰符(decorator)修饰(decorated)。实际上CALLBACK就是__stdcall参数传递方式(calling convention)的宏定义。MSDN中对__stdcall做了如下定义:

The __stdcall calling convention is used to call Win32 API functions. The callee cleans the stack, so the compiler makes vararg functions __cdecl. Functions that use this calling convention require a function prototype.

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。

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;



}

回复
pomelowu 2006-04-21
>ON_CREATE好说,但是其它的消息呢?

常见的办法是处理WM_CREATE的时候,GetWindowLong把指针放到USERDATA里边去(GWL_USERDATA,好像是这个参数名),需要的时候取出来。

另外,一开始的确没仔细看这篇文章。你是不是没有修改pfaCallBack 的定义,还是用的那篇文章中的pfaCallBack ?
回复
嵌云阁主 2006-04-21
太复杂了,不如把MFC中的CHandleMap剥出来或者自己做一个MAP(HWND -> VOID*),效率也不差。
回复
lifeforu 2006-04-21
贴一下可以把非static成员函数作为回调函数的代码,也是上面文章上的例子,可以编译运行:
#include "windows.h"
#include "stdio.h"
#include "stdlib.h"
#include "assert.h"
typedef int (CALLBACK *pfaCallBack)(int, long, char);
//////////////////////////////////////////////////////////////////////////
#ifndef DWORD_PTR
#define DWORD_PTR DWORD
#endif

#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 ;
private:
Thunk m_thunk;
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;
}
public:
void InitThunk()
{
m_thunk.op_movecx = 0xB9;
m_thunk.val_ecx = (DWORD_PTR)this;
m_thunk.op_call = 0xE9;
DWORD_PTR off = 0;
typedef int (Dummy::*pmf)(int intVal, long longVal, char charVal);
pmf p = &Dummy::memberCallback;
_asm
{
mov eax, p
mov DWORD PTR [off], eax
}
m_thunk.val_address =
off - ( (DWORD_PTR)(&m_thunk.val_address) + sizeof(DWORD_PTR) );
}
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);
}
//////////////////////////////////////////////////////////////////////////
int main(int argc, char* argv[])
{
Dummy *dummy1 = new Dummy(9);
dummy1->InitThunk();
pfaCallBack pCallback1 = dummy1->GetStaticEntry();
Trigger(pCallback1);
system("pause");
return 0;
}
问题最后还是给绕开了,通过全局实现,但是感觉很丑陋...
回复
lifeforu 2006-04-21
你可能没仔细看我转贴的这篇文章,这篇文章就是把一个成员函数作为C函数传给注册函数,
实际上在传递this指针给WndProc实在不好处理,ON_CREATE好说,但是其它的消息呢?
我也尝试着通过HWND在全局对像中查找该对象,但还没弄好...
回复
pomelowu 2006-04-21
呵呵。不加static的成员函数会隐藏一个this参数,因此在注册的时候会认为回调函数类型不一致。
至于在static中怎么访问类成员。你想办法把当前对象的this指针传进去就OK了啊。
回复
lifeforu 2006-04-21
TO:codewarrior(会思考的草)
问题是static不能满足我的要求
TO:pomelowu(羽战士)
typedef LRESULT (CALLBACK *pfaCallBack)(HWND , UINT , WPARAM , LPARAM);
pfaCallBack GetStaticEntry();
这应该是一样的吧
但是如果是static还如何去访问类的成员指针?
从转贴的文章上来看是需要声明为static的
但是确实也是这里出问题了,CreateWindowEx的时候出错
回复
pomelowu 2006-04-21
把 GetStaticEntry的声明改成和WndProc的原型一样,然后前面加上static修饰
回复
sunj_study 2006-04-21
study
回复
会思考的草 2006-04-21
成员函数当然可以做callback函数。加上static和__stdcall的关键字即可。
回复
pomelowu 2006-04-21
打不开网页。不过没看到你的回掉函数详细的定义,是static的么,符合调用归约么?
不过相信你参考的帖子都覆盖了这些问题,此外窗口创建不成功可能和你注册窗口有关,比如风格之类的设定等。可你提供的信息少了点。
回复
发帖
VC/MFC
创建于2007-09-28

1.5w+

社区成员

VC/MFC相关问题讨论
申请成为版主
帖子事件
创建了帖子
2006-04-21 12:58
社区公告

        VC/MFC社区版块或许是CSDN最“古老”的版块了,记忆之中,与CSDN的年龄几乎差不多。随着时间的推移,MFC技术渐渐的偏离了开发主流,若干年之后的今天,当我们面对着微软的这个经典之笔,内心充满着敬意,那些曾经的记忆,可以说代表着二十年前曾经的辉煌……
        向经典致敬,或许是老一代程序员内心里面难以释怀的感受。互联网大行其道的今天,我们期待着MFC技术能够恢复其曾经的辉煌,或许这个期待会永远成为一种“梦想”,或许一切皆有可能……
        我们希望这个版块可以很好的适配Web时代,期待更好的互联网技术能够使得MFC技术框架得以重现活力,……