Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Symbian OS Explained - Effective C++ Programming For Smartphones (2005) [eng].pdf
Скачиваний:
60
Добавлен:
16.08.2013
Размер:
2.62 Mб
Скачать

THREAD-LOCAL STORAGE

223

Incidentally, the issue of not allowing non-constant global data in DLLs highlights another difference between the behavior of Windows emulator builds and builds for target hardware. The emulator can use underlying Windows DLL mechanisms to provide per-process DLL data. If you do inadvertently use non-constant global data in your code, it will go undetected on emulator builds and will only fail when building for target hardware.

Symbian OS DLLs must not contain writable global or static data. The only global data which may be used are constants, either of the built-in types or of classes with no constructor.

13.4 Thread-Local Storage

As I mentioned, the lack of writable global data in DLLs can be difficult when you are porting code to run on Symbian OS. However, the operating system does provide a mechanism whereby a DLL can manage writable static data on a per-thread basis using thread-local storage, commonly known as ”TLS”. This allocates a single machine word of writable static data per thread for every DLL, regardless of whether the DLL uses it. Obviously, the memory overhead is far less significant than allocating a 4 KB chunk for each DLL which uses static data. However, the price of using TLS instead of direct memory access is performance; data is retrieved from TLS about 30 times slower than direct access, because the lookup involves a context switch to the kernel in order to access the data.

The use of TLS for per-thread access to global static data is safe because it avoids complications when the DLL is loaded into multiple processes. However, for writable static data to be used by multiple threads, this approach must be extended. One technique uses a server to store the data, which has the benefit of being able to use static data without the need for TLS, because it is a process. The server can make this data available to its clients, which may run in multiple threads.3 Of course, the inter-process context switch required to access the server also has performance implications, as I discuss in Chapter 11.

The TLS slot can be used directly if you have only one machine word of data to store. For extensibility, it is more likely that you’ll use it to store a pointer to a struct or simple T Class which encapsulates all the data you would otherwise have declared as static.

Thread-local storage is usually initialized when the DLL is attached to a thread within the DLL entry point, E32Dll(). Typically, code is

3 You can find an example of this technique in the EpocStat product released by Peroon (www.peroon.com/Downloads.html), for which full source code is available for download.

224

BINARY TYPES

added to construct the struct containing the global data and store it in thread-local storage using the static function Dll::SetTLS(). (When the DLL is detached from the thread, the TLS slot should be reset and the static data deleted.) To access the data, you should use the static function Dll::Tls(). This will return a TAny* which can be cast and used to access the data. For simplicity, you may wish to provide a utility function, or set of functions, to access the data from a single point.

Here’s some example code to illustrate the use of thread-local storage when implementing a task manager which runs as a single instance. The code is a modification of the previous version above, and can now be used within a DLL:

// TaskManager.h

class CTask; // Defined elsewhere

class CTaskManager : public CBase

