Interop: Remove prefix from C# Enums for COM

17. May 2010 09:44 by Mrojas in General  //  Tags:   //   Comments (0)

Sometimes when you do a migration, you still need to “Interop” with legacy applications.

Normally this interop is done thru COM and .NET assemblies can be easily registered with COM.
However the devil is in the details, and there are always subtle details that can be difficult to tackle with
COM Interop.

One of those subtle details is that the tlbexp tool (this is the tool that generates the .tlb for a .NET assembly)
generates a prefix for enum elements.

So if you have something in C# like:

using System;
using System.Runtime.InteropServices;

namespace ClassLibrary1
{
    [ComVisible(true)]
    public enum simpleEnum
    {
     Field1 = 1,
     Field2 = 2,
     Field3 = 3,
     Field4 = 4,
     Field5 = 5
    } ;

}

Sadly that would generate a COM Interface like:

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: <could not determine filename>

[
  uuid(D3DB73ED-08B3-4E2F-AD20-61E44E3FDF17),
  version(1.0)
]
library ClassLibrary1
{
    // TLib :     // Forward declare all types defined in this typelib

    typedef [uuid(9D2C80FF-C124-33D2-9991-676A18DA7CAF), version(1.0)    ,
    custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ClassLibrary1.simpleEnum")    
]
    enum {
        simpleEnum_Field1 = 1,
        simpleEnum_Field2 = 2,
        simpleEnum_Field3 = 3,
        simpleEnum_Field4 = 4,
        simpleEnum_Field5 = 5,
    } simpleEnum;
};

And the problem with that is that your legacy programs are expecting something like:

enum { Field1 = 1, Field2 = 2, Field3 = 3, Field4 = 4, Field5 = 5, } simpleEnum;

Is there a way to solve this?
Well yes there is but it is not a nice one. You have to:

  • take the tlb that the tlbexp tool generates,
  • generate an .idl file from it
  • modify the .idl to correct its syntax
  • change the name of the enum fields
  • compile the .idl file to tlb with the midl compiler
  • use the generated tlb with the legacy application.

Lets see an example. Suppose we have something like:

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace ClassLibrary1
{
    [ComVisible(true)]
    
    public enum simpleEnum
    {
     Field1 = 1,
     Field2 = 2,
     Field3 = 3,
     Field4 = 4,
     Field5 = 5
    } ;

    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.AutoDual)] 
    public class Class1
    {
        public void foo(simpleEnum val)
        {
            switch(val)
            {
                case simpleEnum.Field1:
                    MessageBox.Show("FieldValue1");
                    break;
                case simpleEnum.Field2:
                    MessageBox.Show("FieldValue2");
                    break;
                case simpleEnum.Field3:
                    MessageBox.Show("FieldValue3");
                    break;
                case simpleEnum.Field4:
                    MessageBox.Show("FieldValue4");
                    break;
                case simpleEnum.Field5:
                    MessageBox.Show("FieldValue5");
                    break;
            }
        }
    };

}

When you open that generated IDL with the OLE2VIEW tool:

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: <could not determine filename>

[
  uuid(D3DB73ED-08B3-4E2F-AD20-61E44E3FDF17),
  version(1.0)
]
library ClassLibrary1
{
    // 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 _Class1;

    typedef [uuid(4087FBAF-9633-30D1-8A64-533E37B784B6), version(1.0)    ,
    custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ClassLibrary1.simpleEnum")    
]
    enum {
        simpleEnum_Field1 = 1,
        simpleEnum_Field2 = 2,
        simpleEnum_Field3 = 3,
        simpleEnum_Field4 = 4,
        simpleEnum_Field5 = 5
    } simpleEnum;

    [
      uuid(0D39F056-DF63-3860-9E79-B57F6358FD4D),
      version(1.0),
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ClassLibrary1.Class1")
    ]
    coclass Class1 {
        [default] interface _Class1;
        interface _Object;
    };

    [
      odl,
      uuid(1A87868B-7CE6-3C75-B2FA-71A86F77FC7D),
      hidden,
      dual,
      nonextensible,
      oleautomation,
        custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "ClassLibrary1.Class1")    

    ]
    interface _Class1 : IDispatch {
        [id(00000000), propget,
            custom({54FC8F55-38DE-4703-9C4E-250351302B1C}, "1")]
        HRESULT ToString([out, retval] BSTR* pRetVal);
        [id(0x60020001)]
        HRESULT Equals(
                        [in] VARIANT obj, 
                        [out, retval] VARIANT_BOOL* pRetVal);
        [id(0x60020002)]
        HRESULT GetHashCode([out, retval] long* pRetVal);
        [id(0x60020003)]
        HRESULT GetType([out, retval] _Type** pRetVal);
        [id(0x60020004)]
        HRESULT foo([in] simpleEnum val);
    };
};
 
When you use this tlb for example in VB6 look at the member names for the enum:
 
image 

If you want to be able to use your enum fields unchanged then this is the modified IDL you will have to use. The OLE2View Tool adds an extra “{“ to the IDL custom attribute and the enum name must be put after the enum keyword.

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: <could not determine filename>

[
  uuid(D3DB73ED-08B3-4E2F-AD20-61E44E3FDF17),
  version(1.0)
]
library ClassLibrary1
{
    // 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 _Class1;

    typedef [uuid(4087FBAF-9633-30D1-8A64-533E37B784B6), version(1.0)    ,
    custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary1.simpleEnum")    
]
    enum simpleEnum {
        Field1 = 1,
        Field2 = 2,
        Field3 = 3,
        Field4 = 4,
        Field5 = 5
    } simpleEnum;

    [
      uuid(0D39F056-DF63-3860-9E79-B57F6358FD4D),
      version(1.0),
        custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary1.Class1")
    ]
    coclass Class1 {
        [default] interface _Class1;
        interface _Object;
    };

    [
      odl,
      uuid(1A87868B-7CE6-3C75-B2FA-71A86F77FC7D),
      hidden,
      dual,
      nonextensible,
      oleautomation,
        custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "ClassLibrary1.Class1")    

    ]
    interface _Class1 : IDispatch {
        [id(00000000), propget,
            custom(54FC8F55-38DE-4703-9C4E-250351302B1C, "1")]
        HRESULT ToString([out, retval] BSTR* pRetVal);
        [id(0x60020001)]
        HRESULT Equals(
                        [in] VARIANT obj, 
                        [out, retval] VARIANT_BOOL* pRetVal);
        [id(0x60020002)]
        HRESULT GetHashCode([out, retval] long* pRetVal);
        [id(0x60020003)]
        HRESULT GetType([out, retval] _Type** pRetVal);
        [id(0x60020004)]
        HRESULT foo([in] simpleEnum val);
    };
};

Save the modified .idl file as ClassLibrary1.dll and run a command like:

midl ClassLibrary1.idl /tlb ClassLibrary1_new.tlb

and then register the new tlb with:

regtlib ClassLibrary1_new.tlb

NOTE: I you cannot find the regtlib tool you can look for it at: %windir%\Microsoft.NET\Framework\v2.0.50727 it will be there with the name regtlibv12.zip. If you still cannot find it, download it from HERE