//
you're reading...
CLR

Research Topic : Hidden COM Callable Wrapper.

1. Introduction.

1.1 Recently, someone from the MSDN forum presented a very interesting question.

1.2 He wrote a simple C# class which is COM exported. When he instantiated one such a managed class in an unmanaged C++ application, he noticed that there were two COM-Callable Wrappers created in the application. One for the exported C# class and the other he could not identify.

1.3 The tool he used to detect the count of CCWs was ProcessExplorer.exe. Link provided below :

http://technet.microsoft.com/en-us/sysinternals/bb896653

To see the number of CCWs instantiated for an unmanaged client app, select the client app in ProcessExplorer, right click on it and select the “Properties” context menu item. Thereafter, select the “.NET Performance” tab. In the combo box labelled “.NET Performance Objects”, select “.NET CLR Interop”. The number of CCWs currently instantiated for the application is listed for the “# of CCWs” counter.

1.4 However, when he instantiated the same class another time, only one additional CCW was created. He concluded that one extra hidden CCW seems to be created for the unmanaged client. It appears to be some kind of overhead.

1.5 This blog will attempt to provide an explanation for this phenomenon.

2. Sample Code for Recreation.

2.1 Presented below is a sample C# listing for 2 managed classs which are COM exported :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices; 

namespace CSharpTest01
{
  [ComVisible(true)]
  [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  public interface ITestInterface
  {
    void TestMethod();
  } 

  [ComVisible(true)]
  [ClassInterface(ClassInterfaceType.None)]
  public class Class1 : ITestInterface
  {
    public void TestMethod()
    {
      Console.WriteLine("TestMethod()");
    }
  } 

  [ComVisible(true)]
  [ClassInterface(ClassInterfaceType.None)]
  public class Class2 : ITestInterface
  {
    public void TestMethod()
    {
      Console.WriteLine("TestMethod()");
    }
  }
}

After compiling the C# code, run REGASM.EXE as usual to COM register the Class Library Assembly.

2.2 Presented below is a sample C++ client code listing :

#include "stdafx.h"
#import "..\CSharpTest01\bin\Debug\CSharpTest01.tlb"
using namespace CSharpTest01; 

int _tmain(int argc, _TCHAR* argv[])
{
  HRESULT hr = E_FAIL; 

  hr = ::CoInitialize(0); 

  if (1)
  {
    ITestInterfacePtr p;
    p.CreateInstance(_uuidof(Class1));
    p->TestMethod(); 

    ITestInterfacePtr p2;
    p2.CreateInstance(_uuidof(Class1));
    p2->TestMethod(); 

    ITestInterfacePtr p3;
    p3.CreateInstance(_uuidof(Class2));
    p3->TestMethod(); 

    ITestInterfacePtr p4;
    p4.CreateInstance(_uuidof(Class2));
    p4->TestMethod();
  } 

  ::CoUninitialize();
  return 0;
}

2.3 When we step through the C++ code while keeping ProcessExplorer.exe running at one side, we will observe that when an instance of Class1 is created for “p”, the CCW count becomes 2.

ITestInterfacePtr p;
p.CreateInstance(_uuidof(Class1)); // CCW is counted as 2 here.
p->TestMethod();

2.4 Later, when a second instance of Class2 is created, the CCW count becomes 3 (not 4 as we might expect) :

ITestInterfacePtr p2;
p2.CreateInstance(_uuidof(Class1)); // CCW becomes 3.
p2->TestMethod();

2.5 The same goes for the rest of the code, just one additional CCW is created for every new managed class instantiated (as a COM object). This is so even if a different managed class is instantiated :

ITestInterfacePtr p3;
p3.CreateInstance(_uuidof(Class2));  // CCW count incremented by 1.
p3->TestMethod(); 

ITestInterfacePtr p4;
p4.CreateInstance(_uuidof(Class2));  // CCW count incremented by 1.
p4->TestMethod();

3. My Theory : CCW is Associated with the COM Class Factory.

3.1 My current theory is that the extra CCW is very likely associated with the COM Class Factory for the managed class that is to be instantiated.

3.2 This COM Class Factory is provided by MSCOREE.DLL which is the .NET Runtime Execution Engine that is responsible for instantiating managed classes for COM interop. It is retrieved by the standard COM DLL exported API DllGetClassObject().

3.3. Note that MSCOREE.DLL provides one standard generic COM class factory implementation to create an instance of any .NET Framework class. See “Registering Assemblies with COM” sub-section “Examining Registry Entries” :

http://msdn.microsoft.com/en-us/library/h627s4zy(v=vs.71).aspx

Another interesting article that discusses MSCOREE.DLL can be found in :

http://www.bandgap.cs.rice.edu/classes/comp410/resources/Lists/Windows%20System%20Programming/Attachments/17/Managed%20vs%20Unmanaged%20COM%20components.htm

3.4 Because MSCOREE.DLL provides only one generic COM class factory for each unmanaged application and the fact that only one such special CCW is ever created for the client app makes me believe that the CCW is likely used to wrap a generic managed object that the COM class factory uses to load the required assembly and perform the managed object instantiation.

3.6 If so it is possibly the .NET Activator object. We know that Activator.CreateInstanceFrom() is a static method that can be used generically for instantiating managed objects from various assemblies. Hence only one instance of Activator is necessary. It can certainly be used as part of the managed object creation process.

3.7 This is just my theory. It is nevertheless possibly supported by additional observations which is reported in section 4 below.

4. Special CCW is Created when the IClassFactory::CreateInstance() is Called for the First Time.

4.1 To test my theory, I created 2 new functions the purposes of which is to perform instantiation of the managed class by using the Class Factory object which is exported by MSCOREE.DLL. These are listed below.

4.2 Listed below is a sample function that I used to perform instantiation by using the ::CoGetClassObject() API :

void FirstInstanceCreation_CoGetClassObject(ITestInterfacePtr& pReceiver)
{
  IClassFactoryPtr spIClassFactory = NULL;
  ::CoGetClassObject ( _uuidof(Class1), CLSCTX_ALL, NULL, __uuidof(IClassFactory), (LPVOID*)&spIClassFactory );
  spIClassFactory ->CreateInstance(NULL, __uuidof(ITestInterface), (void**)&pReceiver);
}

4.3 I then changed the code in _tmain() that instantiates the first Class1 class :

ITestInterfacePtr p;
//p.CreateInstance(_uuidof(Class1));
FirstInstanceCreation_CoGetClassObject(p);
p->TestMethod();

4.4 I noticed that when the Class Factory is created, the CCW count is 0. The CCW only increases to 2 when IClassFactory::CreateInstance() is called :

// CCW increases to 2 when this is called.
spIClassFactory->CreateInstance(NULL, __uuidof(ITestInterface), (void**)&pReceiver);

However, this only happened when IClassFactory::CreateInstance() is called for the first time. Hence even if FirstInstanceCreation_CoGetClassObject() is used again to instantiate another Class1 class, the CCW count increases by only one.

4.5 I also went further and created another function for instantiation :

typedef HRESULT (__stdcall *PDllGetClassObject) ( const CLSID & rclsid, const IID & riid, void ** ppv ); 

void FirstInstanceCreation_DllGetClassObject(ITestInterfacePtr& pReceiver)
{
  HINSTANCE hInstDLL = LoadLibrary("c:\\windows\\system32\\mscoree.dll");
  IClassFactoryPtr spIClassFactory = NULL; 

  PDllGetClassObject pDllGetClassObject = (PDllGetClassObject)GetProcAddress(hInstDLL, "DllGetClassObject"); 

  pDllGetClassObject ( __uuidof(Class1), __uuidof(IClassFactory), (void **)&spIClassFactory ); 

  spIClassFactory ->CreateInstance(NULL, __uuidof(ITestInterface), (void**)&pReceiver); 

  FreeLibrary(hInstDLL);
  hInstDLL = NULL;
}

This time, I specifically used the DllGetClassObject() function exported from MSCOREE.DLL to obtain the Class Factory. I then used it for instantiation. The effects were the same.

4.6 I took yet a further step and created a thread that performs instantiation :

DWORD WINAPI ThreadProc ( LPVOID lpParameter )
{
  HRESULT hr = E_FAIL; 

  hr = ::CoInitialize(0); 

  if (1)
  {
    ITestInterfacePtr p; 
    p.CreateInstance(_uuidof(Class1));
    p->TestMethod(); 

    ITestInterfacePtr p2;
    p2.CreateInstance(_uuidof(Class2));
    p2->TestMethod();
  } 

  ::CoUninitialize();
  return 0;
}

I then ran 2 tests : the first was with the thread being run before the main thread created Class1 objects. The second was with the thread being run after the main thread created Class1 objects. The results were the same.

4.7 The conclusion that I draw at this time is that the special hidden CCW is likely a re-usable managed object which is associated with managed class instantiaion.

4.8 If anyone has any additional ideas on the above findings, please do drop a comment. Thanks very much.

Advertisements

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.

Discussion

No comments yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: