COM pointers when programming AutoDesk Inventor

Top  Previous  Next

Here I'm paraphrasing Adam Nagy who helped me understand this stuff.

 

When using COM objects like the ones provided by the Inventor COM API, then you have to take care of releasing the references to the objects you retrieved. This is what CComPtr is helping you with, which is a smart-pointer and provides auto release of objects: when a CComPtr object variable goes out of scope then its destructor will be called and there it will release the COM object it is referencing, i.e. it will decrease the reference counter on that COM object.

 

Here are a couple of examples to help explain when you have to explicitly release the object or how to reorganize your code to release the COM objects in time: all objects need to be released before CoUninitialize() is called.

 

If your CComPtr is declared inside a function (local variable) then it will only be destructed - just like any other local variables - when the function returns, and that's when it will release the reference to the COM object. In case of this sample function the pointer is released too late because of that:

 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

  HRESULT Result = NOERROR;

 

  ::CoInitialize(NULL);

 

  // Access Inventor

  CLSID InvAppClsid;

  Result = CLSIDFromProgID (L"Inventor.Application", &InvAppClsid);

  if (FAILED(Result)) return Result;

 

  CComPtr<IUnknown> pInvAppUnk;

  Result = ::GetActiveObject (InvAppClsid, NULL, &pInvAppUnk);

  if (FAILED (Result))

    TRACE("Could not get the active Inventor instance\n");

 

  if (FAILED(Result)) return Result;

 

  CComPtr<Application> pInvApp;

  Result = pInvAppUnk->QueryInterface(__uuidof(Application), (void **) &pInvApp);

  if (FAILED(Result)) return Result;

 

  ::CoUninitialize();

 

 

  return 0;

 

} // << pInvApp is only destructed now, i.e. it's only releasing the

  // Inventor Application object reference now, which is too late,

  // since CoUninitialize has already been called

 

One easy solution to this would be to place the code part that is interacting with the COM objects into a separate function, so that the local variables will be release by the time we get to CoUninitialize():

 

int accessInventor()

{

  HRESULT Result = NOERROR;

 

  // Access Inventor

  CLSID InvAppClsid;

  Result = CLSIDFromProgID (L"Inventor.Application", &InvAppClsid);

  if (FAILED(Result)) return Result;

 

  CComPtr<IUnknown> pInvAppUnk;

  Result = ::GetActiveObject (InvAppClsid, NULL, &pInvAppUnk);

  if (FAILED (Result))

    TRACE("Could not get the active Inventor instance\n");

  if (FAILED(Result)) return Result;

 

  CComPtr<Application> pInvApp;

  Result = pInvAppUnk->QueryInterface(__uuidof(Application), (void **) &pInvApp);

  if (FAILED(Result)) return Result;

 

  return Result;

}

 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

  HRESULT Result = NOERROR;

 

  ::CoInitialize(NULL);

 

  // Access Inventor

  accessInventor();

 

  // Once this function returned all its local objects have

 

  // been destructed, and so the COM objects have also been released

 

  ::CoUninitialize();

 

  return 0;

}

 

Another simple solution is to create a local scope around the local variables that ends before CoUninitialize():

 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

  HRESULT Result = NOERROR;

 

  ::CoInitialize(NULL);

 

  // Access Inventor inside a local scope created by the curly braces

  {

    CLSID InvAppClsid;

    Result = CLSIDFromProgID (L"Inventor.Application", &InvAppClsid);

    if (FAILED(Result)) return Result;

 

    CComPtr<IUnknown> pInvAppUnk;

    Result = ::GetActiveObject (InvAppClsid, NULL, &pInvAppUnk);

    if (FAILED (Result))

      TRACE(_"Could not get the active Inventor instance\n");

    if (FAILED(Result)) return Result;

 

 

    CComPtr<Application> pInvApp;

    Result = pInvAppUnk->QueryInterface(__uuidof(Application), (void **) &pInvApp);

 

    if (FAILED(Result)) return Result;

 

  } // << Now all the local variables have been destructed,

    // and so the COM objects have been released

 

  ::CoUninitialize();

 

 

  return 0;

}

 

When CComPtr is declared as a global variable then it will only be destructed at the very end, even after the main function of the application has returned:

 

// This will only be destructed after _tmain() returned

// which is too late, because by that time CoUninitialize()

// has been called. So we'll have to release the COM object

// it is referencing by setting it to NULL before calling

// CoUninitialize()

 

CComPtr<Application> pInvApp;

 

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[])

{

  HRESULT Result = NOERROR;

 

  ::CoInitialize(NULL);

 

  // Access Inventor

  {

    CLSID InvAppClsid;

    Result = CLSIDFromProgID (L"Inventor.Application", &InvAppClsid);

    if (FAILED(Result)) return Result;

 

    CComPtr<IUnknown> pInvAppUnk;

    Result = ::GetActiveObject (InvAppClsid, NULL, &pInvAppUnk);

    if (FAILED (Result))

      TRACE("Could not get the active Inventor instance\n");

 

    if (FAILED(Result)) return Result;

 

    // In this case pInvApp is a global variable

    // declared outside the function

    Result = pInvAppUnk->QueryInterface(__uuidof(Application), (void **) &pInvApp);

    if (FAILED(Result)) return Result;

  } 

 

  // Release the referenced COM object by setting the CComPtr

  // object to NULL

  pInvApp = NULL;

 

  ::CoUninitialize();

 

  return 0;

}

 

See what I mean?

 

 

Text, images and diagrams © 2021 Owen F. Ransen. All rights reserved. (But copy the source code as much as you want!)