Shared Library DLLs on Qt for Symbian
Article Metadata
Code Example
Article
This article demonstrates how to create and use shared library DLLs with Qt on Symbian . It is accompanied by a simple code example that populates a label widget with a random number from a DLL when a button is pressed.
Contents |
Introduction
Shared library DLLs are a common paradigm on most operating systems; re-usable functionality is stored in a shared library that executable code can link against at build time and load at runtime. As the functionality in the DLL is shared by several clients the overall code size, memory usage and maintenance are reduced.
Shared library DLLs are created and used in much the same way on the Symbian platform as on other platforms. However there are a number of platform specific issues that developers need to be aware of:
- Symbian uses link by ordinal exclusively: Shared library DLLs provide a fixed API with a number of entry points. On some platforms entry points can be referenced either by name or by ordinal number - on Symbian you can only use the ordinal number (the names are stripped out of the DLL in order to reduce code size). In practice this means that we will need to create a .def file to ensure that the ordinals are kept in the same order between releases and thereby maintain binary compatibility. See Fundamentals of Symbian C++/Compatibility for more information.
- DLLs may need Platform security capabilities.
- DLLs need a unique identifier (UID)
- DLLs that have writeable static data are not fully supported by the free GCCE 3.4.3 compiler. For more information see Symbian Platform Support for Writeable Static Data in DLLs and WSD and the Singleton Pattern
This article walks you through the steps for creating, using and packaging a shared DLL. At the end of the article there is an overview of the code example.
Creating a shared library is relatively easy:
- Create a "skeleton" in exactly the same way as for any other platform
- Add a few Symbian specific entries to the .pro file
- Set the UID
- Export the header files
- Enable static data (if present)
- Specify required capabilities (if any)
- Build and freeze the project
Creating the library
The easiest way to create a skeleton shared library is to use the Qt Creator Wizard: File | New File or Project | Projects | C++ Library. This creates a number of files, which are discussed below.
Project file (.pro)
The .pro file created by the wizard is as shown below. The interesting lines are:
- TEMPLATE=lib which tells qmake that we're building a shared library DLL
- DEFINES += QTENGINEDLL_LIBRARY, which creates a project specific macro which we use (and explain) in the header files section below.
The remaining lines define the sources and headers, and which Qt libraries the DLL will link against. Note that in this case that will only be the "core" libraries because we've removed the "gui" (we might equally well have just put QT = core).
QT -= gui
TEMPLATE = lib
DEFINES += QTENGINEDLL_LIBRARY
SOURCES += qtenginedll.cpp
HEADERS += qtenginedll.h\
qtenginedll_global.h
In addition to the general settings, we define some Symbian-specific sections as shown in the following code fragment.
#Symbian specific definitions
symbian: {
TARGET.UID3 = 0xEF76E062
TARGET.EPOCALLOWDLLDATA = 1
# TARGET.CAPABILITY = ReadDeviceData
BLD_INF_RULES.prj_exports += "qtenginedll.h"
BLD_INF_RULES.prj_exports += "qtenginedll_global.h"
}
Set DLL unique identifier
You must define a UID3 for the application using TARGET.UID3. The UID used below is a random number from the 0xE test range, however if you're deploying an app commercially you'd get a specific UID allocated by Symbian Signed.
Export header files
As this is a DLL we export its public header files into the SDK's /epoc32/include/ directory using the BLD_INF_RULES.prj_exports keyword. This adds the headers into the include path of any application built against the SDK.
Enable static data
DLLs that use writable static data (WSD) must define TARGET.EPOCALLOWDLLDATA = 1 as shown. Writable static data has some associated costs and should be avoided if your DLL is to be shared by many clients. See Symbian Platform Support for Writeable Static Data in DLLs and WSD and the Singleton Pattern for more information.
Set platform security capabilities
Access to sensitive APIs are protected by platform security capabilities - these are discussed in the article Fundamentals of Symbian C++/Platform Security. Capabilities are treated slightly differently for EXES and DLLs. In essence, while an EXE must be granted the capabilities required to call the APIs that it uses (or which are used by its loaded DLLs), a DLL must be given all the capabilities of all the EXEs that might need to load it. For a DLL used by a single application exe this would be the same as the application's capabilities. If however the DLL is to be used by arbitrary clients, you will need to give it as many capabilities as possible. Capabilities are added using TARGET.CAPABILITY; in this example we don't need any, so the option is commented out.
Header files (.h)
The public header files are shown below. The first header file defines a single class QtEngineDll with a constructor and a single method that returns a random number between two values (by default, between zero and one hundred).
#ifndef QTENGINEDLL_H
#define QTENGINEDLL_H
#include "qtenginedll_global.h"
class QTENGINEDLLSHARED_EXPORT QtEngineDll {
public:
QtEngineDll();
int randInt(int aLow=0, int aHigh=100);
};
#endif // QTENGINEDLL_H
The interesting part of the class declaration is the use of the QTENGINEDLLSHARED_EXPORT macro, which marks the class for export/import from the DLL. The definition of this macro in the #includeed header file qtenginedll_global.h show below.
#ifndef QTENGINEDLL_GLOBAL_H
#define QTENGINEDLL_GLOBAL_H
#include <QtCore/qglobal.h>
#if defined(QTENGINEDLL_LIBRARY)
# define QTENGINEDLLSHARED_EXPORT Q_DECL_EXPORT
#else
# define QTENGINEDLLSHARED_EXPORT Q_DECL_IMPORT
#endif
#endif // QTENGINEDLL_GLOBAL_H
What is happening here is basically a trick to ensure that we can use a single header file to apply the correct dllexport / dllimport declaration to the class when exporting / importing the DLL (some compilers don't like you to mix them).
In the DLL .pro file we define the QTENGINEDLL LIBRARY macro; when building the DLL the class is marked with Q_DECL_EXPORT. When we build the application (or other client) we don't define this macro, so the header is marked as Q_DECL_IMPORT (an import).Source file (.cpp)
There is nothing "special" about the source file, however I've included it below for completeness. As you can see, all this does is create a seed for the random number generator based on the time the QtEngineDll class is created, and return the next "random" value in the sequence whenever randInt() is called.
#include <QTime>
#include "qtenginedll.h"
QtEngineDll::QtEngineDll()
{
//Create random number seed
QTime time = QTime::currentTime();
qsrand((uint)time.msec());
}
int QtEngineDll::randInt(int aLow, int aHigh)
{
// Random number between low and high
return qrand() % ((aHigh + 1) - aLow) + aLow;
}
Building and freezing the DLL API
On the Symbian platform the function name is stripped from the DLL in order to reduce its size; instead of linking to the function name we link to its ordinal position in the exports. The only way we can guarantee that a new DLL is binary compatible with the old version is to make sure that the position of the exported functions is the same every time the DLL is built.
The process of ensuring that the ordinals are the same every time is known as freezing the API. This is done through the use of a .def file which records the position of each function. The format of the .def file is different for Emulator (winscw) and target (GCCE/ARMv5). To generate interface definition file you need to add the following instruction to your project profile:
MMP_RULES += EXPORTUNFROZEN
in the build log you may notice warning about your dll interface is unfrozen -- that means your interface is still in development stage and may be changed freely. To freeze your dll interface use Qt SDK command prompt
sbs -c gcce_udeb freeze
Using the DLL
Shared libraries are used in much the same way, irrespective of whether they come as part of Qt, you've created them yourself, or they come from another third party:
- #include the header file(s) for the shared library
- Add the library to the application project (.pro) file
- Create and use the objects.
The library is included and used as shown (in this case iTheEngine is a QtEngineDll that is owned by the user interface)
#include <qtenginedll.h>
...
int currentValue = iTheEngine->randInt();
To add the file to the library to the application, use the LIBS keyword in its .pro file, where qtenginedll is the name of our DLL:
LIBS += -lqtenginedll
Note that the application .pro file should also specify all the capabilities that the EXE needs to use any protected API that it calls or the DLL calls - the DLL should have all the capabilities specified in the EXE (and it may have more).
The rest of the example is trivial, so has not been reproduced here.
The last step in the process is to deploy the DLL. This is discussed in the following section.
Deploying the DLL
Deploying as part of your application
Including the DLL in your application SIS file is a reasonable approach if the application is the only client, and the main reason you've created the library as a DLL is to allow it to be patched when necessary.
Include the DLL in the application's SIS file by adding the following lines to the application project file:
symbian: {
addFiles.sources = \epoc32\release\$(PLATFORM)\$(CFG)\qtenginedll.dll
addFiles.path = \sys\bin
DEPLOYMENT += addFiles
}This will add the following lines to your pkg file:
; DEPLOYMENT
"c:/S60/devices/S60_5th_Edition_SDK_v1.0/epoc32/release/$(PLATFORM)/$(TARGET)/qtenginedll.dll" - "!:\sys\bin\qtenginedll.dll"
Deploying as and embedded SIS
Genuinely shared DLLs (ie DLLs with many clients) shouldn't be added directly into the application SIS; Instead, shared DLLs should be packaged in their own SIS files which should then be embedded in the application's SIS file (or possibly just listed as a dependency, depending on its size). This allows the DLLs to be delivered with every application that needs them and patched separately.
To create a SIS file for the DLL a DEPLOYMENT line must be added to the DLL project file, as shown:
symbian: {
addFiles.sources = qtenginedll.dll
addFiles.path = /sys/bin
DEPLOYMENT += addFiles
}Note that the SIS file would probably require further customisation, e.g. the change the vendor ID or other human readable strings. This is discussed further in Deploying a Qt Application on Symbian.
The DLL SIS file can then be embedded in your main application SIS file by making the following addition to it's project file:
myembeddedsis.pkg_postrules = "@/"C:/the_path_to_the_DLL_SISX_file/qtengine.sisx/",(0xEF76E062)"\
DEPLOYMENT += myembeddedsis
, where the UID specified is that of the DLLs SIS.
Code example overview
Description
The code example that accompanies this article is here:File:QtSymbianDllExample.zip.
The code example consists of a simple DLL (qtenginedll.dll) that exports a constructor, destructor, and a method that returns a random number. The DLL is loaded by a test UI (testuisimpledll.exe), which updates a label using the random number whenever the button is pressed.
Environment
The application (and instructions) have been tested using the following environment (note that it is expected to work in any Qt 4.6 environment):
- Device : N8 software version 111.030.0609
- Qt SDK : 1.2
- build target : Qt 4.7.4 for Symbian Anna
Building and deploying the example
The example can be built through the project file \QtSymbianDllExample\qtssymbiandllexample.pro; this references the individual project files for the DLL and the test UI, ensuring that the DLL is build first.
Assuming you are using Qt Creator you can import the example using File | Open File or Project then select the file: \QtSymbianDllExample\qtssymbiandllexample.pro.
By default Qt Creator will set the correct build configuration for a target device (GCCE). To be able to run the application on device you need to install CODA debug agent on device. You can find CODA installation package in Qt SDK by path: <QtSDK>\Symbian\sis\common\CODA
Folder structure
This is the folder structure for the example:
\QtSymbianDllExample - the parent folder, contains the "master" .pro file
\qtenginedll - engine DLL
\bwins - .def file for winscw
\eabi - .def file for eabi (armv5/GCCE)
\testui_simpledllengine - UI test code exe
Defects affecting this article
- QTCREATORBUG-771 - deployment in sub pro file should be respected.
- QTCREATORBUG-772 - no way to freeze an API
- QTCREATORBUG-760 - to get Qt Creator to build Sis file in correct step by default
- QTBUG-8518 - header files not being exported properly from sub-pro file
Note that this content was originally hosted on the Symbian Foundation developer wiki.


(no comments yet)