1

Given a specific class:

public class Klass
{
    public int value;
    public void doSomething(){
        return;
    }
}

To make said class COM visible, as far as I know, one needs to do a few things:

  1. Import System.Runtime.InteropServices
  2. Create an interface for the class.
  3. Extend the interface created.
  4. Create 2 unique GUIDs, one for the Interface and another for the class.
  5. Add Dispatch IDs to the interface.

Producing something like:

[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface IKlass
{
    [DispId(0)]
    public int value;
    [DispId(1)]
    public void doSomething();
}

[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
    ClassInterface(ClassInterfaceType.None)]
public class Klass : IKlass
{
    public int value;
    public void doSomething(){
        return;
    }
}

The resulting code looks utterly gross in my opinion... The question is, is there a simple cleaner method of creating these COM interfaces? I can imagine modifying the build process myself to give a interop feature. E.G.

public interop class Klass
{
    public interop int value;
    //...
}

However, this is non-standard, which has it's issues as well. Is there anything built-in to Visual Studio / C# that I can use to make building COM interfaces easier/cleaner?

Sancarn
  • 2,575
  • 20
  • 45
  • 2
    Personally, using public member variables, especially when it comes to COM, looks very wrong to me. – Uwe Keim Jul 30 '18 at 20:55
  • @UweKeim huh? How come? `ThisWorkbook.Sheets` <-- Isn't that a public member variable? Or well, I guess it doesn't have to be indeed. But still don't see anything particularly wrong with it. – Sancarn Jul 30 '18 at 20:58
  • 3
    If you think this is ugly, try writing a COM server in native C++. You have no idea how much complexity is being taken care of for you. – John Wu Jul 30 '18 at 20:59
  • Since you are repeatedly saying "interface" probably there isn't a cleaner way. Otherwise, you don't need to define an interface. – Cetin Basoz Jul 30 '18 at 21:03
  • @JohnWu Funnily enough I started writing this interface in C++ before deciding to switch to C# due to the simplicity of it. But it still looks like an utter mess, even if it is better than C++. – Sancarn Jul 30 '18 at 21:03
  • I'm no com expert but doesn't visual studio do all this for you if you check the make com visible checkbox in the build page of the project properties? – Zohar Peled Jul 30 '18 at 21:59
  • 1
    @ZoharPeled huh... According to https://stackoverflow.com/questions/3699767/register-for-com-interop-vs-make-assembly-com-visible you might be right... '"Make assembly COM visible" is a big hammer to make all public types in the assembly [ComVisible].' I'll need to give this a go! – Sancarn Jul 30 '18 at 22:14
  • @ZoharPeled Can't appear to get that to work sadly... The checkbox is actually in Assembly Information but, none of my public classes or properties are com visible and I keep getting the error `".../Test.dll" does not contain any types that can be registered for COM Interop.` So either I am doing something wrong or ... – Sancarn Jul 30 '18 at 23:00
  • As I said I'm not an expert on com. Last time I wrote something com visible was about 6-7 years ago, but I don't remember having to deal with all the interfaces manually... – Zohar Peled Jul 31 '18 at 05:06
  • You need to register using regasm and use the -tlb flag. Check [this SO post](https://stackoverflow.com/questions/7092553/turn-a-simple-c-sharp-dll-into-a-com-interop-component) and [this blog post](https://blogs.msdn.microsoft.com/cristib/2012/10/31/how-com-works-how-to-build-a-com-visible-dll-in-c-net-call-it-from-vba-and-select-the-proper-classinterface-autodispatch-autodual-part12/) – Zohar Peled Jul 31 '18 at 07:43
  • @ZoharPeled thanks for all the information. I finally got it working! – Sancarn Aug 07 '18 at 14:15
  • Glad to help :-) – Zohar Peled Aug 07 '18 at 14:22

2 Answers2

1

As suggested by Zohar Peled the best way is to use RegAsm.exe:

  1. Create some C# class library "TestProject":

using System.Windows.Forms;
namespace TestProject
{
    // Note. Only public classes are exported to COM!
    public class Test
    {
        // Note. Only public methods are exported to COM!
        public void testIt() {
            MessageBox.Show("Yellow world");
        }
    }
}

IMPORTANT: Only public classes are exported to COM. And only public methods of these classes are available via a COM object instance.


  1. Sign the project.

  2. In AssemblyInfo.cs set [assembly: ComVisible(false)] to [assembly: ComVisible(true)]. Note: You can also use attribute [ComVisible(true)] before each class you want to expose to COM. This just sets the default to true making it easier to work with if building an API

  3. Build the project.

  4. Run regasm. Remember to use the correct version of Regasm (32-bit/64-bit) and the version for your .NET framework:


# .NET v4.5 64-bit
"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe" -tlb -codebase "C:\Users\sancarn\Desktop\tbd\TestProject\TestProject\bin\Debug\TestProject.dll" -verbose

# .NET v4.5 32-bit
"C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" -tlb -codebase "C:\Users\sancarn\Desktop\tbd\TestProject\TestProject\bin\Debug\TestProject.dll" -verbose

...

Regasm should output something like this:

Microsoft .NET Framework Assembly Registration Utility version 4.7.3056.0
for Microsoft .NET Framework version 4.7.3056.0
Copyright (C) Microsoft Corporation.  All rights reserved.

Types registered successfully
Type 'TestProject.Test' exported.
Assembly exported to 'C:\Users\sancarn\Desktop\tbd\TestProject\TestProject\bin\Debug\TestProject.tlb', and the type library was registered successfully

Now you can test the file in VBScript for example:

Dim o As Object
Set o = CreateObject("TestProject.Test")
Call o.testIt
Sancarn
  • 2,575
  • 20
  • 45
1

Sancarn answers your question, but note that this makes ALL COM-compatible classes in your project COM-visible as well, which you might not want (see here and here). If you do not explicitly set the UUIDs you are opening yourself up to problems when you deploy if you access the classes with early-bound clients like VB or VBA (not VBScript, which is late-bound).

Yes it's not "clean" but neither is COM, especially when you want to expose it to late-binding clients live VBScript.

I would also change your public field to a property, which is more standard for public members:

    [Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
    public interface IKlass
    {
        [DispId(0)]
        public int value {get; set;}
        [DispId(1)]
        public void doSomething();
    }
D Stanley
  • 149,601
  • 11
  • 178
  • 240
  • Thanks for the extra information! Personally I did want an entirely COM visible assembly. One can always use `[ComVisible(false)]` if one wants to disable this, or disable the setting in AssemblyInformation . Are you sure the Guid thing is an issue? I assumed the GUIDs would be generated from a seed based on the signed key of your project (and thus would be consistent). Also, yes, you can't have fields in COM interfaces apparrently, so a COM visible `get`/`set` is required indeed. – Sancarn Aug 07 '18 at 19:13
  • @Sancarn It may have changes (I haven't done COM in a long time) but at one time you could get different GUIDs depending on the compiler. I have experienced "DLL hell" enough to want to control all of those details myself. – D Stanley Aug 07 '18 at 19:18