Probleme beim Datenaustausch zweier Programme über eine DLL



  • Hallo Zusammen,

    ich habe ein Programm A geschrieben das Daten in einer DLL speichert. Mit Programm B möchte ich die gespeicherten Daten Auslesen. Leider Funktioniert dies nicht. Programm A schreibt seine Daten in die DLL und speichert diese auch. Wenn nun Programm B versucht die Daten Auszulesen sind aber keine vorhanden. Noch nichtmal die Zählervariable guc_AnzFehler wird gespeichert bzw. wird von Programm B richtig ausgelesen.

    Was kann ich falsch gemacht haben?

    hier ist der DLL Code:

    #define KEINZUGRIFF (0)
    #define SCHREIBE (1)
    #define LESE (2)
    
    #define BUFFERLEER (253)
    #define BUFFERVOLL (254)
    #define ZUGRIFFVERWEIGERT (255)
    
    #define MAXFEHLER (100)
    
    struct strMessage
    {
    	unsigned char LuefterZeile;
    	unsigned char LuefterSpalte;
    	unsigned char StatusFehlerMeldung;  //gibt an ob es sich um eine Status oder Fehlermeldung handelt
    	unsigned char FehlerNummer;
    	unsigned char TeilnehmerNummer;
    };
    
    static strMessage Fehler[MAXFEHLER]; //Speicher für Fehler  
    
    static unsigned char guc_AnzFehler=0; //gibt die Anzahl vorhandner Fehler an
    static unsigned char guc_ReadWriteAccessFehler=KEINZUGRIFF; // Semaphore
    
    //Für Ringbuffer, ermmitelt die nächste Position des Buffers (Lesen oder Schreiben)
    int addring(int i, int nmax)
    {
    	if	((i+1) == nmax)
    		return  0;
    	else
    		return i+1;
    }
    
    extern "C" _declspec(dllexport) unsigned char SchreibeFehler(strMessage *msgWrite)
    {
     static int iput=0; //Array index zum Schreiben des Ringbuffers
    
    	if (guc_ReadWriteAccessFehler==KEINZUGRIFF)
    	{
    		guc_ReadWriteAccessFehler=SCHREIBE; //Semaphore
    
    		if(guc_AnzFehler<MAXFEHLER)
    		{
    			Fehler[iput].FehlerNummer=msgWrite->FehlerNummer;
    			Fehler[iput].LuefterSpalte=msgWrite->LuefterSpalte;
    			Fehler[iput].LuefterZeile=msgWrite->LuefterZeile;
    			Fehler[iput].StatusFehlerMeldung=msgWrite->StatusFehlerMeldung;
    			Fehler[iput].TeilnehmerNummer=msgWrite->TeilnehmerNummer;
    
    			printf("%d \n",Fehler[iput].FehlerNummer);
    			printf("%d \n",Fehler[iput].LuefterSpalte);
    			printf("%d \n",Fehler[iput].LuefterZeile);
    			printf("%d \n",	Fehler[iput].StatusFehlerMeldung);
    			printf("%d \n",Fehler[iput].TeilnehmerNummer);
    
    			iput=addring(iput,MAXFEHLER);
    
    			guc_AnzFehler++;
    		}
    		else
    		{
    			guc_ReadWriteAccessFehler=KEINZUGRIFF;
    			return BUFFERVOLL;
    		}
    		guc_ReadWriteAccessFehler=KEINZUGRIFF;
    	}
    	else
    		return ZUGRIFFVERWEIGERT;
    return guc_AnzFehler;
    }
    
    extern "C" _declspec(dllexport) unsigned char LeseFehler(strMessage *msgRead)
    {
    	static int iget=0;
    
    	if (guc_ReadWriteAccessFehler==KEINZUGRIFF)
    	{
    		guc_ReadWriteAccessFehler=LESE; //Semaphore
    
    		if(guc_AnzFehler>0)
    		{
    			msgRead->FehlerNummer=Fehler[iget].FehlerNummer;
    			msgRead->LuefterSpalte=Fehler[iget].LuefterSpalte;
    			msgRead->LuefterZeile=Fehler[iget].LuefterZeile;
    			msgRead->StatusFehlerMeldung=Fehler[iget].StatusFehlerMeldung;
    			msgRead->TeilnehmerNummer=Fehler[iget].TeilnehmerNummer;
    
    			iget=addring(iget, MAXFEHLER);
    			guc_AnzFehler--;
    		}
    		else
    		{
    			guc_ReadWriteAccessFehler=KEINZUGRIFF;
    			return BUFFERLEER;
    		}
    		guc_ReadWriteAccessFehler=KEINZUGRIFF;
    	}
    	else
    		return ZUGRIFFVERWEIGERT;
    return guc_AnzFehler;
    }
    

    Programm A ruft die Funktion SchreibeFehler auf.
    Programm B ruft die Funktion LeseFehler auf.

    Ich Programmiere mit eVC++ für ein Windows CE Betriebssystem.

    Gruß
    spacehelix



  • Soweit ich weiß, verwendet jedes Programm seine eigene Version der DLL (was insbesondere bedeutet, daß jedes Programm ein eigenes "Fehler"-Array hat). Wenn du Daten zwischen den Programmen austauschen willst, mußt du über interne Variablen hinausgehen, z.B. in shared Memory oder auf die Festplatte.

    (btw, du hast einen logischen Fehler in deinen Funktionen: Alle Fehlermeldungen werden im Feld Fehler[0] gespeichert und wieder ausgelesen, d.h. du überschreibst jeweils die letzte Fehlermeldung)



  • Hi,

    wo bekomme ich informationen über "Shared Memory" her? Die Daten sollen auch möglichst schnel ausgetauscht werden (weniger 50ms).

    Gruß
    spacehelix



  • CreateFileMapping
    => Dann teilen sich die Programme den Speicher! Du musst dann nur dafür sorgen, dass Du den Zugriff korrekt synchronisiert...



  • Hi,

    hast du mehr informationen darüber? Ich habe eben gegoogelt und das meiste ist auf Chinesich. In der Hilfe meiner Entwicklungsumgebung (eVC++) finde ich auch nichts darüber.
    Geht das FileMapping dann über eine dll?

    Gruß
    spacehelix





  • Hi,

    Ich habe ein Beispiel gefunden, da ich aber noch Anfänger bin habe ich dazu ein paar Fragen.

    Hier ist erstmal das Beispiel.

    Hallo, 
    Du musst wie folgt vorgehen: 
    1. Definiere Dir einen eigenen Datentypen, welchen Du in den shared Memory halten willst, z.B.: 
    
    typedef struct structSHM_ 
    { 
            TCHAR			szMyString[256]; 
    } STRUCTSHM, *PSTRUCTSHM; 
    2. Definiere folgende zwei globale Variablen in Deiner DLL: 
    
    // globale Variablen 
    HANDLE			hMapping = NULL; 
    PSTRUCTSHM		pSHM; 
    3. Nun erstellst Du ein FileMapping-Object in der DllMain-Methode im Bereich "case DLL_PROCESS_ATTACH:", wie folgt: 
    
    // Create a file mapping object. 
    SECURITY_ATTRIBUTES secAttr; 
    SECURITY_DESCRIPTOR sid; InitializeSecurityDescriptor(&sid, SECURITY_DESCRIPTOR_REVISION); 
    SetSecurityDescriptorDacl(&sid, TRUE, NULL, FALSE); secAttr.nLength=sizeof(SECURITY_ATTRIBUTES); 
    secAttr.lpSecurityDescriptor=&sid; 
    secAttr.bInheritHandle=TRUE; hMapping = ::CreateFileMapping ((HANDLE) INVALID_HANDLE_VALUE, 
                                    &secAttr,  
                                    PAGE_READWRITE, 
                                    0, 
                                    sizeof(STRUCTSHM), 
                                    SHMNAME); 
    if (hMapping == NULL || hMapping == INVALID_HANDLE_VALUE) 
    { 
            // Fehlermeldung ausgeben				 
            return FALSE; 
    } // Convert the handle into a pointer. 
    pSHM = (PSTRUCTSHM) ::MapViewOfFile (hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); 
    if (pSHM == NULL) 
    { 
            // Fehlermeldung ausgeben					::CloseHandle (hMapping); 
            return FALSE; 
    } 
    4. In der DLLMain-Methode im Abschnitt "case DLL_PROCESS_DETACH:" kann der SharedMemory wieder geschlossen werden (wird aufgerufen, wenn kein Prozess mehr einen verweis auf die DLL hat): 
    
    if (pSHM) 
    { 
            ::UnmapViewOfFile (pSHM); 
            pSHM = NULL; 
    } 
    if	(hMapping) 
    { 
            ::CloseHandle (hMapping); 
            hMapping = NULL; 
    } 
    5. In der .def-Datei Deines DLL-Projektes musst Du nun noch den folgenden Eintrag hinzufügen: 
    
    SECTIONS 
       .sdata   READ WRITE SHARED 
    Das wars. Nun kannst Du Daten aus verschiedenen Prozessen, welche Deine DLL laden über den definierten Shared-Memory-Bereich austauschen. Du solltest aber deine DLL Multithreading-fähig programmieren, so dass sich verschiedene Prozesse untereinander nicht ungewollt beeinflussen. 
    
    Viel Spass 
    
    Tino.
    

    Erste Frage:
    3. Nun erstellst Du ein FileMapping-Object in der DllMain-Methode im Bereich "case DLL_PROCESS_ATTACH:".

    Ich habe keine DLLMain-Methode wo muss ich das erstellen?

    Zweite Frage:
    4. In der DLLMain-Methode im Abschnitt "case DLL_PROCESS_DETACH:" kann der SharedMemory wieder geschlossen werden (wird aufgerufen, wenn kein Prozess mehr einen verweis auf die DLL hat):

    Wie erste Frage.

    Dritte Frage:

    5. In der .def-Datei Deines DLL-Projektes musst Du nun noch den folgenden Eintrag hinzufügen:

    SECTIONS
    .sdata READ WRITE SHARED

    Was beudet dies?

    Wenn ich dann dieses Beispiel richtig verstanden habe, ist es egal wieviele Programme die Dll aufrufen, die Variable bzw. Zeiger pSHM verweist immer auf die gleiche adresse. Stimmt das?

    Gruß
    spacehelix



  • Sorry,

    ich hab vergessen meine ganze Dll zu posten. Hier ist sie:

    // data.cpp : Defines the initialization routines for the DLL.
    //
    
    #include "stdafx.h"
    #include "data.h"
    #include <string>
    
    #ifdef _DEBUG
    #define new DEBUG_NEW
    #undef THIS_FILE
    static char THIS_FILE[] = __FILE__;
    #endif
    
    //
    //	Note!
    //
    //		If this DLL is dynamically linked against the MFC
    //		DLLs, any functions exported from this DLL which
    //		call into MFC must have the AFX_MANAGE_STATE macro
    //		added at the very beginning of the function.
    //
    //		For example:
    //
    //		extern "C" BOOL PASCAL EXPORT ExportedFunction()
    //		{
    //			AFX_MANAGE_STATE(AfxGetStaticModuleState());
    //			// normal function body here
    //		}
    //
    //		It is very important that this macro appear in each
    //		function, prior to any calls into MFC.  This means that
    //		it must appear as the first statement within the 
    //		function, even before any object variable declarations
    //		as their constructors may generate calls into the MFC
    //		DLL.
    //
    //		Please see MFC Technical Notes 33 and 58 for additional
    //		details.
    //
    
    /////////////////////////////////////////////////////////////////////////////
    // CDataApp
    
    BEGIN_MESSAGE_MAP(CDataApp, CWinApp)
    	//{{AFX_MSG_MAP(CDataApp)
    		// NOTE - the ClassWizard will add and remove mapping macros here.
    		//    DO NOT EDIT what you see in these blocks of generated code!
    	//}}AFX_MSG_MAP
    END_MESSAGE_MAP()
    
    /////////////////////////////////////////////////////////////////////////////
    // CDataApp construction
    
    CDataApp::CDataApp()
    {
    	// TODO: add construction code here,
    	// Place all significant initialization in InitInstance
    }
    
    /////////////////////////////////////////////////////////////////////////////
    // The one and only CDataApp object
    
    CDataApp theApp;
    
    #define KEINZUGRIFF (0)
    #define SCHREIBE (1)
    #define LESE (2)
    
    #define BUFFERLEER (253)
    #define BUFFERVOLL (254)
    #define ZUGRIFFVERWEIGERT (255)
    
    #define MAXFEHLER (100)
    #define MAXLOESCHEN (100)
    #define MAXSOLLWERT (100)
    
    struct strSollwert
    {
    	unsigned char LuefterZeile;
    	unsigned char LuefterSpalte;
    	unsigned char Sollwert;
    };
    
    struct strMessage
    {
    	unsigned char LuefterZeile;
    	unsigned char LuefterSpalte;
    	unsigned char StatusFehlerMeldung;  //gibt an ob es sich um eine Status oder Fehlermeldung handelt
    	unsigned char FehlerNummer;
    	unsigned char TeilnehmerNummer;
    };
    
    static strSollwert Sollwert[MAXSOLLWERT];
    static strMessage Loeschen[MAXLOESCHEN]; //Speicher für die zu Löschenden Fehler	
    static strMessage Fehler[MAXFEHLER]; //Speicher für Fehler  
    static unsigned char guc_AnzFehler=0; //gibt die Anzahl vorhandner Fehler an	
    static unsigned char guc_ReadWriteAccessFehler=KEINZUGRIFF; // Semaphore
    static unsigned char guc_AnzLoeschen=0; //gibt die Anzahl vorhandner Fehler an	
    static unsigned char guc_ReadWriteAccessLoeschen=KEINZUGRIFF; // Semaphore
    static unsigned char guc_AnzSollwert=0; //gibt die Anzahl vorhandner Fehler an	
    static unsigned char guc_ReadWriteAccessSollwert=KEINZUGRIFF; // Semaphore
    
    //Für Ringbuffer, ermmitelt die nächste Position des Buffers (Lesen oder Schreiben)
    int addring(int i, int nmax)
    {
    	if	((i+1) == nmax)
    		return  0;
    	else
    		return i+1;
    }
    
    extern "C" _declspec(dllexport) unsigned char SchreibeFehler(strMessage *msgWrite)
    {
     static int iput=0; //Array index zum Schreiben des Ringbuffers
    
    	if (guc_ReadWriteAccessFehler==KEINZUGRIFF)
    	{
    		guc_ReadWriteAccessFehler=SCHREIBE; //Semaphore
    
    		if(guc_AnzFehler<MAXFEHLER)
    		{
    			Fehler[iput].FehlerNummer=msgWrite->FehlerNummer;
    			Fehler[iput].LuefterSpalte=msgWrite->LuefterSpalte;
    			Fehler[iput].LuefterZeile=msgWrite->LuefterZeile;
    			Fehler[iput].StatusFehlerMeldung=msgWrite->StatusFehlerMeldung;
    			Fehler[iput].TeilnehmerNummer=msgWrite->TeilnehmerNummer;
    
    			printf("%d \n",Fehler[iput].FehlerNummer);
    			printf("%d \n",Fehler[iput].LuefterSpalte);
    			printf("%d \n",Fehler[iput].LuefterZeile);
    			printf("%d \n",	Fehler[iput].StatusFehlerMeldung);
    			printf("%d \n",Fehler[iput].TeilnehmerNummer);
    
    			iput=addring(iput,MAXFEHLER);
    
    			guc_AnzFehler++;
    		}
    		else
    		{
    			guc_ReadWriteAccessFehler=KEINZUGRIFF;
    			return BUFFERVOLL;
    		}
    		guc_ReadWriteAccessFehler=KEINZUGRIFF;
    	}
    	else
    		return ZUGRIFFVERWEIGERT;
    return guc_AnzFehler;
    }
    
    extern "C" _declspec(dllexport) unsigned char LeseFehler(strMessage *msgRead)
    {
    	static int iget=0;
    
    	if (guc_ReadWriteAccessFehler==KEINZUGRIFF)
    	{
    		guc_ReadWriteAccessFehler=LESE; //Semaphore
    
    		if(guc_AnzFehler>0)
    		{
    			msgRead->FehlerNummer=Fehler[iget].FehlerNummer;
    			msgRead->LuefterSpalte=Fehler[iget].LuefterSpalte;
    			msgRead->LuefterZeile=Fehler[iget].LuefterZeile;
    			msgRead->StatusFehlerMeldung=Fehler[iget].StatusFehlerMeldung;
    			msgRead->TeilnehmerNummer=Fehler[iget].TeilnehmerNummer;
    
    			iget=addring(iget, MAXFEHLER);
    			guc_AnzFehler--;
    		}
    		else
    		{
    			guc_ReadWriteAccessFehler=KEINZUGRIFF;
    			return BUFFERLEER;
    		}
    		guc_ReadWriteAccessFehler=KEINZUGRIFF;
    	}
    	else
    		return ZUGRIFFVERWEIGERT;
    return guc_AnzFehler;
    }
    
    extern "C" _declspec(dllexport) unsigned char LoescheFehlerC(strMessage *msgRead)
    {
    	static int iget=0;
    
    	if (guc_ReadWriteAccessLoeschen==KEINZUGRIFF)
    	{
    		guc_ReadWriteAccessLoeschen=LESE; //Semaphore
    
    		if(guc_AnzLoeschen>0)
    		{
    			msgRead->FehlerNummer=1;//Fehler[iget].FehlerNummer;
    			msgRead->LuefterSpalte=2;//Fehler[iget].LuefterSpalte;
    			msgRead->LuefterZeile=3;//Fehler[iget].LuefterZeile;
    			msgRead->StatusFehlerMeldung=4;//Fehler[iget].StatusFehlerMeldung;
    			msgRead->TeilnehmerNummer=5;//Fehler[iget].TeilnehmerNummer;
    
    			iget=addring(iget, MAXLOESCHEN);
    			guc_AnzLoeschen--;
    		}
    		else
    		{
    			guc_ReadWriteAccessLoeschen=KEINZUGRIFF;
    			return BUFFERLEER;
    		}
    		guc_ReadWriteAccessLoeschen=KEINZUGRIFF;
    	}
    	else
    		return ZUGRIFFVERWEIGERT;
    return guc_AnzLoeschen;
    }
    
    extern "C" _declspec(dllexport) unsigned char LoescheFehlerVB(strMessage *msgWrite)
    {
    	static int iput=0;
    
    	if (guc_ReadWriteAccessLoeschen==KEINZUGRIFF)
    	{
    		guc_ReadWriteAccessLoeschen=SCHREIBE; //Semaphore
    
    		if(guc_AnzLoeschen<MAXLOESCHEN)
    		{
    			Loeschen[iput].FehlerNummer=msgWrite->FehlerNummer;
    			Loeschen[iput].LuefterSpalte=msgWrite->LuefterSpalte;
    			Loeschen[iput].LuefterZeile=msgWrite->LuefterZeile;
    			Loeschen[iput].StatusFehlerMeldung=msgWrite->StatusFehlerMeldung;
    			Loeschen[iput].TeilnehmerNummer=msgWrite->TeilnehmerNummer;
    			iput=addring(iput,MAXLOESCHEN);
    			guc_AnzLoeschen++;
    		}
    		else
    		{
    			guc_ReadWriteAccessLoeschen=KEINZUGRIFF;
    			return BUFFERVOLL;
    		}
    		guc_ReadWriteAccessLoeschen=KEINZUGRIFF;
    	}
    	else
    		return ZUGRIFFVERWEIGERT;
    return guc_AnzLoeschen;
    }
    
    extern "C" _declspec(dllexport) unsigned char SollwertC(strSollwert *msgRead)
    {
    	static int iget=0;
    
    	if (guc_ReadWriteAccessSollwert==KEINZUGRIFF)
    	{
    		guc_ReadWriteAccessSollwert=LESE; //Semaphore
    
    		if(guc_AnzSollwert>0)
    		{
    			msgRead->LuefterSpalte=1;//Sollwert[iget].LuefterSpalte;
    			msgRead->LuefterZeile=2;//Sollwert[iget].LuefterZeile;
    			msgRead->Sollwert=3;//Sollwert[iget].Sollwert;
    			iget=addring(iget,MAXSOLLWERT);
    			guc_AnzSollwert--;
    		}
    		else
    		{
    			guc_ReadWriteAccessSollwert=KEINZUGRIFF;
    			return BUFFERLEER;
    		}
    		guc_ReadWriteAccessSollwert=KEINZUGRIFF;
    	}
    	else
    		return ZUGRIFFVERWEIGERT;
    return guc_AnzSollwert;
    }
    
    extern "C" _declspec(dllexport) unsigned char SollwertVB(strSollwert *msgWrite)
    {
    	static int iput=0;
    
    	if (guc_ReadWriteAccessSollwert==KEINZUGRIFF)
    	{
    		guc_ReadWriteAccessSollwert=SCHREIBE; //Semaphore
    
    		if(guc_AnzSollwert<MAXSOLLWERT)
    		{
    			Sollwert[iput].LuefterSpalte=msgWrite->LuefterSpalte;
    			Sollwert[iput].LuefterSpalte=msgWrite->LuefterZeile;
    			Sollwert[iput].LuefterSpalte=msgWrite->Sollwert;
    			iput=addring(iput,MAXSOLLWERT);
    			guc_AnzSollwert++;
    		}
    		else
    		{
    			guc_ReadWriteAccessSollwert=KEINZUGRIFF;
    			return BUFFERVOLL;
    		}
    		guc_ReadWriteAccessSollwert=KEINZUGRIFF;
    	}
    	else
    		return ZUGRIFFVERWEIGERT;
    return guc_AnzSollwert;
    }
    

    Gruß
    spacehelix



  • @1/2: Ich bin mir nicht ganz sicher, ob das stimmt - aber der Konstruktor und Destruktor deines CDataApp-Objektes wäre imho ein möglicher Ansatzpunkt für die Initialisierung/Freigabe.



  • hi,

    ich komm bei dem ganzen Thema irgendwie nicht richtig weiter. Kann vielleicht jemand mal ne kleine dll schreiben mit der Daten zwischen zwei Programmen ausgetauscht werden können? Es würde mir ausreichen wenn es nur eine Variable ist die z.B. einen integer Wert weitergibt.

    Falls dies zuviel aufwand ist, bedanke ich mich trotzdem für eure hilfe.
    Wenn noch jemand eine andere idee hat bin ich sehr dankbar.

    Gruß
    spacehelix



  • so ungefär...

    #include <stdio.h>
    #include <windows.h>
    
    int main(int argc, char* argv[])
    {
    	HANDLE hMap, hMutex;
    	LPVOID lpView;
    
    	if( (hMutex = CreateMutex( NULL, FALSE, "LOL_MUTEX")) == NULL ) {
    		printf("CreateMutex failed (%i)\n", GetLastError());
    		return 0;
    	}
    
    	if( (hMap = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
    		0, 1024, "LOL" )) == NULL ) {
    		printf("CreateFileMapping failed (%i)\n", GetLastError());
    		return 0;
    	}
    
    	if( (lpView = MapViewOfFile( hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0 )) == NULL ) {
    		printf("MapViewOfFile failed (%i)\n", GetLastError());
    		return 0;
    	}
    
    	if( WaitForSingleObject(hMutex, INFINITE) == WAIT_OBJECT_0 )
    	{
    		strcpy( (char*)lpView, "Hallo von Programm1\n");
    		ReleaseMutex(hMutex);
    	}
    
    	while(1);
    
    	UnmapViewOfFile(lpView);
    	CloseHandle(hMap);
    	return 0;
    }
    
    #include <stdio.h>
    #include <windows.h>
    
    int main(int argc, char* argv[])
    {
    	HANDLE hMap, hMutex;
    	LPVOID lpView;
    
    	if( (hMutex = CreateMutex( NULL, FALSE, "LOL_MUTEX")) == NULL ) {
    		printf("CreateMutex failed (%i)\n", GetLastError());
    		return 0;
    	}
    
    	if( (hMap = CreateFileMapping( INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
    		0, 1024, "LOL" )) == NULL ) {
    		printf("CreateFileMapping failed (%i)\n", GetLastError());
    		return 0;
    	}
    
    	if( (lpView = MapViewOfFile( hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0 )) == NULL ) {
    		printf("MapViewOfFile failed (%i)\n", GetLastError());
    		return 0;
    	}
    
    	if( WaitForSingleObject(hMutex, INFINITE) == WAIT_OBJECT_0 )
    	{
    		printf("%s\n", (char*)lpView);
    		ReleaseMutex(hMutex);
    	}
    
    	return 0;
    }
    


  • Super danke,

    das Funktioniert. Ich muss jetzt noch ausprobieren wie man eine structur übergibt und dan kann ich dies verwenden.

    Falls ich noch fragen habe melde ich mich nochmal.

    Gruß
    spacehelix


Anmelden zum Antworten