How to destroy COM object created by QAxObject?
-
wrote on 11 Oct 2021, 11:58 last edited by Max Gabler 10 Nov 2021, 11:59
Hello,
I'm struggling for quiet some time now but couldn't figure out how instances of an COM object which were created via QAxObject::setControl() could be destroyed.
The documentation of the QAxObject destructor states: "Releases the COM object and destroys the QAxObject, cleaning up all allocated resources.".So, without creating any copies etc. of this object, its lifetime should end when the QAxObject instance is destroyed. BUT: As I've found out the COM object survives the QAxObjects desctructor and lives on until the calling app is shutdown completely.
I've extracted the following simplified example to demonstrate said behaviour:
The main.ccp loads the COM object, calls a method and than destroys it:
// main.cpp #include <QAxObject> #include <QCoreApplication> #include <QDebug> #include <combaseapi.h> int main(int argc, char *argv[]) { qInfo("main: Starting test..."); QCoreApplication a(argc, argv); if (SUCCEEDED(CoInitializeEx(0, COINIT_MULTITHREADED))) { qInfo("main: CoInitializeEx() succeeded"); QAxObject *axObject = new QAxObject(); axObject->setControl("MyComObject.ComObject"); axObject->dynamicCall("SayHello"); delete axObject; qInfo("main: QAxObject deleted"); CoUninitialize(); } else { qFatal("main: CoInitializeEx() failed"); } qInfo("main: Done..."); return a.exec(); }
The following code shows a very simple example of the COM objects implementation in C#:
using System; using System.Runtime.InteropServices; namespace MyComObject { [Guid("8157be30-e99a-4b14-b88d-bc8db831047b")] public interface IComObject { [DispId(1)] void SayHello(); } [Guid("6c080298-7b10-494b-84af-38b6db12a7d4"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface ComObjectEvents { } [Guid("63d3c24d-2a60-4338-b312-63a771c580bd"), ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(typeof(ComObjectEvents))] public class ComObject : IComObject { public ComObject() { Console.WriteLine("ComObject: Object created"); } ~ComObject() { Console.WriteLine("ComObject: Object destroyed"); } public void SayHello() { Console.WriteLine("ComObject: Hello!"); } } }
Running the main program produces the following output:
main: Starting test... main: CoInitializeEx() succeeded ComObject: Object created ComObject: Hello! main: QAxObject deleted main: Done...
What I'm missing here is the output from the COM objects destructor, which tells me that the object isn't destroyed at all.
Adding "QTimer::singleShot(0, &a, SLOT(quit()));" just before "a.exec()" the program actually terminates and produces "ComObject: Object destroyed" as the very last line of the output.My question now is: How is an instance of a COM object which was created by QAxObject::setControl() destroyed properly?
Did I miss something here? -
Hello,
I'm struggling for quiet some time now but couldn't figure out how instances of an COM object which were created via QAxObject::setControl() could be destroyed.
The documentation of the QAxObject destructor states: "Releases the COM object and destroys the QAxObject, cleaning up all allocated resources.".So, without creating any copies etc. of this object, its lifetime should end when the QAxObject instance is destroyed. BUT: As I've found out the COM object survives the QAxObjects desctructor and lives on until the calling app is shutdown completely.
I've extracted the following simplified example to demonstrate said behaviour:
The main.ccp loads the COM object, calls a method and than destroys it:
// main.cpp #include <QAxObject> #include <QCoreApplication> #include <QDebug> #include <combaseapi.h> int main(int argc, char *argv[]) { qInfo("main: Starting test..."); QCoreApplication a(argc, argv); if (SUCCEEDED(CoInitializeEx(0, COINIT_MULTITHREADED))) { qInfo("main: CoInitializeEx() succeeded"); QAxObject *axObject = new QAxObject(); axObject->setControl("MyComObject.ComObject"); axObject->dynamicCall("SayHello"); delete axObject; qInfo("main: QAxObject deleted"); CoUninitialize(); } else { qFatal("main: CoInitializeEx() failed"); } qInfo("main: Done..."); return a.exec(); }
The following code shows a very simple example of the COM objects implementation in C#:
using System; using System.Runtime.InteropServices; namespace MyComObject { [Guid("8157be30-e99a-4b14-b88d-bc8db831047b")] public interface IComObject { [DispId(1)] void SayHello(); } [Guid("6c080298-7b10-494b-84af-38b6db12a7d4"), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] public interface ComObjectEvents { } [Guid("63d3c24d-2a60-4338-b312-63a771c580bd"), ClassInterface(ClassInterfaceType.None), ComSourceInterfaces(typeof(ComObjectEvents))] public class ComObject : IComObject { public ComObject() { Console.WriteLine("ComObject: Object created"); } ~ComObject() { Console.WriteLine("ComObject: Object destroyed"); } public void SayHello() { Console.WriteLine("ComObject: Hello!"); } } }
Running the main program produces the following output:
main: Starting test... main: CoInitializeEx() succeeded ComObject: Object created ComObject: Hello! main: QAxObject deleted main: Done...
What I'm missing here is the output from the COM objects destructor, which tells me that the object isn't destroyed at all.
Adding "QTimer::singleShot(0, &a, SLOT(quit()));" just before "a.exec()" the program actually terminates and produces "ComObject: Object destroyed" as the very last line of the output.My question now is: How is an instance of a COM object which was created by QAxObject::setControl() destroyed properly?
Did I miss something here?wrote on 11 Oct 2021, 12:06 last edited by JonB 10 Nov 2021, 12:11@Max-Gabler
QAxObject::QAxObject(QObject *parent = nullptr)
Creates an empty COM object and propagates parent to the QObject constructor. To initialize the object, call setControl.QAxObject::~QAxObject()
Releases the COM object and destroys the QAxObject, cleaning up all allocated resources.Maybe not your finding, just saying the docs imply destructor does the job for you?
Also void QAxBase::clear() says "Disconnects and destroys the COM object.", don't suppose that's any better for you?
-
wrote on 11 Oct 2021, 12:15 last edited by
"the docs imply destructor does the job for you" <-- That is exactly, what I've expected. But it turned out that this wasn't the case.
I already tried calling QAXBase::clear() before deletion without any change as the destructor calls it anyways... -
"the docs imply destructor does the job for you" <-- That is exactly, what I've expected. But it turned out that this wasn't the case.
I already tried calling QAXBase::clear() before deletion without any change as the destructor calls it anyways...wrote on 11 Oct 2021, 12:31 last edited by@Max-Gabler
Could be wrong, but I don't expect you to get a better answer here. Can only suggest: look at source code and maybe raise as "bug" over at Qt bug forum in the hope that somebody there can answer better? -
wrote on 11 Oct 2021, 14:22 last edited by
Hi, just a guess, but doesn't this occur because C# does not delete objects deterministically, i.e. you have to wait for the garbage collector to kick in (or in your case for the program to terminate).
One workaround is to call the Dispose method in C# explicitly so that the COM object is destroyed directly, add something like this to your C# code:
.... // after SayHello()l [DispId(0x10000002)] void Dispose(); } ... // after SayHello() imp. public void Dispose() { Console.WriteLine("ComObject: disposed"); }
and in your C++ code add a dispose call after SayHello:
... axObject->dynamicCall("SayHello"); axObject->dynamicCall("Dispose"); ...
Note I have written maybe 20 lines of C# code during the last 20 years, so I'm not a C# expert :-)
-
Hi, just a guess, but doesn't this occur because C# does not delete objects deterministically, i.e. you have to wait for the garbage collector to kick in (or in your case for the program to terminate).
One workaround is to call the Dispose method in C# explicitly so that the COM object is destroyed directly, add something like this to your C# code:
.... // after SayHello()l [DispId(0x10000002)] void Dispose(); } ... // after SayHello() imp. public void Dispose() { Console.WriteLine("ComObject: disposed"); }
and in your C++ code add a dispose call after SayHello:
... axObject->dynamicCall("SayHello"); axObject->dynamicCall("Dispose"); ...
Note I have written maybe 20 lines of C# code during the last 20 years, so I'm not a C# expert :-)
wrote on 11 Oct 2021, 15:09 last edited by JonB 10 Nov 2021, 15:10@hskoglund
Hi.Isn't the point of @Max-Gabler's question that he has something which works in C# but he is trying to make it as good as that in C++? That is what I understand it to be.and in your C++ code add a dispose call after SayHello:
Ohhh. So it's a mixed application with C++ calling C# ?
-
@hskoglund
Hi.Isn't the point of @Max-Gabler's question that he has something which works in C# but he is trying to make it as good as that in C++? That is what I understand it to be.and in your C++ code add a dispose call after SayHello:
Ohhh. So it's a mixed application with C++ calling C# ?
wrote on 11 Oct 2021, 19:22 last edited by@JonB said in How to destroy COM object created by QAxObject?:
Ohhh. So it's a mixed application with C++ calling C# ?
Yeah, COM is supposed to be language agnostic so it should work, but I'm guessing C#'s nondeterministic object lifetime is the culprit..
-
wrote on 12 Oct 2021, 09:54 last edited by Max Gabler 10 Dec 2021, 09:56
@JonB : Yes, it's a mixed app as it relays onto some 3rd party libs that are only available in C#. Not nice, but without alternative for now...
@hskoglund : I've been convinced that you were on to something until I learned, that Dispose-Pattern is very useful to free unmanaged resources immediately instead of wating for the garbage collector. But there is no way in C# for an object to destroy itself and free the used memory leaving nothing but a dangling pointer behind...
I dug a little deeper into the topic and it seems, that this isn't Qt related. I tried the same, using only standard C++ and the ComBaseApi (combaseapi.h), and got pretty much to the same result. The COM objects reference count actually drops to zero as it is supposed.
Here I learned tha when creating a COM object a Runtime Callable Wrapper (RCW) is created to own the object and hold a strong reference to it which could possibly prevent tha GC from doing it's job. I guess I will have a closer look on that...
1/8