在C#中使用VB6字符串数组

10

我有一个(遗留的)VB6代码,我想从C#代码中使用它。

这与这个问题有些相似,但它是关于从VB6传递数组来使用C# dll。我的问题则相反。

在VB中,有一个接口在一个dll中,实现在另一个dll中。

接口:

[
  odl,
  uuid(339D3BCB-A11F-4fba-B492-FEBDBC540D6F),
  version(1.0),
  dual,
  nonextensible,
  oleautomation,
      helpstring("Extended Post Interface.")        
]
interface IMyInterface : IDispatch {

    [id(...),helpstring("String array of errors.")]
    HRESULT GetErrors([out, retval] SAFEARRAY(BSTR)* );
};

cMyImplementationClass中的实现(片段):

Private Function IMyInterface_GetErrors() As String()

    If mbCacheErrors Then
        IMyInterface_GetErrors = msErrors
    End If

End Function

我使用 tlbimp.exe 将这两个 dll 文件打包,并尝试从 C# 中调用函数。

public void UseFoo()
{
    cMyImplementationClass foo;
    ...
    var result = foo.GetErrors();
    ...
}

调用 foo.GetErrors() 会导致 SafeArrayRankMismatchException 异常。我认为这表明了一个与 Safe 数组相关的内部处理问题,可以在此处 查看详细信息

建议使用 tlbimp.exe 的 /sysarray 参数或手动编辑生成的 IL。我已经尝试过。

原始的 IL 如下所示:

.method public hidebysig newslot virtual 
    instance string[] 
    marshal( safearray bstr) 
    GetErrors() runtime managed internalcall
{
  .override [My.Interfaces]My.Interface.IMyInterface::GetErrors
} // end of method cImplementationClass::GetErrors

虽然更新后的版本是:

.method public hidebysig newslot virtual 
    instance class [mscorlib]System.Array 
    marshal( safearray) 
    GetErrors() runtime managed internalcall
{
  .override [My.Interfaces]My.Interface.IMyInterface::GetErrors
} // end of method cImplementationClass::GetErrors

我在接口和实现中都进行了相同的函数签名更改,具体过程请参考这里。但是,函数中没有指定返回值(它使用了“in”引用),也没有使用接口。当我运行代码并从C#中调用时,出现了以下错误:

Method not found: 'System.Array MyDll.cImplementationClass.GetErrors()'。

看起来我编辑的IL存在问题,但我不知道如何继续下去。

我怎样才能在不更改VB6代码的情况下从C#中使用此函数?

--编辑-- 重新定义“msErrors”,初始化将被返回的私有数组。

ReDim Preserve msErrors(1 To mlErrorCount)

如果我理解正确,那里的“1”表示该数组是从1开始索引而不是0,这就是我看到抛出异常的原因。


1
我了解您想要先让它运行起来,但是编辑IL似乎不是长期解决方案。 - Eric J.
也许是这样,但在此处提到的编组更改的推荐做法是在此处。顺便说一句,/sysarray标志似乎具有相同的净效果,包括导致的错误。 - ayers
你还没有展示你如何声明从VB6代码返回的数组。它是否具有秩为1和下限为0,例如声明为Dim msErrors(0 To N) As String?此外,如果mbCacheErrors为false,则你当前的实现似乎会返回一个未初始化的数组。 - Joe
我认为下限实际上是1,这就是问题的根源。不幸的是,我无法更改现有的VB6代码。 - ayers
1
我非常不清楚你在做什么,你不应该修补接口的.NET 实现。它是用VB6实现的。在VB6 dll上运行Tlbimp.exe以获取互操作库。而且在results上不要使用var关键字,将其声明为System.Array。 - Hans Passant
@HansPassant 谢谢。看起来 varArray 是个坑。我有一些模糊的直觉,为什么会这样,但我还没有找到详细的资料。你能指出一些关于这种情况的文档吗? - ayers
1个回答

1

我按照您的所有步骤操作,只是没有使用TlbImp.exe。相反,我直接将DLL添加到C#项目引用中。这样做,我得到了介于您给出的两个示例之间的IL代码:

.method public hidebysig newslot abstract virtual 
        instance class [mscorlib]System.Array 
        marshal( safearray bstr) 
        GetErrors() runtime managed internalcall
{
  .custom instance void [mscorlib]System.Runtime.InteropServices.DispIdAttribute::.ctor(int32) = ( 01 00 00 00 03 60 00 00 )                         // .....`..
} // end of method _IMyInterface::GetErrors

我已经做了与你相同的代码,本质上你是在为一个类型为Array的变量赋值。虽然CLR支持下限不为0的数组,但据我所知,没有任何语言,甚至VB.NET也没有内置支持它。
我的测试代码如下:
cMyImplementationClass myImpClass = new cMyImplementationClass();
IMyInterface myInterface = myImpClass as IMyInterface;

myImpClass.CacheErrors = true;

// Retrieve the error strings into the Array variable.
Array test = myInterface.GetErrors();

// You can access elements using the GetValue() method, which honours the array's original bounds.
MessageBox.Show(test.GetValue(1) as string);

// Alternatively, if you want to treat this like a standard 1D C# array, you will first have to copy this into a string[].
string[] testCopy = new string[test.GetLength(0)];
test.CopyTo(testCopy, 0);
MessageBox.Show(testCopy[0]);

这绝对是正确的答案。这个IL确切地就是使用/sysArray标志由TlbImp.exe生成的,然而,我认为我的问题可能被几件事情复杂化了。对于其他遇到类似问题的人,似乎将结果声明为“Array”(而不是“var”)非常重要。我还需要清除GAC中的原始程序集并用新的程序集替换它们。看来我的原始实现在编译时可能是正确的,但IIS在运行时使用了过时的程序集。 - ayers

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