Thursday, November 23, 2006

WinCE - Share a data segment in a DLL

Introduction

A DLL can be loaded by more than one process. For every process the DLL's code segment is shared but each process gets its own data segment by default. Therefore if one process changes the value of the DLL's global variable, the other process can not get the modified value. For sharing some global variables of the DLL among several processes, we can use the shared data segment.

Example Code

// Global and static member variables that are not shared defined here.
...
// Begin the shared data segment
#pragma data_seg("SHARED")
// Define simple variables, the variable must be initialized here, otherwise it will not be shared.
int gSharedTest = 0;
// Do not define classes that require 'deep' copy constructors.
#pragma data_seg()
// End the shared data segment and default back to the normal data segment behavior.
// Tells the linker to generate the shared data segment.
#pragma comment(linker, "/section:SHARED,RWS")

Remarks

1) The upper "SHARED" is the name of the segment, you can use another name. It should be appeared both in the data_seg and comment part.
2) The address of the shared variable must lie in this data segment, so variables allocated from heap can not be shared.
3) The variable must be initialized otherwise it won't be shared.

Wednesday, November 22, 2006

WinCE - Using Protected Server Libraries (PSL)

Introduction

As we know, Windows CE threads can migrate between processes while executing API calls, it borrows the caller's thread and places it in the server process. Such servers are called "Protected Server Libraries" (PSLs) and are processes that export services like DLLs.

Protected Server Libraries (PSL) are used by Windows CE to create API sets that in their own process address space and can server multiple clients concurrently. Windows CE implements all of its system APIs using this technique including all WIN32 APIs, User interface, device driver, and many of its DirectX interfaces. There is a one to one mapping between an API call and its corresponding PSL entry.

Windows CE implements PSL support through a clever technique generating special processor fault conditions that cause the execution of PSL entry points. The system maintains a table of 32 PSL entries. All entries between 0 and 24 are preassigned and may not be used for custom PSLs. PSLs register their APIs, associating their API with one particular entry in the PSL table. Part of the PSL registration process is providing the system with a function table of all the API entry points for a particular PSL. There are also a set of macros defined in psyscall.h which take the PSL id and an index into the registered function table to indicate which particular function for the specified PSL is to be called. This macro generates a 'function pointer' that really is an invalid memory address. When this function pointer gets used it causes the processor to fault. The fault handling code then inspects the faulty address and sees that it is one of the special PSL addresses and extracts the PSL id and function table index. It then turns around and calls the correct entry point in the PSL, just as if the caller had directly jumped to it – with one significant difference in that process address spaces have been jumped across as well. When the PSL function finishes execution and returns, the OS resumes the application that called the PSL to begin with by returning from the fault condition with the return value from the PSL function. The application resumes execution and it looks just as if a function call had occurred. Because PSLs are the core way APIs are implemented in the system the code path for executing this process is very streamlined.

Components of PSL

PSLs have four main components:
  1. Functions implementing the various entry points of the PSL.
  2. Function table (a Vtable) that is an array of function pointers containing all the entry points of the PSL. This is an array of pointers to the functions making up the API set (PSL entries). The first two entries are reserved for the kernel. The first entry is the notification procedure that the kernel will call any time a thread or a process is stopped or started, low memory occurs, or the system is booted. The second entry is reserved by the kernel and should be zero. All remaining entries are available to the PSL implementer.
  3. Signature table describing all the argument types for each function in the function table. Made up of an array of 2 or more entry points with a one to one correspondence to the API function table. You can use these macros: FNSIG0-12, DW - DWORD, PTR - Pointer, I64 - 64 bit integer. PSL functions can have one of three argument types (DW, PTR, I64) or no arguments at. Those types are a DWORD (unsigned long), a pointer (void *), or a 64 bit integer (long long). There return values are the same. They can be used in any combination within a function or across function. There can be no other parameter types but the current parameter types can be used to contain other types (like a WORD or WCHAR in a DWORD, a BSTR in a void *). PSL functions can have from zero to 12 arguments. As can be seen above, each PSL function signiture is defined by using an FNSIG macro that is followed by the number of arguments the function has. The FNSIG macro is then filled in with an argument type specification for each argument it has.
    The data in the signature table is used by the kernel to marshal arguments for the PSL function calls across process address spaces so it is imperative that these be correct or arguments will not get passed correctly from a client calling your PSL into the actual function in your PSL that implements the API call.
  4. A .lib that clients of the PSL link to which contains the code that generates the special invalid memory address that causes the processor to fault and execute the PSL. The code also can be inline functions defined in the header file.

Example

The following is the code fragment of using the PSL technique to add APIs to our customizing Power Manager. The example shows the steps to add the SetSystemPowerRequirement API.

// 1. Functions
// 1.1 Notify function
VOID
PmAPINotifyProc(DWORD dwEvent, DWORD dwProcessId, DWORD dwThreadId)
{
switch (dwEvent)
{
case DLL_PROCESS_ATTACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_PROCESS_ATTACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_THREAD_ATTACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_THREAD_ATTACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_THREAD_DETACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_THREAD_DETACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_PROCESS_DETACH:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_PROCESS_DETACH ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_PROCESS_EXITING:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_PROCESS_EXITING ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
case DLL_SYSTEM_STARTED:
PMLOGMSG(ZONE_INIT,(TEXT("DLL_SYSTEM_STARTED ProcessId %X, ThreadId %X\r\n"), dwProcessId, dwThreadId));
break;
default:
break;
}
}

// 1.2 Real API
EXTERN_C HANDLE WINAPI
PmSetSystemPowerRequirement(LPCTSTR pszName)
{
HANDLE hRequirement = NULL;
//...
return hRequirement;
}

// 2. API function table
static VOID PmAPINotifyProc(DWORD dwEvent, DWORD dwProcessId, DWORD dwThreadId);
const PFNVOID VTable[] = {
(PFNVOID)PmAPINotifyProc,
(PFNVOID)NULL,
(PFNVOID)PmSetSystemPowerRequirement,
};

// 3. Signatures table
#define NUM_APIS (sizeof(VTable) / sizeof(VTable[0]))
const DWORD SigTable[NUM_APIS] = {
FNSIG3(DW, DW, DW), // PmAPINotifyProc
FNSIG0(), // Kernel reserved entry
FNSIG1(PTR), // Signature of PmSetSystemPowerRequirement
};

// 4. Client APIs (defined in header file)
#define PM_APISET_ID 25 // APISet ID
#define PM_APISET_NAME "CPM" // APISet Name

#define PmSetSystemPowerRequirement_xxx \
PRIV_IMPLICIT_DECL(HANDLE, PM_APISET_ID, 3, (LPCTSTR psz))

__inline HANDLE
SetSystemPowerRequirement(LPCTSTR pszState) {
ASSERT(QueryAPISetID(PM_APISET_NAME) == PM_APISET_ID);
return IsAPIReady(PM_APISET_ID) ? PmSetSystemPowerRequirement_xxx(pszState) : NULL;
}

// 5. Create and register APISet
// PSL Initialization
ghPmAPISet = CreateAPISet(PM_APISET_NAME, NUM_APIS, VTable, SigTable);
if (ghPmAPISet == NULL) {
PMLOGMSG(ZONE_ERROR, (_T("%s: CreateAPISet() failed: %d\r\n"), pszFname, GetLastError()));
return FALSE;
}
if (!RegisterAPISet(ghPmAPISet, PM_APISET_ID)) {
PMLOGMSG(ZONE_ERROR, (_T("%s: RegisterAPISet() failed: %d\r\n"), pszFname, GetLastError()));
CloseHandle(ghPmAPISet);
return FALSE;
}