COM interaction: how to use ICustomMarshaler to call a third-party component

I want to call a method in a COM component from C # using COM interaction. These are the signature of the methods:

long GetPrecursorInfoFromScanNum(long nScanNumber, LPVARIANT pvarPrecursorInfos, LPLONG pnArraySize) 

and this is an example code (which I checked really works) 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 is the corresponding C # signature after importing the typelib COM component:

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

If I'm not mistaken, I need to pass a null value to pvarPrecursorInfos to make COM interoperability a marshal as the expected VT_EMPTY option. When I do this, I get a SafeArrayTypeMismatchException - not surprisingly, looking at how the result is expected to be processed in the sample. So I tried to use my own marshaler. Since a cannot change the component itself, I tried to present it as follows:

 [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 attributes are the same as in the original version. This works until the MyMarshaller.GetInstance () method is called, but I don't get the callback in MyMarshaller.NativeToManaged. Instead, an access violation is reported. So is this a reliable approach? If so, how can I make it work? If not: are there any alternatives?

(Just a footnote: theoretically, I could try using managed C ++ to invoke the component initially. However, there are many other methods that work well with COM interoperability, so I would really like to stick with C # if there is any way. )

+4
source share
1 answer

Since someone asked about this, here is 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(&currentPrecursor), PrecursorInfo::typeid)); } SafeArrayUnaccessData(vPrecursorInfos.parray); return infos; } 
+2
source

Source: https://habr.com/ru/post/1392462/


All Articles