.NET 4.8 - 使用参数化属性编写 COM 库

Som*_*ude 5 .net c# vb6 com ole

我正在致力于迁移使用 regsvr32 注册并主要在 vbscript 和 ASP Classic 中使用的旧版 VB6 COM 库。

新的 .NET 4.8 替代库已通过 regasm 注册。

一切都很好,直到我遇到了一些无法直接迁移的功能,因为我在这个领域没有足够的知识。

旧版 VB6 库具有以下 Typelibrary 代码(来自 OleView,为方便起见进行了缩减):

[id(0x00068), propget] 
VARIANT_BOOL IncomeOverride([in] short index);
[id(0x00068), propput] 
void IncomeOverride(
    [in] short index,
    [in] VARIANT_BOOL rhs
);
[id(0x00069), propget] 
CY IncomeOverrideAmt([in] short index);
[id(0x00069), propput] 
void IncomeOverrideAmt(
    [in] short index,
    [in] CY rhs
);
Run Code Online (Sandbox Code Playgroud)

调用者使用这些方法如下:

oTest.IncomeOverride(1) = True
oTest.IncomeOverrideAmt(1) = 12
Run Code Online (Sandbox Code Playgroud)

首先我尝试使用属性。但由于 C# 中没有可用的参数化属性,我不得不尝试如下方法:

[DispId(0x00068)]
bool IncomeOverride([In] short index);
[DispId(0x00068)]
void IncomeOverride([In] short index, [In] bool v);
[DispId(0x00069)]
decimal IncomeOverrideAmt([In] short index);
[DispId(0x00069)]
void IncomeOverrideAmt([In] short index, [In] decimal v);
Run Code Online (Sandbox Code Playgroud)

它可以编译,但不起作用,因为 regasm 在注册时发出警告并拒绝生成正确的 tlb。警告如下:

类型库导出器警告处理“TestLibrary”。警告:该类型指定了一个或多个重复的 DISPID。重复的 DISPID 被忽略。

生成的类型库远远不能满足需要:

[id(0x0001)] 
VARIANT_BOOL IncomeOverride([in] short index);
[id(0x0002)] 
void IncomeOverride_2(
    [in] short index,
    [in] VARIANT_BOOL v
);
[id(0x0003)] 
CY IncomeOverrideAmt([in] short index);
[id(0x0004)] 
void IncomeOverrideAmt_2(
    [in] short index,
    [in] CY v
);
Run Code Online (Sandbox Code Playgroud)

下一次迭代是使用索引器。

[DispId(0x00068)]
[System.Runtime.CompilerServices.IndexerName("IncomeOverride")]
bool this[short index]
{
    [return: MarshalAs(UnmanagedType.Currency)]
    get;
    [param: In, MarshalAs(UnmanagedType.Currency)]
    set;
}
[DispId(0x00069)]
[System.Runtime.CompilerServices.IndexerName("IncomeOverrideAmt")]
decimal this[short index]
{
    [return: MarshalAs(UnmanagedType.Currency)]
    get;
    [param: In, MarshalAs(UnmanagedType.Currency)]
    set;
}
Run Code Online (Sandbox Code Playgroud)

这实际上是一个很好的解决方案,但不幸的是它甚至无法编译,因为您不能在单个接口/类中拥有具有不同 IndexerName 值的多个索引器。

尝试适应此类功能的最后一次尝试是使用返回带有索引器的数组/对象的属性:

[DispId(0x00068)]
bool[] IncomeOverride { get; }
[DispId(0x00069)]
decimal[] IncomeOverrideAmt { get; }

[DispId(0x00068)]
SomeClassWithBoolIndexer IncomeOverride { get; }
[DispId(0x00069)]
SomeClassWithDecimalIndexer IncomeOverrideAmt { get; }
Run Code Online (Sandbox Code Playgroud)

它可以编译并注册,但由于它生成了不正确的类型库 - 它不能在 vbscript/VB6/ASP Classic 中使用。

那么问题来了——如何实现这样的功能呢?

还是只是模仿?

为 C# 库编写自定义 Typelibrary?

这是否可以通过 C# 的方式实现?

预先感谢。

更新:

在这里发现了类似的问题:How to make C# COM class supportparameterizedproperties from VB6

它实际上完成了大部分工作,但与该问题的作者不同 - 如果不使用 .Value 作为参数化属性,它就无法工作。

尝试深入研究并找出 VBscript 无法识别默认属性(值)的原因。除非这一切都像冠军一样顺利!

但是如果没有默认的属性识别,它就死了,因为需要重写大量的遗留代码......我们目前不需要二进制兼容,只是为了使后期绑定(vbscript)工作。

任何人都知道为什么它没有按预期工作?

更新2

好的,目前有两个主要解决方案(显然没有一个有效):

1.使用PropertyAccessor类

VB脚本:

