多线程传入对象成员函数问题

ztenv 版主
博客专家认证
2009-06-22 04:43:24
_beginthreadex()函数在创建多线程传入回调函数时,好像只能传入全局函数或类的静态成员函数,请问能不能传入类的成员函数呢(非静态)?
网上看了一下例子,好像写得不对;
谢谢各位!
...全文
605 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
Meteor_Code 2009-06-23
  • 打赏
  • 举报
回复
类成员方法是一个比较特殊的函数,它在编译时会被转化成普通函数,比如有TMyClass类:
class TMyClass
{
void Func();
};

这个TMyClass::Func最终会转化成 void Func(TMyClass *this); 也就是说在原第一个参数前插入指向对象本身的this指针。
------------------------------------------
这个说法不全面,只有你的成员函数是变长参数列表的时候会把this插到左边,如果成员函数的参数列表是有限的,那么this会被保存在ECX中传递,VC6是这么做的,其他的编译器不一定。

Meteor_Code 2009-06-23
  • 打赏
  • 举报
回复
[Quote=引用 11 楼 lianshaohua 的回复:]
引用 10 楼 Meteor_Code 的回复:
明显不能!因为类成员大多是__thiscall类型调用,而线程函数必须是__stdcall的
UNION转换也不行__thiscall需要一个隐含参数this,这个参数用ECX保存或者用列表中最左边的参数传递.你直接使用将带来栈错误.



可是我已经实现了,并且调用成功,可能存在移植的问题,感觉这样做很危险
[/Quote]

你调用成功是很危险的。
因为__thiscall在return后要求外部处理栈,__stdcall是函数自己处理栈。
你把__thiscall当作,__stdcall调用,调用之后由于函数本身不是,__stdcall他不会处理栈,但_beginthread认为他是__stdcall所以不会帮他处理栈,于是在栈里面会留下残留。_beginthread又要求你必须给线程函数一个参数,尽管你不使用这个参数。
你可以用调试工具试试,查一下线成的ESP,你就知道危险在那里了.
ztenv 版主 2009-06-23
  • 打赏
  • 举报
回复
[Quote=引用 10 楼 Meteor_Code 的回复:]
明显不能!因为类成员大多是__thiscall类型调用,而线程函数必须是__stdcall的
UNION转换也不行__thiscall需要一个隐含参数this,这个参数用ECX保存或者用列表中最左边的参数传递.你直接使用将带来栈错误.
[/Quote]

可是我已经实现了,并且调用成功,可能存在移植的问题,感觉这样做很危险
Meteor_Code 2009-06-23
  • 打赏
  • 举报
回复
明显不能!因为类成员大多是__thiscall类型调用,而线程函数必须是__stdcall的
UNION转换也不行__thiscall需要一个隐含参数this,这个参数用ECX保存或者用列表中最左边的参数传递.你直接使用将带来栈错误.
ztenv 版主 2009-06-23
  • 打赏
  • 举报
回复
看了楼上的文章,感觉用union实现或thunk实现,都会有些问题,决定还是老老实实的用静态函数实现吧;
不知道还有没有更好的方法;
期待中。。。。。。
谢谢楼上大侠!
老邓 2009-06-23
  • 打赏
  • 举报
回复
[Quote=引用 7 楼 lianshaohua 的回复:]
引用 4 楼 goodname 的回复:
我运行了一下,没有什么问题,请楼主明示问题。

大概_USERENTRY没有定义。


可以自定义一下
#define _USERENTRY __cdecl

或者参考
http://www.linuxquestions.org/questions/programming-9/what-does-userentry-mean-in-c-285169/



由于我用的是vs2008,估计是ide的问题,原来_USERENTRY被定义成了_cdecl
我在实际应用中如下定义的:

C/C++ code

union { …
[/Quote]

这种方法只适用于x86平台开发Win32程序,将来移植到x64时将面临崩溃!!
目前最好的解决方法仍然是thunk,我曾经研究过x86和x64都可以用的thunk,有兴趣的话,看看:http://www.qpsoft.com/blog/x64-thunk-callback-conversion/
ztenv 版主 2009-06-23
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 goodname 的回复:]
我运行了一下,没有什么问题,请楼主明示问题。

大概_USERENTRY没有定义。


可以自定义一下
#define _USERENTRY __cdecl

或者参考
http://www.linuxquestions.org/questions/programming-9/what-does-userentry-mean-in-c-285169/
[/Quote]

由于我用的是vs2008,估计是ide的问题,原来_USERENTRY被定义成了_cdecl
我在实际应用中如下定义的:


