Anmelden

Archiv verlassen und diese Seite im Standarddesign anzeigen : Dynamische Funktion/Code in C (ANSI C)



Pingu
28-04-2007, 09:17
Ich habe gerade ein kleines Problem, sonst würde ich hier wohl auch nicht schreiben ;).

Ich habe ein Modul/Library/Treiber mit dem ich eine Hardware zum Senden und Empfangen von Nachrichten ansprechen kann. Dieser Treiber hat eine Initialisierungsfunktion. In der Initialisierungsfunktion kann ich eine Callback übergeben, die aufgerufen wird, wenn neue Nachrichten zur Verfügung stehen.
extern int DLLIMPORT InitBoard(UINT16 board_seg, t_UsrRxIntHdlr fp_int_hdlr, t_UsrExcHdlr fp_exc_hdlr);

typedef void (*t_UsrRxIntHdlr)(UINT16 count, CAN_OBJ *p_obj);
typedef void (*t_UsrExcHdlr)(FUNC_NUM, int, int, char *);

In meinem Programm kann es jetzt aber passieren, dass ich diesen Treiber mehrfach instanziieren muss, weil ich mehrere Hardwareinterfaces habe. Prinzipiell sollte das kein Problem sein. Konkret sehe ich aber ein Problem: für die Callbackfunktion(en) kann ich keinen eigenen Datenpointer übergeben. Wenn somit die Callback aufgerufen wird, weiß ich nicht, von welchem konkreten Interface der Aufruf und damit die Daten kommen.

Mir ist eine Idee gekommen eine Art Template-Funktion zu definieren, die ich dann instanziieren kann. Ich würde gerne so etwas machen (Pseudo code):

void RxCallbackTemplate(UINT16 count, CAN_OBJ *p_obj) {

void *handle;

RxCallbackWithHandle(handle, count, p_obj);

}

InitTreiber() {

// do stuff and generate myPrivateHandle

RxCallback = Instanziiere(RxCallbackTemplate);

RxCallback->handle = myPrivateHandle;

InitBoard(board_seg, &RxCallback, NULL);

}

Eigentlich müßte es soch theoretisch möglich sein, indem ich mir eine Template-Funktion als Inline-Assembler definiere. Der Inline-Assembler müsste nur den Aufruf einer anderen Funktion realisieren. Dann reserviere ich etwas Speicher; kopiere den Inline-Assembler-Code dahin; ändere ein paar Adressen (setze das Handle) und übergebe die Speicher-Adresse als Callback an das InitBoard(). So müßte es doch gehen?
Das ganze soll, wenn möglich, unter allen Windows-Versionen (95 bis XP, Vista) laufen.

Ich habe leider kein C++ oder ähnliches zur Verfügung. Es muss in simplen ANSI-C möglich sein, da mein Compiler der Compiler von National Instruments LabWindows/CVI ist, der nur ANSI-C kann.

Weiß jemand wie man so etwas realisiert? Hat jemand eine andere Idee, das besser zu machen? Oder kann mir jemand Tips geben, wonach ich mit Google nach einer Lösung suchen kann?

Danke

Pingu

anda_skoa
28-04-2007, 17:56
Hmm, ich bin kein C Guru, aber ich glaube nicht, daß das geht.

Wenn du API Threadsave ist, könnte man jedes Board mit einem anderen Thread bearbeiten.

Sonst könnte man noch mit einem Kindprozess arbeiten, der hat einen eigenen Adressraum

Ciao,
_

Pingu
28-04-2007, 19:14
Ich hatte vorhin eine Idee (dirty workaround). Aber die geht nicht. Ich habe eine Funktion definiert, die dann die eigentliche Callback aufrufen soll:
void __RxCallback(unsigned short usCount, CAN_OBJ *pCanObjs)
{
void *hChannel = (void *)0xFFFFFFFF;

__MyRxCallback(hChannel, usCount, pCanObjs);

// hChannel = (void *)0xA5A5A5A5;

} /* function __RxCallback() */

Dann wollte ich einfach mir Speicher reservieren, diese Funktion da rein kopieren und meine Kennung (0xFFFFFFFF) durch die richtige Adresse ersetzen:
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
///
/// DONT TRY THIS AT HOME --- WORKAROUND
///
///
/// we now generate a dynamic Function

// first count the number of bytes we need
usTmp = 0;
pcTmp = (char *)(&__RxCallback);
while ((*(pcTmp + usTmp++) != 0xC3) && // 0xC3 -> ret
(*(pcTmp + usTmp++) != 0x90) && // 0x90 -> nop
(*(pcTmp + usTmp++) != 0x55)); // 0x55 -> pop bp (next function)

// now reserve the memory for our function
pChannel->RxCB = calloc(--usTmp, sizeof(char));

// now copy the data over
memcpy(pChannel->RxCB, pcTmp, usTmp);

// now search for our marker (0xFFFFFFFF)
usTmp = 0;
pcTmp = (char *)(pChannel->CotiRxCB);
while ((*(pcTmp + usTmp++) != 0xFF) &&
(*(pcTmp + usTmp++) != 0xFF) &&
(*(pcTmp + usTmp++) != 0xFF) &&
(*(pcTmp + usTmp++) != 0xFF));

