2013-02-19 21 views
5

Przykro mi, to nie jest pytanie, ale więcej, aby pomóc ludziom mającym problemy z tymi konkretnymi rzeczami. Problem, nad którym pracuję, wymaga użycia Serial I/O, ale działa głównie w Windows CE 6.0. Jednakże niedawno zapytano mnie, czy można również uruchomić aplikację pod Windows, więc postanowiłem rozwiązać ten problem. Spędziłem sporo czasu, rozglądając się, aby sprawdzić, czy ktoś ma odpowiedzi, których szukałem, a wszystko to dało mi wiele dezinformacji i rzeczy, które w niektórych przypadkach były w zasadzie złe. Więc rozwiązawszy ten problem, myślałem, że podzielę się moimi odkryciami ze wszystkimi, aby każdy, kto napotkał te trudności, miałby odpowiedzi.Serial I/O Overlapped/Non-Overlapped z Windows/Windows CE

W systemie Windows CE obsługiwane są OVERLAPPED I/O NOT. Oznacza to, że komunikacja dwukierunkowa przez port szeregowy może być dość kłopotliwa. Głównym problemem jest to, że gdy czekasz na dane z portu szeregowego, nie możesz wysyłać danych, ponieważ spowoduje to zablokowanie głównego wątku do czasu zakończenia operacji odczytu lub przekroczenia czasu oczekiwania (w zależności od tego, czy zostały ustawione limity czasu oczekiwania)

Podobnie jak większość ludzi wykonujących szeregowe operacje wejścia/wyjścia, miałem szeregowy wątek czytnika ustawiony do odczytu portu szeregowego, który używał WaitCommEvent() z maską EV_RXCHAR do oczekiwania na dane szeregowe. Teraz pojawia się problem z Windows i Windows CE.

Jeśli mam prosty gwint czytnika w ten sposób, jako przykład: -

UINT SimpleReaderThread(LPVOID thParam) 
{ 
    DWORD eMask; 
    WaitCommEvent(thParam, &eMask, NULL); 
    MessageBox(NULL, TEXT("Thread Exited"), TEXT("Hello"), MB_OK); 
} 

Oczywiście w powyższym przykładzie, nie czytam dane z portu szeregowego lub cokolwiek, a ja zakładając że thParam zawiera otwarty uchwyt do portu komunikacyjnego itp. Teraz problem jest pod Windows, gdy twój wątek wykonuje i uderza w WaitCommEvent(), twój wątek czytnika przejdzie w stan uśpienia czekając na dane portu szeregowego. Okay, w porządku i tak powinno być, ale ... jak zakończyć ten wątek i uzyskać komunikat MessageBox()? Jak się okazuje, nie jest to takie proste i jest zasadniczą różnicą między Windows CE i Windows w sposobie, w jaki wykonuje swoje Serial I/O.

W Windows CE można zrobić kilka rzeczy, aby przewinąć WaitCommEvent(), takie jak SetCommMask (COMMPORT_HANDLE, 0) lub nawet CloseHandle (COMMPORT_HANDLE). Umożliwi to prawidłowe zakończenie wątku i zwolnienie portu szeregowego w celu ponownego wysłania danych. Jednak żadna z tych rzeczy nie zadziała pod Windows i oba sprawią, że wątek, z którego je wywołasz, będzie spał czekając na zakończenie WaitCommEvent(). Jak zakończyć działanie WaitCommEvent() w systemie Windows? Zwykle używałbyś OVERLAPPED I/O i blokowanie wątków nie byłoby problemem, ale ponieważ rozwiązanie musi być kompatybilne również z Windows CE, OVERLAPPED I/O nie jest opcją. Jest jedna rzecz, którą możesz zrobić w systemie Windows, aby zakończyć działanie WaitCommEvent() i to jest wywołanie funkcji CancelSynchronousIo(), a to zakończy działanie WaitCommEvent(), ale pamiętaj, że może to być zależne od urządzenia. Głównym problemem związanym z funkcją CancelSynchronousIo() jest to, że nie jest ona obsługiwana przez system Windows CE, więc nie masz szczęścia, że ​​używasz tego problemu!

Jak to zrobić? Faktem jest, że aby rozwiązać ten problem, po prostu nie można użyć funkcji WaitCommEvent(), ponieważ nie ma sposobu na zakończenie tej funkcji w systemie Windows obsługiwanym przez system Windows CE. To pozostawia Ci ReadFile(), który znowu będzie blokował podczas czytania NON OVERLAPPED I/O i ten WILL pracuje z Comm Timeouts.

Używanie ReadFile() i struktury COMMTIMEOUTS oznacza, że ​​będziesz musiał mieć ciasną pętlę czekając na dane szeregowe, ale jeśli nie otrzymujesz dużej ilości danych szeregowych, nie powinno to stanowić problemu. Również zdarzenie kończące pętlę z małym limitem czasu zapewni również, że zasoby zostaną przekazane do systemu, a procesor nie zostanie obciążony przy 100% obciążeniu.Poniżej znajduje się rozwiązanie, które wymyśliłem i doceniłbym pewne opinie, jeśli uważasz, że można to poprawić.

typedef struct 
{ 
    UINT8 sync; 
    UINT8 op 
    UINT8 dev; 
    UINT8 node; 
    UINT8 data; 
    UINT8 csum; 
} COMMDAT; 

COMSTAT cs = {0}; 
DWORD byte_count; 
COMMDAT cd; 