union { // 联合类,用于转换类成员方法指针到普通函数指针(试过编译器不允许在这两种函数之间强制转换),不知道有没有更好的方法。
unsigned (_stdcall *ThreadProc)(void *); //由于用的是_beginthreadex()函数,所以定义成这样
unsigned (_stdcall ProductThread::*MemberProc)(); //这里好像不能带任何参数;
} Proc;


_beginthreadex(NULL,0,Proc.ThreadProc,this,CREATE_SUSPENDED,NULL);
此函数在ProductThread类的一个方法中调用,所以传入this指针,这样就搞定了;
谢谢

goodname
的提点!!!!
ztenv 版主 2009-06-23
  • 打赏
  • 举报
回复
认真看完了您的回复,非常感谢您!
感谢您在我以前的贴子的回复,谢谢!我尽量不用这种方法,以前是想知道可不可行,看来是真的不可行。因为我想用面向对象的方式来实现线程的操作;
CodeMasterShiller 2009-06-22
  • 打赏
  • 举报
回复
经过验证,确实可行。长见识了。不过这样也太别扭了。
taodm 2009-06-22
  • 打赏
  • 举报
回复
呃,网文,要从万千错误的网文中找到没有错误的那些,可真够困难的。
goodname 2009-06-22
  • 打赏
  • 举报
回复
我运行了一下,没有什么问题,请楼主明示问题。

大概_USERENTRY没有定义。


可以自定义一下
#define _USERENTRY __cdecl

或者参考
http://www.linuxquestions.org/questions/programming-9/what-does-userentry-mean-in-c-285169/
Walf_ghoul 2009-06-22
  • 打赏
  • 举报
回复
mark!
ztenv 版主 2009-06-22
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 goodname 的回复:]
把你看到的例子贴出来看看。
[/Quote]


C++类成员函数直接作为线程回调函数2009年06月01日 星期一 17:01我以前写线程时要么老老实实照着声明写,要么使用C++类的静态成员函数来作为回调函数,经常会因为线程代码而破坏封装.之前虽然知道类成员函数的展开形式,但从没想过利用过它,昨天看深入ATL时无意中学会了这一招:)

类成员方法是一个比较特殊的函数,它在编译时会被转化成普通函数,比如有TMyClass类:
class TMyClass{
void Func();
};

这个TMyClass::Func最终会转化成 void Func(TMyClass *this); 也就是说在原第一个参数前插入指向对象本身的this指针。

我们可以利用这个特性写一个非静态类成员方法来直接作为线程回调函数,先看_beginthread函数的定义:
unsigned long _RTLENTRY _EXPFUNC _beginthread (void (_USERENTRY *__start)(void *),unsigned __stksize, void *__arg);
其中的第一个参数就是作为线程执行主体的回调函数。它的原型是:void Func(void *),这个void*参数是作为自定义数据传入的。对比一下上面所说的TMyClass::Func的最终形式,它正好可以符合这里的要求。

现在做个实验:
#include <stdio.h>
#include <process.h>

class TMyClass{
int m_nCount;
int m_nId;
public:
TMyClass(int nId,int nCount)
:m_nId(nId),m_nCount(nCount)
{
}

void _USERENTRY ThreadProc() // 类成员方法
{
for(int i=0; i<m_nCount; i++) // 根据m_nCount成员打印一排数字
{
printf("Class%d : %d\n",m_nId,i);
}
}
};

int main(int argc, char* argv[])
{
union { // 联合类,用于转换类成员方法指针到普通函数指针(试过编译器不允许在这两种函数之间强制转换),不知道有没有更好的方法。
void (_USERENTRY *ThreadProc)(void *);
void (_USERENTRY TMyClass::*MemberProc)();
} Proc; // 尽管联合里的两种函数类型现在看起来有很大不同,但它们的最终形式是相同的。

TMyClass MyClass1(1,10),MyClass2(2,5); // 产生两个TMyClass对象

Proc.MemberProc = &TMyClass::ThreadProc; // 转换,Proc.ThreadProc就是对应的普通函数指针了

_beginthread(Proc.ThreadProc,4096,&MyClass1); // 开始线程,这里的Proc.ThreadProc实际上是TMyClass::ThreadProc, 它要的this指针是我们给的&MyClass1。
_beginthread(Proc.ThreadProc,4096,&MyClass2);
system("pause");
return 0;
}

运行!神奇吧?:-)

其实不止线程回调函数,其实只要是形如Func(void*,...)的回调函数都可以用这种方法直接使用类成员方法。(前提是第一个void*是自定义数据,也就是说它不能有其它功能)。

转自:http://blog.csdn.net/waiting4you/archive/2007/12/29/2000796.aspx

goodname 2009-06-22
  • 打赏
  • 举报
回复
把你看到的例子贴出来看看。

64,680

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

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