JoeMjr2
JoeMjr2

Reputation: 3944

COM object written in C# - Get class, but not methods

I have written a simple COM object in C# with only one method, which is called GetMac. I can't get it to work. I am trying to access it from a legacy Borland C++ Builder 4 (BCB4) application, which I know is old, and not used much anymore, but I am able to access other COM objects from it fine.

The Borland development machine is running Windows XP, so I make the C# COM object target the .NET 4.0 framework. I copied the DLL and PDB file over from the C# Visual Studio machine to the XP machine. I registered it via the following command:

"%WINDIR%\Microsoft.NET\Framework\v4.0.30319\regasm.exe" TRSDotNetCOM.dll /tlb /nologo /codebase

I am able to instantiate the COM object (class) fine via the following line of code:

Variant TDN = CreateOleObject("TRSDotNetCOM.TRSCOM_Class");

If I change the name string, it doesn't work, so I know I have this part correct.

However, when I try to call the method as follows:

MacV = TDN.OleFunction(funcNameV,counterV,macKeyV);

... I get a runtime exception (unfortunately, there's an issue with BCB4's exception handling for OLE calls, so the only info the debugger gives me is "Exception Occurred").

Since I am able to call other COM objects from the same BCB4 application in the same manner, I don't think the problem is with my C++ code. I think it is an issue with either the C#-created COM DLL, or the registration thereof.

To explore this, I used Microsoft OLE/COM Object Viewer to browse my system for the OLE object. I was able to find my object as "TRSDotNetCOM.TRSCOM_Class", as expected.

I'm brand new at using the OLE/COM Object Viewer, so I hope I am looking at the right things below:

When I expand the class, I see the following: My class expanded

I right-clicked on _Object and chose "View", then "View Type Info". Then, the pane on the right shows:

[   uuid(65074F7F-63C0-304E-AF0A-D51741CB4A8D),   hidden,   dual,   nonextensible,
    custom({0F21F359-AB84-41E8-9A78-36D110E6D2F9}, "System.Object")

] dispinterface _Object {
    properties:
    methods:
        [id(00000000), propget,
            custom({54FC8F55-38DE-4703-9C4E-250351302B1C}, "1")]
        BSTR ToString();
        [id(0x60020001)]
        VARIANT_BOOL Equals([in] VARIANT obj);
        [id(0x60020002)]
        long GetHashCode();
        [id(0x60020003)]
        _Type* GetType(); };

When I expand the tree on the left, this is what I see: Expanded _Object

I do not see my method "GetMac" listed anywhere in there. So, I'm thinking that somehow the method is not visible to COM, or that it's not getting registered via regasm.

Here is the source for the COM object:

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

namespace TRSDotNetCOM
{
    [Guid("80ef9acd-3a75-4fcd-b841-11199d827e8f")]
    public interface TRSCOM_Interface
    {
        [DispId(1)]
        string GetMac(string counter, string macKey);
    }

    // Events interface Database_COMObjectEvents 
    [Guid("67bd8422-9641-4675-acda-3dfc3c911a07"),
    InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface TRSCOM_Events
    {
    }


    [Guid("854dee72-83a7-4902-ab50-5c7a73a7e17d"),
    ClassInterface(ClassInterfaceType.None),
    ComVisible(true),
    ComSourceInterfaces(typeof(TRSCOM_Events))]
    public class TRSCOM_Class : TRSCOM_Interface
    {
        public TRSCOM_Class()
        {
        }

        [ComVisible(true)]
        public string GetMac(string counter, string macKey)
        {
            // convert counter to bytes
            var counterBytes = Encoding.UTF8.GetBytes(counter);
            // import AES 128 MAC_KEY
            byte[] macKeyBytes = Convert.FromBase64String(macKey);
            var hmac = new HMACSHA256(macKeyBytes);
            var macBytes = hmac.ComputeHash(counterBytes);
            var retval = Convert.ToBase64String(macBytes);
            return retval;
        }

    }
}

I did make sure and go into the project properties and check the "Register for COM interop" checkbox. I also generated a Secure Name file with the "sn" utility, and loaded the file in the Signing section of settings.

So...

1) Am I looking in the correct place in the OLE/COM Object Viewer for my method?

2) If so, why would my method not be visible or not get registered?

3) Any ideas of what else could be wrong?

UPDATE: Here is the updated code with Joe W's and Paulo's suggestions. (It still does not work however)

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

namespace TRSDotNetCOM
{
    [Guid("80ef9acd-3a75-4fcd-b841-11199d827e8f"),
    ComVisible(true)]
    public interface TRSCOM_Interface
    {
        [DispId(1)]
        string GetMac(string counter, string macKey);
    }

    // Events interface Database_COMObjectEvents 
    [Guid("67bd8422-9641-4675-acda-3dfc3c911a07"),
    ComImport,
    ComVisible(true),
    InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface TRSCOM_Events
    {
    }


    [Guid("854dee72-83a7-4902-ab50-5c7a73a7e17d"),
    ClassInterface(ClassInterfaceType.None),
    ComDefaultInterface(typeof(TRSCOM_Interface)),
    ComVisible(true),
    ComSourceInterfaces(typeof(TRSCOM_Events))]
    public class TRSCOM_Class : TRSCOM_Interface
    {
        public TRSCOM_Class()
        {
        }


        public string GetMac(string counter, string macKey)
        {
            // convert counter to bytes
            var counterBytes = Encoding.UTF8.GetBytes(counter);
            // import AES 128 MAC_KEY
            byte[] macKeyBytes = Convert.FromBase64String(macKey);
            var hmac = new HMACSHA256(macKeyBytes);
            var macBytes = hmac.ComputeHash(counterBytes);
            var retval = Convert.ToBase64String(macBytes);
            return retval;
        }

    }
}

Upvotes: 2

Views: 1345

Answers (2)

acelent
acelent

Reputation: 8135

You're missing just a few bits.

Declare your interfaces as ComVisible:

[ComVisible(true)]
public interface TRSCOM_Interface

If your assembly is already COM visible by default (you can check this in the project's properties or typically in AssemblyInfo.cs), you don't need to do this, but it does no harm and it'll keep the interface available for regasm.exe and tlbexp.exe in case you revert this configuration.

Declare the events interface as ComImport:

[ComImport]
public interface TRSCOM_Events

My guess here is that this interface is defined outside your C# project, probably by the BCB4 application or one of its modules.

If my guess is wrong and your C# project is the one defining this interface, then [ComVisible(true)].

If this interface has event methods, you then implement then as events in the class.

Finally, to avoid having another interface exported for your class, you may want to add the ClassInterface attribute:

[ClassInterface(ClassInterfaceType.None)]
public class TRSCOM_Class : TRSCOM_Interface

This way, you're telling that TRSCOM_Interface is your class default interface, as it is the first one you implement, and regasm.exe /tlb won't generate a class interface.

Depending on the order of implemented interfaces is not reassuring, so you can also use the ComDefaultInterface attribute:

[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(TRSCOM_Interface))]
public class TRSCOM_Class : TRSCOM_Interface

Now, you can have any order in the implemented interfaces list without worrying about changing the default class interface.

Upvotes: 2

Joseph Willcoxson
Joseph Willcoxson

Reputation: 6050

That is the first time I have ever seen a method declared ComVisible. I would forgo that and instead declare the TRSCOM_Interface interface ComVisible.

Upvotes: 0

Related Questions