Tech Note 12: Creating Symbian Native Libraries

December 09, 2008

© NSB Corporation. All rights reserved.

This information in this Tech Note is adapted from StyleTap's documentation with their permission. Copyright 2007 StyleTap Inc. All Rights Reserved.

NS Basic/Symbian OS does not provide direct access to the Symbian OS API functions. However, it is possible to use them by wrapping them in a Symbian DLL, which is then installed with your app. These DLLs need to be developed using Symbian's Carbide C++ development environment. Such work is best done by someone experienced in Symbian development.

If you are experienced in Symbian C++ development and are interested in creating DLLs for use with NS Basic/Symbian OS, please let us know. We will put you in touch with users that need apps developed.

Here's the sample of a dll that has one function that just adds a bunch of integers and returns the result. There is also a sample app to call it. http://download.styletap.com/9d46f57268cddfd7d72d0612dac04cc0/SampleNativeLibrary.zip

Introduction

In certain situations, an application needs to access system facilities or third- party libraries that are not part of the StyleTap Platform API. In these situations, the application developer might consider porting the entire application to the native OS (at great expense) or asking the StyleTap engineering team to extend its API (not necessarily on the developers' schedule). Instead, native library support provides a quick and cost-effective alternative: It allows the developer to access native facilities directly, without requiring any special support from StyleTap engineering or third-party vendors.

Native library support allows an application to load a Symbian dynamic-link library (DLL) and to call functions exported by that library. The DLL can be supplied by the developer or a third party. The DLL in turn can call Symbian OS APIs directly, or other DLLs. Using native library support is not a trivial programming exercise. As developer, you must deal with these issues:

1. DLLs run as native ARM code.

2. Native ARM code runs in little-endian mode, so that all 2-, 4- and 8-byte values are stored with the least significant bytes first. You are responsible for converting the endianness of values - both on input and on output.

3. Native ARM memory access to 4-byte values must be on 4-byte aligned addresses.

4. Parameters passed to native ARM functions must follow the ARM standard (see ARM-THUMB Procedure Call Standard, document number SWS ESPC 0002 B-01 from ARM Limited). In general, this means that all parameters must be widened to 4-bytes values. StyleTap Platform takes care of moving the parameters to the appropriate registers and the stack before calling a native function, but you must still form the list of parameters as an array of 4-byte values (this array has to be 2-byte aligned). Each function returns one or two 4-byte values, depending on the type of the return value (e.g. two words for a returned type of double).

5. You must make sure that your DLL has a superset of the your app's security capabilities, or else the LoadNativeLibrary() call will fail. The capabilities are defined in the .mmp file with the CAPABILITIES line. At the time of writing, following capabilities are used: LocalServices, NetworkServices, ReadUserData, WriteUserData.

6. If your DLL needs to use TLS, then it must be careful not to conflict with StyleTap executes UserSvr::DllSetTls (1, anAddress), and therefore DLLs should use a different handle value than 1.

7. Finally, NS Basic/Symbian OS applications run on a thread that is not the main UI thread, and thus the native library functions will be operating in this context. Certain Symbian OS APIs will not work unless they are called from the UI thread; unfortunately, neither Symbian nor its licensee's seem to document such restrictions. The StyleTap application thread does have an Active Object scheduler installed, a cleanup stack installed, and a large sized stack (e.g. 0x14000 bytes) and heap chunk allocated (e.g. 8MB).

Tutorial

The first call you make is LoadNativeLibrary(), which uses the library path parameter to find and load the DLL. Note that the path parameter only contains the filename in the Symbian environment, because all executables and DLLs are stored in the same location (\sys\bin) since the advent of platform security in the EKA2 kernel first available in v9.

LoadNativeLibrary() returns a handle to the library that you use subsequently to retrieve the function addresses of functions exported by the library. For each function, call GetNativeLibraryFunctionAddressByOrdinal() with the ordinal number of the function and the library handle. The returned function address remains valid as long as the library remains loaded.

What is the ordinal number? The ordinal number is determined by the build tools for each exported function from the DLL. After your DLL has been built, you can find the ordinals by running the tool PETRAN with the -dump e option on your .dll file. The output of PETRAN will show the address of the exported function and the ordinal number. Find the actual name of the exported function by locating the address in the .map file.

It is by far simplest to export only standalone functions from your DLL, although it is possible to export C++ classes. With C++ classes, you will need to find the ordinals for all of the static member functions, and the address of the vtable for the virtual functions. In this paper, we will only discuss standalone functions.

To call the native function, use CallNativeLibraryFunction(), passing the function address that was returned by GetNativeLibraryFunctionAddressByOrdinal(). You must supply a contiguous block of memory containing all of the parameters for the function you are calling. The block consists of an array of 4-byte words and each word generally contains one parameter; and this array must be 2-byte aligned. The first word contains the first parameter and so forth. Note that each word is in little-endian format with the least significant byte first and the most significant byte last - and this applies to addresses or pointers as well as to data values. If the parameter is of a type smaller than 4 bytes (for example, a short 2-byte integer), it must first be "widened" to 4 bytes when stored in the block. For types that occupy 8 bytes (for example, C++ type double), the value occupies two contiguous words in the parameter block, with the first being the least significant word.

The return value will be zero, one or two words in size, depending on the function type. A void function returns zero words. Common types (4 bytes or less in size) are returned in a single word, widened if necessary. Types that occupy 8 bytes are returned in two words. Types larger than 8 bytes are converted by the compiler to a hidden pointer parameter that precedes all other parameters, and that pointer references a local variable that is to contain the return value.

For more details, see ARM-THUMB Procedure Call Standard, document number SWS ESPC 0002 B-01 from ARM Limited.

Finally, call UnloadNativeLibrary() after you no longer need to use any functions in the library. This will free the resources used by the library.