线程通信

Interprocess Communication

There are many cases where two Windows CE processes need to communicate. The walls between processes that protect processes from one another prevent casual exchanging of data. The memory space of one process isn't exposed to another process. Handles to files or other objects can't be passed from one process to another. Windows CE doesn't support the DuplicateHandle function available under Windows NT, which allows one process to open a handle used by another process. Nor, as I mentioned before, does Windows CE support handle inheritance. Some of the other more common methods of interprocess communication, such as named pipes, are also not supported under Windows CE. However, you can choose from plenty of ways to enable two or more processes to exchange data.

Finding Other Processes

Before you can communicate with another process, you have to determine whether it's running on the system. Strategies for finding whether another process is running depend mainly on whether you have control of the other process. If the process to be found is a third-party application in which you have no control over the design of the other process, the best method might be to use FindWindow to locate the other process's main window. FindWindow can search either by window class or by window title. You can also enumerate the top-level windows in the system using EnumWindows. You can also use the ToolHelp debugging functions to enumerate the processes running, but this works only when the ToolHelp DLL is loaded on the system and unfortunately, it generally isn't included, by default, on most systems.

If you're writing both processes, however, it's much easier to enumerate them. In this case, the best methods include using the tools you'll later use in one process to communicate with the other process, such as named mutexes, events, or memory-mapped objects. When you create one of these objects, you can determine whether you're the first to create the object or you're simply opening another object by calling GetLastError after another call created the object. And the simplest method might be the best; call FindWindow and send a WM_USER message to the main window of the other process.

WM_COPYDATA

Once you've found your target process, the talking can begin. If you're staying at the window level, a simple method of communicating is to send a WM_COPYDATA message. WM_COPYDATA is unique in that it's designed to send blocks of data from one process to another. You can't use a standard, user-defined message to pass pointers to data from one process to another because a pointer isn't valid across processes. WM_COPYDATA gets around this problem by having the system translate the pointer to a block of data from one process's address space to another's. The recipient process is required to copy the data immediately into its own memory space, but this message does provide a quick and dirty method of sending blocks of data from one process to another.

Named memory-mapped objects

The problem with WM_COPYDATA is that it can be used only to copy fixed blocks of data at a specific time. Using a named memory-mapped object, two processes can allocate a shared block of memory that's equally accessible to both processes at the same time. You should use named memory-mapped objects so that the system can maintain a proper use count on the object. This procedure prevents one process from freeing the block when it terminates while the other process is still using the block.

Of course, this level of interaction comes with a price. You need some synchronization between the processes when they're reading and writing data in the shared memory block. The use of named mutexes and named events allows processes to coordinate their actions. Using these synchronization objects requires the use of secondary threads so that the message loop can be serviced, but this isn't an exceptional burden.

I described how to create memory-mapped objects in Chapter 6. The example program that shortly follows uses memory-mapped objects and synchronization objects to coordinate access to the shared block of memory.

Communicating with files and databases

A more basic method of interprocess communication is the use of files or a custom database. These methods provide a robust, if slower, communication path. Slow is relative. Files and databases in the Windows CE object store are slow in the sense that the system calls to access these objects must find the data in the object store, uncompress the data, and deliver it to the process. However, since the object store is based in RAM, you see none of the extreme slowness of a mechanical hard disk that you'd see under Windows NT or Windows 98.

The XTalk Example Program

The following example program, XTalk, uses events, mutexes, and a shared memory-mapped block of memory to communicate among different copies of itself. The example demonstrates the rather common problem of one-to-many communication. In this case, the XTalk window has an edit box with a Send button next to it. When a user taps the Send button, the text in the edit box is communicated to every copy of XTalk running on the system. Each copy of XTalk receives the text from the sending copy and places it in a list box also in the XTalk window. Figure 8-1 shows two XTalk programs communicating.

xtalk

Figure 8-1. The desktop showing two XTalk windows.

To perform this feat of communication, XTalk uses a named memory-mapped object as a transfer buffer, a mutex to coordinate access to the buffer, and two event objects to indicate the start and end of communication. A third event is used to tell the sender thread to read the text from the edit control and write the contents to the shared memory block. Figure 8-2 shows the source code for XTalk.

XTalk.rc

//======================================================================
// Resource file
//

// Written for the book Programming Windows CE
// Copyright (C) 1998 Douglas Boling
//======================================================================
#include "windows.h"
#include "xtalk.h"                        // Program-specific stuff

