Search code examples
c#vba.net-corevb6com-interop

.NET 8 COM class using GeneratedComInterfaceAttribute not visible to VB6/VBA or OLEViewer


Following the Source generation for ComWrappers article from Microsoft, I've created a simple .NET 8 Class Library project in Visual Studio 2022 using the GeneratedComInterfaceAttribute attribute, with the intent of using this library in VB6.

My class is based on a similar class described in this other Stack Overflow question where .NET 5 is used.

This is the code for the default Class1.cs:

using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshalling;

namespace ComHostTest
{
    [GeneratedComClass]
    [Guid("63C60D62-317F-4BAB-BB20-0AB41F34A345")]
    public partial class Class1
    {
        public string SayHello() => "Hello World from .NET " + RuntimeInformation.FrameworkDescription;
    }
}

This is my project file:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <PlatformTarget>x86</PlatformTarget>
        <EnableComHosting>true</EnableComHosting>
        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
        <Platforms>AnyCPU;x86</Platforms>
    </PropertyGroup>

</Project>

My build configuration and PlatformTarget are set for x86. The project builds properly and generates a ComHostTest.comhost.dll:

project output

Using an elevated command prompt, I successfully registered the comhost.dll:

enter image description here enter image description here

Then, using either the VB6 IDE's References window or the OLEViewer, I do not see the ComHostTest assembly as an available COM type library:

enter image description here enter image description here

Any idea why?

I have read this very similar question where the person uses .NET 8 also but not the GeneratedComInterfaceAttribute.


Edit

I adjusted my code following Simon Mourier's comments:

using System.Runtime.InteropServices;

namespace ComHostTest
{

    [ComVisible(true)]
    [Guid("72433A8C-941E-4876-8CC2-15F6F4235F32")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IComHostTest
    {
        public string SayHello();
    }

    [ComVisible(true)]
    [Guid("63C60D62-317F-4BAB-BB20-0AB41F34A345")]
    [ClassInterface(ClassInterfaceType.None)]
    public class ComHostTest : IComHostTest
    {
        public string SayHello() => "Hello World from .NET " + RuntimeInformation.FrameworkDescription;
    }

}

The project file was not touched since it already included <EnableComHosting>true</EnableComHosting>.

I downloaded the dscom32 release from GitHub and then, from the Developer PowerShell window in Visual Studio, I executed the following command:

dscom32 tlbexport "D:\.NET Projects\Development\COM host\ComHostTest\bin\x86\Debug\net8.0\ComHostTest.dll"

This generated a ComHostTest TLB file which I included in my .csproj file:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
        <PlatformTarget>x86</PlatformTarget>
        <EnableComHosting>true</EnableComHosting>
        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
        <Platforms>AnyCPU;x86</Platforms>
    </PropertyGroup>

    <ItemGroup>
        <ComHostTypeLibrary Include="ComHostTest.tlb" Id="1" />
    </ItemGroup>

</Project>

Still no luck seeing it in the VB6 IDE but I must be close.


Solution

  • Following Simon Mourier's comments and his answer on this similar question, I have managed to get the library to show up in VB6's References list.

    Writing the interfaces and classes

    First, the easy part. Here is the code for the interface and class:

    using System.Runtime.InteropServices;
    
        namespace ComHostTest
        {
        
            [ComVisible(true)]
            [Guid("72433A8C-941E-4876-8CC2-15F6F4235F32")]
            [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
            public interface IComHostTest
            {
                public string SayHello();
            }
        
            [ComVisible(true)]
            [Guid("63C60D62-317F-4BAB-BB20-0AB41F34A345")]
            [ClassInterface(ClassInterfaceType.None)]
            public class ComHostTest : IComHostTest
            {
                public string SayHello() => "Hello World! from .NET " + RuntimeInformation.FrameworkDescription;
            }
        
        }
    

    You can use the Create GUID window in Visual Studio (Tools > Create GUID) to generate new values for the Guid() attributes.

    The project file (.csproj):

    <Project Sdk="Microsoft.NET.Sdk">
    
        <PropertyGroup>
            <TargetFramework>net8.0</TargetFramework>
            <ImplicitUsings>enable</ImplicitUsings>
            <Nullable>enable</Nullable>
            <PlatformTarget>x86</PlatformTarget>
            <EnableComHosting>true</EnableComHosting>
            <Platforms>AnyCPU;x86</Platforms>
        </PropertyGroup>
    
    </Project>
    

    Set the project platform to x86 in the Configuration Manager. This will compile a 32-bit DLL which is what VB6 expects.