{

public:

IMPORT_C static CTaskManager* New();

CTaskManager(); public:

IMPORT_C void AddTaskL(CTask* aTask); // ... omitted for clarity

private:

CTaskManager(); void ConstructL();

private:

CTaskManager(const CTaskManager&); // Not implemented CTaskManager& operator =(const CTaskManager&); // prevents copying

private:

RPointerArray<CTask>* iTasks; // Single instance };

//Accesses the task manager transparently through TLS inline CTaskManager* GetTaskManager()

{return (static_cast<CTaskManager*>(Dll::Tls())); }

//TaskManager.cpp

GLDEF_C TInt E32Dll(TDllReason aReason)

{

TInt r =KErrNone;

CTaskManager* taskManager = NULL;

switch (aReason)

{

#ifdef __WINS__

//On Windows, DLL attaches to the current process, not a thread case EDllProcessAttach:

#else

case EDllThreadAttach:

#endif

// Initialize TLS

taskManager = CTaskManager::New(); if (taskManager)

{

Dll::SetTls(taskManager);

THREAD-LOCAL STORAGE

225

}

break;

#ifdef __WINS__

case EDllProcessDetach:

#else

case EDllThreadDetach:

#endif

// Release TLS

taskManager = static_cast<CTaskManager*>(Dll::Tls()); if (taskManager)

{

delete taskManager; Dll::SetTls(NULL);

}

break;

default:

break;

}

return(r);

}

// Non-leaving because it is called by E32Dll() which cannot leave EXPORT_C CTaskManager* CTaskManager::New()

{

CTaskManager* me = new CTaskManager(); if (me)

{

me->iTasks = new RPointerArray<CTask>(4); if (!me->iTasks)

{

delete me; me = NULL;

}

}

return (me);

}

If you look at the documentation for class DLL in your SDK, you may find that it directs you to link against EUser.lib to use the Tls() and SetTls() functions. You’ll find this works for emulator builds, but fails to link for ARM. This is because the methods are not implemented in EUser.dll – you should now link against edllstub.lib, before linking to EUser.lib.

Thread-local storage (TLS) can be used to work around the prohibition of writable global data. However, the use of TLS affects performance; data is retrieved from TLS about 30 times slower than direct access because the lookup involves a context switch to the kernel.

226

BINARY TYPES

13.5The DLL Loader

An interesting scenario can arise when you attempt to replace a DLL with a version you have rebuilt, perhaps a debug version or one that includes tracing statements to help you track down a problem. You may find that it is not ”picked up” by the loader, which continues to run the original version.

As I described earlier, a DLL running from RAM is only loaded once, even if multiple processes use it. The DLL loader uses reference counting, and only unloads the DLL when none of its clients are running. So, if the DLL is loaded by other processes, it will not be unloaded and cannot be replaced by a newer version until the original is unloaded. This is also relevant if you are attempting to replace a ROM-based4 DLL. If a ROM version of the DLL you wish to replace is used before your component loads, your component will also end up using the ROM-based version. In effect, this means that you can never replace a DLL which is used by the application launcher shell.

The DLL is loaded by name lookup (with additional UID checking which I’ll describe shortly). If a DLL is not already loaded, the DLL loader uses a particular lookup order to find it, as follows:

1.The same directory as the process wishing to load the DLL (often c:\system\libs).

2.The directory associated with the filesystem’s default session path (which is usually the root of the C: drive).

3.The \system\libs directories on all available drives, in the following order: C: (the internal storage drive), A:, B:, D:, E:, ..., Y: and finally, Z: (the ROM).

If you wish to replace a DLL, you should ensure that you put the replacement where it will be loaded before the original version.

13.6UIDs

A UID is a signed 32-bit value which is used as a globally unique identifier. Symbian OS uses a combination of up to three UIDs to create a TUidType compound identifier. UIDs are used extensively, for example, to identify the type of a component and to verify that it is compatible and supports a particular interface. Thus, a DLL can register a type to reflect the interface it is implementing. The DLL loader can check the type of

4 In case you were wondering, you can never replace a DLL used by a component on ROM, because the ROM binaries are linked together when the ROM is built. However, you can replace a ROM DLL if the component that is using it isn’t running from ROM.

UIDs

227

a DLL (using RLibrary::Type()) to determine whether a component is of the correct type, and prevent other files which may share the same name from being loaded.

The three UIDs are identified as UID1, UID2 and UID3 and are generally used as follows:

UID1 is a system-level identifier to distinguish between EXEs (KExecutableImageUid = 0x1000007a) and DLLs (KDynamicLibraryUid = 0x10000079)

UID2 distinguishes between components having the same UID1, for example between shared libraries (KSharedLibraryUid =

0x1000008d) or polymorphic DLLs such as applications (KUidApp = 0x100039CE), recognizers (0x10003A19) or front-end processors (0x10005e32)

UID3 identifies a component uniquely. In order to ensure that each binary that needs a distinguishing UID is assigned a genuinely unique value, Symbian manages UID allocation through a central database.5

For test code, or while your code is under development, you may prefer to use a temporary UID from a range reserved for development only. These values lie in the range 0x01000000 to 0x0fffffff. You must still take care to avoid re-using UIDs in this region because a UID clash may prevent a library from loading. For this reason, these values must not be used in any released products.

You don’t need to specify UID1 for a component, because it is defined implicitly by the targettype you choose for your component (I’ll discuss the different options of targettype in the next section). For example, a component which is specified as targettype epocexe is assigned UID1=KExecutableImageUid by the system, which is built directly into the binary. By comparison, targettype dll (for a shared library component) is automatically assigned UID1=KDynamicLibraryUid. A component’s second and third UIDs, if used, must be specified as hexadecimal values in the .mmp file of the component.

For native binaries, Symbian OS uses UIDs as the primary means of identification, rather than filenames or filename extensions.

5 You can make a request to Symbian for allocation of one or more UIDs by submitting an email with the subject ”UID Request” to Symbian (UID@symbiandevnet.com). You can ask to be assigned a single UID or a block of values, usually no more than ten, although you will be granted more if you state your reasons for needing them. Your submission should also include your name (or that of the application) and your return email address.

228

BINARY TYPES

13.7The targettype Specifier

The targettype specifier in the .mmp (project) file allows you to define the particular binary type of your component. The targettype is not necessarily the extension assigned to the component when it builds, although it may be, but categorizes it for the build tools. I describe below the most common binary types you’ll encounter on Symbian OS. Various other plug-in targettypes, such as app, fep, mdl, prn and ecomiic, may be used for a polymorphic DLL.

targettype epocexe

You would logically think that any component running on Symbian OS as a separate, ”out-of-process” component, such as a server, would be built with the .exe extension. However, as I described earlier, the Windows emulator runs in a single process. So, while you can run multiple Symbian OS processes on hardware (ARM builds), on Windows each Symbian OS process is built as a DLL which runs inside a separate thread that emulates a Symbian OS process within the single Win32 emulator process, EPOC.exe.

On target hardware, if you browse the file system, select and click on a

.exe file, it will start a different process. However, this is not possible on the emulator, which is why the epocexe type was introduced in v6.0 to simulate this behavior. It instructs the build tools to build the component as a .exe for multi-process hardware platforms and as a .dll for single-process emulator builds. This allows an epocexe component to be launched directly, both in the single process of the emulator and as a separate process on target hardware. An example of a typical epocexe component is the contacts server (built as cntsrv.dll on Windows and cntsrv.exe for target hardware).

This is true for versions of Symbian OS earlier than v8.0. The new kernel in Symbian OS v8.0 has more complete process emulation on Windows, and an EXE may now be launched directly both in the emulator (although it still runs within the single emulator process) and on hardware. As a result, targettype epocexe is no longer needed and code which runs as a separate process, such as a Symbian OS server, may now be built as an EXE for both Windows and hardware platforms.

Components of this targettype should implement WinsMain(), which is exported as ordinal 1 for emulator builds, to form the DLL entry point. There should be no other exports besides this entry point for emulator builds, and there need be no exported functions at all for ARM builds. For example:

GLDEF_C TInt E32Main() // Process entry point function

{

THE targettype SPECIFIER

229

... // Omitted for clarity return (KErrNone);

}

#if defined(__WINS__) EXPORT_C TInt WinsMain()

{

E32Main();

return (KErrNone);

}

TInt E32Dll(TDllReason)

{ // DLL entry point for the DLL loader return (KErrNone);

}

#endif

targettype exedll

This targettype exists as an alternative to epocexe and allows separate process components to export functions to clients for both hardware and emulator builds. Like epocexe, the build tools interpret this differently on each platform and build the component as .exe for the multi-process hardware (ARM) platforms and as .dll for singleprocess emulator platforms. An example of this targettype is the random server, which builds as randsvr.exe for ARM builds and randsvr.dll to run on the emulator.

A component of this targettype must implement the DLL entry point function E32Dll() for emulator builds only, to allow it to be loaded as a DLL. This should be the first exported function in the .def file.

In releases up to and including v5.0, the epocexe type did not exist and exedll was used instead. This targettype is also due to be retired in EKA26 versions of Symbian OS, because the enhanced process emulation, described above, allows out-of-process components to be built as a .exe for both ARM and emulator platforms. However, to allow this type of component to export functions, a new targettype will be introduced to replace it. This will be called exexp and, on all platforms, will build components as .exe, which may export any number of entry point functions.

targettype exe

The build tools build a component of this targettype to have the .exe extension on both the emulator and target hardware. On EKA1, it is only used for basic console applications such as Symbian OS command-line

6 You may recall from Chapter 10 that Symbian identifies the new hard real-time kernel in Symbian OS v8.0 as ‘EKA2’ which stands for ‘EPOC Kernel Architecture 2’. The kernel in previous versions of Symbian OS is referred to as EKA1.