ReadFile mit AnsiString
-
ich verwende für RS232 die Funktion ReadFile. Ich habe für die Verbindung mit RS232 eine Klasse geschrieben mit einer Methode ReceiveData, welche ReadFile enthält. Die Methode sieht so aus:
// Daten empfangen DWORD SerialInterface::ReceiveData(char* Data, int n) { DWORD NumberOfBytesRead; // Anzahl der gelesenen Bytes ReadFile(hCom, // handle des Com-Ports Data, // Adresse des Datenpuffers n, // Anzahl der zu lesenden Bytes &NumberOfBytesRead, // Adresse übergeben 0); // kein overlapped I/O return NumberOfBytesRead; }
Die Daten empfang ich dann so:
const int bufferSize = 100; char buffer[bufferSize] = {0}; rs232.ReceiveData(buffer, bufferSize); //rs232 is mein Objekt der Klasse SerialInterface AnsiString receivedData = AnsiString(buffer);
Kann ich auf das char-Array verzichten und in der Methode ReceiveData mit AnsiString arbeiten? Am besten wäre ja, wenn mir die Methode ReceiveData gleich einen AnsiString mit den Daten zurückgibt. Mich stört auch, dass die Puffergröße hier fest ist. So verschenk ich ja Speicher. Da der empfangene String nicht immer gleich lang ist, hab ich den Puffer entsprechend groß gewählt.
Da mein Programm irgendwann keine Daten mehr empfängt, vermute ich einen Pufferüberlauf.
-
Auf den verschwendeten Speicher würde ich keinen Wert legen, die paar Bytes spielen nun wirklich keine Rolle. Du solltest aber darauf achten, dass die Telegramm nicht immer komplett in einem Rutsch gelesen werden könnten oder mehr als ein Telegramm gleichzeitig ankommt. Daher solltest du die Daten zweistufig verarbeiten, in der ersten Stufe hängst du gelesene Daten an einen Empfangspuffer an, in der zweiten Stufe wertest du die Daten im Empfangspuffer aus und entfernst die bereits bearbeiteten Daten.
class SerialInterface { std::vector<char> ReadBuffer_; public: DWORD receive() { char Buffer[100] = { 0 }; DWORD BytesRead = 0; do { if( !::ReadFile( hCom, Data, 100, &BytesRead, 0 ) ) { // Fehlerbehandlung } if( BytesRead != 0 ) { // gelesene Daten hinten an Vektor anfügen ReadBuffer_.insert( ReadBuffer_.end(), Buffer, Buffer + BytesRead ); } } while( BytesRead != 0 ) } return ReadBuffer_.size(); }
An der Kovertierung zu AnsiString kommst du wohl nicht vorbei. Du könntest zwar einen AnsiString mit entsprechender Länge erzeugen und direkt in den Stringpuffer lesen, aber std::vector ist da wesentlich komfortabler. Wenn performance eine Rolle spielt müsste man sich statt std::vector was anderes überlegen, gerade das Entfernen am Anfang ist nicht so schön. Aber dafür liegen die Daten wenigstens zusammenhängend im Speicher.
-
da hab ich ein paar Fragen:
In der Funktion receive() fehlen doch die beiden Parameter "char* data" und "int n"?
Die Funktion receive(char* data, int n) wird in der Execute-Methode eines Threads aufgerufen. Dort wird das char-Feld dann in einen String umwandelt. Nach Extrahierung der Messwerte aus den String werden diese fortwährend in einer variable gespeichert und auf der Hauptoberfläche angezeigt. Das passiert so lange wie der Thread läuft.void __fastcall Messgeraet::Execute() { while(!Terminated) { const int bufferSize = 100; char buffer[bufferSize] = {0}; rs232.ReceiveData(buffer, bufferSize); AnsiString receivedData = AnsiString(buffer); // dann wird der Messwert hier aus dem String extrahiert und in einer Variable gespeichert Synchronize(UpdateCaption); // Methode UpdateCaption zeigt den Wert der Variablen auf der Hauptoberfläche an Sleep(1000); } }
Also funktuioniert das so irgendwie nicht, da der Code ja auf 2 Dateien aufgeteilt ist. Der Vektor müsste dann im Thread definiert werden oder?
Frage2: die beiden Doppelpunkte vor ReadFile müssen nicht gesetzt werden oder?
Frage3: Was macht die Methode insert beim Vektor? Ich habe einen Vektor bisher immer mit push_back gefüllt.Edit: erzeuge ich da keine Endlosschleife, da ja BytesRead immer den Wert 100 hat?
-
- Die Funktion receive() benötigt keine Parameter mehr, da sie in den internen vektor der Klasse SerialInterface liest, statt in einen der Methode übergebenen Puffer. Nach dem Einlesen musst du den Eingangspuffer überprüfen, ob er gültige Daten enthält und diese dann zur Bearbeitung entfernen:
void __fastcall SerialInterface::Execute() { while( !Terminated ) { // Daten in Puffer lesen receive(); // Telegrammende (0x00) im Puffer suchen vector<char>::iterator pos = find( ReadBuffer_.begin(), ReadBuffer_.end(), 0x00 ); while( pos != ReadBuffer_.end() ) { // 0x00 gefunden, Zeichen bis zur Fundstelle als string interpretiern, // 0x00 selbst mit in String einschließen pos++; string StringData( ReadBuffer_.begin(), pos ); // behandelte Daten aus dem Puffer entfernen ReadBuffer_.erase( readBuffer_.begin(), pos ); // GUI aktualisieren (StringData irgendwie an UpdateCaption übergeben) // nächstes Telegrammende im Puffer suchen pos = find( ReadBuffer_.begin(), ReadBuffer_.end(), 0x00 ); } } }
2. Die vorangestellten :: bedeuten global namespace, hab mir angewöhnt, das bei Win32 Funktionen zu machen. Kann man auch weglassen.
3. insert() fügt Daten in einem rutsch in den Vektor ein, statt die Daten einzeln per push_back() anzuhängen. Ist für den Vektor bequemer und schneller.
4. Nein, es führt zu keiner Endlosschleife. Die Telegramme werden in 100 Byte Happen gelesen, BytesRead enthält nach dem Aufruf von ReadFile die Anzahl der tatsächlich gelesenen Bytes. Wenn es nix zu lesen gibt wird BytesRead 0 und erfüllt die Abbruchbedingung.
-
wollt das grad probieren und hab was festgestellt. Die Methode Execute ist nicht in der Klasse SerialInterface sondern eine extra Threadklasse, die in einer externen cpp steht. Daher hab ich dann dort kein Zugriff auf den Vektor und die Variablen. Vielleicht sollte ich die ganzen hier nicht gezeigten Methoden zur Verbindung mit der Schnittstelle mit in die Threadklasse schreiben? Dann hab ich ja Zugriff darauf. Oder ich lasse es so und schreibe entsprechende GetMethoden um die Variablen zu lesen? Da ich aber mehrere RS232 Geräte zum Auslesen habe, die alle die SerialInterface-Klasse verwenden, müsste ich alle Threaddateien ändern. Wie geh ich vor?
-
hab mal die Methode ReceiveData ausprobiert und mit dem Debugger getestet.
DWORD SerialInterface::ReceiveData() { const int bufferSize = 100; char buffer[bufferSize] = {0}; DWORD bytesRead = 0; do { if(!ReadFile(hCom, buffer, bufferSize, &bytesRead, 0 )) ShowLastError("ReadFile"); //Fehlerausgabe if(bytesRead != 0) readBuffer.insert(readBuffer.end(), buffer, buffer + bytesRead); } while(bytesRead != 0); return readBuffer.size(); }
Das Messgerät, mit dem ich die Daten empfange sendet automatisch nach dem die Verbindung aufgebaut ist Daten. Alle 5 Sekunden kommt ein String. Die Variable bytesRead hat die ganze Zeit den Wert 100. Geh ich mit F8(einzelne Anweisungen) im Debugger durch, bleibe ich die ganze Zeit in der Schleife und der String wird immer wieder in den Vektor geschrieben.
if( !::ReadFile( hCom, Data, 100, &BytesRead, 0 ) )
Ich hab das Data mal mit buffer ersetzt.
string StringData( ReadBuffer_.begin(), pos );
Ist das korrekt so? Welche Parameter erwartet diese Funktion?
-
Laut meiner Doku trägt ReadFile in die Variable, die als 4. Parameter übergeben wird, zu Beginn 0 ein und aktualisiert sie später mit der Anzahl der tatsächlich gelesenen Bytes. Wenn da bei dir immer 100 drinsteht, dann wird die Funktion wohl immer 100 Bytes lesen, kannst sie dir ja mal in der Konsole ausgeben lassen, um zu gucken, ob da immer was neues ankommt.
Das Data war ein Flüchtigkeitsfehler, hab den Code nur so runtergeschrieben, ohne das zu kompilieren. Vielleicht debuggst du zu langsam durch den Code, denn wenn du mehr als 5s brauchst stehen ja schon wieder neue Daten an, die von ReadFile gelesen werden.
Das, was du als Funktion bezeichnest, ist die Definition einer lokalen String Variable, die als Konstruktionsargumente zwei Iteratoren erhält.