函数指针和类成员函数指针的问题,新新新,欢迎大家继续讨论

huanGe 2001-07-21 11:09:00
在http://www.csdn.net/expert/topic/189/189742.shtm
我问过怎么转化个类的方法赋给一个普通的函数指针?
最后似乎得到无解的答案
但今天看帖子http://www.csdn.net/expert/Topic/193/193682.shtm,
我找到答案,这是有解的~~~~~~~

class A;
void __stdcall G_Fun(A* p)
{
cout<<"G_Fun(A*)"<<endl;
};
class A
{
public:
int x;
int c;
virtual void __stdcall fun();
virtual void __stdcall fun1();
virtual void __stdcall fun2();
};

void __stdcall A::fun()
{
cout<<"A::fun()"<<endl;
}
void __stdcall A::fun1()
{
cout<<"A::fun1()"<<endl;
}
void __stdcall A::fun2()
{
cout<<"A::fun2()"<<endl;
}
int main()
{
void __stdcall (*xxx)(A*);
A* pA = new A ;
(int )xxx=(**(int**)pA); //转化第一个成员方法
xxx(pA);
(int )xxx=(*(*(int**)pA+1)); //转化第二个成员方法
xxx(pA);
(int )xxx=(*(*(int**)pA+2)); //转化第三个成员方法
xxx(pA);
cout<<"first,A::fun()"<<endl;
pA->fun();
//now let we modified the vtable.
(**(int**)pA)=(int)G_Fun;;
cout<<"second,G_Fun()"<<endl;
pA->fun();
delete pA;
return 0 ;
}

...全文
790 23 打赏 收藏 转发到动态 举报
写回复
用AI写文章
23 条回复
切换为时间正序
请发表友善的回复…
发表回复
quickman 2001-07-25
  • 打赏
  • 举报
回复
牛逼!!!
sunbo 2001-07-24
  • 打赏
  • 举报
回复
注意,bcb5不是VC

我试过了,Win2000下,VC6是不可能通过运行的!
lz_0618 2001-07-24
  • 打赏
  • 举报
回复
看的糊涂,向大家学习
LionSun 2001-07-24
  • 打赏
  • 举报
回复
to everyone:

vtable 在不同编译器中的处理是不一样的,它不一定在对象内存空间的首位,但在VC里,它确实是在对象内存空间的开始出。

city_tiger 2001-07-24
  • 打赏
  • 举报
回复



这里有英文原版C++经典书籍,可以在线阅读:
http://go7.163.com/bros4/tech/cpptech.htm



huanGe 2001-07-23
  • 打赏
  • 举报
回复
to:dongyingtao(dongyingtao)
你屏蔽的代码//(**(int**)pA)=(int)G_Fun;;//2000下不能修改是不对的
我最上面的例子就是在2000上BCB5运行通过的
Kevin_qing 2001-07-23
  • 打赏
  • 举报
回复
tiongkohlang(SDK) 不错啊,解释的真详细。

我再补充一点,对X86,float是32位的,而dword是64位的,所以float用一个dword传递,
double用2个dword传递(QWORD),但是是用2个push dword实现,所以相当于是两个参数(VC)。
softmaster 2001-07-22
  • 打赏
  • 举报
回复
我的qq 75290236 ,愿与大家交个朋友!
softmaster 2001-07-22
  • 打赏
  • 举报
回复
不对,vc 的编译器的设置是函数的默认选项是 __cdecl ,这可以在 vc 的 Project setting 菜单选项中查到,__stdcall 是微软自己弄出来的一个调用约定,为了与其他平台下的编译器兼容,__stdcall 必须由程序员显示的指定 。至少对于微软的编译器我们不必担心其实应该说在windows平台下我们不必担心
tiongkohlang 2001-07-22
  • 打赏
  • 举报
回复
补充一下。这几种方式:__cdecl,__stdcall,__fastcall,都以dword为单位传送数据。即使你的声明是char或者short也无济于事。但是如果是float或者double,那么回按照你的尺寸。因为double或者float不比dword小。
tiongkohlang 2001-07-22
  • 打赏
  • 举报
