在 C# 中使用 VB6 字符串数组
Consuming VB6 string array in C#
我有(遗留)VB6 代码,我想从 C# 代码中使用这些代码。
这有点类似于this question,但它指的是从VB6 传递一个数组使用C# dll。我的问题正好相反。
在VB中,一个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. I think this indicates a marshaling problem as described in the Safe Arrays section here.
建议使用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
我在接口和实现中进行了相同的函数签名更改。此过程描述为 here。但是,它没有在函数中指定 return 值(它使用 "in" 引用),也不使用接口。当我 运行 我的代码并从 C# 调用时,我收到错误
Method not found: 'System.Array MyDll.cImplementationClass.GetErrors()'.
看来是我编辑的 IL 出了点问题,虽然我不知道从这里到哪里去了。
如何在不更改 VB6 代码的情况下从 C# 使用此函数?
--编辑--
"msErrors" 的重新定义,它初始化了得到 returned.
的私有数组
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 的数组,但据我所知,没有语言,甚至 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]);
我有(遗留)VB6 代码,我想从 C# 代码中使用这些代码。
这有点类似于this question,但它指的是从VB6 传递一个数组使用C# dll。我的问题正好相反。
在VB中,一个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. I think this indicates a marshaling problem as described in the Safe Arrays section here.
建议使用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
我在接口和实现中进行了相同的函数签名更改。此过程描述为 here。但是,它没有在函数中指定 return 值(它使用 "in" 引用),也不使用接口。当我 运行 我的代码并从 C# 调用时,我收到错误
Method not found: 'System.Array MyDll.cImplementationClass.GetErrors()'.
看来是我编辑的 IL 出了点问题,虽然我不知道从这里到哪里去了。
如何在不更改 VB6 代码的情况下从 C# 使用此函数?
--编辑-- "msErrors" 的重新定义,它初始化了得到 returned.
的私有数组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 的数组,但据我所知,没有语言,甚至 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]);