Hallo,
ich suche einen Sample Code für ein GUI App das über einen Thread Daten
von der rs232 Schnittstelle empfangen und senden kann und das auch noch
in C für Windows.
Ich habe hier schon einiges gefunden aber eben nur Anwendungen für
Konsolen Programme ohne Thread (Samples von Blackbird z.B.).
In der Vergangenheit scheinen sich ja auch andere damit beschäftigt
zu habe ev. hat jemand so ein App schon fertig gestellt. Das würde mir
sehr helfen.
Gruß Milbrot
Die "Samples" habe ich aus meinen GUI-Applikationen "extrahiert", damit
sie übersichtlich bleiben. Denn Windows GUI-Apps sind etwas
umfangreicher als eine Seite (z.B. 50 Zeilen). Und das Aussehen und die
Funktion bestimmen auch den Code - ein allgemeingültiges Template gibt
es nicht. MS hat das mit der MFC versucht. Ist aber nicht so richtig zum
Standard geworden (zu recht, wie ich finde).
Wenn die seriellen Daten ASCII-Zeichen sind, kann man die empfangenen
Zeichen gut in einem Edit-Fenster darstellen. Im gleichen oder einem
zweiten Edit-Fenster kann man seine Daten schreiben, die dann per
<ENTER> oder Knopfdruck gesendet werden.
Dazu gehören noch ein paar Menüs, die das Speichern, Drucken, Beenden,
etc. zur Wahl stellen. Öffnen, Schließen und Eigenschaften der seriellen
Schnittstelle müssen auch irgendwo, z.B. via Menü eingestellt werden
können. Und eine Statusleiste oder ein Status-Feld sollte über den
jeweiligen Zustand der seriellen Schnittstelle Auskunft geben. Üblich
ist auch ein Info-Fenster und eine kleine Hilfe.
Das war jetzt nur die Oberfläche, die Thread-Aufteilung im Hintergrund
ist ein eigenes Thema. Ebenso die Frage, ob und wo das Programm sich was
merken soll.
Das alles nicht in einem File, sondern schön funktionell getrennt. Und
Resourcen (z.B.: Icons, Menüs, etc.) gibt es auch noch.
Ach, den Installer/Deleter hab' ich noch vergessen. Gehört zwar nicht
unmittelbar zum Programm, könnte aber auch nützlich sein.
Das Ganze nennt sich dann Terminal und die gibt es massenweise.
Oder es soll ein an ein Projekt (z.B.: µC) angepaßtes Programm werden
mit einigen Sonderfunktion, die kein Terminalprogramm bietet?
Dafür gibt es aber kein "Template".
Wenn Du also das "Design" Deiner GUI hast, dann kannst Du auch die
einzelnen Teile "bauen".
Im Anhang ein kleines Windowsprogramm, hier ohne Menü. Hat eine Editbox,
einen Timer und eine Statusleiste.
Blackbird
Das Programm ist ja eigentlich schon fertig verwendet aber derzeit eine
Delphi DLL die für das senden und empfangen zuständig ist. Bei dem
Empfänger handelt es sich um einen einem Dimmer mit AVR. Irgendwie ist
mir das aber zu unflexibel und daher würde ich diesen Teil lieber ganz
in mein Programm integrieren.
Verwundert hat mich das ich bei der Suche nur auf Fragen zu dem Thema
gestoßen bin aber eigentlich kein fertiges Projekt gefunden habe wo
jemand so etwas schon mal realisiert hat. Projekte und Code für C++ gibt
es dagegen anscheinend ausreichend.
Wie dem auch sei beim durchsehen der gefundenen Code Schnipsel habe ich
mir nun ein Konzept überlegt wie ich das am besten in mein Programm
einbauen könnte. Mal sehen ob es auch so laufen wird wie gedacht.
Milbrot
Na wenn das Programm schon fertig ist .... ;)
Den Empfang ("ReadFile") legt man in einen eigenen Thread, z.B. so:
(hier fehlt noch die Einstellung aller COM-Parameter!)
HANDLE hCom = INVALID_HANDLE_VALUE;
//--------------------------------------------------------------------
// COMThread
//
// erstellt eine Queue, setzt danach den Event ("Queue ist ready").
// Schließt eine geöffneten COM-Port und öffnet den neuen (Nummer ist
// in der Strukur, auf die lpParam zeigt).
// liest die queue aus.
//
// Thread Funktion
// Liest eventgesteuert den Status von CTS, DSR, ... ein und
// sendet in dwEvtMask alle zum Event gehörenden Ereignisse
// zum Monitor-Thread.
//
//--------------------------------------------------------------------
DWORD WINAPI ComThread (LPVOID lpParam)
{
long lTime;
volatile PPARAMS pparams; // struct-Instanz
pparams = (PPARAMS) lpParam; // Zuweisung zu lpParam
HWND hWndEdit = pparams->hWndEdit; // Parameter auslesen
int iPortNum = pparams->iComPortNum; // ...
DWORD dwMonitorThreadId = pparams->dwMonitorThreadId; // ...
char szMsg[80];
char szPort[15];
static OVERLAPPED o;
// Maske für SetCommMask, die bestimmt, welche Ereignisse auftreten
können
DWORD dwEvtMaskIn = EV_CTS | EV_DSR | EV_BREAK | EV_RING | EV_RXCHAR |
EV_RLSD | EV_ERR | EV_RXFLAG | EV_TXEMPTY;
DWORD dwEvtMask = 0; // Maske, in die WaitCommEvent aktuelle Werte
schreibt
BOOL bRet;
CloseHandle (hCom); // "alten" COM-Port schließen
// Com Port öffnen
wsprintf (szPort, "COM%d", iPortNum);
hCom = CreateFile (szPort,
GENERIC_READ | GENERIC_WRITE,
0, // exclusive access
NULL, // no security attributes
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (hCom == INVALID_HANDLE_VALUE)
{
wsprintf (szMsg, "\r\nComThread: COM%d kann nicht geöffnet werden",
iPortNum);
AddNewText (hWndEdit, szMsg);
pparams->ThreadStoppen = TRUE;
}
else
{
if (!SetCommMask (hCom, dwEvtMaskIn))
AddNewText (hWndEdit, "\r\nComThread: SetCommMask
fehlgeschlagen");
// Create an event object for use in WaitCommEvent.
o.hEvent = CreateEvent (NULL, // no security attributes
FALSE, // auto reset event
FALSE, // not signaled
NULL); // no name
}
DWORD dwProcessId; // own process identifier
HANDLE hProcess; // handle to the own process
DWORD dwDesiredAccess = PROCESS_SET_INFORMATION; // access flag
BOOL bInheritHandle = TRUE; // handle inheritance flag
BOOL bReturn; // If the SetPriorityClass succeeds, then
nonzero
//WORD dwPriorityClass = REALTIME_PRIORITY_CLASS; // priority class
value
DWORD dwPriorityClass = HIGH_PRIORITY_CLASS;
// Setzt die Priorität "RealTime" für das Programm
dwProcessId = GetCurrentProcessId ();
hProcess = OpenProcess (dwDesiredAccess, bInheritHandle, dwProcessId);
bReturn = SetPriorityClass (hProcess, dwPriorityClass);
while (!pparams->ThreadStoppen) // solange weitermachen bis TRUE
{
WaitCommEvent (hCom, &dwEvtMask, &o); // EventMask "scharf machen"
// kommt der Event, ist auch die dwEvtMask geladen und es kann
weitergehen
if (WAIT_OBJECT_0 == WaitForSingleObject (o.hEvent, INFINITE)) //
warten bis Event
{
lTime = queryPrecisionTime (); // Differenzzeit zum letzten
Aufruf holen
// Message senden. In dwEvtMask können mehrere Ereignisse gesetzt
sein
bRet = PostThreadMessage (dwMonitorThreadId, (QS_ALLPOSTMESSAGE +
MSG_TIME),
(WPARAM)dwEvtMask , (LPARAM)lTime);
} // end of: if (WAIT_OBJECT_0 == ...
} // end of: while (...
CloseHandle (o.hEvent);
CloseHandle (hCom); // COM-Port schließen
return (0);
}
Dieser Thread wird aufgerufen und beendet, wenn man in der
Callback-Routine der WinMain folgenden Eintrag hat:
(hier sind Menü-Punkte, die angeklickt werden, die "Auslöser")
//######################################################################
case WM_COMMAND:
switch (LOWORD(wParam))
{
//######################################################################
case IDM_COMPORT1:
// Erstes Statusfeld beschreiben:
WriteStatusBarField (sBar, 0, "COM1 geöffnet");
CheckMenuItem (hMenu, IDM_COMPORT2, MF_UNCHECKED);
CheckMenuItem (hMenu, IDM_COMPORT1, MF_CHECKED);
// "alten" Thread stoppen
p.ThreadStoppen = TRUE;
CloseHandle (hComThread); // "alten" Thread stoppen
p.iComPortNum = 1;
// ComThread: Starten und Parameterstruct übergeben
// ThreadID wird nicht benötigt
p.ThreadStoppen = FALSE;
hComThread = CreateThread ( // Handle des Threads
NULL, // no security attributes
0, // use default stack size
ComThread, // thread function
&p, // argument to thread function
0, // use default creation flags
&dwComThreadId); // returns the thread identifier
if (hComThread == NULL)
AddNewText (hEdit, "\r\nCreateThread (ComThread)
fehlgeschlagen");
break;
//######################################################################
case IDM_COMPORT2:
// Erstes Statusfeld beschreiben:
WriteStatusBarField (sBar, 0, "COM2 geöffnet");
CheckMenuItem (hMenu, IDM_COMPORT1, MF_UNCHECKED);
CheckMenuItem (hMenu, IDM_COMPORT2, MF_CHECKED);
// "alten" Thread stoppen
p.ThreadStoppen = TRUE;
CloseHandle (hComThread); // "alten" Thread stoppen
p.iComPortNum = 2;
// ComThread: Starten und Parameterstruct übergeben
// ThreadID wird nicht benötigt
p.ThreadStoppen = FALSE;
hComThread = CreateThread ( // Handle des Threads
NULL, // no security attributes
0, // use default stack size
ComThread, // thread function
&p, // argument to thread function
0, // use default creation flags
&dwComThreadId); // returns the thread identifier
if (hComThread == NULL)
AddNewText (hEdit, "\r\nCreateThread (ComThread)
fehlgeschlagen");
break;
Da die Message-Queue der WinMain etwas langsam ist und dort auch alle
anderen Mesages auflaufen, habe ich die alle Events des ConThreads zu
einem "MonitorThread" genannten Thread geschickt. Dort kann je nach
Event dann das ReadFile stehen, die empfangenen Daten Komplettiert und
per SendMessage an die Callback-Routine des WinMain gesendet werden.
Oder man schreibt gleich in das Editfenster (habe ich hier mit
"AddNewText" gemacht).
Der Monitorthread wird im WM_CREATE gestartet:
...
// MonitorThread:
// Erstellt den Event für die Thread Queue
hEvent = CreateEvent (NULL, false, true, QEVENT);
// Thread starten und Editbox-Handle hEdit übergeben
// uMonThreadID für ComThread
hMonThread = CreateThread ( // Handle des Threads
NULL, // no security attributes
0, // use default stack size
MonitorThread, // thread function
&p, // argument to thread function
0, // use default creation flags
&dwMonThreadId); // returns the thread identifier
if (hMonThread == NULL)
AddNewText (hEdit, "\r\nCreateThread (MonitorThread)
fehlgeschlagen");
p.dwMonitorThreadId = dwMonThreadId; // merken, für ComThread
// wait for queue completition
dwState = WaitForSingleObject (hEvent, 100);
CloseHandle (hEvent);
...
Schreiben mit WriteFile kann man von überall her, nur sollte man vorher
testen, ob der COM-Port auch offen ist.
Das ist nuer eine von vielen Varianten, wie man über COM-Ports mit C
(und API) unter Windows(-GUI) schreiben und lesen kann.
Die Code-Schnipsel stammen aus einem Projekt, das nur die Events am
COM-Port erkennt und loggt. Wie ein langsamer Datenlogger oder Logic
Analyzer. Deshalb sind die Baudrate und die Timeouts hier nicht nötig.
Es sind auch ein paar Dinge drin (z.B.: Zeitstempel) die für eine
Terminalfunktion nicht gebraucht werden.
Fehlerbehandlung sollte auch sein. Das Schreiben kann auch in einem
eigenen Thread erfolgen, da man ja nie genau weiß, wann denn die Daten
nun genau weg sind. Und eine Message-Queue aa einem solchen
"WriteThread" dient dann als Puffer, wenn man viel in kurzer Zeit senden
muß.
Blackbird
hi,
such mal nach (mit z.b. google) 'mttty' (mit 3 't' - multi threaded tty)
das ist ein terminal-programm (in c geschrieben, source code ist dabei)
von m$ gweissermassen als beispiel, wie man die rs232 einer windows-box
ansteuert und dabei 'overlapped i/o' benutzt.
ach, hier ist es:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp
Ein fertiges Programm sehr gut.
Was spricht eigentlich dagegen einen Thread nicht mit CreateThread
zu erzeugen sondern _beginthread zu verwenden? Alle Samples die ich
gesehen habe verwenden CreateThread.
Milbrot
Frag einfach Bill ;-)
http://msdn2.microsoft.com/en-us/library/7t9ha0zh(VS.80).aspx
"_beginthread and _beginthreadex are similar to the CreateThread
function in the Win32 API but has these differences:
* _beginthread and _beginthreadex let you pass multiple arguments to
the thread.
* They initialize certain C run-time library variables. This is
important only if you use the C run-time library in your threads.
* CreateThread helps provide control over security attributes. You can
use this function to start a thread in a suspended state."
Eine eigene Frage?
Dieser Thread ist über 20 Jahre alt.
Stell sie in einem eigenen Thread – dort lesen mehr Leute mit, und sie wird öfter und schneller beantwortet.