COM 互操作:如何使用 ICustomMarshaler 调用第 3 方组件
我想使用 COM 互操作从 C# 调用 COM 组件中的方法。这是方法签名:
long GetPrecursorInfoFromScanNum(long nScanNumber,
LPVARIANT pvarPrecursorInfos,
LPLONG pnArraySize)
这是在 C++ 中调用它的示例代码(我检查过确实有效):
struct PrecursorInfo
{
double dIsolationMass;
double dMonoIsoMass;
long nChargeState;
long nScanNumber;
};
void CTestOCXDlg::OnOpenParentScansOcx()
{
VARIANT vPrecursorInfos;
VariantInit(&vPrecursorInfos);
long nPrecursorInfos = 0;
m_Rawfile.GetPrecursorInfoFromScanNum(m_nScanNumber,
&vPrecursorInfos,
&nPrecursorInfos);
// Access the safearray buffer
BYTE* pData;
SafeArrayAccessData(vPrecursorInfos.parray, (void**)&pData);
for (int i=0; i < nPrecursorInfos; ++i)
{
// Copy the scan information from the safearray buffer
PrecursorInfo info;
memcpy(&info,
pData + i * sizeof(MS_PrecursorInfo),
sizeof(PrecursorInfo));
}
SafeArrayUnaccessData(vPrecursorInfos.parray);
}
这是导入 COM 组件的 typelib 后相应的 C# 签名:
void GetPrecursorInfoFromScanNum(int nScanNumber, ref object pvarPrecursorInfos, ref int pnArraySize);
如果我没记错的话,我需要为 pvarPrecursorInfos 传入 null 以使 COM 互操作将其编组为预期的 VT_EMPTY 变体。当我这样做时,我得到一个 SafeArrayTypeMismatchException - 并不奇怪,看看示例中预计如何处理结果。所以我尝试使用自定义封送拆收器。由于 a 无法更改组件本身,因此我尝试以这种方式引入它:
[Guid("06F53853-E43C-4F30-9E5F-D1B3668F0C3C")]
[TypeLibType(4160)]
[ComImport]
public interface IInterfaceNew : IInterfaceOrig
{
[DispId(130)]
int GetPrecursorInfoFromScanNum(int nScanNumber, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshaler))] ref object pvarPrecursorInfos, ref int pnArraySize);
}
TypeLibType 和 DispID 属性与原始版本中的相同。只要调用 MyMarshaller.GetInstance() 方法,这种方法就有效,但我在 MyMarshaller.NativeToManaged 中没有得到回调。相反,会报告访问冲突。那么这是一个可靠的方法吗?如果是的话-我怎样才能让它发挥作用?如果没有:还有其他选择吗?
(只是一个脚注:理论上,我可以尝试使用托管 C++ 来本地调用该组件。但是,其中还有许多其他方法可以与 COM 互操作配合使用,因此如果有的话,我非常愿意坚持使用 C#反正。)
I want to call a method in a COM component from C# using COM interop. This is the methods signature:
long GetPrecursorInfoFromScanNum(long nScanNumber,
LPVARIANT pvarPrecursorInfos,
LPLONG pnArraySize)
and this is sample code (which I checked is really working) to call it in C++:
struct PrecursorInfo
{
double dIsolationMass;
double dMonoIsoMass;
long nChargeState;
long nScanNumber;
};
void CTestOCXDlg::OnOpenParentScansOcx()
{
VARIANT vPrecursorInfos;
VariantInit(&vPrecursorInfos);
long nPrecursorInfos = 0;
m_Rawfile.GetPrecursorInfoFromScanNum(m_nScanNumber,
&vPrecursorInfos,
&nPrecursorInfos);
// Access the safearray buffer
BYTE* pData;
SafeArrayAccessData(vPrecursorInfos.parray, (void**)&pData);
for (int i=0; i < nPrecursorInfos; ++i)
{
// Copy the scan information from the safearray buffer
PrecursorInfo info;
memcpy(&info,
pData + i * sizeof(MS_PrecursorInfo),
sizeof(PrecursorInfo));
}
SafeArrayUnaccessData(vPrecursorInfos.parray);
}
And here's the corresponding C# signature after importing the typelib of the COM component:
void GetPrecursorInfoFromScanNum(int nScanNumber, ref object pvarPrecursorInfos, ref int pnArraySize);
If I'm not mistaken, I need to pass in null for pvarPrecursorInfos to make COM interop marshal it as the expected VT_EMPTY variant. When I'm doing it, I get a SafeArrayTypeMismatchException - not really surprising, looking at how the result is expected to be handled in the sample. So I was trying to use a custom marshaler. Since a cannot alter the component itself, I tried to introduce it this way:
[Guid("06F53853-E43C-4F30-9E5F-D1B3668F0C3C")]
[TypeLibType(4160)]
[ComImport]
public interface IInterfaceNew : IInterfaceOrig
{
[DispId(130)]
int GetPrecursorInfoFromScanNum(int nScanNumber, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshaler))] ref object pvarPrecursorInfos, ref int pnArraySize);
}
The TypeLibType and DispID attribute are the same as in the original version. This works as far as that the MyMarshaller.GetInstance() method is called, but I do not get a callback in MyMarshaller.NativeToManaged. Instead, an access violation is reported. So is this a reliable approach? If yes - how can I make it work? If no: are there any alternatives?
(Just a footnote: in theory I could try to use managed C++ to call the component natively. However, there are lots of other methods in it that work fine with COM interop, so I would very much like to stick with C# if there is any way.)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
由于有人要求,这是我在托管 C++ 中的解决方案。
Since someone asked for it, here's my solution in Managed C++.
我查看了github代码mzLib,我认为它与这个主题相关。代码看起来不错,在它调用的地方
我认为它可能会导致一些问题,更好地使用
代码然后在编译时似乎工作得很好。但是,当作为 dll 导入时,它可能会出现运行问题。我通过添加一个构造函数来解决这个问题,例如
然后,它似乎适当地获取了前驱信息和参数。
I look at the github code mzLib, which I believe is related to this topic. The code looks good, where it calls
I think it may cause some problem, better use
The code then seems to be worked just fine when compiles. However, it might run in problem when imported as dll. I worked on that by adding a constructor,such as
Then, it seems to get precursorInfo and parameters appropriately.