Important: |
This is retired content. This content is outdated and is no
longer being maintained. It is provided as a courtesy for
individuals who are still using these technologies. This content
may contain URLs that were valid when originally published, but now
link to sites or pages that no longer exist. |
Microsoft Corporation
April 2000
Summary:Microsoft Windows CE is a preemptive multitasking
operating system that supports the Microsoft Win32 process and
thread model using a round-robin, priority-based scheduler. This
paper discusses how to create, manage, and terminate processes and
threads using this Win32 process and thread model. (15 printed
pages)
Contents
Introduction
Processes
Threads
Interprocess Synchronization
Introduction
Microsoft Windows CE is a compact, scalable operating system. It
uses a subset of the Microsoft Win32 application programming
interface (API) that is used on Windows-based desktop and server
computers. You can use the same development tools, such as,
Microsoft Visual C++ or Microsoft Visual Basic, to create
applications for Windows CE–powered devices.
Windows CE is a preemptive multitasking operating system.
Multiple applications, or processes, can run within the system at
the same time. A process consists of one or more threads, where
each thread executes code in that process. When you correctly use
threads, your application runs more quickly and efficiently.
Windows CE uses the hardware memory management unit (MMU) to
implement virtual memory and to protect processes. While other
operating systems either do not use the MMU or make limited use of
it, Windows CE uses the MMU to make the system more flexible and
reliable. The hardware protects processes from one another, making
the system suitable for products where processes are added after
the system is built, without the knowledge or help of the system
builder. By enabling these third-party programs, the end product
can be an open platform supporting a broader range of customer
solutions without giving up reliability.
Virtual memory allows the system builder to make more efficient
use of memory and code space. Code can be executed in read-only
memory (ROM), compressed in ROM, and paged into random access
memory (RAM), or stored in a file system and paged into RAM at
execution time.
To help you take advantage of the efficiency offered by Windows
CE, this paper provides information about how to create, manage,
and terminate processes and threads using the Win32 process and
thread model supported by Windows CE.
Processes
A process is a single instance of a running application. Windows
CE supports a maximum of 32 simultaneous processes. Each process
has at least a primary thread, which executes code within the
process. You can also create additional threads in a process,
limited only by RAM.
Windows CE has a 4GB address space with full protection between
processes. The virtual address space for each process is 32MB. You
can allocate additional memory outside of the 32MB by using
memory-mapped files. Also, allocations greater than 2MB using the
VirtualAlloc function with the MEM_RESERVE and PAGE_NOACCESS flags
and without a start address are allocated outside of the process
address space.
A process contains a default heap whose size is limited only by
physical memory. The process can own other resources such as files
and dynamic-link libraries (DLLs). Windows CE does not support
environment variables or current directory, and the search order
for .exe files is as follows:
- Windows (\windows) directory
- Root directory (\) of the Windows CE device
- OEM-specified directories
- For Microsoft Windows CE Platform Builder 2.11 users only:
OEM-defined shell (\ceshell) directory
Also note that Windows CE does not support process priority
classes, which restrict the thread priorities that can be assigned
in a process.
Creating a Process
When you create a process, Windows CE allocates memory for the
stack and heap and creates the primary thread for the process. The
system then schedules the process to be run.
The CreateProcess function creates a process and returns the
handles and identifiers for the process and its primary thread.
Many of this function's parameters are set to NULL or FALSE because
Windows CE does not support features on the desktop, such as,
security, current directory, or handle inheritance. The resulting
function and required parameters are as follows:
BOOL CreateProcess (LPCTSTR
lpApplicationName, LPTSTR lpCommandLine, NULL, NULL, FALSE, DWORD
dwCreationFlags, NULL, NULL, NULL, LPPROCESS_INFORMATION
lpProcessInformation);
Parameter |
Description |
lpApplicationName |
Specifies the name of the application to execute.
This is a Unicode sting, and you cannot pass NULL. |
lpCommandLine |
Specifies the command line passed to the new
process. The command line is always passed as a Unicode sting. |
dwCreationFlags |
Specifies the initial state of the process after it
has been loaded. This parameter must be one of the following flags:
0 creates a standard
process.
CREATE_SUSPENDED creates the process
then suspends the primary thread.
DEBUG_PROCESS treats the process being
created as a process being debugged by the caller.
DEBUG_ONLY_THIS_PROCESS, when combined
with DEBUG_PROCESS, debugs a process but does not debug any child
processes that are launched by the process being
debugged.
CREATE_NEW_CONSOLE forces creation of a
new console. |
lpProcessInformation |
Points to a PROCESS_INFORMATION structure that the
CreateProcess function fills with information about the new
process. You can pass NULL. |
If the process cannot be run, the CreateProcess function returns
FALSE. For more detailed information about the failure, use the
GetLastError function.
Terminating a Process
The preferred method for process termination is for a process to
terminate itself by returning from its primary thread. You can
terminate the primary thread of the process by calling the
ExitThread function. For more information, see the section on
Terminating a Thread. The ExitThread function returns the exit code
of the process.
You can also terminate a process with one of the following
methods:
- Use interprocess communication to tell the process to terminate
itself. Interprocess communication is discussed later in this
paper.
- If the process has a message queue, send a WM_CLOSE message to
the main window of the process. An application may not close if it
does not receive this message and may display a message box.
- As a last resort, you can use the TerminateProcess function,
which does not notify attached DLLs that the process is
terminating. Specify the handle and exit code of the process, as
follows:
BOOL TerminateProcess (HANDLE hProcess, DWORD uExitCode);
You can determine the exit code for a process with the
GetExitCodeProcess function. If the process is still running, the
returned exit code is the constant STILL_ACTIVE. Specify the handle
to the process, and the function returns the exit code, as
follows:
BOOL GetExitCodeProcess (HANDLE
hProcess, LPDWORD lpExitCode);
As an alternative, you can also use the WaitForSingleObject
function. Pass the process handle as the first parameter and a
timeout value for the second parameter-for more information, see
the section on Wait Functions. The process handle is signaled when
the process terminates.
Also, when you no longer need a process handle, close it by
using the CloseHandle function. This function frees data associated
with an object.
Other Process Information
Windows CE also supports the following process functions.
If you want to know the: |
Use: |
Handle to a process |
HANDLE GetCurrentProcess (void); |
Identifier of a process |
DWORD GetCurrentProcessId (void); |
Handle of another process |
HANDLE OpenProcess (0, FALSE, DWORD
dwProcessId); |
Process identifier for the process that created a
particular window |
DWORD GetWindowThreadProcessId (HWND hWnd, LPWORD
lpdwProcessId); |
A process can directly read from or write to an accessible
memory space of another process using the ReadProcessMemory and
WriteProcessMemory functions. The functions and required parameters
are as follows:
BOOL ReadProcessMemory (HANDLE
hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize,
LPDWORD lpNumberOfBytesRead);
BOOL WriteProcessMemory (HANDLE
hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, DWORD nSize,
LPDWORD lpNumberOfBytesWritten);
Parameter |
Description |
hProcess |
Specifies the handle of the other process. Use the
OpenProcess function to return a process handle. |
lpBaseAddress |
Specifies base address of the process to be read or
written. |
lpBuffer |
Specifies name of the local buffer from or to which
the data is to be read or written. |
nSize |
Specifies size of the local buffer from or to which
the data is to be read or written. |
lpNumberOfBytesReadlpNumberOfBytesWritten |
Specifies number of bytes read or written. |
Threads
A thread executes code in a process. Each thread has a stack,
where the linker sets the stack size of all threads created in a
process. Each process has a primary thread and can have as many
additional threads as permitted by available RAM. Threads within a
process share the address space of the process.
A thread also has associated registers such as the instruction
pointer. These associated registers are known as the context. You
can use the GetThreadContext function to retrieve the context of
the specified thread and the SetThreadContext function to set the
context in the specified thread, as follows:
BOOL GetThreadContext (HANDLE hThread,
LPCONTEXT lpContext); BOOL SetThreadContext (HANDLE hThread, CONST
CONTEXT *lpContext);
Threads can be in one of the following states: running,
suspended, sleeping, blocked, or terminated. Multi-threaded
applications must avoid two threading problems: deadlocks and race
conditions. A deadlock occurs when each thread is waiting for the
other to do something. A race condition occurs when threads are
sharing a value and the final result of the value depends on the
scheduling order of the threads.
Creating a Thread
To create a thread, first define a thread routine and then call
the CreateThread function, as follows:
HANDLE CreateThread NULL, 0,
LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD
dwCreationFlags, LPDWORD lpThreadId);
Parameter |
Description |
lpStartAddress |
Points to the start of the thread routine. |
lpParameter |
Specifies an application-defined value that is
passed to the thread routine. |
dwCreationFlags |
Set to 0 or CREATE_SUSPENDED. |
lpThreadId |
Points to a DWORD that receives the new thread's
identifier. |
The CreateThread function returns the handle and identifier of
the new thread. If you otherwise need to know the identifier or
handle of a thread, use the GetCurrentThreadId function to return
the thread identifier, and the GetCurrentThread function to return
a handle to the thread. These functions are defined as follows:
HANDLE GetCurrentThread
(void);
DWORD GetCurrentThreadId
(void);
Threads are always created with a priority of
THREAD_PRIORITY_NORMAL. To change the priority level of a thread,
call the SetThreadPriority function. For more information about
priorities, see the section on Scheduling Threads.
Terminating a Thread
Thread termination is consistent with Windows-based desktop
platforms. Thread and process termination are linked in the
following ways:
- If an exception occurs in a thread, its process is terminated.
- If a primary thread terminates, its process is also terminated.
- When a process is terminated, all its threads are terminated.
The ExitThread function terminates a thread and frees all
resources used by the thread. The only parameter is the thread's
exit code, as follows:
VOID ExitThread (DWORD
dwExitCode);
You can determine the exit code of a thread by using the
GetExitCodeThread function. If the thread is still running, the
exit code is STILL_ACTIVE. The function takes the handle to the
thread and returns the exit code, as follows:
BOOL GetExitCodeThread (HANDLE hThread,
LPDWORD lpExitCode);
As a last resort, you can use the TerminateThread function,
which does not execute termination code and may not free resources.
The parameters are the handle and exit code of the thread, as
follows:
TerminateThread (HANDLE hThread, DWORD
dwExitCode);
When you no longer need a thread handle, be sure to close it
with the CloseHandle function to free resources.
Scheduling Threads
Windows CE uses a priority-based time-slice algorithm to
schedule the execution of threads. Because Windows CE does not have
priority classes, the process in which the thread runs does not
influence thread priorities. All the priorities can be used in the
same process. A thread can have one of the eight priorities shown
in the following table.
Priority Value |
Typically used for: |
THREAD_PRIORITY_TIME_CRITICAL |
Real-time processing and device drivers |
THREAD_PRIORITY_HIGHEST |
Real-time processing and device drivers |
THREAD_PRIORITY_ABOVE_NORMAL |
Kernel threads and normal applications |
THREAD_PRIORITY_NORMAL |
Kernel threads and normal applications |
THREAD_PRIORITY_BELOW_NORMAL |
Kernel threads and normal applications |
THREAD_PRIORITY_LOWEST |
Applications that can always be preempted |
THREAD_PRIORITY_ABOVE_IDLE |
Applications that can always be preempted |
THREAD_PRIORITY_IDLE |
Applications that can always be preempted |
To query the priority level of a thread, call the
GetThreadPriority function. To change the priority level of a
thread, call the SetThreadPriority function. Both functions take
the handle to the thread, and the SetThreadPriority function takes
the new priority level, as follows:
int GetThreadPriority (HANDLE
hThread);
BOOL SetThreadPriority (HANDLE hThread,
int nPriority);
Threads with a higher priority are scheduled to run first.
Threads with the same priority run in a round-robin fashion-when a
thread has stopped running, all other threads of the same priority
run before the original thread can continue. Threads at a lower
priority do not run until all threads with a higher priority have
finished, that is, until they either yield or are blocked. If one
thread is running and a thread of higher priority is unblocked, the
lower-priority thread is immediately suspended and the
higher-priority thread is scheduled.
Threads run for a time slice, or quantum, which has a default
value of 25 milliseconds, but the OEM can specify a different
quantum. After that time, if the thread has not relinquished its
time slice and is not time critical, it is suspended and another
thread is scheduled to run. Threads at the highest priority
level-THREAD_PRIORITY_TIME_CRITICAL-continue executing without
interruption until they have finished, unless interrupted by an
Interrupt Service Routine (ISR).
Thread priorities are fixed and do not change. Windows CE does
not age priorities, boost foreground thread priorities, or mask
interrupts based on these priority levels. The kernel can
temporarily modify thread priorities when a low-priority thread is
using a resource that delays the execution of a high-priority
thread wanting to use the same resource. Windows CE boosts the
priority of the low-priority thread until it releases the resource
required by the higher priority thread. This scenario is called
priority inheritance, and is designed to prevent a low-level thread
running before higher-level threads, a problem known as priority
inversion. Windows CE will go through the entire chain of threads
trying to correct a priority inversion problem.
You can suspend a thread using the SuspendThread function. To
resume running a thread, call the ResumeThread function. You must
match multiple calls to the SuspendThread function with the same
number of calls to the ResumeThread function. The only parameter
for both functions is the handle to the thread. To suspend a thread
for a specified number of milliseconds, use the Sleep function. If
you specify a sleep time of less than the quantum, the thread
sleeps for a quantum. These functions are defined as follows:
DWORD SuspendThread (HANDLE
hThread);
DWORD ResumeThread (HANDLE
hThread);
void Sleep (DWORD
dwMilliseconds);
You can profile the performance of a thread using the
GetThreadTimes function, which returns how much time a thread has
run.
Thread Local Storage
Thread local storage (TLS) lets a thread maintain separate
instances of data. Each thread within a process can have its own
set of values that are guaranteed to be unique. The TlsAlloc
function reserves a TLS slot and sets the value in the slot to zero
for all currently running threads. New threads are also created
with their TLS array initialized to zero.
After being assigned a TLS slot, each thread can access its
unique data by calling the TlsGetValue or TlsSetValue function. The
TlsFree function frees the TLS slot. The TLS functions are defined
as follows, where dwTlsIndex specifies the slot that contains the
data and lpTlsValue specifies the value to be stored in the calling
thread's TLS slot:
DWORD TlsAlloc (void);
BOOL TlsSetValue (DWORD dwTlsIndex,
LPVOID lpTlsValue);
LPVOID TlsGetValue (DWORD
dwTlsIndex);
BOOL TlsFree (DWORD
dwTlsIndex);
Thread Synchronization
Synchronization is needed to avoid conflict over shared
resources. You can coordinate the activities of threads by using
wait functions, synchronization objects, and interlocked functions.
Windows CE supports the following synchronization objects:
- Event objects
- Mutex (mutual exclusion) objects
- Critical sections
Windows CE queues event, mutex, and critical section requests in
"FIFO-by-priority" order: a different FIFO queue is defined for
each of the eight priority levels. A new request from a thread at a
given priority is placed at the end of that priority's list.
Wait Functions
Windows CE supports single-object and multiple-object wait
functions that block or unblock a thread based on a signaled or
non-signaled state of an object. A wait function causes a thread to
enter an efficient state that requires very little CPU processing
power and battery power.
The WaitForSingleObject function lets a thread wait on a single
object. The object can be a synchronization object, such as, an
event or mutex object, or can be handles to processes and threads.
These handles are signaled when their processes or threads
terminate. Thus, a process can monitor another process or thread
and perform some action based on the status of the process or
thread.
The WaitForMultipleObjects function lets a thread wait on
multiple synchronization objects. The wait can end when any one of
the objects is signaled. Windows CE does not support waiting for
all objects to be signaled.
Do not use the WaitForSingleObject or WaitForMultipleObjects
function on the thread that processes messages. A thread that is
blocked waiting on an object cannot retrieve and dispatch messages.
You can use the MsgWaitForMultipleObjects function. This function
combines the WaitForMultipleObjects function with a check into the
message queue so that the function returns if any of the selected
categories of messages are received during the wait.
The wait functions are defined as follows:
DWORD WaitForSingleObject (HANDLE
hHandle, DWORD dwMilliseconds);
DWORD WaitForMultipleObjects (DWORD
nCount, CONST HANDLE *lpHandles, FALSE, DWORD
dwMilliseconds);
DWORD MsgWaitForMultipleObjects (DWORD
nCount, LPHANDLE pHandles, FALSE, DWORD dwMilliseconds, DWORD
dwWakeMasks);
Parameter |
Description |
hHandle |
Specifies the handle to the object being waited
on. |
dwMilliseconds |
Specifies a timeout value. If you do not want a
timeout, pass the value INFINITE. |
nCount |
Specifies a count of the number of event or mutex
objects to wait on. |
*lpHandles orpHandles |
Specifies a pointer to the array of handles for the
event or mutex object. |
dwWakeMasks |
Specifies a flag that indicates the message
category that, when received in the message queue of the thread,
causes the function to return. Windows CE supports the following
flags:
QS_ALLINPUT = any message
QS_INPUT = input message
QS_KEY = key up, key down, or syskey up or down message
QS_MOUSE = mouse move or mouse click message
QS_MOUSEBUTTON = mouse click message has been received
QS_MOUSEMOVE = mouse move message
QS_PAINT = WM_PAINT message
QS_POSTMESSAGE = posted message, other than one in this list
QS_SENDMESSAGE = sent message, other than one in this list
QS_TIMER = WM_TIMER message |
Event Objects
An event object notifies a thread that an event has occurred.
Use the CreateEvent function to create an event object. An event
object can be named or unnamed, as specified in the lpName
parameter. It can automatically reset itself from a signaled state
to a nonsignaled state or require a manual reset, as specified in
the bManualReset parameter. The bInitialState parameter specifies
whether the event object is created in the signaled or nonsignaled
state. The function is as follows:
HANDLE CreateEvent (NULL, BOOL
bManualReset, BOOL bInitialState, LPTSTR lpName);
Named events can be shared with other processes. Threads in
other processes can open a handle to an existing event object by
specifying its name in a call to the CreateEvent function. To
determine whether a CreateEvent function created a new object or
opened an existing object, you can call the GetLastError function
immediately after calling the CreateEvent function. If the
GetLastError function returns ERROR_ALREADY_EXISTS, the call opened
an existing event. In other words, if the name specified in a call
to the CreateEvent function matches the name of an existing event
object, the function returns the handle of the existing object.
When using this technique for event objects, none of the calling
processes should request immediate ownership of the event, because
it is uncertain which process actually gets ownership.
To signal an event, use the SetEvent or PulseEvent function.
SetEvent does not automatically reset the event object to a
nonsignaled state. The PulseEvent function signals the event then
resets it. For manual events, the PulseEvent function unblocks all
threads waiting on the event. For automatic events, the PulseEvent
function unblocks only one thread.
If an event object can reset itself, you need only use the
SetEvent function to signal. The event object is then automatically
reset to the nonsignaled state when one thread is unblocked after
waiting on that event. To manually reset an event, use the
ResetEvent function. These event functions are defined as
follows:
BOOL ResetEvent (HANDLE
hEvent);
BOOL SetEvent (HANDLE
hEvent);
BOOL PulseEvent (HANDLE
hEvent);
To close an event object, call the CloseHandle function. If the
event object is named, Windows CE maintains a use count on the
object, and you must make one call to the CloseHandle function for
every call to the CreateEvent function.
Mutex Objects
A mutex (mutual exclusion) object is a synchronization object
that is signaled when it is not owned by a thread and nonsignaled
when it is owned. A mutex object is especially useful for
coordinating exclusive access to a resource, such as, a block of
memory across multiple threads, including threads in other
processes.
Use the CreateMutex function to create a mutex object. A mutex
object can be named or unnamed, as specified in the lpName
parameter. The bInitialOwner parameter specifies if the calling
thread should immediately own the newly created mutex object, as
follows:
HANDLE CreateMutex (NULL, BOOL
bInitialOwner, LPCTSTR lpName);
Threads in other processes can open a handle to an existing
mutex object by specifying its name in a call to the CreateMutex
function. To determine whether a mutex object already exists, you
can call the GetLastError function immediately after calling the
CreateMutex function. If the GetLastError function returns
ERROR_ALREADY_EXISTS, the call opened an existing mutex object.
A thread requests ownership of a mutex object by using a wait
function. If another thread owns the mutex object, the wait
function blocks the requesting thread until the owning thread
releases the mutex object with the ReleaseMutex function. This
function takes the handle to the mutex, as follows:
BOOL ReleaseMutex (HANDLE
hMutex);
If a thread owns a mutex object and specifies it in repeated
calls to one of the wait functions, the wait call immediately
returns. This prevents a thread from blocking itself while waiting
for a mutex object that the thread already owns. To release
ownership of a mutex object, the thread must call the ReleaseMutex
function once for each call to the wait function.
If a thread terminates without releasing ownership of a mutex
object, the object is considered abandoned. A waiting thread can
acquire ownership of an abandoned mutex object, but the return
value for the wait function indicates that the mutex object is
abandoned. To be safe, assume that an abandoned mutex object
indicates that an error has occurred and that any shared resource
protected by the mutex object is in an undefined state. If the
thread proceeds as though the mutex object had not been abandoned,
the object's abandoned flag is cleared when the thread releases
ownership. Clearing the abandoned flag restores typical behavior if
a handle to the mutex object is subsequently specified in a wait
function.
Now a few words about naming mutex and event objects. The name
of an event or mutex object is limited to the number of characters
defined by MAX_PATH. The name can include any character except the
backward slash (\). The names of mutex and event objects share the
same space. When you create an object, if you specify a name that
is in use by an object of another type, the function succeeds, but
the GetLastError function returns ERROR_ALREADY_EXISTS. To avoid
this error, use unique names and check return values for
duplicate-name errors.
Critical Sections
When multiple threads have shared access to the same resource,
the threads can interfere with one another. A critical section
protects a section of code by blocking other threads from entering
that section when it already contains a thread. A critical section
cannot be shared with other processes.
To use a critical section, you first declare a CRITICAL_SECTION
structure and then create a critical section handle with the
InitializeCriticalSection function. A thread calls the
EnterCriticalSection function to request ownership of a critical
section and the LeaveCriticalSection function to release ownership.
If another thread already owns a critical section, the thread
calling the EnterCriticalSection function is blocked until the
other thread releases the critical section.
If a thread owns a critical section and makes additional calls
to the EnterCriticalSection function, the function returns
immediately. This prevents a thread from blocking itself while
waiting for a critical section that the thread already owns. To
release ownership, the thread must call the LeaveCriticalSection
function once for each time the thread entered the critical
section. When you are finished with a critical section, call the
DeleteCriticalSection function to release the system resources that
were allocated when you initialized the critical section.
All of the critical section functions take a pointer to a
CRITICAL_SECTION structure, as follows:
void InitializeCriticalSection
(LPCRITICAL_SECTION lpCriticalSection);
void EnterCriticalSection
(LPCRITICAL_SECTION lpCriticalSection);
void LeaveCriticalSection
(LPCRITICAL_SECTION lpCriticalSection);
void DeleteCriticalSection
(LPCRITICAL_SECTION lpCriticalSection);
Interlocked Functions
Interlocked functions synchronize access to a variable that is
shared by multiple threads. The purpose is to prevent a thread from
being preempted when it is in the middle of modifying or checking a
variable.
The InterlockedIncrement and InterlockedDecrement functions
increment and decrement a shared variable and check the resulting
value. The InterlockedExchange function exchanges the values of the
specified variables. The InterlockedTestExchange function exchanges
the values of the specified variables if one of the variables is
equal to a specified value. These functions are defined as
follows:
LONG InterlockedIncrement (LPLONG
lpAddend);
LONG InterlockedDecrement (LPLONG
lpAddend);
LONG InterlockedExchange (LPLONG
Target, LONG Value);
LONG WINAPI InterlockedTestExchange
(LPLONG Target, LONG OldValue, LONG NewValue);
Parameter |
Description |
lpAddend |
Specifies a pointer to the variable to increment or
decrement. |
Target |
Specifies pointer to target variable. |
Value |
Specifies new value of the variable. |
OldValue |
Specifies value to check against. |
NewValue |
Specifies value to set to if Target is equal to
OldValue. |
Messages
Threads rely on messages to initiate processing, control system
resources and communicate with the operating system and user.
Windows messages originate from a variety of sources, including the
operating system; user activity, such as, keyboard, mouse, or touch
screen actions; and other running processes or threads.
When a message is sent to a thread, that message is placed in a
message queue for that thread for eventual handling. Each thread
owns a message queue that is completely independent of message
queues owned by other threads. Threads typically have a message
loop that runs continuously, retrieving and handling messages. When
no messages are in the queue and the thread is not engaged in any
other activity, the system can suspend the thread, thereby saving
CPU resources. Not all threads have a message queue. Only a thread
that has an associated window has a message queue.
You can use messages for control purposes, to initiate various
types of processing in your application, and to pass data using
message parameters. For example, a thread might receive a message
indicating that a touch screen has been activated, and the message
parameter might indicate the x and y coordinates of the user's
action. In another type of message, the parameter might include a
pointer or handle to a data structure, window, or other object.
Interprocess Synchronization
Walls between processes protect against the casual exchange of
data; however, two processes may need to communicate at times.
Windows CE does not support the DuplicateHandle function or handle
inheritance. You can use WM_COPYDATA or a memory-mapped object to
communicate between processes.
WM_COPYDATA
WM_COPYDATA sends blocks of data from one process to another,
but is limited to copying fixed blocks of data at a specific
time.
WM_COPYDATA wParam = (WPARAM)(HWND) hwndFrom; lParam =
(LPARAM)(PCOPYDATASTRUCT) pcds;
The hwnd parameter is the handle to the window passing the data,
and the pcds parameter is a pointer to a COPYDATASTRUCT structure
that contains the data to be passed.
Memory-Mapped Objects
Two processes can use a named memory-mapped object to allocate a
shared block of memory that is equally accessible to both processes
at the same time. With a named memory-mapped object, the system
keeps a proper use count on the object to prevent one process from
freeing the block while the other process is still using the
block.
To synchronize processes when they are reading and writing data
in the memory block, you can use named event and mutex objects.
Once a process has created a named event or mutex object, other
processes can use the name in a call to the CreateEvent or
CreateMutex function to open a handle to the object. Because
multiple processes can have handles to the same event or mutex
object, these objects can be used to synchronize processes. Note
that name comparison is case sensitive.
When using a memory-mapped object for interprocess
communication, you only need to share memory, which means you are
not required to have a memory-mapped file created by the
CreateFileForMapping function. You just need to pass -1 in the
handle field of the CreateFileMapping function. You can then use
the MapViewOfFile function to map a view to the object, which
allocates system resources and returns a pointer to the
memory-mapped object. You now have a cheap and effective way to
access a region of memory from multiple processes. When you are
finished, unmap the view by calling the UnmapViewOfFile
function.
The mapping functions are defined as follows:
HANDLE CreateFileMapping (HANDLE hFile,
NULL, DWORD flProtect,
DWORD dwMaximumSizeHigh, DWORD
dwMaximumSizeLow, LPCTSTR lpName);
LPVOID MapViewofFile (HANDLE
hFileMappingObject, DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh, DWORD
dwFileOffsetLow,
DWORD
dwNumberOfBytesToMap);
BOOL UnmapViewofFile (LPCVOID
lpBaseAddress);
Parameter |
Description |
hFile |
Specifies handle to an open memory-mapped file. Set
to -1 if you are not using a memory-mapped file. |
flProtect |
Specifies protection flags for the virtual pages
that will contain the file data. |
dwMaximumSizeHigh anddwMaximumSizeLow |
Specify expected maximum size of the object. |
lpName |
Specifies name of object.v |
hFileMappingObject |
Specifies handle of the mapping object. |
dwDesiredAccess |
Specifies access rights: FILE_MAP_READ,
FILE_MAP_WRITE, or FILE_MAP_ALL. |
dwFileOffsetHigh anddwFileOffsetLow |
Specify the starting point in the file where the
view starts. |
dwNumberOfBytesToMap |
Specifies the size of the view window. |
lpBaseAddress |
Specifies the pointer to the base address of the
view. |