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

C# 在C语言中使用VB6字符串数组#,c#,vb6,com-interop,C#,Vb6,Com Interop,我有(遗留的)VB6代码,我想从C#代码中使用 这有点类似于,但它指的是从使用C#dll的VB6传递数组。我的问题恰恰相反 在VB中,一个dll中有一个接口,另一个dll中有一个实现 接口: [ odl, uuid(339D3BCB-A11F-4fba-B492-FEBDBC540D6F), version(1.0), dual, nonextensible, oleautomation, helpstring("Extended Post Interface

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

这有点类似于,但它指的是从使用C#dll的VB6传递数组。我的问题恰恰相反

在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)* );
};
CMYImplementation类中的实现(片段):

Private Function IMyInterface_GetErrors() As String()

    If mbCacheErrors Then
        IMyInterface_GetErrors = msErrors
    End If

End Function
我用tlbimp.exe包装了这两个DLL,并尝试从C#调用该函数

调用foo.GetErrors()会导致错误。我认为这表明存在安全阵列部分中描述的封送问题

建议使用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#调用时,我得到了错误

找不到方法:“System.Array MyDll.cImplementationClass.GetErrors()”

我编辑的IL中似乎有什么地方出了问题,尽管我不知道从这里可以走到哪里

如何在不更改VB6代码的情况下从C#使用此函数

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

ReDim Preserve msErrors(1 To mlErrorCount)

如果我理解正确,那么其中的“1”意味着数组是从1而不是从0索引的,这就是引发异常的原因。

除了使用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以外的下限的数组,但AFAIK、任何语言,甚至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似乎不是一个长期的解决方案。也许是这样,但这是封送所提到的更改的推荐做法。FWIW,/sysarray标志似乎具有相同的净效果,包括产生的错误。您尚未显示如何声明从VB6代码返回的数组。它是否具有秩1和下限0,即声明为类似于
Dim msErrors(0到N)的字符串
?另外,如果mbCacheErrors为false,则当前实现似乎返回未初始化的数组。我认为下限实际上是1,这是问题的根源。不幸的是,更改现有的VB6代码对我来说是不可行的。我不清楚您在做什么,您不应该修补接口的.NET实现。它是在VB6中实现的。在VB6 dll上运行Tlbimp.exe以获取互操作库。不要在结果中使用
var
关键字,将其声明为System.Array。这绝对是正确的答案。这个IL正是TlbImp.exe使用/sysArray标志生成的,然而,我认为我的问题可能因为一些事情而变得更加复杂。对于其他有类似问题的人来说,将结果声明为
数组(而不是
var
)似乎很重要。我还需要清除GAC中的原始组件,并用新组件替换它们。看起来我的原始实现在编译时可能是正确的,但是IIS在运行时在GAC中使用了过时的程序集。
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]);