1. Introduction.
1.1 When a .NET assembly (DLL or EXE) that contains a COM-visible class is compiled and registered via REGASM.EXE, a type library will be generated.
1.2 A type library may also be generated directly via TLBEXP.EXE.
1.3 The fact that this type library was generated from a managed source can be detected.
1.4 This is possible because the type library exporter, which handles the creation of the type library for the assembly, will insert a special custom IDL attribute for the type library.
1.5 This article will show an example of this and will demonstrate how a C++ client application can determine whether a type library is derived from a .NET assembly.
2. A Sample Assembly.
2.1 The following is an extremely simple code listing for a managed class library written in C# :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
namespace CSCOMServer
{
[ComVisible(true)]
public class CSCOMClass
{
}
}
It contains only one COM-visible class named CSCOMClass. After compilation, it produces the class library CSCOMServer.dll.
2.2 I then create a type library from this class library using TLBEXP :
tlbexp CSCOMServer.dll /out:CSCOMServer.tlb
2.3 The following will be the contents of such a type library in IDL :
[
uuid(0852199A-2E95-49E9-96D1-5D499BE8BD61),
version(1.0)
]
library CSCOMServer
{
// TLib : // TLib : mscorlib.dll : {BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}
importlib("mscorlib.tlb");
// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface _CSCOMClass;
[
uuid(E18D31BE-5B5D-39AC-A1B7-745009884002),
version(1.0),
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "CSCOMServer.CSCOMClass")
]
coclass CSCOMClass {
[default] interface _CSCOMClass;
interface _Object;
};
[
odl,
uuid(D6D1C92A-2B7D-3839-99D5-54C89212CE55),
hidden,
dual,
oleautomation,
custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "CSCOMServer.CSCOMClass")
]
interface _CSCOMClass : IDispatch {
};
};
2.4 Readers who have read my article How to Determine if a coclass is a Managed Class would recognize the GUID_ManagedName ({0F21F359-AB84-41E8-9A78-36D110E6D2F9}) custom attribute being applied to the CSCOMClass coclass which is a managed class exported to COM.
2.5 This custom attribute is inserted into the coclass definition of the output type library when the class library assembly is exported by TLBEXP.EXE.
2.6 Also inserted into the output type library by TLBEXP.EXE will be a custom attribute for the library statement itself which is the following part in the IDL :
[
uuid(0852199A-2E95-49E9-96D1-5D499BE8BD61),
version(1.0)
]
library CSCOMServer
{
...
}
2.7 The custom attribute applied to the CSCOMServer library item is identified by the GUID {90883F05-3D28-11D2-8F17-00A0C9A6186D}. This is a better known as GUID_ExportedFromComPlus.
2.8 Custom attributes, when applied to the library statement, are hidden when the type library is viewed by the OLEVIEW.EXE tool. Hence we do not see the presence of this custom attribute in the library statement of the IDL code of point 2.3.
2.9 This, however, does not prevent us from being able to detect its presence when we load the type library in code using the LoadTypeLib() API and then navigating through the type library using the ITypeLib and the ITypeLib2 interfaces. I will provide a demonstration of this in the next section.
3. Accessing the Type Library Custom Attribute.
3.1 As mentioned in point 2.9, to access the custom attribute of a type library, we need to first load the type library in code using the LoadTypeLib() API.
3.2 This API will return to us a pointer to an ITypeLib interface. We need to QueryInterface() this ITypeLib interface pointer for a ITypeLib2 interface.
3.3 Once we are able to access the ITypeLib2 interface, we can use its GetCustData() method to obtain a VARIANT which represents the value for the custom attribute identified by the GUID_ExportedFromComPlus GUID.
3.4 The following C++ code (for a console application) demonstrates this :
// CPPClientApp01.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <comutil.h>
// {90883F05-3D28-11D2-8F17-00A0C9A6186D}
static const GUID GUID_ExportedFromComPlus =
{ 0x90883F05, 0x3D28, 0x11D2, { 0x8F, 0x17, 0x00, 0xA0, 0xC9, 0xA6, 0x18, 0x6D } };
HRESULT GetTypeLibCustomAttribute
(
LPCTSTR lpszTypeLibraryPath, // Path to type library.
REFGUID refguid_custom_attribute, // GUID of the Custom Attribute.
VARIANT& varCustomAttributeReceiver // Receiver of the Custom Attribute.
)
{
_bstr_t bstTypeLibraryPath = lpszTypeLibraryPath;
ITypeLib* pTypeLib = NULL;
ITypeLib2* pITypeLib2 = NULL;
HRESULT hrRet = S_OK;
// Initialize receiver.
VariantInit(&varCustomAttributeReceiver);
hrRet = LoadTypeLib((const OLECHAR FAR*)bstTypeLibraryPath, &pTypeLib);
if (SUCCEEDED(hrRet))
{
if (pTypeLib)
{
hrRet = pTypeLib -> QueryInterface(IID_ITypeLib2, (void**)&pITypeLib2);
pTypeLib->Release();
pTypeLib = NULL;
}
if (pITypeLib2)
{
hrRet = pITypeLib2 -> GetCustData
(
refguid_custom_attribute,
&varCustomAttributeReceiver
);
pITypeLib2 -> Release();
pITypeLib2 = NULL;
}
}
return hrRet;
}
int _tmain(int argc, _TCHAR* argv[])
{
VARIANT varCustomAttribute;
VariantInit(&varCustomAttribute);
GetTypeLibCustomAttribute
(
"CSCOMServer.tlb",
GUID_ExportedFromComPlus,
varCustomAttribute
);
VariantClear(&varCustomAttribute);
return 0;
}
The above source contains a listing of the GetTypeLibCustomAttribute() helper function which performs the tasks as laid out in points 3.1 through 3.3.
When this code is run and the GetTypeLibCustomAttribute() helper function returns, the varCustomAttribute will contain the following BSTR value :
"CSCOMServer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
3.5 This BSTR value, however, is not as important as the fact that the type library contains a custom attribute identified by GUID_ExportedFromComPlus. This signifies the fact that the type library was generated from a .NET assembly, a managed source.
3.6 If GetTypeLibCustomAttribute() is run on a type library generated from a non-managed COM server, varCustomAttribute will remain as VT_EMPTY (due to it being initialized by VariantInit()).
4. In Conclusion.
4.1 If CSCOMServer.tlb was referenced in a managed code project, the following error message will be displayed :
A reference to ‘CSCOMServer.tlb’ could not be added.
The ActiveX type library ‘CSCOMServer.tlb’ was exported from a .NET assembly and cannot be added as a reference.
Add a reference to the .NET assembly instead.
4.2 This error message is invoked precisely due to the fact that the library object contained in the type library is marked with the GUID_ExportedFromComPlus custom attribute.
Discussion
Trackbacks/Pingbacks
Pingback: How to Determine if a coclass is a Managed Class. « limbioliong - November 6, 2011