COM 互操作:如何使用 ICustomMarshaler 调用第 3 方组件

发布于 2024-12-28 22:55:52 字数 2051 浏览 4 评论 0原文

我想使用 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

中二柚 2025-01-04 22:55:52

由于有人要求,这是我在托管 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;
}

Since someone asked for it, here's my solution in Managed 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;
}
來不及說愛妳 2025-01-04 22:55:52

我查看了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);


};

然后,它似乎适当地获取了前驱信息和参数。

I look at the github code mzLib, which I believe is related to this topic. The code looks good, where it calls

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

I think it may cause some problem, better use

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

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

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


};

Then, it seems to get precursorInfo and parameters appropriately.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文