    You should be able to build this project and get a ComHostTest.comhost.dll. This is a COM server:

    A COM server is any object that provides services to clients; these services are in the form of COM interface implementations that can be called by any client that is able to get a pointer to one of the interfaces on the server object.

    Register this DLL using regsvr32. This writes the IComHostTest and ComHostTest GUIDs to the Windows registry. From an elevated Command Prompt:

    regsvr32 ComHostTest.comhost.dll
    

    At this point, you can activate your COM class from a .NET project using [ComImport] and [CoClass] attributes, but we need to go further for VB6 to recognize the class.

    Creating and Registering TLB

    For VB6 to reference your assembly, you first need to create a type library file (TLB) for it. If you don't already have it, get dscom32.exe from GitHub. You can get the compiled version from dSPACE-group/dscom Releases. This tool will generate a TLB file from your DLL:

    dscom32 tlbexport "D:\.NET Projects\Development\COM host\ComHostTest\bin\x86\Debug\net8.0\ComHostTest.dll"
    

    You now have a TLB for your assembly.

    To save you from manually running the command, you can set up a Build Task to create the TLB file automatically at compile time. You will need to add the dSPACE.Runtime.InteropServices.BuildTasks package from NuGet. Please see Build Tasks in the dscom README. Once you've added the package, your .csproj file will contain a new ItemGroup with the default settings. You should not have to change anything in there to generate the TLB.

    If you want to use/test the assembly on your machine, you can use the dscom32 to register the TLB:

    dscom32 tlbregister "D:\.NET Projects\Development\COM host\ComHostTest\bin\x86\Debug\net8.0\ComHostTest.tlb"
    

    This should return:

    Type library was registered successfully

    To register the TLB using the build task, you can use DsComRegisterTypeLibrariesAfterBuild in your .csproj. See the parameters section of the dscom README.

    Adding Reference in VB6

    Finally, in VB6, open the References window and locate your library:

    enter image description here

    You should now be able to see and create an instance of your class in IntelliSense:

    enter image description here

    Run your VB6 code to make sure everything is working. You should be good to go at this point.

    If you get the following error, you have not properly registered the <your project>.comhost.dll:

    enter image description here

    Run-time error '-2147221164 (80040154)': Class not registered

    Updating your DLL

    If you need to make changes to your DLL:

    1. If the interface hasn't changed, you only need to rebuild your DLL.
    2. If the interface has changed, you will need to create a new TLB and register it using dscom32.

    Deployment

    You can create a setup project using the Microsoft Visual Studio Installer Projects 2022 Extension. Use the 'Extensions > Manage Extensions' menu item to install it if you do not already have it.

    These instructions are based on this Visual Studio Installer Projects Extension and .NET article.

    Your main concern here is to register the TLB and <your project>.comhost.dll files as has been done above.

    1. In your Class Library project, setup a Publish profile first. This will handle generating the files you want to deploy (including any dependencies your DLL might have). You can do this by right-clicking the project name in the Solution Explorer and selecting 'Publish...' Create a profile with the following options:

    Publish profile

    1. Right-click the Setup project name and add a Project Output group. Select Publish Items in the 'Add Project Output Group' window and click OK.
    2. Click the 'Publish Items from ' item in the Setup project and set the PublishProfilePath property to the Publish profile you created in step 1.
    3. Add the TLB file to the Setup project by right-clicking the setup project name in Solution Explorer and selecting 'Add > File...' in the context menu.
    4. Select the TLB file in the setup project and set the Register property to vsdrfCOM.
    5. Add the <your project>.comhost.dll also, even though it will be included in the Publish profile. This will let you control the Register property for that file.
    6. Select the <your project>.comhost.dll file and set the Register property to vsdrfCOMSelfReg.
    7. Build the setup project. You will get two warnings about Two or more objects have the same target location and Unable to create registration information for file named '<your project>.comhost.dll' but you can ignore these.

    In the Publish profile, if you are using Framework-dependent Deployment mode, you can setup Prerequisites in your Setup project to make sure the correct .NET runtime is installed.

    Notes

    1. The order in which you register the TLB and <your project>.comhost.dll does not matter, but they both must happen.
    2. The <ComHostTypeLibrary Include="ComHostTest.tlb" Id="1" /> line in the project file mentioned in the question text is not necessary.
    3. This also works for WPF Class Library projects.
    4. If you use Application.Current.Dispatcher.Invoke to update WPF view models, you might run into unexpected Null Reference exceptions when running your code in VB6/VBA since Application.Current is null.

    All credit goes to Simon Mourier.