ZeroMemory(&cd, sizeof(COMMDAT)); 
bool recv = false; 
do 
{ 
    ClearCommError(comm_handle, 0, &cs); 
    if (cs.cbInQue == sizeof(COMMDAT)) 
    { 
     ReadFile(comm_handle, &cd, sizeof(COMMDAT), &byte_count, NULL); 
     recv = true; 
    } 
} while ((WaitForSingleObject(event_handle, 2) != WAIT_OBJECT_0) && !recv); 
ThreadExit(recv ? cd.data : 0xFF); 

Tak, aby zakończyć wątek po prostu zasygnalizować zdarzenia w event_handle i które pozwalają zamknąć wątek prawidłowo i oczyścić zasobów i działa poprawnie na Windows i Windows CE.

Mam nadzieję, że pomaga wszystkim, kogo widziałem, mieć problemy z tym problemem.

+0

Dlaczego nie próbują dynamicznie ładować CancelSynchronousIo() w czasie wykonywania, a jeśli jesteś po prostu zadzwoń pod CE pusty niedopałek? Czy korzystanie z biblioteki zachodziło na siebie, jeśli jest na pulpicie, ale nie nakładało się na Windows CE? Wydaje się, że sposoby rozwiązania tego problemu są łagodniejsze. – ctacke

+0

Nie można załadować dymnie CancelSynchronousIo(), jest to funkcja API systemu Windows, która nie jest częścią Windows CE. Chodzi o to, aby mieć rozwiązanie działające na Windows CE i Windows bez modyfikacji. Nie ma sensu posiadanie wersji OVERLAPPED dla Windows i oddzielnej wersji Non OVERLAPPED dla Windows CE, tak jak dobrze, wystarczy # ifdef/# endif i warunkowo skompiluj swój kod dla platformy docelowej. –

+0

'SetCommMask' zawsze pracował dla mnie, aby" WaitCommEvent "zakończył się wcześniej. Jednak w moim kodzie zawsze wyzwalało zakończenie trwającego w toku 'WaitCommEvent'. Czy mówisz, że nakładające się 'WaitCommEvent' ma zostać zakończone przez' SetCommMask', ale synchroniczne nie? –

Odpowiedz

5

Ponieważ uważam, że w moim komentarzu było nieporozumienie, tutaj jest więcej szczegółów na temat dwóch możliwych rozwiązań, które nie używają ciasnej pętli. Zauważ, że używają one określenia czasu wykonywania, a zatem są grzywny w obu systemach (mimo że i tak musisz kompilować oddzielnie dla każdego celu), a ponieważ żaden nie używa #ifdef, mniej prawdopodobne jest, że w końcu złamie kompilator po jednej lub drugiej stronie, bez natychmiastowego zauważenia.

Po pierwsze, można dynamicznie załadować CancelSynchonousIo i używać go, gdy jest obecny w systemie operacyjnym. Nawet opcjonalnie robiąc coś zamiast Anuluj dla CE (jak być może zamykanie klamki?);

typedef BOOL (WINAPI *CancelIo)(HANDLE hThread); 

HANDLE hPort; 

BOOL CancelStub(HANDLE h) 
{ 
    // stub for WinCE 
    CloseHandle(hPort); 
} 

void IoWithCancel() 
{ 
    CancelIo cancelFcn; 

    cancelFcn = (CancelIo)GetProcAddress(
     GetModuleHandle(_T("kernel32.dll")), 
     _T("CancelSynchronousIo")); 

    // if for some reason you want something to happen in CE 
    if(cancelFcn == NULL) 
    { 
     cancelFcn = (CancelIo)CancelStub; 
    } 

    hPort = CreateFile(/* blah, blah */); 

    // do my I/O 

    if(cancelFcn != NULL) 
    { 
     cancelFcn(hPort); 
    } 
} 

Inną opcją, która zajmuje nieco więcej pracy, jak masz zamiar prawdopodobnie mają różne modele gwintowania (choć jeśli używasz C++, byłoby doskonałym przypadku odrębnych klas opartych na platformie anyway) byłoby określenie i wykorzystanie platformy nakłada na pulpicie:

HANDLE hPort; 

void IoWithOverlapped() 
{ 
    DWORD overlapped = 0; 
    OSVERSIONINFO version; 

    GetVersionEx(&version); 
    version.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); 
    if((version.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) 
     || (version.dwPlatformId == VER_PLATFORM_WIN32_NT)) 
    { 
     overlapped = FILE_FLAG_OVERLAPPED; 
    } 
    else 
    { 
     // create a receive thread 
    } 

    hPort = CreateFile(
     _T("COM1:"), 
     GENERIC_READ | GENERIC_WRITE, 
     FILE_SHARE_READ | FILE_SHARE_WRITE, 
     NULL, 
     OPEN_EXISTING, 
     overlapped, 
     NULL); 
} 
+0

OK, podaję twój punkt dotyczący ustalania istnienia CancelSynchronousIo() i używania go, jeśli jest dostępny! Dobrze zrobione, nigdy tak naprawdę nie przyszło mi do głowy ... Po prostu poszedłem do kodu, który był taki sam dla obu platform. Cholera, mój oldschoolowy bajt oszczędza! –

+0

'cancelFcn (hPort);' jest błędne; ta funkcja przejmuje uchwyt wątku, aby anulować synchroniczne wejście/wyjście. Albo podaj ten uchwyt (tak jak zwrócił go na przykład 'CreateThread'), albo użyj' CancelIoEx', który zajmuje uchwyt portu. –