草庐IT

c++ - COM 互操作 : how to use ICustomMarshaler to call 3rd party component

coder 2024-02-24 原文

我想使用 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(&currentPrecursor), 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/

有关c++ - COM 互操作 : how to use ICustomMarshaler to call 3rd party component的更多相关文章

随机推荐