回复
softmaster(softmaster)有一点误解了我的意思。我的意思有两个
1,对于微软的编译器,我们可以认为类的实例的结构和以下的结构一致:
struct someclass
{
void *_vfptr;
....//其他的数据。
}
也就是说有虚函数的类的第一个真正的成员是虚表的指针。在这一点上,我们不必对微软的编译器担心。
2,如果你在类的成员函数前面加上__stdcall,那么微软会连同this的遵守__stdcall约定,也就是它会把this最后压入堆栈。在这一点上我们不必担心。
但是如softmaster(softmaster)所说,如果你不显示的注明函数的调用约定,那么就会有以下结果。
对于普通的函数,调用约定是__cdecl。
对于类的成员函数,调用约定是__thiscall,就是dongyingtao(dongyingtao所说的方法。

最后介绍一下调用约定。
__cdecl,绝大部分C编译器都支持这种调用约定,这也是常说的C语言调用方式。
参数传递:从右到左压入堆栈。
堆栈的维护:调用者修改栈指针。
命名修饰:函数名前边加一个_,即下划线。
__stdcall,Window中广泛使用的调用方式。
参数传递:从右到左压入堆栈。
堆栈的维护:被调用的函数将参数弹出堆栈。
命名修饰,函数名前边加一个下划线,后边加上@和参数的字节数。
__fastcall
参数传递:最左边的两个dword放入ecx和edx,另外的参数从右向左压入堆栈。
堆栈的维护:被调用的函数将参数弹出堆栈。
命名修饰:函数名前边加一个@,后边加一个@和参数的字节数。
msdn上有详细的说明。
thiscall
thiscall和__stdcall类似,但是this不压入堆栈,而是放入ecx。
还有其他的调用方式,比如__fortran,__pascal,不过微软的VC6的编译器只支持以上的方式。其他的编译器可能还有其他的方式。
微软的缺省的方式是__cdecl,其他的编译器我不知道。不过borland的缺省方式应该也是__cdecl。不过不要以为所有的编译器都这样。我用过一次watcomC/C++,那里的缺省方式是watcom的一种方式,而不是__cdecl。
YK2000 2001-07-22
  • 打赏
  • 举报
回复
我仔细看了每一篇回复,对于我这个初学者来说还是难了些。我想问一下,关于_stdcall等 在哪里可以看到,我学c++一段时间了,应该说大部分的问题都还算清楚,只是从来没有接触过这个东西。能否请各位高人解释一下这个_stdcall等东西的概念 ,或是在哪些书介绍到他们。好让我可以学一下。多谢了 。
yug 2001-07-21
  • 打赏
  • 举报
回复
这行吗?试试?
luhongjun 2001-07-21
  • 打赏
  • 举报
回复
学习。
乱码 2001-07-21
  • 打赏
  • 举报
回复
看的头昏脑胀
这就是C++
破烂!!!
huanGe 2001-07-21
  • 打赏
  • 举报
回复
修改vtable指针,这至少是个技巧,可以加深对于vtable的理解,应该也是有用的,只是暂时还没用到,但类成员函数指针的使用我是大量用在程序中的
tiongkohlang 2001-07-21
  • 打赏
  • 举报
回复
顺便解释一下为什么cout<<"A::fun()"<<endl;会产生3个call而不是2个。原因是endl是一个inline函数,里边又有两个<<。所以是3个call。刚开始我也不明白,后来一看MS的ostream.h,才恍然大悟。
swat 2001-07-21
  • 打赏
  • 举报
回复
但我想vtable指针在编译期间应该是被保护的,你的真能成功么?试先!而且,修改vtable指针有什么好处?未明,请指教!
tiongkohlang 2001-07-21
  • 打赏
  • 举报
回复
"我不赞成G_fun中加入参数A*的做法,你可能认为编译器要将this指针传入函数,但事实上VC并不是这样做的,我记得上次也说过,它是利用ecx传递this指针的,加入A*则会导致运行时堆栈错误
二、__stdcall这些标志是决定了编译器对堆栈的处理方式,类成员函数的堆栈处理方式实际上已经是由函数自身清理堆栈,所以在A::fun前的__stdcall是不需要的,而G_fun前是必须的"

以上是dongyingtao(dongyingtao)的看法。对此我必须澄清一下,否则会造成初学者的头脑混乱。

原来的那个贴子上我贴了一个例子。现在huanGe的代码的意思和我的差不多。我的基本观点是,我们的代码没什么问题。当然dongyingtao(dongyingtao)的例子也没什么问题。

首先。如果我们不在类的成员函数前边加任何调用方式的声明,即诸如__stdcall之类,那么不同的编译器可以有不同的处理方法。对此我并不清楚,但是对于VC,你说的应该是合理的。
但是如果我们在成员函数前面加上__stdcall,编译器将如何处理呢?我认为编译器会按照__stdcall的方法处理,包括对于this指针。
为什么我这么认为?因为我看了一下COM接口的声明。微软声称COM对于任何高级语言都是兼容的,那么它的C++声明和C声明就应该是一样的。以下是IUnknown的定义:

#if defined(__cplusplus) && !defined(CINTERFACE)

MIDL_INTERFACE("00000000-0000-0000-C000-000000000046")
IUnknown
{
public:
BEGIN_INTERFACE
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject) = 0;

virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;

virtual ULONG STDMETHODCALLTYPE Release( void) = 0;

END_INTERFACE
};

