COM互操作:如何使用ICustomMashaler调用第三方组件

COM interop: how to use ICustomMarshaler to call 3rd party component

本文关键字:调用 第三方 组件 ICustomMashaler 何使用 互操作 COM      更新时间:2023-10-16

我想使用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);

如果我没有弄错的话,我需要为pvarPrecursorInfo传递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()方法而言,这是有效的,但我在MyMarshalller.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(&currentPrecursor), PrecursorInfo::typeid));
    }
    SafeArrayUnaccessData(vPrecursorInfos.parray);
    return infos;
}

我查看了github代码mzLib,我认为它与本主题有关。代码看起来不错,它称之为

pin_ptr<const wchar_t> wch = PtrToStringChars(path);

我认为这可能会引起一些问题,最好使用

pin_ptr<const wchar_t> pathChar = static_cast<wchar_t*>(System::Runtime::InteropServices::Marshal::StringToHGlobalUni(path).ToPointer());

然后,代码在编译时似乎工作得很好。但是,当作为dll导入时,它可能会出现问题。我通过添加一个构造函数来实现这一点,比如

public ref class ThermoDLLClass 
{
public:
    ThermoDLLClass();
    PrecursorInfo GetPrecursorInfo(int scanNum, String^ path);

};

然后,它似乎可以适当地获得前兆信息和参数。