PDA

View Full Version : Access a COM object from a 3rd party dll across threads



dean
22nd September 2010, 16:11
Hi!

I've been around here for quite a while now and found a lot of time saving hints. Thanks a lot!

For the problem I stumbled upon 2 days ago I did not find any suitable hint so any help is greatly appreciated.

Roughly speaking, I developed a multithreaded application that converts some data into a predefined format. Now I have to add another input format that is delievered in form a dll packed API.

When I put something like


QAxObject *myObj = new QAxObject();
myObj->setControl(CLASS_ID_OF_DLL);

somewhere in the main thread everything works fine and I can access the documented methods of the COM Object via dynamicCall(). But the same code in the process() method of another thread will lead to this error message:


QAxBase::setControl: requested control {....} could not be instantiated.

I can't find anything if it is simply not possible to call a COM object from different threads simultaneously or if I'm just to blind to see the solution.

Thanks in advance
Dean

wysota
22nd September 2010, 16:52
QAxObject is a QObject subclass and as such cannot be used accross threads.

dean
22nd September 2010, 18:24
Ok, this means I should able to create one individual QAxObject in each thread and access the dll but that results in the error listed above.

The library provides a 3rd party interface for accessing a propriatary file format and is needed by each converter thread that operates on his own file.

wysota
22nd September 2010, 18:33
Have the COM objects live in one thread that acts as a server for other threads. You can use signals and slots to communicate between threads, just don't call the COM wrapper directly.

dean
22nd September 2010, 18:43
That would be a nice idea. Unfortunatly this will not work because the processing of one image takes too long for sharing the COM object (it is blocked during the processing because it holds a lock on the current image).

Is it definitely impossible to create two QAxObjects on one COM object at the same time from different threads?

wysota
22nd September 2010, 18:54
Is it definitely impossible to create two QAxObjects on one COM object at the same time from different threads?
I don't know that. But if the com object is blocked, you won't be able to process more than one request at the same time anyway.

dean
22nd September 2010, 19:07
Well, it's not technically blocked. An ID representing the currently processed file is assigned to the COM object and only released after the processing has finished. Therefore more than one object is needed for parallel processing or otherwise I'm working two times on the same image.

Thank you for your hints so far, but the error from my first post is still persisting.

squidge
22nd September 2010, 19:30
Perhaps instead of doing multi threading you could use multi processing and communicate using IPC. This would solve your problem as each object would be in a different process space.

dean
22nd September 2010, 19:50
Till now different importers work together fine using multitasking. I just read through QProcess and IPC roughly but I will give it a try when I'm back in the office tomorrow. Thanks.

dean
23rd September 2010, 13:09
Thanks for your advices so far.

I digged into this thing a little bit further and stumbled across a really strange behaviour. For now I'm able to instantiate my QAxObject in a new thread. But only if accessed it from the main thread before.

The following code works fine but commenting out test() in main() will deliver an error that the control could not be instantiated. I'm getting a little confused now, since I can't find anything about that in the docs. Could someone please lead me in the right direction?


class MyThread : public QThread {
public:
MyThread(QObject *parent = 0);

protected:
void run();

private:
void process();
};

MyThread::MyThread(QObject *parent) : QThread(parent) {
moveToThread(this);
}

void MyThread::process() {
qDebug() << thread() << "accessing";
QAxObject *comObj = new QAxObject();
comObj->setControl("{CLASSID_HERE}");
::CoInitialize(comObj);
}

void MyThread::run() {
process();
}



void test() {
QAxObject *comObj = new QAxObject();
qDebug() << qApp->thread() << "accessing";
comObj->setControl("{CLASSID_HERE}");
CoInitialize(comObj);

QVariantList params = QVariantList();
params.append("D:\\path\\to\\file");
comObj->dynamicCall("openFile(QString)", params);

delete comObj;
}

int main(int argc, char* argv[]) {
QApplication a(argc, argv);

test();

MyThread *mt = new MyThread();
mt->start();

return a.exec();
}

_Stefan
27th October 2010, 12:50
I don't know if this still needs answering, but I stumbled upon this, looking for some answers myself.

The reason your code does not work, when not calling test, is because you are calling CoInitialize in there.
I think you misinterpreted what this function does, as you are passing your com object pointer.

To use COM, you need to initialize it, using CoInitialize. The pointer parameter is a reserved parameter and you should not pass anything in it.
But it still needs to be called, before creating a COM object (so before ->setControl), else it will fail. So I am guessing, the comObj->setControl in your test function, always fails.

However, since you are calling CoInitialize right after that (although with an invalid pointer, you should not pass), it works when you create your thread after that.
Hence, if you do not call test, COM is not intialized yet, so that is why creating your COM object fails in the thread.

So you should do something like this

CoInitialize(0); // So pass 0, instead of some pointer!

However, since you are using COM cross thread, you could also look at CoInitializeEx and pass COINIT_MULTITHREADED as second parameter. Look this up in the MSDN documentation.

Now you can create COM objects, using QAxObject and setControl, which should now work.
When you are done using COM, call CoUninitialize(); to release COM stuff.

And that should work ;)

dyndez
2nd March 2012, 21:06
Thanks for the tips on CoInitialize(); been chasing that problem for a week.

gmc
26th July 2012, 10:57
Hi,
I'm facing the same problem, but the above codes do not work for me... i have a compile time error "CoInitialize has not been declared" and cannot find any Qt documentation about this. Do you use MSVC compiler, and what includes do you have to access this function?
Thanks!

ChrisW67
27th July 2012, 02:41
It's nothing to do with Qt. CoInitialize() is a deprecated Windows API call, http://msdn.microsoft.com/en-us/library/windows/desktop/ms678543%28v=vs.85%29.aspx, declared in objbase.h but typically included via windows.h and ole2.h. The ActiveQt server startup code calls the equivalent function on your behalf anyway.

Maxbester
10th April 2013, 18:19
The ActiveQt server startup code calls the equivalent function on your behalf anyway.

So what's wrong if the error remains?

I have the following error after a second thread is started:

QAxBase: Error calling IDispatch member Version: Unknown error

lanz
11th April 2013, 07:57
You should call CoInitializeEx in every thread that uses COM. ActiveQt initializes GUI thread for you, but for every extra thread you need to call CoInitializeEx/CoUninitialize,

Another thing, on VS I had to use ::CoInitializeEx(i.e. using global namespace modifier)

Maxbester
11th April 2013, 09:53
You should call CoInitializeEx in every thread that uses COM. ActiveQt initializes GUI thread for you, but for every extra thread you need to call CoInitializeEx/CoUninitialize,

Another thing, on VS I had to use ::CoInitializeEx(i.e. using global namespace modifier)

Okay thanks.

To be sure, when to call CoInitializeEx? I guess in the object constructor before instantiating any COM object.

lanz
11th April 2013, 10:35
No, see http://msdn.microsoft.com/en-us/library/windows/desktop/ms695279(v=vs.85).aspx

CoInitializeEx should be called once per thread, say right after it started. And of course before instantiating any COM object.