2

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;
        }

    }
}

2 Answers 2

2

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Paulo, I already had the [ClassInterface(ClassInterfaceType.None)] attribute on my class. My class does not have any events, which is why the TRSCOM_Interface declaration is empty. It is not defined anywhere else, other than the source that I posted. Anyway, I went ahead and added your other suggestions (see updated source that I added in my question). However, it still does not work. I still get the exception, and I still don't see my GetMac method in the OLE/COM Object Browser.
I copy-pasted your current code, compiled with csc /target:library test.cs, then I ran tlbexp test.dll. Using OleView on the resulting test.tlb, there's a GetMac method that takes 2 in BSTRs and 1 out BSTR. Can you copy-paste the output of OleView after these steps?
Paulo, thanks. I just realized that I was looking in the wrong place in OleView. I was going into _Object, when I should have been going into TRSCOM_Interface. When I go into that one, I do see my GetMac method. Still not working in BCB4, though.
I think something is missing in your question. Do you have the [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] attribute on the TRSCOM_Interface interface? That would explain why BCB4 can't find the method, .NET's IDispatch::GetIDsOfNames would say GetMac is an unknown member. Are you using this SSCCE, or are you adapting and testing with your own, bigger library? Perhaps your example isn't the exact SSCCE that replicates your problem.
Paulo, yes, that source code that I posted is the exact source code that I'm trying to get to work. I just tried adding the [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] attribute to the TRSCOM_Interface declaration, and the result is that now, my C++ call to Variant TDN = CreateOleObject("TRSDotNetCOM.TRSCOM_Class"); fails as well, saying "Invalid class string".
0

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.

9 Comments

Joe W. - Ok, I just tried your suggestion. I removed ComVisible from the function and added it to the interface declaration. I rebuilt the DLL, copied it over, re-registered it, and it did not make a difference. I still get the exception, and I still don't see anything about my method in the OLE/COM Object Viewer program.
I would write a simple VB script to test it out and take the Borland stuff out of the equation. Maybe the VB script error would have better diagnostics...
Joe W, thanks for the suggestion. I think that's a great next step. I don't know VB, however. Any chance I could get instructions for doing this, along with the equivalent VB code to instantiate my object and call my method?
Don't use VB, but VB Script. Create a .vbs file: set obj = CreateObject("TRSDotNetCOM.TRSCOM_Class") MsgBox TypeName(obj) res = obj.GetMac("abc", "123") MsgBox res You can run it with wscript.exe or cscript.exe which will be on your WIndows path. It looks like you registered your component with 32-bit .Net runtime. So, if you're on a 64-bit box, you will have to call c:\windows\SysWOW64\cscript.exe to run it.
Joe W, thanks for that. Here is the result. The first MsgBox displays, "TRSCOM_Class". Then I get an error on the next line: "ComTest.vbs(3, 1) Microsoft VBScript runtime error: Object required: 'obj'"
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.