Forum: PC-Programmierung GUI App und COM Port


von Milbrot (Gast)


Lesenswert?

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

von Blackbird (Gast)


Angehängte Dateien:

Lesenswert?

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

von Milbrot (Gast)


Lesenswert?

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

von Blackbird (Gast)


Lesenswert?

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

von Milbrot (Gast)


Lesenswert?

Danke für die Code-Schnipsel ist schon mal ein guter Start.

Milbrot

von pittbull_nt@yahoo.com (Gast)


Lesenswert?

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

von Milbrot (Gast)


Lesenswert?

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

von Stefan (Gast)


Lesenswert?

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."



Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.