//----------------------------------------------------------------------
// Icons and bitmaps
//
ID_ICON ICON   "xtalk.ico"                // Program icon

//----------------------------------------------------------------------
xtalk DIALOG discardable 10, 10, 120, 60
STYLE  WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | 
       DS_CENTER | DS_MODALFRAME 
CAPTION "XTalk"
CLASS "xtalk"
BEGIN
    LTEXT "&Text"                     -1,   2,  10,  20,  12
    EDITTEXT                 IDD_OUTTEXT,  25,  10,  58,  12, 
                                             WS_TABSTOP | ES_AUTOHSCROLL
    PUSHBUTTON "&Send",     IDD_SENDTEXT,  88,  10,  30,  12, WS_TABSTOP

    LISTBOX                   IDD_INTEXT,   2,  25, 116,  40, 
                                             WS_TABSTOP | WS_VSCROLL
END

XTalk.h

//======================================================================
// Header file

//
// Written for the book Programming Windows CE
// Copyright (C) 1998 Douglas Boling
//======================================================================
// Returns number of elements
#define dim(x) (sizeof(x) / sizeof(x[0])) 
//----------------------------------------------------------------------
// Generic defines and data types
//
struct decodeUINT {                            // Structure associates
    UINT Code;                                 // messages 
                                               // with a function.
    LRESULT (*Fxn)(HWND, UINT, WPARAM, LPARAM);
}; 
struct decodeCMD {                             // Structure associates
    UINT Code;                                 // menu IDs with a 
    LRESULT (*Fxn)(HWND, WORD, HWND, WORD);    // function.
};

//----------------------------------------------------------------------
// Generic defines used by application
#define  ID_ICON             1   

#define  IDD_INTEXT          10                // Control IDs
#define  IDD_SENDTEXT        11
#define  IDD_OUTTEXT         12

#define  MMBUFFSIZE          1024              // Size of shared buffer
#define  TEXTSIZE            256

// Interprocess communication structure mapped in shared memory
typedef struct {
    int nAppCnt;
    int nReadCnt;
    TCHAR szText[TEXTSIZE];
} SHAREBUFF;
typedef SHAREBUFF *PSHAREBUFF;

//----------------------------------------------------------------------
// Function prototypes
//
int InitApp (HINSTANCE);
HWND InitInstance (HINSTANCE, LPWSTR, int);
int TermInstance (HINSTANCE, int);

// Window procedures
LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
// Message handlers
LRESULT DoCommandMain (HWND, UINT, WPARAM, LPARAM);
LRESULT DoDestroyMain (HWND, UINT, WPARAM, LPARAM);

// Command functions
LPARAM DoMainCommandSend (HWND, WORD, HWND, WORD);
LPARAM DoMainCommandExit (HWND, WORD, HWND, WORD);

// Thread functions
int SenderThread (PVOID pArg);
int ReaderThread (PVOID pArg);

XTalk.c

//======================================================================

// XTalk - A simple application for Windows CE
//
// Written for the book Programming Windows CE
// Copyright (C) 1998 Douglas Boling
//======================================================================
#include 
 
 
  
                   // For all that Windows stuff
#include 
  
  
   
                   // Command bar includes
#include "xtalk.h"                   // Program-specific stuff

//----------------------------------------------------------------------
// Global data
//
const TCHAR szAppName[] = TEXT ("xtalk");
HINSTANCE hInst;                     // Program instance handle

HANDLE g_hMMObj = 0;                 // Memory-mapped object
PSHAREBUFF g_pBuff = 0;              // Pointer to mm object
HANDLE g_hmWriteOkay = 0;            // Write mutex
HANDLE g_hSendEvent = 0;             // Local send event
HANDLE g_hReadEvent = 0;             // Shared read data event
HANDLE g_hReadDoneEvent = 0;         // Shared data read event

