平时都没写东西的习惯,现在开始从事软件开发工作,觉得遇到问题的时候写点总结蛮好的,便于以后备忘并提醒自己别犯同样的错误。
在最近的项目中主要碰到一下几个问题:
1.qt调用其他编译器编译得到的动态库文件.dll中库函数问题;
2.回调函数在自定义类中实现需要定义为类的静态成员函数问题;
3.类的静态成员函数(还是回调函数时),怎样调用类的非静态成员问题;
4.多线程中,不同线程间如何实现相互通信问题;
问题1简答:
qt类库中QLibrary就是用于解决qt调用动态库.dll问题,假设有一动态库文件为dllName.dll,其中有一库函数为 int add(int x,int y);
加载动态库及获得库函数函数指针代码如下:
- #include ...
- typedef int (*AddFunc)(int x,int y);//定义函数指针类型
- AddFunc subAdd = NULL;//定义函数指针变量
- int main(int argc,char *argv[])
- {
- QApplication app(argc,argv);
- QLibrary dllLib("dllName.dll");
- if(!dllLib.load())
- {
- return -1;
- }
- else
- {
- subAdd = (AddFunc)dllLib.resolve("add");
- if(subAdd == NULL)
- {
- return -1;
- }
- else
- {
- printf("sum = %d\n",subAdd(1,1));
- //print sum = 2;
- }
- }
- int ret = app.exec();
- return ret;
- }
因为正常应用中加载的dll中会有比较多的库函数,这个时候我们可以考虑自定义一个类CDllLibrary,继承QLibrary,对dll库加载和获得库函数函数指针在该类中实现,该类提供与dll相同的成员函数,成员函数中通过获得库函数指针调用库函数,这样封装下,显得比较清晰。
需要特别注意的是:qt加载其他不同编译器的dll时,对加载的库函数有要求,需要在库函数声明或定义前加extern "C" _declspec(dllexport),如上面例子加声明: extern "C" _declspec(dllexport) int add(int x,int y);因为对于C,大多数的编译结果差不多,可以通用。详细见qt Assistant QLibrary。
问题2的简答:
在项目中给了一个dll库,库中提供设置回调函数的库函数,
- void setCallbackFunc(void* pfunc)
- {
- callBackFunc = (CallBackRecv)pfunc;
- }
我负责的是客户端界面,与服务器socket通信的模块在dll库中用VC实现,通信中当收到数据时在dll中就调用callBackFunc(recvBuf,recvLen),这个callBackFunc是一个函数指针,在那里只是占了个位,就是为了获得接受数据。而具体的要自己写个回调函数getRecv(char *,int),在通过调用setCallbackFunc()函数,将回调函数指针传给callBackFunc函数指针。注意回调函数的参数与dll中调用的callBackFunc函数参数一致。
项目中需要通过回调函数返回服务器发过来的数据,再将该数据现在在界面中的某个控件中。这时我就想到将getRecv()函数作为界面类的一个成员函数,但是编译出错。究其原因,原来进入类成员函数的时候,会把this指针压入堆栈。也就是说,一般的成员函数,其真正的原型比你书写出来的要多一个this,而回调函数要求与调用时的函数参数个数和类型要相同。而static的成员函数则不会多出来这个参数,所以可以将回调函数设置为界面类的静态函数,而静态函数只能调用静态数据成员,这样就引出了第3个问题,怎么调用界面类中控件将得到的数据显示在控件上?
问题3简答:
一般类静态函数想要对类中非静态成员或成员函数操作时,可以在静态函数参数中加个参数,然后再将this指针传给该参数实现,如VC中通过创建静态实例的实现:
- class CPackData
- {
- public:
- CPackData(void);
- ~CPackData(void);
- static inline CPackData* getInstance();
- static void doSomething(void *lParam){...};
- private:
- static CPackData _instance;
- };
- CPackData* CPackData::getInstance()
- {
- return &_instance;
- }
- CPackData CPackData::_instance;
调用doSomething()时, 在函数中直接调用CPackData::getInstance()就能获得this指针,而对于非静态实例时就比较麻烦,对于qt,在函数执行时要先初始化QApplication,在初始化QApplication之前创建的QObjects对象,主进程只处理这些对象的posted事件,而不处理其他任何事件,因为这些对象不属于任何线程。
而静态类在预编译时,就已经创建了,所以会有点问题。
用非静态实例时,也可以通过传参数的方法将this指针传进去,而回调函数的参数与调用该函数的函数指针要求参数个数和类型相同,所以不能通过增加参数的方式向回调函数传this指针。由于程序从main函数开始运行,当初始化app和类的实例后,通过调用app.exec()进入事件循环,所以可以在调用app.exec()之前将类实例地址传进类中,通过在类中增加一个静态函数用于从mian函数中将类实例的地址传进来给类的静态数据成员pThis指针,回调函数并可以通过调用静态成员pThis指针得到类实例的地址了,这样就可以调用其成员了。
/*20111124新增*/
今天在实践中发现了解决问题3的另一个较方便的方法,但实际目的都是一样的,将this指针传入回调函数中使用,不同的是上面这种方法只能对单实例程序有用,多实例时静态pThis指针就指向最后一个实例了,下面这种方法对多实例问题也可解决。
主要思想就是,首先动态库是可以修改的,比如说动态库有组内其他人编写,你可以让他在回调函数中给你增加一个参数void *lParam,再在所要调用的实例构造函数外、回调函数前,将回调函数注册,并将this指针在调用回调函数时传入就行了。
问题4简答:
可以继承QThread,重新实现run()函数,并增加信号,通过信号和槽的机制实现多线程间的通信,socket线程受到数据时,在回调函数中发送QThread子类的一个信号,该信号在GUI线程(主线程)中,通过connect函数,将该信号和类实例相应的槽函数关联起来,这样就OK了!!