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

    }
}
JoeMjr2
  • 3,804
  • 4
  • 34
  • 62

2 Answers2

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.

acelent
  • 7,965
  • 21
  • 39
  • 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. – JoeMjr2 Mar 19 '15 at 17:49
  • 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? – acelent Mar 19 '15 at 20:21
  • 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. – JoeMjr2 Mar 20 '15 at 05:48
  • 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](http://sscce.org/), or are you adapting and testing with your own, bigger library? Perhaps your example isn't the exact SSCCE that replicates your problem. – acelent Mar 20 '15 at 10:08
  • 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". – JoeMjr2 Mar 20 '15 at 16:43
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.

Joseph Willcoxson
  • 5,853
  • 1
  • 15
  • 29
  • 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. – JoeMjr2 Mar 19 '15 at 05:35
  • 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... – Joseph Willcoxson Mar 19 '15 at 19:17
  • 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? – JoeMjr2 Mar 20 '15 at 05:50
  • 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. – Joseph Willcoxson Mar 20 '15 at 15:08
  • 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'" – JoeMjr2 Mar 20 '15 at 16:59
  • For grins, I took your source code and built it and it worked fine--except for an error about Base64 encoding which means it got to the GetMac() method just fine. I built it against .NET 3.5 and set the Register for COM interop checkbox. My question is, are you building it against .NET 4.0? In your message it says you registered it that way, but you also have to build it against it. I have seen it let me do a regasm without any errors (except at runtime) when I make that mistake of having inconsistent .NET runtimes for build and registration. – Joseph Willcoxson Mar 20 '15 at 18:40
  • Yes, I built it against 4.0. – JoeMjr2 Mar 21 '15 at 01:50
  • Joe W. I went into VS in the project options, and changed the .NET Framework target to 3.5. I rebuilt the solution, and copied the new .DLL and .PDB files to my XP machine. I ran the following command: "%WINDIR%\Microsoft.NET\Framework\v2.0.50727\regasm.exe" TRDotNetCOM.dll /tlb /nologo /codebase It said it registered successfully. I then ran your VB Script, and I get the same 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' Any ideas what I might be doing wrong? – JoeMjr2 Mar 23 '15 at 16:57
  • I don't have any ideas. I would probably go to the registry entries for ProgId and CLSID and wipe them out completely. I would check versioning of your assembly. Is it set to an actual number "1.0.0.0" or does it have wildcards in it. I would try paring down to a minimum even what you have now. I'd try forgetting the interface and the InterfaceType declarations and just try to make a basic COM object that can be driven by pure automation. Your code works fine on my computer, so I am perplexed as to why it doesn't work on yours. – Joseph Willcoxson Mar 23 '15 at 20:10