// Message dispatch table for MainWindowProc
const struct decodeUINT MainMessages[] = {
    WM_COMMAND, DoCommandMain,
    WM_DESTROY, DoDestroyMain,
};
// Command Message dispatch for MainWindowProc
const struct decodeCMD MainCommandItems[] = {
    IDOK, DoMainCommandExit,
    IDCANCEL, DoMainCommandExit,
    IDD_SENDTEXT, DoMainCommandSend,
};
//======================================================================
// Program entry point
//
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPWSTR lpCmdLine, int nCmdShow) {
    MSG msg;
    int rc = 0;
    HWND hwndMain;

    // Initialize application.
    rc = InitApp (hInstance);
    if (rc) return rc;

    // Initialize this instance.
    hwndMain = InitInstance (hInstance, lpCmdLine, nCmdShow);
    if (hwndMain == 0) 
        return TermInstance (hInstance, 0x10);

    // Application message loop
    while (GetMessage (&msg, NULL, 0, 0)) {
        if ((hwndMain == 0) || !IsDialogMessage (hwndMain, &msg)) {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
    }
    // Instance cleanup
    return TermInstance (hInstance, msg.wParam);
}
//----------------------------------------------------------------------
// InitApp - Application initialization
//
int InitApp (HINSTANCE hInstance) {
    WNDCLASS wc;

    // Register application main window class.
    wc.style = 0;                             // Window style
    wc.lpfnWndProc = MainWndProc;             // Callback function
    wc.cbClsExtra = 0;                        // Extra class data
    wc.cbWndExtra = DLGWINDOWEXTRA;           // Extra window data
    wc.hInstance = hInstance;                 // Owner handle
    wc.hIcon = NULL,                          // Application icon
    wc.hCursor = NULL;                        // Default cursor
    wc.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
    wc.lpszMenuName =  NULL;                  // Menu name
    wc.lpszClassName = szAppName;             // Window class name

    if (RegisterClass (&wc) == 0) return 1;
    return 0;
}
//----------------------------------------------------------------------
// InitInstance - Instance initialization
//
HWND InitInstance (HINSTANCE hInstance, LPWSTR lpCmdLine, int nCmdShow){
    HWND hWnd;
    HANDLE hThread;
    INT rc;
    BOOL fFirstApp = TRUE;

    // Save program instance handle in global variable.
    hInst = hInstance;

    // Create mutex used to share memory-mapped structure.
    g_hmWriteOkay = CreateMutex (NULL, TRUE, TEXT ("XTALKWRT"));
    rc = GetLastError();
    if (rc == ERROR_ALREADY_EXISTS)
        fFirstApp = FALSE;
    else if (rc) return 0;

    // Wait here for ownership to ensure the initialization is done.
    // This is necessary since CreateMutex doesn't wait.
    rc = WaitForSingleObject (g_hmWriteOkay, 2000);
    if (rc != WAIT_OBJECT_0)
        return 0;

    // Create a file-mapping object.
    g_hMMObj = CreateFileMapping ((HANDLE)-1, NULL, PAGE_READWRITE, 0, 
                                  MMBUFFSIZE, TEXT ("XTALKBLK"));
    if (g_hMMObj == 0) return 0;

    // Map into memory the file-mapping object.
    g_pBuff = (PSHAREBUFF)MapViewOfFile (g_hMMObj, FILE_MAP_WRITE, 
                                         0, 0, 0);
    if (!g_pBuff) 
        CloseHandle (g_hMMObj);

    // Initialize structure if first application started.
    if (fFirstApp) 
        memset (g_pBuff, 0, sizeof (SHAREBUFF));
    // Increment app running count. Interlock not needed due to mutex.
    g_pBuff->nAppCnt++;   

    // Release the mutex.  We need to release the mutext twice 
    // if we owned it when we entered the wait above.
    ReleaseMutex (g_hmWriteOkay);
    if (fFirstApp) 
        ReleaseMutex (g_hmWriteOkay);

    // Now create events for read and send notification.
    g_hSendEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
    g_hReadEvent = CreateEvent (NULL, TRUE, FALSE, TEXT ("XTALKREAD"));
    g_hReadDoneEvent = CreateEvent (NULL, FALSE, FALSE, 
                                    TEXT ("XTALKDONE"));
    if (!g_hReadEvent || !g_hSendEvent || !g_hReadDoneEvent)
        return 0;

    // Create main window.
    hWnd = CreateDialog (hInst, szAppName, NULL, NULL);
    rc = GetLastError();

    // Create secondary threads for interprocess communication.
    hThread = CreateThread (NULL, 0, SenderThread, hWnd, 0, &rc);
    if (hThread)
        CloseHandle (hThread);
    else { 
        DestroyWindow (hWnd);
        return 0;
    }
    hThread = CreateThread (NULL, 0, ReaderThread, hWnd, 0, &rc);
    if (hThread)
        CloseHandle (hThread);
    else { 
        DestroyWindow (hWnd);
        return 0;
    }
    
    // Return fail code if window not created.
    if (!IsWindow (hWnd)) return 0;

    // Standard show and update calls
    ShowWindow (hWnd, nCmdShow);
    UpdateWindow (hWnd);
    return hWnd;
}
//----------------------------------------------------------------------
// TermInstance - Program cleanup
//
int TermInstance (HINSTANCE hInstance, int nDefRC) {

    // Free memory-mapped object.
    if (g_pBuff) {
        // Decrement app running count. 
        InterlockedDecrement (&g_pBuff->nAppCnt);
        UnmapViewOfFile (g_pBuff);
    }
    if (g_hMMObj)
        CloseHandle (g_hMMObj);

    // Free mutex.
    if (g_hmWriteOkay)
        CloseHandle (g_hmWriteOkay);

    // Close event handles.
    if (g_hReadEvent)
        CloseHandle (g_hReadEvent);

    if (g_hReadDoneEvent)
        CloseHandle (g_hReadDoneEvent);

    if (g_hSendEvent)
        CloseHandle (g_hSendEvent);
    return nDefRC;
}
//======================================================================
// Message handling procedures for main window
//----------------------------------------------------------------------
// MainWndProc - Callback function for application window
//
LRESULT CALLBACK MainWndProc (HWND hWnd, UINT wMsg, WPARAM wParam, 
                              LPARAM lParam) {
    INT i;
    //
    // Search message list to see if we need to handle this
    // message.  If in list, call procedure.
    //
    for (i = 0; i < dim(MainMessages); i++) {
        if (wMsg == MainMessages[i].Code)
            return (*MainMessages[i].Fxn)(hWnd, wMsg, wParam, lParam);
    }
    return DefWindowProc (hWnd, wMsg, wParam, lParam);
}
//----------------------------------------------------------------------
// DoCommandMain - Process WM_COMMAND message for window.
//
LRESULT DoCommandMain (HWND hWnd, UINT wMsg, WPARAM wParam, 
                       LPARAM lParam) {
    WORD    idItem, wNotifyCode;
    HWND    hwndCtl;
    INT    i;

    // Parse the parameters.
    idItem = (WORD) LOWORD (wParam);
    wNotifyCode = (WORD) HIWORD (wParam);
    hwndCtl = (HWND) lParam;

    // Call routine to handle control message.
    for(i = 0; i < dim(MainCommandItems); i++) {
        if(idItem == MainCommandItems[i].Code)
            return (*MainCommandItems[i].Fxn)(hWnd, idItem, hwndCtl, 
                                           wNotifyCode);
    }
    return 0;
}
//----------------------------------------------------------------------
// DoDestroyMain - Process WM_DESTROY message for window.
//
LRESULT DoDestroyMain (HWND hWnd, UINT wMsg, WPARAM wParam, 
                       LPARAM lParam) {
    PostQuitMessage (0);
    return 0;
}
//======================================================================
// Command handler routines
//----------------------------------------------------------------------
// DoMainCommandExit - Process Program Exit command
//
LPARAM DoMainCommandExit (HWND hWnd, WORD idItem, HWND hwndCtl, 
                          WORD wNotifyCode) {

    SendMessage (hWnd, WM_CLOSE, 0, 0);
    return 0;
}
//----------------------------------------------------------------------
// DoMainCommandSend - Process Program Send command.
//
LPARAM DoMainCommandSend (HWND hWnd, WORD idItem, HWND hwndCtl, 
                          WORD wNotifyCode) {

    SetEvent (g_hSendEvent);
    return 0;
}
//======================================================================
// SenderThread - Performs the interprocess communication
//
int SenderThread (PVOID pArg) {
    HWND hWnd;
    INT nGoCode, rc;
    TCHAR szText[TEXTSIZE];

    hWnd = (HWND)pArg;
    while (1) {
        nGoCode = WaitForSingleObject (g_hSendEvent, INFINITE);
        if (nGoCode == WAIT_OBJECT_0) {
            SendDlgItemMessage (hWnd, IDD_OUTTEXT, WM_GETTEXT, 
                                sizeof (szText), (LPARAM)szText);

            rc = WaitForSingleObject (g_hmWriteOkay, 2000);
            if (rc == WAIT_OBJECT_0) {
                lstrcpy (g_pBuff->szText, szText);
                g_pBuff->nReadCnt = g_pBuff->nAppCnt;
                PulseEvent (g_hReadEvent);

                // Wait while reader threads get data.
                while (g_pBuff->nReadCnt) 
                    rc = WaitForSingleObject (g_hReadDoneEvent, 
                                              INFINITE);
                ReleaseMutex (g_hmWriteOkay);
            } 
        } else 
            return _1;
    }
    return 0;
}
//======================================================================
// ReaderThread - Performs the interprocess communication
//
int ReaderThread (PVOID pArg) {
    HWND hWnd;
    INT nGoCode, rc, i;
    TCHAR szText[TEXTSIZE];

    hWnd = (HWND)pArg;
    while (1) {
        nGoCode = WaitForSingleObject (g_hReadEvent, INFINITE);
        if (nGoCode == WAIT_OBJECT_0) {
            i = SendDlgItemMessage (hWnd, IDD_INTEXT, LB_ADDSTRING, 0, 
                                (LPARAM)g_pBuff->szText);
            SendDlgItemMessage (hWnd, IDD_INTEXT, LB_SETTOPINDEX, i, 0);

            InterlockedDecrement (&g_pBuff->nReadCnt);
            SetEvent (g_hReadDoneEvent);
        } else {
            rc = GetLastError();
            wsprintf (szText, TEXT ("rc:%d"), rc);
            MessageBox (hWnd, szText, TEXT ("ReadThread Err"), MB_OK);
        }
    }
    return 0;
}
  
  
 
 

Figure 8-2. The source code for XTalk.

The interesting routines in the XTalk example are the InitInstance procedure and the two thread procedures SenderThread and ReaderThread. The relevant part of InitInstance is shown below with the error checking code removed for brevity.

// Create mutex used to share memory-mapped structure.
g_hmWriteOkay = CreateMutex (NULL, TRUE, TEXT ("XTALKWRT"));
rc = GetLastError();
if (rc == ERROR_ALREADY_EXISTS)
    fFirstApp = FALSE;

// Wait here for ownership to insure the initialization is done.
// This is necessary since CreateMutex doesn't wait.
rc = WaitForSingleObject (g_hmWriteOkay, 2000);
if (rc != WAIT_OBJECT_0)
    return 0;

// Create a file-mapping object.
g_hMMObj = CreateFileMapping ((HANDLE)-1, NULL, PAGE_READWRITE, 0, 
                              MMBUFFSIZE, TEXT ("XTALKBLK"));

// Map into memory the file-mapping object.
g_pBuff = (PSHAREBUFF)MapViewOfFile (g_hMMObj, FILE_MAP_WRITE, 
                                     0, 0, 0);

// Initialize structure if first application started.
if (fFirstApp) 
    memset (g_pBuff, 0, sizeof (SHAREBUFF));

// Increment app running count. Interlock not needed due to mutex.
g_pBuff->nAppCnt++;   

// Release the mutex.  We need to release the mutex twice 
// if we owned it when we entered the wait above.
ReleaseMutex (g_hmWriteOkay);
if (fFirstApp) 
    ReleaseMutex (g_hmWriteOkay);

// Now create events for read and send notification.
g_hSendEvent = CreateEvent (NULL, FALSE, FALSE, NULL);
g_hReadEvent = CreateEvent (NULL, TRUE, FALSE, TEXT ("XTALKREAD"));
g_hReadDoneEvent = CreateEvent (NULL, FALSE, FALSE, 
                                TEXT ("XTALKDONE"));

This code is responsible for creating the necessary synchronization objects as well as creating and initializing the shared memory block. The mutex object is created first with the parameters set to request initial ownership of the mutex object. A call is then made to GetLastError to determine whether the mutex object has already been created. If not, the application assumes the first instance of XTalk is running and later will initialize the shared memory block. Once the mutex is created, an additional call is made to WaitForSingleObject to wait until the mutex is released. This call is necessary to prevent a late starting instance of XTalk from disturbing communication in progress. Once the mutex is owned, calls are made to CreateFileMapping and MapViewOfFile to create a named memory-mapped object. Since the object is named, each process that opens the object opens the same object and is returned a pointer to the same block of memory.

Once the shared memory block is created, the first instance of XTalk zeros out the block. This procedure also forces the block of RAM to be committed because memory-mapped objects by default are autocommit blocks. Then nAppCnt, which keeps a count of the running instances of XTalk, is incremented. Finally the mutex protecting the shared memory is released. If this is the first instance of XTalk, ReleaseMutex must be called twice because it gains ownership of the mutex twice—once when the mutex is created and again when the call to WaitForSingleObject is made.

Finally, three event objects are created. SendEvent is an unnamed event, local to each instance of XTalk. The primary thread uses this event to signal the sender thread that the user has pressed the Send button and wants the text in the edit box transmitted. The ReadEvent is a named event that tells the other instances of XTalk that there's data to be read in the transfer buffer. The ReadDoneEvent is a named event signaled by each of the receiving copies of XTalk to indicate that they have read the data.

The two threads, ReaderThread and SenderThread are created immediately after the main window of XTalk is created. The code for SenderThread is shown here:

int SenderThread (PVOID pArg) {
    HWND hWnd;
    INT nGoCode, rc;
    TCHAR szText[TEXTSIZE];

    hWnd = (HWND)pArg;
    while (1) {
        nGoCode = WaitForSingleObject (g_hSendEvent, INFINITE);
        if (nGoCode == WAIT_OBJECT_0) {
            SendDlgItemMessage (hWnd, IDD_OUTTEXT, WM_GETTEXT, 
                                sizeof (szText), (LPARAM)szText);

            rc = WaitForSingleObject (g_hmWriteOkay, 2000);
            if (rc == WAIT_OBJECT_0) {
                lstrcpy (g_pBuff->szText, szText);
                g_pBuff->nReadCnt = g_pBuff->nAppCnt;
                PulseEvent (g_hReadEvent);

                // Wait while reader threads get data.
                while (g_pBuff->nReadCnt) 
                    rc = WaitForSingleObject (g_hReadDoneEvent,
                                              INFINITE);
                ReleaseMutex (g_hmWriteOkay);
            } 
        }
    }
    return 0;
}

The routine waits on the primary thread of XTalk to signal SendEvent. The primary thread of XTalk makes the signal in response to a WM_COMMAND message from the Send button. The thread is then unblocked, reads the text from the edit control, and waits to gain ownership of the WriteOkay mutex. This mutex protects two copies of XTalk from writing to the shared block at the same time. When the thread owns the mutex, it writes the string read from the edit control into the shared buffer. It then copies the number of active copies of XTalk into the nReadCnt variable in the same shared buffer, and pulses the ReadEvent to tell the other copies of XTalk to read the newly written data. A manual resetting event is used so that all threads waiting on the event will be unblocked when the event is signaled.

The thread then waits for the nReadCnt variable to return to 0. Each time a reader thread reads the data, the nReadCnt variable is decremented and the ReadDone event signaled. Note that the thread doesn't spin on this variable but uses an event to tell it when to check the variable again. This would actually be a great place to use WaitForMultipleObjects and have all reader threads signal when they've read the data, but Windows CE doesn't support the WaitAll flag in WaitForMultipleObjects.

Finally, when all the reader threads have read the data, the sender thread releases the mutex protecting the shared segment and the thread returns to wait for another send event.

The ReaderThread routine is even simpler. Here it is:

int ReaderThread (PVOID pArg) {
    HWND hWnd;
    INT nGoCode, rc, i;
    TCHAR szText[TEXTSIZE];

    hWnd = (HWND)pArg;
    while (1) {
        nGoCode = WaitForSingleObject (g_hReadEvent, INFINITE);
        if (nGoCode == WAIT_OBJECT_0) {
            i = SendDlgItemMessage (hWnd, IDD_INTEXT, LB_ADDSTRING, 0, 
                                    (LPARAM)g_pBuff->szText);
            SendDlgItemMessage (hWnd, IDD_INTEXT, LB_SETTOPINDEX, i, 0);

            InterlockedDecrement (&g_pBuff->nReadCnt);
            SetEvent (g_hReadDoneEvent);
        }
    }
    return 0;
}

The reader thread starts up and immediately blocks on ReadEvent. When it's unblocked, it adds the text from the shared buffer into the list box in its window. The list box is then scrolled to show the new line. After this is accomplished, the nReadCnt variable is decremented using InterlockedDecrement to be thread safe, and the ReadDone event is signaled to tell the SenderThread to check the read count. After that's accomplished, the routine loops around and waits for another read event to occur.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值