如何将结构体或类从.NET COM dll返回到使用的VB6应用程序?

3

看起来这似乎是一个在这里很容易找到的问题,但如果此前已经有人问过,那我就没看到在哪里。

基本上,我是一名.NET开发人员,必须暂时使用VB6并学习制作COM DLL。我正在使用C#进行工作,尝试使用该语言创建的COM DLL向一些VB6代码返回自定义类/结构,并且虽然这里的答案在从COM方法返回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")

当返回值是stringintresult被声明为String时,此方法非常适用,并且该值可以传递到MsgBox并正常显示给用户。但是,如果返回SomeClassTest中的任何一个,则尝试将result.[toString()/Test1/Test2]传递给MsgBox会导致消息“start”和“end”仍然正常显示给用户,但在其中没有任何内容显示(甚至没有空白消息)。

值得注意的是,通过返回Test的实例并保留result声明为String,对MsgBox(result)的调用将显示“One.Two.Three.Test”,这表明那里正在发生一些事情。

那么......问题是:

还需要做什么才能使该对象相对于VB6应用程序更加易于访问?

特别是,我将需要返回一个数组,List<T>或某些具有多个成员的对象。再次,这似乎是应该在Google或SO上相对容易找到的东西,但它似乎被其他搜索结果淹没了。

P.S.该dll的.NET Framework为4.5。(我使用了4.0文件夹中的RegAsm,而不是链接答案中特别提到的2.0)。如果需要将其降至4.0,那么我可能可以做到,但可能无法完全降至2.0。

1个回答

3
这项工作有多种方式,但是这里提供一种与您目前操作方式相匹配的简单方法:
C#代码可以很简单,您不需要显式声明接口,也不需要在各处使用"ref"关键字等,但是您需要在您的类上添加一个"ComVisible(true)"属性(string、int等均默认为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,现在我可以引用一个使用regasm /tlb创建的TLB,并在VB/VBA中使用这段代码:

Dim c1 As New SomeClass1
Set c2 = c1.SomeFunction("hello world")
MsgBox c2.Text
ClassInterfaceType.AutoDual不被推荐,因为如果你使用这种方式,你必须确保COM客户端(如VB/VBA)和COM服务器(如C#)之间的二进制文件完全同步,否则你可能会遇到严重的VB/VBA崩溃。然而,这是一种折衷方案,我认为智能感知是一个很棒的生产力工具。

最后关于返回List<T>,你可能会遇到麻烦。如果你遇到了,请提出另一个问题 :-)


1
谢谢!在调用CallByName时添加Set关键字似乎对我的情况最重要。 - Panzercrisis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接