Search code examples
c#.net-corecomvb6

.NET 8 exposing to VB6 using COM


.NET 8 exposing to VB6 using COM

Issue

We have a large application with a lot of GUI code in VB6 and the logic mainly in .NET Framework 4.7.2 libraries. Now we want to move to .NET 8 and having difficulties exposing the libraries to VB6 using COM:

  1. Set oObject = New COMObject is not working (class is empty in VB6 Object Browser)
  2. Events are not exposed

Both issues seem to be related to exposing the classes with(out) the [ClassInterface(ClassInterfaceType.AutoDual)] attribute.

We are using dscom32 (tlbexport/tlbregister) and are aware of CA1408.

How do we overcome our issues (1 and 2) without rewriting a lot of VB6 code in a type-unsafe way and without creating an extra .NET Framework 4.7.2 layer?

.NET 8 Project file

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

    <PropertyGroup>
        <TargetFramework>net8.0</TargetFramework>
        <EnableComHosting>True</EnableComHosting>
        <PlatformTarget>x86</PlatformTarget>
    </PropertyGroup>

    <Target Name="ServerUsage" AfterTargets="Build">
        <Message Importance="High" Text="Register:%0a    regsvr32.exe &quot;$(ProjectDir)$(OutputPath)$(ComHostFileName)&quot;" />
        <Message Importance="High" Text="Unregister:%0a    regsvr32.exe /u &quot;$(ProjectDir)$(OutputPath)$(ComHostFileName)&quot;" />
    </Target>

</Project>

.NET 8 code

using System.Runtime.InteropServices;

namespace TestCOM
{
    [ComVisible(true)]
    [Guid("A5447236-9520-4596-B4C6-D40175EBDA10")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface ITestCOM
    {
        string GetString();
    }

    [ComVisible(true)]
    [Guid("3FCD4A46-F73D-495F-9DE3-85D50924ED06")]
    //[ClassInterface(ClassInterfaceType.AutoDual)]
    public class TestCOM : ITestCOM
    {
        string ITestCOM.GetString() => "Test";
    }
}

VB6 Code

Sub Main()

    ' This is not working:
    ' Dim oTest As TestCOM
    ' Set oTest = New TestCOM
    '
    ' Requires ClassInterfaceType.AutoDual
    '
    ' dscom32 tlbexport "%cd%\Test.dll"
    ' -> Failed to export type library. Dual class interfaces not supported!
    
    ' This is working:
    Dim oTest As ITestCOM
    Set oTest = CreateObject("TestCOM.TestCOM")
    Debug.Print oTest.GetString()

End Sub

New code based on Seva Alekseyev

Both issues are now solved for me.

.NET 8 code

using System;
using System.Runtime.InteropServices;

namespace TestCOM
{
    internal delegate void OnGetStringCOM(EventArgsCOM args);

    [ComVisible(true)]
    [Guid("E6BCA6A0-70F3-4218-B344-3632F638D51A")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IEventArgsCOM
    {
        string Message { get; set; }
    }

    [ComVisible(true)]
    [Guid("77414A17-1CA5-4E45-940E-F26C8F50BE3E")]
    [ClassInterface(ClassInterfaceType.None)]
    public class EventArgsCOM(string message) : EventArgs, IEventArgsCOM
    {
        public string Message { get; set; } = message;
    }

    [ComVisible(true)]
    [Guid("891E4AFB-8CD3-4611-80F6-F3AC755EF3B4")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface ITestCOMEvents
    {
        [DispId(1)]
        void OnGetStringCOM(EventArgsCOM args);
    }

    [ComVisible(true)]
    [Guid("A5447236-9520-4596-B4C6-D40175EBDA10")]
    [InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface ITestCOM
    {
        string GetString();
    }

    [ComVisible(true)]
    [Guid("3FCD4A46-F73D-495F-9DE3-85D50924ED06")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(ITestCOMEvents))]
    public class TestCOM : ITestCOM
    {
        private event OnGetStringCOM OnGetStringCOM;

        public string GetString()
        {
            OnGetStringCOM?.Invoke(new EventArgsCOM("test raised"));
            return "test returned";
        }
    }
}

VB6 Code

Private WithEvents moTest As Test.TestCOM

Private Sub Class_Initialize()

    Set moTest = New TestCOM
    
End Sub

Public Function PrintString()

    Debug.Print moTest.GetString()
    ' test returned

End Function

Private Sub moTest_OnGetStringCOM(ByVal args As Test.IEventArgsCOM)

    Debug.Print args.Message
    ' test raised
    
End Sub

Solution

  • Try annotating the class (not the interface!) with [ClassInterface(ClassInterfaceType.None)]. You don't want .NET to autogenerate a COM interface for the class also - it will get in the way.

    For the interface, try [InterfaceType(ComInterfaceType.InterfaceIsDual)] or InterfaceIsIDispatch. I don't recall if VB6 can consume custom (i. e. not derived from IDispatch) interfaces, but IDispatch based automation was always Visual Basic's native mode of operation.

    Then rebuild, then reestablish a reference from the VB project. I don't think VB6 picks up typelib changes on build.