| 

.NET C# Java Javascript Exception

7
Ich habe hier ein ziemlich delikates COM-Interop-Problem. Es geht um den Aufruf einer Methode in einer COM-Komponente aus C# heraus (für's Protokoll: die Komponente ist der MSFileReader von Thermo Scientific).

Die Methode ist mit dieser Signatur dokumentiert:

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


und in der Typelib finde ich sie so:

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


Kopfzerbrechen bereitet mir der zweite Parameter, pvarPrecursorInfos. Was die COM-Komponente dort erwartet und zurückgibt, läßt sich eigentlich nur per Code-Beispiel beschreiben:

struct PrecursorInfo
{
double dIsolationMass;
double dMonoIsoMass;
long nChargeState;
long nScanNumber;
};

void CTestOCXDlg::OnOpenParentScansOcx()
{
try
{
VARIANT vPrecursorInfos;
VariantInit(&vPrecursorInfos);
long nPrecursorInfos = 0;
// Get the precursor scan information
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));
// Process the paraent scan information ...
}
SafeArrayUnaccessData(vPrecursorInfos.parray);
}
catch (...)
{
AfxMessageBox(_T("There was a problem while getting the parent scan
information."));
}
}


Preisfrage: wie kann ich diese Methode aus C# heraus aufrufen? Ich hab schon eine ganze Menge probiert, bekomme aber nur wahlweise Access Violations, COM-Exceptions (0x8002802B, Element not found) oder einfach null. Es ist bislang die einzige Methode in der Komponente, die solchen Ärger macht. Ich könnte natürlich auf Managed C++ zurückgreifen und so die COM-Interop umgehen, aber da sonst alles mit C# funktioniert, wäre das nur mein letzter Rettungsanker. Weiß jemand Rat?

Update
Um unnötiges Raten ins Blaue zu vermeiden: bislang habe ich vor allem versucht, pvarPrecursorInfos auf unterschiedliche Weise zu initialisieren. Das brachte folgende Ergebnisse:

  • pvarPrecursorInfos=null: SafeArrayTypeMismatchException. Das angegebene Array hat nicht den erwarteten Typ.
  • pvarPrecursorInfos=new MS_PrecursorInfo() oder = pvarPrecursorInfos=(new MS_PrecursorInfo[1])[0]: COMException wurde nicht behandelt. Element not found. (Ausnahme von HRESULT: 0x8002802B (TYPE_E_ELEMENTNOTFOUND))
  • var bytes = new byte[100]; object pvarPrecursorInfos = bytes[0]: keine Exception, aber nur Nullen in bytes. Mit double oder int statt byte dasselbe

Außerdem habe ich gestern angefangen, die Methode selbst zu deklarieren statt die Variante aus der Typelib zu verwenden, so dass ich mit MarshalAs & Co. arbeiten kann. Bislang ohne Erfolg, aber die Vorschläge von luedi und Xantiva werde ich mir jetzt ansehen.
News:
20.12.2011
Matthias Hlawatsch 13,2k 4 9
3 Antworten
4
Ich habe mir bei solchen Fragen bisher sehr gut mit dem PInvoke Interop Assistant helfen können. Du kopierst den unmanaged Code dort hinein (mit allen erforderlichen Deklarationen) und bekommst einen Vorschlag für den Aufruf via .NET. Wenn Du dann noch eine statische CodeAnalyse darüber laufen lässt, bekommst Du noch den ein oder anderen wichtigen Hinweis.
21.12.2011
Xantiva 2,3k 2 9
Klingt vielversprechend. Das Tool hatte ich sogar schon installiert, aber die Funktion war mir bislang entgangen. Ich bekomme es aber nicht zum Laufen, es kommen Meldungen der Art
"Expected token of type ParenClose but found [Ampersand|Number|Period|IntKeyword] instead." Deinem Kommentar zu luedis Antwort zufolge hast Du es für mein Beispiel schon eingesetzt. Kannst Du mal das Snippet posten, dass Du verwendet hast?
Matthias Hlawatsch 21.12.2011
1
Ich klicke auf den Tab "SigImp Translate Snippet" und kopiere dann den unmanaged Code in "Native Code Snippet" hinein. In dem Fall z. B.

struct PrecursorInfo
{
double dIsolationMass;
double dMonoIsoMass;
long nChargeState;
long nScanNumber;
};

Wenn Du dann die Methode hineinkopierst, dann musst Du eben auch alle benötigten Deklarationen mit hineinkopieren. HTH
Xantiva 21.12.2011
1
Ah, ist vielleicht missverständlich. Du darfst nicht Deine Methode OnOpenParentScansOcx() nehmen, sondern nur die Signatur von z. B. GetPrecursorInfoFromScanNum ...
Xantiva 21.12.2011
Ok, danke - was das Tool angeht bin ich damit einen Schritt weiter. Der generiert dann eine Menge Code, um den Variant-Typ zu simulieren. Beim Aufruf gibt es dann aber eine AccessViolationException :-( Ich sehe 2 Möglichkeiten: ich muss vor dem Aufruf mit dem Variant-Objekt noch etwas tun, was dem VariantInit() im Beispiel entspricht. Oder mein Ansatz, der Interop-Methode meine Signatur statt der aus der Typelib unterzuschieben, ist falsch. Ich hab das so gemacht, dass ich von dem Interface aus der Typelib abgeleitet und darin die Methode überschrieben habe. Geht das so?
Matthias Hlawatsch 21.12.2011
Ich bin damals (für einen Kameratreiber) hingegangen und habe die eine Klasse "SafeNativeMethods" erstellt und dort alle meine benötigten Aufrufe selber deklariert, z. B.
Xantiva 21.12.2011

[DllImportAttribute(
"dcamapi.dll", EntryPoint = "dcam_init",
CallingConvention = CallingConvention.StdCall,
CharSet = CharSet.Ansi, BestFitMapping = false, ThrowOnUnmappableChar = true)]
[return: MarshalAsAttribute(UnmanagedType.Bool)]
internal static extern bool dcam_init(
IntPtr reserved1,
out int numberOfCamerasDetected,
[MarshalAsAttribute(UnmanagedType.LPStr)] String initApiOption);
Xantiva 21.12.2011
Diese Komponente hier hat ca. 200 Methoden, von denen ich (zunächst?) ca. 20 brauche. Wenn der Preis für den Aufruf von GetPrecursorInfoFromScanNum wäre, die komplette Typelib durch eigene Deklarationen ersetzen zu müssen, würde ich eher zu C++ wechseln. Es ist eh nur ein Wrapper, der da entsteht, also außer den eigentlichen Aufrufen und ein paar Konvertierungen oder Parameter-Checks kein weiterer Code.
Matthias Hlawatsch 21.12.2011
??? Ich kopiere die unmanaged DLL mit ins Ausgabeverzeichnis und habe auch nur die von mir benötigten Methoden deklariert ...
Xantiva 21.12.2011
Es geht hier um eine COM-Komponente. Die ist auf dem Rechner installiert und registriert, im VS-Projekt habe ich sie über Verweis hinzufügen -> COM -> Typelib auswählen hinzugefügt.
Um sie zu nutzen, muss ich eine Instanz davon erzeugen:
rawFile = new MSFileReader_XRawfile() as IXRawfile5;
Alle Aufrufe gehen dann über diese Instanz (hier ist interner Zustand mit im Spiel). Für alle Funktionen, bei denen das geht, möchte ich die normale COM-Interop nutzen, weil für mich sonst auf Dauer der Aufwand zur Pflege der Deklarationen den Nutzen überwiegt.
Matthias Hlawatsch 21.12.2011
1
Schuss ins Blaue mit Aussicht auf Diskussionen ;-)

...
object vPrecursorInfos = null;
int nPrecursorInfos;
PrecursorInfo[] preIArray;

m_Rawfile.GetPrecursorInfoFromScanNum(m_nScanNumber, ref vPrecursorInfos,
ref nPrecursorInfos);
preIArray = (PrecursorInfo[])vPrecursorInfos;
...
20.12.2011
Eiger 1,9k 2 9
Eiger 1,9k 2 9
Nice try ;-) Siehe Update meiner Frage.
Das war auch mein erster Versuch. Bei dem rein syntaktisch recht ähnlich aussehenden GetMassListFromScanNum aus der gleichen Komponente hat das auch funktioniert. Hier leider nicht.
Trotzdem danke für's Eindenken!
Matthias Hlawatsch 21.12.2011
1
Möglicherweise musst du den zweiten Parameter mit dem MarshalAs-Atrribut annotieren. Mit diesem Attribut kannst du festlegen, wie deine Variable interpretiert wird. Hierzu kannst du einen Wert der UnmanagedType-Enumeration verwenden. Ich würde es mal mit den Werte Struct oder SafeArray probieren.

