COM-взаимодействие: как использовать ICustomMarshaler для вызова стороннего компонента - PullRequest
4 голосов
/ 23 января 2012

Я хочу вызвать метод в 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);
}

А вот соответствующая сигнатура C # после импорта библиотеки типов компонента COM:

void GetPrecursorInfoFromScanNum(int nScanNumber, ref object pvarPrecursorInfos, ref int pnArraySize);

Если я не ошибаюсь, мне нужно передать значение null для pvarPrecursorInfos, чтобы 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 # если есть способ.)

Ответы [ 2 ]

2 голосов
/ 23 марта 2015

Так как кто-то просил об этом, вот мое решение в 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(&currentPrecursor), PrecursorInfo::typeid));
    }

    SafeArrayUnaccessData(vPrecursorInfos.parray);

    return infos;
}
0 голосов
/ 08 октября 2018

Я смотрю на 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);


};

Затем, похоже, он получил предкурсные данные и параметры соответствующим образом.

...