Pointer auf InProc COM Server in zweiter dll benutzen



  • Hi Leute,

    zuerst mal ein paar Infos, was ich gerade vor habe (Achtung, es fällt der Begriff Java, doch es ist eine C++ Frage):

    - Ich habe einen IVI-COM Treiber, der zu einem USB Oszilloskop von Agilent gehört.
    - Das Oszilloskop möchte ich von einer Java Applikation aus fernsteuern/bedienen.
    - Von Java greife ich auf das COM Objekt des Treibers mit Hilfe der Com4J Projekts zu, welches mir alle Schnittstellen des angegebenen COM Objektes in Java zur Verfügung stellt, so dass ich keinen eigenen JNI Wrapper für den Treiber schreiben muss (eigentlich . . .)
    - Com4J setzt mir aber nur 98% der Schnittstellen und Methoden des Treibers in Java Code um. Gerade die Methode ReadMeasurement zum abholen der Oszillogramm-Punkte funktioniert nicht, da Com4J nicht mit dem SAVEARRAY Datentyp umgehen kann, welcher in der besagten Methode benutzt wird.

    Zuerst habe ich versuch den Com4J Source so weit zu verstehen, dass ich die SAVEARRAY Sache evtl selber dort einfüge. Dies übersteigt aber etwas meine Fähigkeiten. . .

    Nun zur eigentlichen Frage:
    Zur Zeit verfolge ich den Ansatz, dass ich das Com4J Paket für alles benutze, was es mir korrekt in Java Code generiert hat und schreibe mir nur den JNI Wrapper für die SAVEARRAY benutzenden Funktionen.
    Ich würde gerne das selbe COM Objekt des Treibers benutzen, dass ich auch schon vom Com4J Code aus initialisiert und parameterisiert habe.

    Dazu übergebe ich den Pointer auf das Com4J generierte COM Objekt des Treibers an meine JNI dll und versuche mit Hilfe der mir bekannten IIDs an das benötigte Interface und somit an die Methode ReadWaveform heranzukommen.

    Folgender Code läuft bis zur Zeile 37 problemlos:

    // u2702a_jni.cpp : Defines the exported functions for the DLL application.
    //
    
    #include "stdafx.h"
    #include "u2702aNativeWaveformAccess.h"
    #include <Unknwn.h>
    #include <Objbase.h>
    #include <IviDriverTypeLib.h>
    #include <IviScopeTypeLib.h>
    
    JNIEXPORT jobject JNICALL Java_oscilloscope_u2702a_jni_U2702ANativeWaveformAccess_returnSAVEARRAYData(JNIEnv * env, jobject obj, jlong drvPtr, jobject method, jlong timeout)
    {
    	IUnknown* iu = reinterpret_cast<IUnknown*>(drvPtr);
    	void* vTablePtr;
    	IIviScopeMeasurements *measurementsPtr = NULL;
    	IIviScopeMeasurement *measurementPtr = NULL;
    	BSTR ch1 = L"CHANNEL1";
    	IID iid = IID_NULL;
    	//LPOLESTR lpsz = L"{1F0ABA4C-ECF9-4CA5-8E3A-0BB657084470}";
    	LPOLESTR lpsz = L"{AF67FF35-33E9-42F6-BEB7-48A29856C165}"; // IIviScopeMeasurements IID
    
    	HRESULT hr = IIDFromString(lpsz, &iid);
    
    	if(hr != S_OK)
    		return NULL;
    
    	hr = iu->QueryInterface(iid, &vTablePtr);
    
    	if(hr != S_OK)
    		return NULL;
    
    	measurementsPtr = reinterpret_cast<IIviScopeMeasurements*>(vTablePtr);
    
    	// folgender Aufruf generiert laufzeit Exception, der auf unterschiedliche Calling Convention hindeutet.
    	hr = measurementsPtr->get_Item(L"CHANNEL1", &measurementPtr);
    
    	return NULL;
    }
    

    Die Fehlermeldung lautet:
    Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

    Das der Aufruf in Zeile 29 von einem S_OK gekrönt wurde hat mich zunächst sehr optimistisch gestimmt, da das Objekt an der aus dem Java Programm übergebenen Adresse scheinbar wirklich das von mir gesuchte COM-Objekt des Treibers ist.

    Um die Methode ReadWaveform aufrufen zu können brauche ich nun noch den Pointer auf die IIviScopeMeasurement Schnittstelle in der die Methode implementiert ist. Allerdings bekomme ich hier besagte Laufzeit Exception.

    Nun zu den Fragen:
    - Ist dieser Weg überhaupt Erfolg versprechend?
    - Welche Alternativen gibt es?
    - Will und kann mir jemand helfen das SAVEARRAY Handling in Com4J zu implementieren (da ja alles Opensource ist und auch der Treiber frei verfügbar ist muss ja nichts investiert werden).

    Gruß,
    Maik



  • Die Aufrufkonvention stimmt nicht zwischen Java und der DLL... das solltest Du zuerst lösen... bau dazu eine MessageBox ein und lass man das ganze IVI Zeugs weg... erst wenn die MessageBox korrekt angezeigt wird UND auch der Rücksprung wieder korrekt geht, dann kannst Du das andere probieren...
    Siehe __stdcall..



  • Hi Jochen,

    ich denke eigentlich nicht, dass das JNI irgendetwas damit zu tun hat.

    Ich habe trotzdem mal die Beispielfunktion von dieser MSDN Seite in meiner dll ausprobiert und das hat problemlos geklappt. (Rücksprung nach Java klappt auch problemlos, wenn ich entweder die Zeile mit der Exception auskommentiere, oder die Exception aus VC++ Express heraus einfach quittiere.)

    Das Problem scheint meiner Meinung nach viel mehr zu sein, dass ich nicht auf die Methode get_Item() zugreifen kann. Diese ist ja in meinem Treiber COM-Server implementiert.
    Meine Wrapper dll habe ich mit dem Parameter __stdcall kompiliert. Die COM Architektur benutzt diese Calling Convention auch so weit ich informiert bin. Trotzdem scheint sich hier ein Problem zu ergeben (zwischen meiner dll und dem COM InProg Server).

    Hat denn schon einmal jemand etwas ähnliches versucht?

    Nochmal, das der Aufruf in Zeile 29 erfolgreich war spricht ja sehr dafür, dass ich schon das richtige Objekt im Speicher anspreche. Wie gehe ich nun vor, dass ich eine vom Objekt implementierte Interface Methode aufrufen kann?

    Hat denn hier jemand Erfahrung mit dem ganzen IVI Kram? Da hätte ich nämlich auch noch ein paar Fragen drüber.

    Gruß,
    Maik



  • Wo genau tritt denn der Fehler auf? Beim Rücksprung von get_Item oder beim Rücksprung aus Deiner Methode?

    Aus meiner Sicht ist das Problem (wie es auch in der Fehlermeldung heisst) eine falsche Calling-Convention...

    Ich hab schn Agilent angebunden... zwar nur das Multimeter...



  • Hi,

    der Fehler tritt genau in Zeile 37 auf. Das heißt, ich setze einen Breakpoint auf Zeile 37, gehe einen Schritt weiter, quittiere die Exception-Meldung im Debugger und springe dann wieder ins Java programm zurück, dass ganz normal weiterläuft( weil ich dort den rückgabewert NULL abfange).

    Du hast also schon eimal ein Agilent Multimeter angebunden, okay . . .

    Hast du das mit dem IVI-COM oder mit dem IVI-C Treiber gemacht?

    Wie gesagt, ich arbeite ja gerade mit IVI-COM, weil die Infos auf der "IVI Foundation" Webseite und auch alle anderen Ressourcen suggerieren, dass man dann "Interchangebility" erreichen kann. Aufgrund dessen wird auch immer wieder empfohlen, dass man den COM Treiber benutzen soll und nicht den C-Treiber.

    Äußere Umstände, die ich auch nicht ändern kann, zwingen mich nun dazu das Oszilloskop in eine Java App einzubinden. Da ich gerne die "interchangebility" erreichen möchte Mühe ich mich nun mit COM ab.

    Ich frage mich aber z.B. immer, warum, wenn von "interchangebility" die Rede ist, die COM Beispile aus dem Treiberpaket im Sourcecode immer den Namen des Agilent Oszilloskops benutzen. Das wird dann mit einem Oszilloskop der Firma Tektronix nicht ohne weiteres zu ersetzen sein.
    Wie erreiche ich denn nun die "Interchangebility"?
    Ich habe auf der IVI Foundation Webseite etwas über eine "Ivi Session Factory" gelesen, mit der das wohl zu erreichen sei. Leider finde ich keine eindeutigen Beispiele in diese Richtung und habe das Gefühl, dass man eine Menge Hintergrundwissen benötigt, um diese ganze IVI Spezifikation zu verstehen.
    Kannst du mir da weiterhelfen?

    Gruß,
    Maik


  • Mod

    Für die COM-Interfaces kann es keine unterschieldiche Calling Convention geben.
    Evtl. sind Deine Interface Definitionen falsch und passen nicht zu der installierten Software.

    Hast Du die TLB direkt aus dem COM Objekt ausgelesen?



  • Hi Martin,

    schön, dass sich noch andere für dieses Thema interessieren . . . 😉

    Also, dass ich prinzipiell auf dem richtigen COM Objekt im Speicher operiere meine ich in Zeile 29 verifiziert zu haben.

    Wenn ich dort nämlich QueryInterface aufrufe, und die IID des gesuchten Interfaces übergebe und anschließend S_OK zurück bekomme, so kann ich doch davon ausgehen, dass dem Code an dieser Stelle genau dieses Interface bekannt ist und es sich somit um mein gesuchtes Objekt handelt.

    Dass ich hier auch noch keine "Calling Convention" Exception bekommen habe lässt mich auch vermuten, dass diese Laufzeitmeldung evtl. irreführend ist, weil es sich gar nicht um genau diesen Fehler handelt, sondern vom Debugger nur dafür gehalten wird.

    Aus diesem Grund habe ich die Überschrift zu diesem Thread auch etwas allgemeiner gehalten.

    Mir geht es vorrangig darum zu verstehen, ob und warum diese Methode zum Erfolg führen kann, oder eben nicht.

    Gestern beim herumspielen (manchmal sieht man ja den Wald vor lauter Bäumen nicht) ist mir auch aufgefallen, dass mein Java Code nicht die das COM Objekt lädt, welches z.B. das IIviScopeMeasurements Interface anspricht (welches ich ja gerne wegen der oben schon genannten "Interchangebility" verwenden möchte), sondern ein IAgilentU2701AMeasurements Interface benutzt. Dieses Interface erbt sozusagen von IIviScopeMeasurements und sollte (damit es IVI kompatibel ist) auch die selbe Funktionalität anbieten.

    Das könnte schon mal erklären, warum bei meinem Codebeispiel besagte Zeile in die Hose geht.

    Leider habe ich es auch mit diesem neuen Wissen nicht zum Laufen bringen können. . . .

    Wie gesagt, ich würde mich freuen, wenn sich jemand an der Forschung beteiligen würde, da ja alle Ressourcen frei verfügbar sind, und das Thema (meiner Meinung nach) auch sehr interessant und lehrreich ist.

    Ich bin mittlerweile bei dem Punkt angekommen, dass ich JNI Wrapper um den IVI-C Treiber schreiben werde, da ich nicht absehen kann, ob meine IVI-COM Versuche zum Erfolg führen werden. Allerdings werde ich die Forschung an dieser Stelle parallel weiter betreiben.

    Gruß,
    Maik



  • BSTR ch1 = L"CHANNEL1";
    //...
    hr = measurementsPtr->get_Item(L"CHANNEL1", &measurementPtr);
    

    Darf/muss/kann ich davon ausgehen, dass der erste Parameter von get_Item auch ein BSTR ist?
    Wenn ja, dann mach dich auf eine ÜBLE Überraschung gefasst: ein Wide-Character String, wie du da einen übergibst, IST KEIN BSTR!

    Ein BSTR hat nämlich an Position ptr[-1] noch die Länge abgespeichert.

    Dass BSTR nur ein Typedef auf wchar_t* ist, ist eine ÜBEL ÜBEL schlimme Design-Entscheidung von MS, die so manchem Programmierer so manches graue Haar beschert haben wird.

    Wenn du nen BSTR willst, nimm entweder die MSVC Hilfsklasse _bstr_t, oder verwende SysAllocString.
    Also z.B.

    #include <comutil.h>
    //...
    hr = measurementsPtr->get_Item(_bstr_t(L"CHANNEL1"), &measurementPtr);
    

    Bin mir grad nicht sicher ob das dein Problem erklären kann, aber versuchs mal. Und merk dir auf jeden Fall das mit den BSTRings, wenn man mit COM rummacht braucht man das öfters.



  • Hi,

    ja, du kannst davon ausgehen, dass es ein BSTR ist.

    Dies ist die Prototypdefinition aus der *.tlh Datei:

    virtual HRESULT __stdcall get_Item (
            /*[in]*/ BSTR Name,
            /*[out,retval]*/ struct IAgilentU2701AMeasurement * * val ) = 0;
    

    Du hast recht, dass meine Definition nicht der eines BSTR entspricht. Die habe ich aus einer Beispielapplikation, die dem Treiber beiliegt abgeguckt. Dort stehen die Zeilen:

    IAgilentU2701AMeasurementPtr meas;
    spAgDrvr->Measurements->get_Item(L"CHANNEL1", &meas);
    

    Da das so zur Laufzeit keinen fehler gibt habe ich gedacht, dass passt schon. . . .

    Auf alle Fälle ein super Tip, den ich heute abend mal zu hause ausprobieren werde. Vielen Dank!

    Gruß,
    Maik



  • Hi,

    nur zur Info:

    Das

    _bstr_t(L"CHANNEL1")
    

    hat leider nichts an der Runtime Exception geändert . . .

    Gruß,
    Maik


  • Mod

    Kiamur schrieb:

    Also, dass ich prinzipiell auf dem richtigen COM Objekt im Speicher operiere meine ich in Zeile 29 verifiziert zu haben.

    Wenn ich dort nämlich QueryInterface aufrufe, und die IID des gesuchten Interfaces übergebe und anschließend S_OK zurück bekomme, so kann ich doch davon ausgehen, dass dem Code an dieser Stelle genau dieses Interface bekannt ist und es sich somit um mein gesuchtes Objekt handelt.

    Das mag ja sein, aber woher hast Du die Deifnitionen wie das Interface aussieht?
    Per #import besorgt (dann wäre es aktuell), oder hast Du einen Headerdatei dafür? Oder gar selbst gebastelt?

    Dass ich hier auch noch keine "Calling Convention" Exception bekommen habe lässt mich auch vermuten, dass diese Laufzeitmeldung evtl. irreführend ist, weil es sich gar nicht um genau diesen Fehler handelt, sondern vom Debugger nur dafür gehalten wird.

    Diese Meldung deutet einzig darauf hion, dass der Stack zerstört wird!



  • Hi Martin,

    zuerst habe ich mit dem Header gearbeitet, der allerdings auch mit in dem aktuellen Treiber/API Paket entahlten ist. Deswegen gehe ich mal davon aus, dass er auch aktuell ist.

    Dann habe ich ja gemerkt, dass ich nicht mit der COM Klasse des Agilent Scopes direkt gearbeitet habe, sondern mit der IVI COM Klasse (zu der ich eben den Header habe). Dies ist ja eigentlich der Weg, den ich gehen möchte (Stichwort "Interchangeability").
    Die Agilent Scope Klasse erbt allerdings von dieser Klasse. Deswegen hat der Aufruf QueryInterface nach meinem IviScopeMeasurements Interface wohl auch funktioniert.

    Naja, da ich das ja alles aus den bekannten Gründen nicht zum Laufen gebracht habe, habe ich mir anschließend per #import die Agilent Scope COM Klasse verfügbar gemacht. Leider hat an dieser Stelle schon der erste Schritt nicht funktioniert, nämlich das casten meines Pointers auf das InProc COM Objekt, den ich ja über die JNI Schnittstelle aus dem Java Programm (und von da aus dem Com4J Paket) übergeben bekam.
    Ich habe keine Ahnung, warum ich an der Stelle, wo ich caste (also noch nicht einmal, wenn ich versuche auf das Agilent Scope Objekt zuzugreifen) schon eine Access Violation Meldung bekomme.
    Wie gesagt, caste ich den Pointer auf ein IVI Scope Objekt, dann bekomme ich keine Access Violation Meldung.

    Irgendetwas habe ich noch nicht verstanden an der IVI Treiberarchitektur, bzw an COM . . .

    Gruß,
    Maik


Anmelden zum Antworten