HnxGC uses reference counting (combined with tracing collection) to manage life time of objects.
It supports Microsoft Component Object Model (COM) by nature.
(1) You can replace Microsoft
ATL (Active Template Library)
smart pointers with HnxGC smart pointers, and
(2) you can create COM objects under the management of HnxGC to make them circular collectible.
First, let's begin with an example from Microsoft MSDN DOM documentation -
MSDN: / Win32 and COM Development / XML / MSXML / DOM / DOM Concepts / How Do I Use DOM? /
Program with DOM in C/C++ Using Smart Pointer Class Wrappers /
Load an XML DOM Object from an XML File.
The example creates an XML DOM object and loads an XML data file into it.
It performs the following steps:
(1) Creates an XML DOM object (pXMLDom) and sets it to synchronous mode.
(2) Calls the load method on pXMLDom, specifying the path to stocks.xml.
The following is the original Visual C++ source code. (XML data file: stocks.xml)
#include <stdio.h>
#import <msxml4.dll>
using namespace MSXML2;
int main(int argc, char* argv[])
{
using MSXML2::IXMLDOMDocumentPtr;
IXMLDOMDocumentPtr pXMLDom;
HRESULT hr;
CoInitialize(NULL);
hr = pXMLDom.CreateInstance(__uuidof(DOMDocument30));
if (FAILED(hr))
{
printf("Failed to instantiate an XML DOM.\n");
return -1;
}
pXMLDom->async = VARIANT_FALSE; // default - true,
if(pXMLDom->load("stocks.xml")!=VARIANT_TRUE)
{
printf("Failed to load stocks.xml:\n%s\n",
(LPCSTR)pXMLDom->parseError->Getreason());
return -1;
}
else
printf("XML DOM loaded from stocks.xml:\n%s\n",
(LPCSTR)pXMLDom->xml);
pXMLDom.Release();
CoUninitialize();
return 0;
}
In HnxGC, you can rewrite the C++ source code as the following:
#include <stdio.h>
#import <msxml4.dll>
using namespace MSXML2;
#include "hnxGC.h"
int main(int argc, char* argv[])
{
using MSXML2::IXMLDOMDocument;
gcptr<IXMLDOMDocument> pXMLDom;
HRESULT hr;
CoInitialize(NULL);
hr= pXMLDom.CoCreateInstance(__uuidof(DOMDocument30));
if (FAILED(hr))
{
printf("Failed to instantiate an XML DOM.\n");
return -1;
}
pXMLDom->async = VARIANT_FALSE; // default - true,
if(pXMLDom->load("stocks.xml")!=VARIANT_TRUE) {
printf("Failed to load stocks.xml:\n%s\n",
(LPCSTR)pXMLDom->parseError->Getreason());
return -1;
}
else
printf("XML DOM loaded from stocks.xml:\n%s\n",
(LPCSTR)pXMLDom->xml);
pXMLDom = nullptr;
CoUninitialize();
return 0;
}
In this HnxGC example, visual C++ smart pointer IXMLDOMDocumentPtr was replaced by HnxGC smart pointer gcptr<IXMLDOMDocument>, a HnxGC managed pointer to IXMLDOMDocument interface. The call to the smart pointer's CoCreateInstance() method creates a COM object and then stores the returning interface in the smart pointer.
After the COM interface is stored into the HnxGC managed pointer, you are free to use the HnxGC pointer to replace the use of a traditional C/C++ pointer to the COM interface.
In this example, we use CoCreateInstance() method to store an interface into HnxGC pointer. There are several other ways to store a COM interface to a managed pointer, which will be described below.
(1) First of all, you can attach a COM interface to a HnxGC pointer. HnxGC pointers have Attach / AttachQI methods, which accept a pointer to COM interface as input parameter. These methods will clear the original contents of the HnxGC pointer, and assume a new reference to the specified COM interface. The method Attach only accepts a COM interface of the type 'T' of the same of HnxGC pointer type, meanwhile AttachQI accepts any COM interface and will invoke QueryInterface on the COM interface to get a "T" type interface.
In the above case of loading a XML data file, the statement
hr = pXMLDom.CoCreateInstance(__uuidof(DOMDocument30));
can be rewritten to:
IXMLDOMDocument * ppv = 0;
hr = ::CoCreateInstance(__uuidof(DOMDocument30),
NULL, CLSCTX_INPROC_SERVER,
__uuidof(IXMLDOMDocument),
(void **)&ppv);
if (hr == S_OK) {
pXMLDom.Attach(ppv);
}
Note, after attached to a HnxGC pointer, you should not call the Release method upon the original native interface pointer, since the HnxGC smart pointer will do that automatically.
Click to see detailed descriptions of Attach / AttachQI methods.
(2) You can use
CoCreateInstance
method of HnxGC pointer to invoke COM's CoCreateInstance
system service, and store the resulting reference to the newly created COM object in the
HnxGC pointer. In the above example, the statement
hr = pXMLDom.CoCreateInstance(__uuidof(DOMDocument30));
is an example of calling the CoCreateInstance method.
(3) You can use QueryInterface method of HnxGC pointer to query for a "T" interface from any given COM interface, and store the result in the HnxGC pointer.
IUnknown * pUnkwn; gcptr<IXMLDOMDocument> pXMLDom; ... ... pXMLDom.QueryInterface(pUnkwn);
(4) You can use GetQIRef method of HnxGC pointer to accept a COM interface from an output parameter of function call.
For example, you can use GetQIRef to remove the temporay variable 'ppv' in the example of the (1) as the follows:
hr = ::CoCreateInstance(__uuidof(DOMDocument30),
NULL, CLSCTX_INPROC_SERVER,
__uuidof(IXMLDOMDocument),
pXMLDom.GetQIRef());
(5) You can assign a COM interface to HnxGC pointer from a Microsoft ATL smart pointer. For example, in the above case of loading a XML data file, you can "move" reference from Visual C++ ATL smart pointer to HnxGC as demonstrated below.
IXMLDOMDocumentPtr pXMLDomTemp;
gcptr<IXMLDOMDocument> pXMLDom;
HRESULT hr;
hr = pXMLDomTemp.CreateInstance(__uuidof(DOMDocument30));
if (FAILED(hr)) { ... }
pXMLDom = pXMLDomTemp;
...
Note: In above example, the assigning an IXMLDOMDocumentPtr pointer to HnxGC pointer will clear the IXMLDOMDocumentPtr, such behavior is more like a "moving reference" than "copying/duplicating reference".
HnxGC not only enables you define a C++ class that are garbage collectible, but also enables you define a C++ class for COM. You just puts the HNXGC_IMPLEMENT_IUNKNOWN macro in your class declaration and follows some rules, then the HnxGC system will manipulate reference counting and implement IUnknown COM interface for you.
You can use the HNXGC_IMPLEMENT_IUNKNOWN macro accompany with or without HNXGC_ENABLE macro, but you have to guarantee that instances of the class are created via gcnew (or HNXGC_NEW) statement, i.e. the instances of the class must be under the management of HnxGC system, no matter the class is defined as a HnxGC managed or as a traditional native one.
Besides, you have to define two member functions for your class:
(1) void * _hnxgcGetCompleteObject()
This method should be implemented by you to returns the address of the outermost object that gcnew returns. Normally, you can just return 'this' in the implementation of this method. HnxGC system will use the address to implement AddRef()/Release() methods for reference count maintenance. For aggregation case, you should return the address of the outer HnxGC managed object.
(2) void * _hnxgcQueryInterface(const IID & iid)
This method should be implemented by you to return a COM interface or NULL according to the specified interface GUID. This is the actual implementation of query interface that will be called in IUnknown::QueryInterface() in HNXGC_IMPLEMENT_IUNKNOWN.
Following, we will give a pseudo code example of defining a HnxGC class for COM.
class CFoo : public IFoo {
... // < implementation of CFoo and COM interface IFoo, etc >
void * _hnxgcGetCompleteObject() {
return this;
}
void * _hnxgcQueryInterface(const IID & iid) {
if (iid == __uuidof(IUnknown)) {
return static_cast<IUnknown*>(this);
} else if (iid == __uuidof(IFoo)) {
return static_cast<IFoo*>(this);
}
... // < other supported interfaces >
return NULL;
}
public:
HNXGC_IMPLEMENT_IUNKNOWN
};
// create an object and return its IFoo interface
IFoo* CreateObject() {
gcptr<CFoo> pObj = gcnew CFoo();
pObj->AddRef();
return static_cast<IFoo*>(pObj);
}
Note - The reference counting provided by HNXGC_IMPLEMENT_IUNKNOWN is intelligently adaptive to the environment, so you can just consider it safe automatically for multi-threading and multi-processors.
It is well-known that, once references between COM objects establish a circular or cyclic relationship, these objects and their descendants cannot be collected because the reference counts of them will never drop to zero after use. However, if you adapt to use HnxGC managed pointers to control the life-time of COM objects, those circular-referenced COM objects and descendants might be collected by HnxGC system.
Suppose you define some COM classes using HNXGC_IMPLMENT_IUNKNOWN macro and create some instances of them. Then, those objects referenced by traditional COM objects or codes are treated reachable and live by HnxGC system, i.e. objects will only be collected by HnxGC system when there is no traditional reference to them directly or indirectly. References from HnxGC managed pointer, such as gcptr<> and variants, will be intelligently examined by HnxGC system, so that even unused objects establish circular relations the system can detect and reclaim them.
In the following, we will give an example demonstrates how circular-referenced COM objects be collected by HnxGC.
#include <ole2.h>
#include "hnxGC.h"
struct __declspec(uuid("2ECBF8CD-E787-43c0-BC72-5547B5EB11D1"))
INode : public IUnknown {
virtual void SetLink(IUnknown * pNext) = 0;
};
class CFoo : public INode {
private:
HNXGC_ENABLE(CFoo)
HNXGC_IMPLEMENT_IUNKNOWN
~CFoo() {
printf("COM object [%p] destroyed\n", this);
}
void * _hnxgcGetCompleteObject() {
return this;
}
void * _hnxgcQueryInterface(const IID & iid) {
if (iid == __uuidof(IUnknown)) {
return static_cast<IUnknown*>(this);
} else if (iid == __uuidof(INode)) {
return static_cast<INode*>(this);
}
return NULL;
}
gcptr<IUnknown> m_pNext;
void SetLink(IUnknown *pNext) {
pNext->AddRef();
m_pNext.Attach(pNext);
}
public:
static INode* CreateInstance();
};
INode * CFoo::CreateInstance()
{
gcptr<CFoo> pObj = gcnew CFoo;
INode * pRet = pObj;
pRet->AddRef();
return pRet;
}
int main(int argc, char* argv[])
{
CoInitialize(NULL);
gcptr<INode> pFirst;
gcptr<INode> pLast;
// create a single-linked list of nodes
for (int i = 0; i < 5; i++) {
gcptr<INode> pCurr;
pCurr.Attach(CFoo::CreateInstance());
if (!pFirst) {
pFirst = pCurr;
}
if (pLast) {
pLast->SetLink(pCurr);
}
pLast = pCurr;
}
// form a circular-referenced relation
pLast->SetLink(pFirst);
hnxgc.Collect(); // <-- this will not collect live objects
// after use, make objects unreachable
printf("=== After Use ===\n");
pFirst = pLast = nullptr;
// explicit garbage collection before CoUnitialize()
hnxgc.Collect();
printf("=== End of Program ===\n");
CoUninitialize();
return 0;
}
In the above example, the detail implementation of class CFoo is encapsulated and only export a public static function for creating an instance of class and return a INode COM interface. The main() function accesses objects via INode interfaces and establish a circular single-linked list of COM objects. These objects are live and reachable, and will not be collected by HnxGC. After use, reference to the list are removed from execution stack, but references between objects are remained. These unused objects cannot be collected by traditaionl COM reference counting, but they are collectable for tracing garbage collection of HnxGC. Destructors of these objects are executed during the garbage collection and messages from these destructors can be seen from the output of the program.
There are some other source codes:
Traditional ATL (cannot collect circular-referenced garbage)