Dim oTestObject: Set oTestObject = CreateObject("TestSharpLib.TestObject")
wsh.echo "oTestObject.IncomeOverride(1): [" & oTestObject.IncomeOverride(1) & "]"  'THIS LINE WORKS
oTestObject.IncomeOverride(1).Value = "True" 'THIS LINE WORKS
oTestObject.IncomeOverride(1) = "True"                                             'THIS LINE DOES NOT WORK
wsh.echo "oTestObject.IncomeOverride(1): [" & oTestObject.IncomeOverride(1) & "]" 
Run Code Online (Sandbox Code Playgroud)

C#

using System;
using System.Runtime.InteropServices;
using TestVBNET;
namespace TestSharpLib
{
    [ComVisible(true)]
    [ProgId("TestSharpLib.TestObject")]
    [Guid("49067FAC-A1B3-4469-9032-4E958AA7381C")]
    public class TestObject: : ITestObject
    {
        public object IncomeOverride(short index)
        {
            return new PropertyAccessor(this, index);
        }

        public class PropertyAccessor
        {
            TestObject _owner;
            short _index;
            public PropertyAccessor(TestObject owner, short index)
            {
                _owner = owner;
                _index = index;
            }
            [DispId(0)]
            public string Value { 
                get => _index.ToString();
                set { }
            }
        }
    }
    
    [ComVisible(true)]
    [Guid("4C0FDA39-1027-415A-968C-9A6DF9BEB499")]
    public interface ITestObject
    {
        [DispId(1)]
        object IncomeOverride(short index);
    }    
}
Run Code Online (Sandbox Code Playgroud)

根据 Erik Lippert 的说法,似乎您不能省略 VBScript 中的默认属性。 https://learn.microsoft.com/en-us/archive/blogs/ericlippert/vbscript-default-property-semantics

2. 使用VB.NET中声明的接口

VB脚本:

Dim oTestObject: Set oTestObject = CreateObject("TestSharpLib.TestObject")
wsh.echo "oTestObject.IncomeOverride(1): [" & oTestObject.IncomeOverride(1) & "]"  'THIS LINE NOT WORKING
oTestObject.IncomeOverride(1) = "True"                                             'THIS LINE NOT WORKING
wsh.echo "oTestObject.IncomeOverride(1): [" & oTestObject.IncomeOverride(1) & "]" 
Run Code Online (Sandbox Code Playgroud)

C#

using System;
using System.Runtime.InteropServices;
using TestVBNET;
namespace TestSharpLib
{
    [ComVisible(true)]
    [ProgId("TestSharpLib.TestObject")]
    [Guid("49067FAC-A1B3-4469-9032-4E958AA7381C")]
    public class TestObject: IIncomeOverride
    {
        public string get_IncomeOverride(int index)
        {
            return index.ToString();
        }

        public void set_IncomeOverride(int index, string Value)
        {
            //throw new NotImplementedException();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

网络

Imports System.Runtime.InteropServices
<ComVisible(True)>
<Guid("F40144F0-6BA1-4983-B6EE-D593CA0A2F75")>
Public Interface IIncomeOverride
    <DispId(1745027127)>
    Property IncomeOverride(ByVal index As Integer) As String
End Interface
Run Code Online (Sandbox Code Playgroud)

这个解决方案根本不起作用。尽管 regasm 说 dll 已注册并且 OleView 实际上显示了正确的类型库 - VBScript 根本看不到 IncomeOverride 属性。

c:\Users\Administrator\Desktop\test.vbs(2, 1) Microsoft VBScript runtime error: Object doesn't support this property or method: 'IncomeOverride'
Run Code Online (Sandbox Code Playgroud)

类型库:

[
  uuid(f40144f0-6ba1-4983-b6ee-d593ca0a2f75),
  dual,
  oleautomation
]
interface IIncomeOverride : IDispatch#i {
    [id(0x68030037), propget] 
    HRESULT IncomeOverride(
        [in] long index,
        [out, retval] BSTR* pRetVal
    );
    [id(0x68030037), propput] 
    HRESULT IncomeOverride(
        [in] long index,
        [in] BSTR pRetVal
    );
    
};
[
  uuid(f40144f0-6ba1-4983-b6ee-d593ca0a2f75),
  dual
]
dispinterface IIncomeOverride {
    methods:
**removed object methods***
        [id(0x68030037), propget] 
        BSTR IncomeOverride([in] long index);
        [id(0x68030037), propput] 
        void IncomeOverride(
            [in] long index,
            [in] BSTR rhs
        );
};
Run Code Online (Sandbox Code Playgroud)

所以现在我仍在挖掘新的想法。主要令人失望的是 VBScript 没有看到已实现的(在 VB.NET 接口中声明的)属性/方法。

更新 3(最终解决方案) 经过一段时间的测试和研究,发现解决此问题的更简单且可能是最好的方法是使用 VB.NET 代理类,它模拟所有需要的接口,但将逻辑代理到 C# 类。

以这种方式实现它解决了所有问题。感谢大家的讨论和提出的解决方案。

我会将问题标记为已解决,并选择建议此解决方案的评论之一,但不能,因为评论已隐藏/删除。