using System.Runtime.InteropServices;

namespace Test
{

// eventuell muss folgendes Attribut gesetzt werden
// [StructLayout(LayoutKind.Sequential, Pack = 4)]
public struct PrecursorInfo
{
public double dIsolationMass;
public double dMonoIsoMass;
public int nChargeState;
public int nScanNumber;
};

public class Program
{

public static extern int GetPrecursorInfoFromScanNum(int nScanNumber, [MarshalAs(UnmanagedType.SafeArray)] ref object pvarPrecursorInfos, ref int pnArraySize);
// oder alternativ
// public static extern int GetPrecursorInfoFromScanNum(int nScanNumber, [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_VARIANT)] ref object pvarPrecursorInfos, ref int pnArraySize);
// oder alternativ
// public static extern int GetPrecursorInfoFromScanNum(int nScanNumber, [MarshalAs(UnmanagedType.Struct)] ref object pvarPrecursorInfos, ref int pnArraySize);

public void GetPrecursorInfo(int pScanNumber)
{
object infos = null;
int infoSize = 0;

int result = GetPrecursorInfoFromScanNum(pScanNumber, ref infos, ref infoSize);
if(0 == result)
{
PrecursorInfo[] cursorInfos = (PrecursorInfo[])infos;

}

}
}
}
21.12.2011
luedi 2,0k 1 9
Sowas ähnliches hatte ich gestern schon mal probiert. Neu ist, auch PrecursorInfo selbst zu deklarieren mit StructLayout. Das werde ich dann mal ausprobieren. Schon mal Danke!
Matthias Hlawatsch 21.12.2011
1
Das meine ich mit dem PIvoke Assistant. Der schlägt z. B. vor:

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public struct PrecursorInfo {
/// double
public double dIsolationMass;
/// double
public double dMonoIsoMass;
/// int
public int nChargeState;
/// int
public int nScanNumber;
}
Xantiva 21.12.2011

Stelle deine .net-Frage jetzt!
TOP TECHNOLOGIES CONSULTING GmbH