如何从 .NET COM dll return 结构或 class 到消费 VB6 应用程序?

How do you return an struct or class from a .NET COM dll to a consuming VB6 application?

这似乎很容易在这里找到,但如果以前有人问过,我不知道在哪里。

基本上,我是 a.NET 开发人员,我必须花一分钟时间使用 VB6 并学习如何制作 COM DLL。我在 C# 中工作,并试图让用该语言制作的 COM DLL return 自定义 class/struct 到一些 VB6 代码,虽然答案 here 很容易使用当从 COM 方法 returning stringint 时,我无法让它与实际对象一起工作。

示例代码:

C#

using System;
using System.Runtime.InteropServices;

namespace One.Two.Three
{
    [Guid("<some GUID>"), ClassInterface(ClassInterfaceType.None)]
    public class SomeClass : ISomeClass
    {
        public string Test1 { get; set; } = "Tesuto Ichiban";
        public string Test2 { get; set; } = "Tesuto Niban";

        public SomeClass SomeFunction(ref string str1, ref string str2, ref string str3,
                ref bool someBool, string str4)
        {
            return new SomeClass();
        }
    }

    [Guid("<another GUID>")]
    public interface ISomeClass
    {
        SomeClass SomeFunction(ref string str1, ref string str2, ref string str3, ref bool
                someBool, string str4);
    }

    public class Test
    {
        public string Test1 { get; set; } = "Tesuto Ichiban";
        public string Test2 { get; set; } = "Tesuto Niban";
    }
}

VB6

  MsgBox ("start")
  Dim result As Object
  Dim someObj
  Set someObj = CreateObject("One.Two.Three.SomeClass")
  result = CallByName(someObj, "SomeFunction", VbMethod, "1", "2", "3", True, "4")
  MsgBox (result)
  'MsgBox (result.toString())
  'MsgBox (result.Test1)
  'MsgBox (result.Test2)
  MsgBox ("end")

当 return 值为 stringint 时(以及当 result 声明为 String 时,这种方法非常有效),此时该值可以传递到 MsgBox 并显示给用户就好了。但是如果或者SomeClass或者Test被return编辑,任何试图通过result.[toString()/Test1/Test2]MsgBox 导致消息 "start" 和 "end" 仍然很好地显示给用户,但两者之间没有任何显示(甚至没有空白消息)。

值得注意的是,通过 return Test 的实例并将 result 声明为 String,对 MsgBox (result) 的调用将显示 "One.Two.Three.Test" - 这表明那里正在发生某些事情。

所以...问题是:

还需要做些什么才能让 VB6 应用程序公平地访问此对象?

特别是,我将需要 return 一个数组,List<T>,或者一些每个都有多个成员的对象。同样,这似乎应该在 Google 或 SO 上相对容易找到,但它似乎被其他搜索结果掩盖了。

P.S。 dll 的 .NET Framework 是 4.5。 (我使用了 4.0 文件夹中的 RegAsm,而不是链接答案中特别提到的 2.0)。如果需要降到 4.0,我可能可以,但可能无法一直降到 2.0。

不仅有一种方法可以完成这项工作,还有一种与您的工作方式相匹配的简单方法:

C# 代码可以非常简单,你不需要显式声明接口,你不需要到处使用 ref 关键字等等,但是你 do 需要为你的 类 添加一个 ComVisible(true) 属性(字符串、整数等是隐含的 ComVisible):

[ComVisible(true)]
[ProgId("One.Two.Three.SomeClass")]
public class SomeClass1
{
    public SomeClass2 SomeFunction(string text)
    {
        return new SomeClass2 { Text = text };
    }
}

[ComVisible(true)]
public class SomeClass2
{
    public string Text { get; set; }
}

VB/VBA代码:

Dim result As Object
Dim someObj
Set someObj = CreateObject("One.Two.Three.SomeClass")
' note the "Set" keyword, it's mandatory for COM objects...
Set result = CallByName(someObj, "SomeFunction", VbMethod, "hello world")
MsgBox result.Text

但是,如果您想从 VB/VBA 获取 Intellisense,您也可以像这样声明 类:

// progid is now optional because we won't need CreateObject anymore
[ProgId("One.Two.Three.SomeClass")]
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class SomeClass1
{
    public SomeClass2 SomeFunction(string text)
    {
        return new SomeClass2 { Text = text };
    }
}

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
public class SomeClass2
{
    public string Text { get; set; }
}

我刚刚添加了 ClassInterfaceType.AutoDual,现在我可以引用一个 TLB(我使用 regasm /tlb 创建),并在 VB/VBA:

中使用此代码
Dim c1 As New SomeClass1
Set c2 = c1.SomeFunction("hello world")
MsgBox c2.Text

ClassInterfaceType.AutoDual 不是 "recommended" 因为如果你走那条路,你必须确保 COM 客户端(这里是 VB/VBA)和 COM 服务器(这里的 C#) 是 100% 同步的,否则你可能会遇到严重的 VB/VBA 崩溃。然而,这是一个妥协,intellisense 是一个很棒的生产力工具恕我直言。

关于返回的最后一句话 List<T>,你会遇到麻烦。如果你面对它,再问一个问题:-)