Documente Academic
Documente Profesional
Documente Cultură
DatasharingandpersistEnce
Data Sharing and Persistence
part of the Using Symbian OS series
Published by:
Symbian Software Limited
2-6 Boundary Row
Southwark
London SE1 8HP
UK
www.symbian.com
Introduction
This booklet introduces four important mechanisms provided by Symbian OS for sharing data
securely across threads and processes:
• Symbian SQL
• Publish and Subscribe
• Message queues
• Central Repository.
Two of these mechanisms – Symbian SQL and the Central Repository – provide mechanisms for
persisting data. The other two – Publish and Subscribe and message queues – provide
mechanisms for transient communications (that is, the data that does not persist after the
phone reboots).
This booklet ends with a brief mention of using shared memory regions (which are known as
chunks). However, their use is beyond the scope of this booklet and is not covered in detail.
We do not cover the Symbian OS client-server architecture, which is another mechanism for
sharing data on Symbian OS. Similarly, we do not cover persisting data to files. For an
introduction to these topics, refer to the Symbian Press book, Developing Software for
Symbian OS, Second Edition by Steve Babin, at
developer.symbian.com/main/documentation/books/books_files/dasos/index.jsp.
Symbian SQL
Symbian OS includes an SQL database service to applications and other Symbian OS software
components. As Figure 1 shows, it is implemented using the Symbian OS client-server model
and is based on the SQLite database engine. SQLite is a widely deployed open source
database engine. You can find out more about SQLite at www.sqlite.org.
Here is an example of the data definition part of the language (DDL) that shows using the
CREATE keyword to create a database table:
Notice that we are using the PRIMARY KEY keyword which causes a unique index to be
created automatically.
We can then use the data manipulation language (DML) keywords INSERT INTO to add a
record to the table:
Other common DML keywords are UPDATE to modify data and DELETE FROM to delete
records.
Finally, here is an example of a query, which is the most common type of SQL operation:
This returns a record set containing a row for each record in the booking table, sorted in
descending alphabetical order of student name.
Queries start with the SELECT keyword. You can use the WHERE clause to restrict the results
to certain criteria and you can use ORDER BY to sort the results. The supported query syntax
is extensive and includes using JOIN to combine the results from two or more tables (unlike
the older Symbian OS DBMS database service, which does not support the JOIN keyword).
Like many programming languages, you can often achieve the same results in more than one
way, but some ways are more efficient than others. Some of the details are specific to how the
underlying components have been implemented. For detailed information about the best way
to write SQL for Symbian OS, see
developer.symbian.com/main/documentation/sdl/symbian94/sdk/doc_source/guide/System-
Libraries-subsystem-guide/SQL/SQlHowToMakeEfficientUseOf.guide.html.
3
RSqlDatabase
RSqlDatabase is a handle to the SQL database. This provides functions for creating,
opening, copying and deleting a database, etc. Here is an example of creating a non-secure
database:
RSqlDatabase myDatabase;
_LIT(KDBFileName, "C:\\public\\data\\MyDatabase.db");
TInt err = myDatabase.Create(KDBFileName);
You can create a secure database by passing the security policies that you want to apply to
the database to the Create() method. We’ll look at the database access types in the
‘Database access types’ section later in the booklet.
You can use the Open() method to open an existing database. For example:
And as you might expect, you use Close() to close the handle to the database to free
allocated resources.
RSqlDatabase also provides an Exec() function for executing a simple one-shot SQL
statement. Here we are executing a simple DML statement to update a record in the event table:
You can use the same approach to insert a row into the table. However, in real code you often
need to insert multiple rows, using different values each time. One way to do this is to build
up a different SQL statement for each row. A more efficient alternative, however, is to use the
RSqlStatement class, which enables you to prepare a single statement and substitute
different parameters for each row, as shown in the code sample on the next page.
RSqlStatement
But first let’s look at an example of using RSqlStatement to execute a SELECT statement.
You must use RSqlStatement to execute SELECT statements because RSqlDatabase
does not provide a mechanism for accessing the results of a query.
Here is an example:
RSqlStatement myStatement; // 1
CleanupClosePushL(myStatement);
myStatement.PrepareL(myDatabase, KSelectString); // 2
TInt ret;
4
if (ret != KSqlAtEnd)
{
User::LeaveIfError(ret);
}
CleanupStack::PopAndDestroy(&myStatement); // 5
Notice that in this example we are potentially retrieving multiple rows from the table, so we
read the results and retrieve the next record in a loop. We execute the loop while the return
code is KSqlAtRow. This means that we drop out of the loop if an error occurs when we
retrieve a row. If no errors occur, the loop ends when we get to the end of the rows
(KSqlAtEnd). We must also check the return value after the end of the loop in order to
handle any errors.
As mentioned earlier, RSqlStatement is also useful when you need to execute multiple DML
statements (such as INSERT and UPDATE), because you can prepare the statement once and
then substitute parameters. For example, you can use this technique to add multiple rows to a
database table, like this:
RSqlStatement myStatement; // 1
CleanupClosePushL(myStatement);
myStatement.PrepareL(myDatabase, KInsertString);
_LIT(KEvent, ":event_no");
_LIT(KName, ":student_name");
User::LeaveIfError(myStatement.BindInt(eventParam, events[i]));
User::LeaveIfError(myStatement.BindText(nameParam,(*names)[i]));
User::LeaveIfError(myStatement.Exec());
myStatement.Reset();
}
CleanupStack::PopAndDestroy(&myStatement); // 4
When you need to do more than one INSERT, UPDATE or DELETE operation, it is best to
execute them within an explicit transaction. You do this by executing the BEGIN statement
prior to the first change and executing COMMIT after all of the changes have finished. This not
only avoids inconsistencies in the data but also speeds performance, because only one time-
consuming COMMIT operation occurs.
For example, we could add the following code before the first line of the previous example to
execute BEGIN:
_LIT(KBeginTransaction, "BEGIN");
User::LeaveIfError(myDatabase.Exec(KBeginTransaction));
CleanupStack::PushL(TCleanupItem(DoRollback, &myDatabase));
Notice that we have pushed a TCleanupItem that points to a rollback function onto the
cleanup stack. This means that we can revert the changes if a leave occurs mid-transaction.
Here is an example of the rollback function:
Then we would add the following to execute COMMIT. We need to add this after the loop that
executes the INSERT statements:
6
_LIT(KCommitTransaction, "COMMIT");
User::LeaveIfError(myDatabase.Exec(KCommitTransaction));
CleanupStack::Pop();
• To create a public shareable database, do not provide a security policy, then create the
database in a public folder, as shown in the example in the ‘RSqlDatabase’ section.
• To create a private database, use the same syntax but with the location set to the
application’s private data cage.
• To create a secure shareable database, you need to provide one or more security policies
in a container. The database is then automatically created in the SQL server’s private data
cage. For an example of creating a secure shareable database, see
developer.symbian.com/main/documentation/sdl/symbian94/sdk/doc_source/guide/System-
Libraries-subsystem-guide/SQL/UsingSymbianSQLFramework.html.
Client-server issues
Because Symbian SQL is implemented using the Symbian OS client-server model, two or more
applications or processes can share a secure database without the need to wrap it within its
own server. However, sometimes you might need to consider implementing an intermediate
server, for example, if you want to provide notifications when the database is updated
(currently, Symbian SQL does not provide notifications).
Backing up databases
Symbian OS includes software to back up data to, and restore data from, a connected PC. If
you want your application’s data and files to be backed up and restored, you need to create
an XML registration file (called backup_registration.xml) in the application’s private
data cage. This should specify the names of the files and/or directories to be backed up and
restored, including your private and public databases. For a public or private database, you
simply need to include the name or directory of the database in the backup registration file.
In the example below, the passive_backup element specifies that the entire private data
cage directory should be backed up.
For a secure shareable database, use the SID attribute of the proxy_data_manager
element in the backup registration file and set it to the Symbian SQL server’s secure ID (SID)
of 0x10281e17, as highlighted in the example above.
7
POSIX support
Symbian OS v9.5 will provide the native SQLite C API as part of its P.I.P.S. offering. P.I.P.S. is a
recursive abbreviation for ‘P.I.P.S. Is POSIX on Symbian OS’ and provides an API layer, above
the native Symbian C++ APIs, that is more closely aligned with industry standard APIs. This
makes Symbian OS more accessible to developers who program using the Standard C
language. For more information about P.I.P.S., please see
developer.symbian.com/main/documentation/books/books_files/pdf/P.I.P.S..pdf.
Publish and Subscribe is suitable for transient settings only because the properties are not
preserved when the phone shuts down, although they persist beyond the lifetime of the defining
process. Typical scenarios include multicasting the status of battery and communication links.
As Figure 2 illustrates, a publisher is a thread that defines and sets a property and a
subscriber is a thread that reads a property’s value and subscribes to notifications of changes.
The publisher and subscriber can be different threads within the same process or different
processes. Any program can act as a publisher or subscriber or both. Publishers and
subscribers do not need to know about each other or link to special client APIs.
Publish and Subscribe can be used to notify critical tasks that require real-time guarantees.
When real-time guarantees are not required, Publish and Subscribe can be used to trigger
actions that are based on the state of the property even when the client has missed a
previous state.
For properties that are updated frequently, it is recommended that you do not perform a
substantial amount of processing on each update notification because this can negatively
affect performance.
8
Properties
A Publish and Subscribe property has an identity and a type:
• The identity consists of a 64-bit integer made up of a category and a key. The category is
identified by a UID, which is normally the secure ID (SID) of the process that defines the
property. An error is generated if any other process attempts to delete the property. In
effect, this forms a data cage, preventing a process from defining, or ‘occupying,’ another
process's property. The key is a 32-bit value that identifies the property within the category.
• The type defines the data type that can be stored in the property. It can be a single
32-bit value, a contiguous set of bytes, referred to as a byte array, or Unicode text. Byte
arrays and text properties are both limited to 512 bytes; there are separate types for
larger arrays but these cannot be used in real-time code.
A property’s identity, type and value are the only things that are shared between the
publisher(s) and subscriber(s).
• Define() – create a property and define its type and identity, optionally specifying
security policies
• Delete() – remove a property from the system
• Set() – change the value of a property
• Get() – retrieve the value of a property
• Attach() – create a handle to a property
• Subscribe() – register for notification of changes
• Cancel() – unsubscribe to notification of changes.
The write operations are atomic, which means that they either succeed or do nothing. As a
result, when a thread reads a property, the data is guaranteed to be valid.
Setting and getting values is accomplished in bounded time (and are thus suitable for real-
time code) under the following conditions:
• The value being published is either a 32-bit value, or a byte array that has been
preallocated to a sufficient length at Define() time.
• The publishing thread already has an RProperty object that has been attached to the
desired category/property using Attach(), and the one-argument version of
Set()/Get() is used.
Note that the following are not accomplished in bounded time and are therefore not suitable
for real-time code:
Also see the Publish and Subscribe white paper on the Symbian Developer Network:
developer.symbian.com/main/downloads/papers/publishandsubscribe/PublishAndSubscribe_v1.0.pdf
9
Message queues
Symbian OS message queues also allow the sharing of transient data, by providing one-way,
one-to-one communication between two threads. Threads can send and receive messages from
a message queue, be notified when a message enters the queue and be notified when space
becomes available in the queue.
Message queues lend themselves to the passing of memory buffers from one thread to another
in the same or different processes. This usage can be employed effectively for processing intensive
and/or multimedia applications.
As Figure 3 illustrates, a message queue is a block of memory in the kernel. Each message is a
slot in that memory and there are a fixed number of slots, determined at the time of creation.
Messages are therefore of fixed size and the queue has a limited capacity and can potentially run
out of space. This means that message queues are not real-time safe – it is not possible to write
a message to a queue in bounded time because the queue may be full.
Message queues are transient: neither the messages nor the queues are persistent. When the last
handle to the queue is closed, cleanup is performed and the messages and queue disappear.
Message queues can be created ‘locally’ to allow communication between threads in the same
process, or ‘globally’ to allow threads in different processes to communicate. A global message
queue can be named and publicly visible to other threads or it can be anonymous, which means
that it does not have a registered global name. Using a global name is not recommended because
it is insecure. When using an anonymous message queue, you share its handle with a child process
at creation time, or with any process via the client-server architecture. This prevents other
processes from accessing the queue, which makes it a more secure way of sharing messages.
It is the responsibility of the receiving thread to validate the data that is received. There is no
in-built validation of the data passed in the messages other than basic type checking; that is,
the data type is specified as the template argument.
Although it is possible for any number of threads to have the same message queue open, in
practice there are limitations that mean that the main use case for message queues is one-way,
one-to-one communication between two threads. The reasons for this include:
• The communication is unicast in nature and so only one thread can receive each
message.
• Only one thread can do a blocking send at any one time because the kernel object has
only one slot for a thread to wait for space to become available. Similarly, only one
thread can do a blocking receive at any one time because the kernel has only one slot
for a thread to wait for data to arrive. A thread panics if it attempts to do a blocking
send or receive whilst another thread is occupying the corresponding waiting slot.
• To handle multiple threads doing non-blocking sends and/or receives, you would need to
use a polling mechanism. However, polling is not a recommended solution on a Symbian OS
smartphone, because it can quickly run down the battery.
If you require two-way communication between two threads, it is possible to use a single message
queue provided that the communication is strictly alternating – that is, Thread A sends a message
and Thread B reads that message before sending a message back. Thread A then reads this
message before sending another message back, and so on. For any other type of two-way
communication between two threads, you should set up two message queues, one for each
direction.
RMsgQueue<TInt> myQueue;
TInt message = 6;
Notice that in this example we are using the KNullDesC global constant to set the name to an
empty string. This means that the global message queue is anonymous and we need to pass its
handle to the thread to which we want to send data. This is the recommended way of working
with global queues.
Now let’s launch another process and pass the handle to the message queue:
When we pass the message queue handle in the child process’s environment slot, we can simply
pass the message queue itself, because RMsgQueue is itself derived from RHandleBase. When
we close the message queue in the sending process, the handle in the receiving process keeps
the message queue open.
Here is an example of the receiver process opening the queue and receiving a message:
RMsgQueue<TInt> myQueue;
Clearly this is an unrealistically simple example, but it demonstrates that the message queue API
is essentially straightforward. Further information about message queues can be found at
developer.symbian.com/main/documentation/sdl/symbian94/sdk/doc_source/guide/Base-
subsystem-guide/e32/MessageQueue.
The Central Repository is implemented using the Symbian OS client-server architecture and it is
fully integrated into platform security. This gives it a major advantage compared to using Symbian
INI files.
12
Central Repository settings are stored in repositories (also known as key spaces). Each repository
is identified by a UID, which must be allocated through Symbian Signed in the normal way (see
www.symbiansigned.com for further details). The UID is usually the secure ID (SID) of the owning
application. It is possible for an application to own more than one repository, but this is not
common, because each repository can store up to 232 key-value pairs (which are called settings).
Each setting is identified by an integer key and has a value that can be integer, float, text or
binary. You specify security policies that control who can read and write the settings in the
initialization file. You can also specify metadata that controls whether settings are included in
backup and RFS (restore factory settings) operations.
Initialization files
You cannot create a repository through the API; instead, you create one by supplying an
initialization file. Smartphone manufacturers include the initialization files for applications and
other executables in the \private\10202be9\ folder in ROM. Provided that they are Symbian
Signed, after-market applications can also provide initialization files, and do so by providing
them for installation by the software installer. Currently, applications that are not Symbian Signed
cannot use the Central Repository mechanism.
When applications (including those installed in ROM) are upgraded by a SIS file, their Central
Repository initialization files can be upgraded too.
Initialization files are named after the repository UID and a filename extension that depends on
the file format – .txt for plain text format (such as F0009396.txt) and .cre for the binary
format (such as F0009396.cre). When using the plain text format, you must save the file in
UTF-16 format.
The binary format provides faster access. However, unless the file is large, the plain text format
is generally preferred because it is easier to work with. But you can use the CentRepConv tool
to convert a binary file to text for debugging, etc. There’s more on this in the ‘The CentRepConv
conversion tool’ section, later in the booklet.
13
You need to pay particular attention to the format and syntax of the initialization file, because
these are a common source of errors. Also, because you can’t change the security policy and
metadata after initialization, you need to think these through in advance. Try to think what will
be needed in the future and provide for it. A common approach is to specify security policies for
reserved ranges of settings in order to cover future eventualities.
The initialization file has a number of sections. Here is an example initialization file, which is
followed by brief explanations of the various sections under separate subheadings.
[owner]
0x2000A970 # The SID of app that is responsible for backup/restore
[defaultMeta]
0x03000000 # Enable backup/restore & restore factory settings (RFS)
[platsec]
# Default security policy
cap_rd=AlwaysPass cap_wr=WriteDeviceData
[main]
# Syntax is: Key Type InitialValue <Metadata> <platsec>
1 string "Unknown"
2 int 100
3 real 54.56
Header
The header contains two lines that are always the same. The version number refers to the file
format version; currently this should be version 1.
You can use the hash (#) sign anywhere in the file to add comments.
[Owner]
The [Owner]section is optional and is used to specify the SID of the application that owns the
repository and is responsible for the backup and restore operation, if relevant. There’s more on
backup and restore in the ‘Backing up your repository’ section.
[defaultMeta]
The [defaultMeta]section is also optional. If present, it specifies default metadata values
which may be overridden by individual metadata values assigned in the [main]section. The
metadata is a 32-bit number, of which the most significant 8 bits of the value are reserved for
internal use and the least significant 24 are made available for client metadata. Currently, there
are two reserved metadata bits that are published. These are:
In our example file, there are two values in this section. The first applies to all of the settings to
which we don’t give a different metadata value. It is a bitwise AND of 0x01000000 (which means
enable backup and restore) and 0x02000000 (which means enable restore factory settings). The
second value applies to keys in the range 0x1000 to 0x2000 and it enables these settings to
be backed up and restored but stops them being restored to their factory settings during an RFS
operation. The factory settings are the original values specified in the [main] section.
You can also use a key mask to specify metadata values for a group of settings. There’s more on
this in the ‘Key masks’ section.
[platsec]
The [platsec]section is compulsory and specifies default security policies, in terms of SIDs
(which restrict access to identified applications) and/or capabilities (which enable sharing based
on capabilities). You must have a general default security policy, otherwise none of the settings
will be accessible. You can also specify security policies for ranges of settings, as we did in the
default metadata section, or groups of settings by using a key mask. Like the default metadata
settings, the default security policies can be overridden on individual settings in the [main]
section.
In our example we have two security policies in this section. The first applies to all of the settings
to which we don’t give a different security policy. It specifies that, by default, the settings can be
read by all processes and can only be modified by processes that have the WriteDeviceData
capability. The second security policy applies to a group of keys identified by a key mask/partial
key combination. It specifies that the ReadDeviceData capability is required to read these
settings and that only a process that has a specific SID can modify them. There’s more on key
masks shortly.
Note that when you specify a SID, you can only specify up to three capabilities, rather than the
maximum of seven when you do not specify a SID.
[main]
The [main]section enables you to specify the data type and initial values (and optionally
metadata and security policies) for individual settings, ranges of settings or groups of settings
specified with a key mask and partial key.
It is not necessary to specify all settings in the initialization file; it is possible to create settings
dynamically through the API. In fact, the [main]section can be empty. However, if you do specify
settings in the [main]section, you must define their initial values.
In our example we have simply specified initial values for keys 1, 2 and 3.
Key masks
Sometimes you may want to define more complex data structures, such as a 2D structure of
columns and rows or a 3D structure of tables, columns and rows. You can do this by using some
bits in the 32-bit key to identify the table, some to identify the column and the rest the row.
To illustrate how this works, suppose you want to store four pieces of information about your
application’s most recently used files, such as file name, description, date last used and duration
of use. You could create a 2D data structure within the repository by using two bits in the 32-bit
15
key to represent the fields (file name, description, date last used and duration of use) and the
other bits to represent the row number. So 0x00000011 could be the key for the name of the
most recently used file, 0x00000021 could be the key for its description, 0x00000031 could
be the key for its last use date, 0x00000041 could be the key for its duration of use and
0x00000012 could be the key for the name of the second most recently used file, etc., as
shown in the following table.
Table 1: Example keys for storing information about most recently used files
On occasions you may want to refer to groups of settings without specifying them individually
by key. We can do this using a key mask and a partial key. The key mask is a 32-bit hexadecimal
number that represents a bit mask, in which binary 1s indicate mandatory values and binary 0s
indicate non-mandatory ones. The partial key represents the pattern to be matched.
For example, continuing our recently used files example, suppose you want to set a platform
security setting on the recently used file name field. Here is the key mask:
0XFFFFFFF0
0x00000010
Taken together, these specify all of the repository keys with the most significant 28 bits equal to
0x0000001, which in this example are the keys representing ‘File name’ of the most recently
used files.
You can use key masks to specify groups of settings in some of the API functions as well as in
the initialization file, as we saw in the example shown earlier in the ‘Initialization files’ section.
To open several repositories, you must create one CRepository object for each of them.
The CRepository object provides Get(), Set(), Create() and Delete() functions for
working with settings. Set() creates the setting if it doesn’t exist, so Create() is not generally
used. Note that Create() fails if the setting already exists.
16
Here is an example of using the Get() function to retrieve a file name stored in the Central
Repository:
TBuf<KMaxFileName> filename;
const TUint32 KKey1 = 0x01;
There are functions for finding settings, FindL(), FindNeqL(), FindEqL(), requesting
notification of changes to settings, NotifyRequest(), and restoring the default values,
sometimes called factory settings specified in the initialization file Reset().
2. In the [owner]section in the Central Repository initialization file, specify the SID of the
application that owns the repository and is responsible for the backup and restore.
17
3. Use metadata values in the initialization file to specify the settings to be backed up. A setting
is backed up if bit 25 of its metadata value is set. You can set this bit for the whole repository
and groups of settings in the [defaultMeta] section or for individual settings using the
metadata field in the [main] section.
Run the tool from the eshell prompt in the Windows emulator. Note that file paths are Symbian
OS paths, not paths in the PC’s native file system. The tool can tell whether it is a text to binary
conversion or vice versa from the filename extension.
The -nowait option is useful when the tool is run as part of an automated process, such as from
a build script, as it stops the tool from waiting for an acknowledgement.
Memory chunks
Symbian OS provides support for shared memory regions that can be accessed directly across
multiple threads and processes. These shared memory regions are known as memory chunks
(usually shortened to chunks). When a chunk can be shared across processes, it is known as a
global chunk and when shared across threads in the same process, it is known as a local chunk.
You create a chunk and access existing chunks by using the RChunk API.
Global chunks have the advantage that they are a fast and efficient way of sharing data between
processes. However, their use has a number of important caveats:
• The chunk may not be mapped at the same memory address in each process and
therefore storing ordinary pointers inside the chunk cannot be guaranteed to work. In
each process you should express the address of a data item in the chunk as the chunk’s
base address plus an offset. You can use RChunk::Base() to obtain the base address
of a chunk within a given process.
• You must implement your own concurrency mechanism, for example, using a mutex to
prevent more than one thread from updating data at the same time and semaphores to
enforce the order of execution. Concurrency mechanisms can be complex and you need to
design and implement them well in order for them to work correctly, even though the
basic building blocks are provided. The introduction of SMP (symmetric multiprocessing)
enabled versions of Symbian OS will make it even more important that your concurrency
mechanisms are well designed.
• There is no built-in validation or security. For example, you need to check that a pointer
does in fact point to data within the chunk, otherwise the thread will panic. If you are
using the chunk to share data with code that you do not trust, you must not dereference
any pointers without validating that they refer to an expected and reasonable location,
and must never call virtual methods on shared objects. It is much safer to communicate
with untrusted code via the client-server architecture.
18
Nevertheless, global chunks can sometimes be a useful solution, particularly when you need to
share large data structures between different parts of your own code.
The term shared memory chunk generally refers to chunks that are used for sharing data between
a user-side and a kernel-side process and are typically used by device drivers. The details of
using shared memory chunks differ from local and global chunks. However, the caveats, including
the need to be careful to synchronize access to the chunk through mechanisms such as mutexes,
remain the same.
Conclusion
Symbian OS provides a variety of mechanisms for sharing data between threads and processes
and it provides the SQL service and Central Repository for persisting application data and settings
respectively.
This booklet has introduced four of these mechanisms and touched upon the use of memory
chunks. The amount of space allocated in this booklet to each these mechanisms does not
necessarily reflect their relative importance. Symbian SQL and the Central Repository are both
covered in some detail because the former is new and the latter has some setup details that you
need to understand in order to take advantage of its features. Publish and Subscribe and message
queues are covered more briefly because their usage is relatively straightforward and Publish
and Subscribe is already well documented in the public domain.
New from
Symbian OS Explained
by Jo Stichbury
Symbian OS Internals
by Jane Sales
Published Booklets
.NET Development on S60
A Guide to P.I.P.S.
Carbide.c++ v1.3
Coding Standards
Coding Tips
Coverity Prevent on Symbian OS (SDN++ only)
Creating Location-Aware Applications
Essential S60 - Developers’ Guide
Getting Started
Getting to Market
Java ME on Symbian OS
Localization (SDN++ only)
Performance Tips
Platform Security for all
Quick Recipes Taster
Ready for ROM
Translated Booklets
Chinese Russian
Japanese Persian
Korean Italian
Spanish
Using Symbian OS
DatasharingandpersistEnce
Symbian Press
Symbian Press publishes books designed to
communicate authoritative, timely, relevant and
practical information about Symbian OS and related
technologies. Information about the Symbian Press
series can be found at developer.symbian.com/books