#else /* C style interface */

typedef struct IUnknownVtbl
{
BEGIN_INTERFACE

HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
IUnknown __RPC_FAR * This,
/* [in] */ REFIID riid,
/* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
IUnknown __RPC_FAR * This);

ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
IUnknown __RPC_FAR * This);

END_INTERFACE
} IUnknownVtbl;

interface IUnknown
{
CONST_VTBL struct IUnknownVtbl __RPC_FAR *lpVtbl;
};

STDMETHODCALLTYPE就是__stdcall,既然MS声称IUnknown的C++和C声明是一样的,那么既然用C声明的虚表函数要严格的用__stdcall来调用,那么C++声明的__stdcall的虚成员函数当然要连同this一起严格地用__stdcall调用,否则就乱套了。

最后我们来具体分析一下以下的代码。

#include <iostream.h>

class A;

void __stdcall G_Fun(A* p)
{
cout<<"G_Fun(A*)"<<endl;
};

class A
{
public:
virtual void __stdcall fun();
};

void __stdcall A::fun()
{
cout<<"A::fun()"<<endl;
}

int main()
{
A* pA = new A ;
__asm nop

cout<<"first,A::fun()"<<endl;

__asm nop

pA->fun();

//now let we modified the vtable.
__asm nop

(**(int**)pA)=(int)G_Fun;

__asm nop

cout<<"second,G_Fun()"<<endl;

__asm nop

pA->fun();

__asm nop

delete pA;

return 0 ;
}
为了能看清汇编后的代码,我在每一件事之后都加了一个nop指令。
这是汇编以后的代码。(实际上是先用VC编成EXE,然后又用wdasm反汇编的)

* Possible StringData Ref from Data Obj ->"G_Fun(A*)"
|
:00401000 6840904000 push 00409040
:00401005 B9C0B94000 mov ecx, 0040B9C0
:0040100A E858020000 call 00401267
:0040100F 6810114000 push 00401110
:00401014 6A0A push 0000000A
:00401016 8BC8 mov ecx, eax
:00401018 E803010000 call 00401120
:0040101D 8BC8 mov ecx, eax
:0040101F E8CC000000 call 004010F0
:00401024 C20400 ret 0004


:00401027 90 nop
:00401028 90 nop
:00401029 90 nop
:0040102A 90 nop
:0040102B 90 nop
:0040102C 90 nop
:0040102D 90 nop
:0040102E 90 nop
:0040102F 90 nop

* Possible StringData Ref from Data Obj ->"A::fun()"
|
:00401030 684C904000 push 0040904C
:00401035 B9C0B94000 mov ecx, 0040B9C0
:0040103A E828020000 call 00401267
:0040103F 6810114000 push 00401110
:00401044 6A0A push 0000000A
:00401046 8BC8 mov ecx, eax
:00401048 E8D3000000 call 00401120
:0040104D 8BC8 mov ecx, eax
:0040104F E89C000000 call 004010F0
:00401054 C20400 ret 0004


:00401057 90 nop
:00401058 90 nop
:00401059 90 nop
:0040105A 90 nop
:0040105B 90 nop
:0040105C 90 nop
:0040105D 90 nop
:0040105E 90 nop
:0040105F 90 nop

* Referenced by a CALL at Address:
|:00401D7C
|
:00401060 56 push esi
:00401061 6A04 push 00000004
:00401063 E8570C0000 call 00401CBF
:00401068 83C404 add esp, 00000004
:0040106B 85C0 test eax, eax
:0040106D 740A je 00401079
:0040106F C700C8804000 mov dword ptr [eax], 004080C8
:00401075 8BF0 mov esi, eax
:00401077 EB02 jmp 0040107B

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040106D(C)
|
:00401079 33F6 xor esi, esi

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401077(U)
|
:0040107B 90 nop

* Possible StringData Ref from Data Obj ->"first,A::fun()"
|
:0040107C 6868904000 push 00409068
:00401081 B9C0B94000 mov ecx, 0040B9C0
:00401086 E8DC010000 call 00401267
:0040108B 6810114000 push 00401110
:00401090 6A0A push 0000000A
:00401092 8BC8 mov ecx, eax
:00401094 E887000000 call 00401120
:00401099 8BC8 mov ecx, eax
:0040109B E850000000 call 004010F0
:004010A0 90 nop
:004010A1 8B06 mov eax, dword ptr [esi]
:004010A3 56 push esi
:004010A4 FF10 call dword ptr [eax]
:004010A6 90 nop
:004010A7 8B0E mov ecx, dword ptr [esi]
:004010A9 C70100104000 mov dword ptr [ecx], 00401000
:004010AF 90 nop

* Possible StringData Ref from Data Obj ->"second,G_Fun()"
|
:004010B0 6858904000 push 00409058
:004010B5 B9C0B94000 mov ecx, 0040B9C0
:004010BA E8A8010000 call 00401267
:004010BF 6810114000 push 00401110
:004010C4 6A0A push 0000000A
:004010C6 8BC8 mov ecx, eax
:004010C8 E853000000 call 00401120
:004010CD 8BC8 mov ecx, eax
:004010CF E81C000000 call 004010F0
:004010D4 90 nop
:004010D5 8B16 mov edx, dword ptr [esi]
:004010D7 56 push esi
:004010D8 FF12 call dword ptr [edx]
:004010DA 90 nop
:004010DB 56 push esi
:004010DC E8D30B0000 call 00401CB4
:004010E1 83C404 add esp, 00000004
:004010E4 33C0 xor eax, eax
:004010E6 5E pop esi
:004010E7 C3 ret

以下
* Possible StringData Ref from Data Obj ->"A::fun()"
|
:00401030 684C904000 push 0040904C
:00401035 B9C0B94000 mov ecx, 0040B9C0
:0040103A E828020000 call 00401267
:0040103F 6810114000 push 00401110
:00401044 6A0A push 0000000A
:00401046 8BC8 mov ecx, eax
:00401048 E8D3000000 call 00401120
:0040104D 8BC8 mov ecx, eax
:0040104F E89C000000 call 004010F0
:00401054 C20400 ret 0004
可以明显的看出是A::fun的代码。首先,对于想cout<<"A::fun()<<endl;这个语句。确实如
dongyingtao(dongyingtao)所说,是把this放在ecx里边的。大家可以看出有三个call之前都往ecx里边放了东西。什么呢,就是cout指针。
但是很明显,最后是ret 0004,显然是把this“pop”出去了,这不是很明显的__stdcall吗?为什么?就是因为我在fun前边加上了__stdcall。

这是p->fun()
:004010A1 8B06 mov eax, dword ptr [esi]
:004010A3 56 push esi
:004010A4 FF10 call dword ptr [eax]
可以看出,我在fun加了__stdcall,编译器就生成连带this的__stdcall的调用。

所以结论是:
如果你的函数G_Fun是遵守你的编译器的类成员函数的调用约定的,那么你就像dongyingtao(dongyingtao)那样,使你的函数满足编译器的类成员函数的调用约定。前提是你必须知道编译器的类成员函数的调用约定。
如果你的函数是必须采用其他的调用约定,比如IUnknown的三个方法,那么你就使用你的约定,同时必须修改类成员函数的调用约定。前提是编译器承认并且连同this实现这种修改,比如MS的编译器。

至于softmaster(softmaster)的担心,也是不必要的。既然微软这样声明它的COM接口,那么至少对于微软的编译器我们不必担心。那么对于别的编译器呢?Borland的编译器也用同样的头文件,只是在每一个头文件开始加一个pragma。不知道这个pragma是不是和虚函数有关,如果无关,那很好。如果有关,起码对于COM接口,我们不必又这种担心。
Kevin_qing 2001-07-21
  • 打赏
  • 举报
回复
给你参考,嘿嘿
// t1.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"
struct vBase{
virtual LPVOID QueryFunction(LPCSTR funname){
return NULL;
}
};

struct ClassA
:public vBase
{
ClassA(int idx)
{
m_idx=idx;
}
virtual void hello()
{
printf("Hello from %u\n",m_idx);
}
virtual LPVOID QueryFunction(LPCSTR funname){
if(!strcmp(funname,"hello"))
{
LPDWORD vtable=*(LPDWORD*)this;
return (LPVOID)vtable[1];
}
return NULL;
}
int m_idx;

};

int main(int argc, char* argv[])
{
ClassA * pClsa=new ClassA(1);
vBase * pBase=pClsa;
LPVOID fun=pBase->QueryFunction("hello");
pClsa->hello();
__asm
{
mov eax,fun
mov ecx,pBase
or eax,eax
jz _exit_
call eax
_exit_:
}
return 0;
}
加载更多回复(3)

69,369

社区成员

发帖
与我相关
我的任务
社区描述
C语言相关问题讨论
社区管理员
  • C语言
  • 花神庙码农
  • 架构师李肯
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