我想使用 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 interop 将其编码为预期的 VT_EMPTY 变体。当我这样做时,我得到一个 SafeArrayTypeMismatchException - 这并不奇怪,看看示例中预期如何处理结果。所以我试图使用自定义编码(marshal)拆收器。由于 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#如果有任何办法。)
最佳答案
由于有人要求,这里是我在托管 C++ 中的解决方案。
array<PrecursorInfo^>^ MSFileReaderExt::GetPrecursorInfo(int scanNumber)
{
VARIANT vPrecursorInfos;
VariantInit(&vPrecursorInfos);
long nPrecursorInfos = -1;
//call the COM component
long rc = pRawFile->GetPrecursorInfoFromScanNum(scanNumber, &vPrecursorInfos, &nPrecursorInfos);
//read the result
//vPrecursorInfos.parray points to a byte sequence
//that can be seen as array of MS_PrecursorInfo instances
//(MS_PrecursorInfo is a struct defined within the COM component)
MS_PrecursorInfo* pPrecursors;
SafeArrayAccessData(vPrecursorInfos.parray, (void**)&pPrecursors);
//now transform into a .NET object
array<PrecursorInfo^>^ infos = gcnew array<PrecursorInfo^>(nPrecursorInfos);
MS_PrecursorInfo currentPrecursor;
for (int i=0; i < nPrecursorInfos; ++i)
{
currentPrecursor = pPrecursors[i];
infos[i] = safe_cast<PrecursorInfo^>(Marshal::PtrToStructure(IntPtr(¤tPrecursor), PrecursorInfo::typeid));
}
SafeArrayUnaccessData(vPrecursorInfos.parray);
return infos;
}
关于c++ - COM 互操作 : how to use ICustomMarshaler to call 3rd party component,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/8971486/