// now replace our marker with the correct handle value
pcTmp[--usTmp] = (char)(((int)pChannel >> 24) & 0xFF);
pcTmp[--usTmp] = (char)(((int)pChannel >> 16) & 0xFF);
pcTmp[--usTmp] = (char)(((int)pChannel >> 8) & 0xFF);
pcTmp[--usTmp] = (char)(((int)pChannel ) & 0xFF);

///
/// DONT TRY THIS AT HOME --- WORKAROUND
///
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////
Das gibt eine schöne fette Fehlermeldung: General protection fault.
Es ist schon der erste Speicherzugriff, also while ((*(pcTmp + usTmp++) != 0xC3). Ich denke, daß ich auf das Code-Segment nicht zugreifen darf. Wobei der Speicherschutz nicht durch Windows sondern durch die LabWindows/CVI realisiert wird. Denn die Fehlermeldung sieht wie eine Fehlermeldung des LabWindows/CVI-Debuggers aus. Ist ja eigentlich auch richtig so :(.

Den Vorschlag mit den Threads muss ich demnächst auch noch mal probieren.

Andere Vorschläge?

Aber jetzt werde ich ersteinmal damit aufhöhren und es nicht implementieren. Sondern ich werde schönes gutes altes Polling machen. Auch wenn dies das Problem hat, dass ich Race-Conditions derart bekomme, dass Nachrichten sich überholen können. Dummerweise ist bei mir aber genau die Reihenfolge und der Bezug der gesendeten und empfangenen Nachrichten wichtig. Denn ich implementiere einen Test, der andere Implementierungen testet.

Vincent Vega
29-04-2007, 21:10
Dieser Ansatz mit dem Wrapper ist mehr oder weniger gut. Um sicherzugehn, dass ich Dich richtig verstanden habe, hier meine Auffassung: Du schreibst eine Funktion, die das Handle hart-kodiert belegt. Das Handle wird als weiterer Parameter an den generischen Callback Handler übergeben. Dann kopierst Du zur Laufzeit diese Funktion und änderst den Wert des Handles. Jetzt registrierst Du die Adresse der kopierten und veränderten Funktion als Callback-Handler einer Instanz des Treibers.

Das Prinzip funktioniert, allerdings müssen Dir ein paar Punkte bewusst sein:


Du musst die Wrapper-Funktion in Assembler schreiben, denn sonst kann sich die Position wo der Wert des Handles initialisiert wird und die Art wie es initialisiert wird je nach Compiler und Compiler-Flags ändern.
Durch die Verwendung von Assembler legst Du Dich auf eine Prozessorarchitektur fest.
Die Art und Weise wie Parameter übergeben werden, hängen von der verwendeten ABI ab. Damit schränkst Du Dich noch weiter ein, nml auf ein Betriebssystem und ein Binärformat.
Bei manchen Betriebssystemen werden Speicherseiten entweder als schreib- oder als ausführbar markiert. Das bedeutet, Du kannst eine ausführbare Seite (Deine Wrapper-Funktion) zwar lesen und kopieren, die kopierte Seite wurde aber als schreibbar markiert und ist damit nicht ausführbar. Das würde Deinen Segmentation-Fault erklären, ich kenne aus dem Kopf jetzt aber auch keinen Workaround.


Es ist machbar, aber nicht schön und nicht einfach.

RHBaum
02-05-2007, 11:10
In meinem Programm kann es jetzt aber passieren, dass ich diesen Treiber mehrfach instanziieren muss
Also die Firma die den Treiber zur verfuegung stellt, sollte schon wissen was sie tut.
Gängige Praxis ist es schon, ein handle fuer die Instanz mitzugeben, damit du unterscheiden kannst, von woher die daten kommen.
Ansonsten isses von der Firma vielleicht ned vorgesehen, das man im Prozess den treiber mehrfach instanziiert. Ich mein wen sowas in der schnittstelle nich vorgesehen ist, werd ich nicht davon ausgehen dass sich ne mehrfache instanziierung nicht durch die verwendung von globaler variablen in deren Api selbst abschiesst .....

Was du aber Testen kannst, ist ob du gleichzeitig mit 2 prozessen(2 anwendungen) zwei unterschiedliche Geraete mit dem selben treiber anziehen kannst.
Wenn das geht, solltest du dein problem mit multiprozessing umgehen koennen. Du ziehst fuer jedes "Hardwareinterface" nen eigenen Prozess auf, und laesst sie mittels IPC die daten zurueck zum Mainprozess schreiben (diesmal mit Info wo die daten herkommen). Damit ruestest du wahrscheinlich genau die funktionalitaet nach, die der hersteller sich aufwandstechnisch ned antun wollte :-) Und nur so kannst sichergehen das sich eventuell prozess-globale ressourcen ned ins gehege kommen.

Ne frage am Rande .... CAN_OBJ ... Dein Hardware Interface ist ne Anbindung an einen CAN (Controller Area Network) Bus ? Welcher Hersteller macht sowas ?

Ciao ...