Prev: World Server Example
Module Class
Each native module is implemented as a class inheriting from abstract class
NativeModule, defined in
NativeModule.h. It
must implement the following abstract virtual functions:
In addition, it
can implement the following virtual functions:
- virtual const char *ModuleVersion();
Get the version of this module. This should return a number in major.minor notation. Surrounding non-numeric characters are trimmed. If it returns NULL (the default), the module version is set to 0.0.
- virtual void Cancel();
Cancel any outstanding asynchronous request started in ExecuteL(). The default implementation does nothing.
- virtual TInt HandleError(TInt error);
Handle an error of an asynchronous request started in ExecuteL(). This should return KErrNone if the error will be handled by the next call to ExecuteL(), otherwise return an error code to be handled by the runtime system. The default implementation simply returns error.
- virtual const TDesC& GetErrorMessage(TInt error);
Get a human readable translation for an error code produced by functions in this module, or an empty string for a default message. The default implementation returns the empty string.
- virtual TBool HandleProcessEventL(TEventCode type, const TAny *param);
Handle an event on the process console or UI. This can be used to show and hide alternative views on top of the process view, or to otherwise react to console being hidden or shown. The default implementation does nothing. See also Runtime::ProcessEvent.
The only variable visible to subclasses is:
- protected: Runtime *runtime;
A pointer to the m runtime system, providing functions to create and manipulate m objects and to access the application.
World Server Example
We start our "world" module by including the necessary files and defining the skeleton of our class. Our module will maintain a connection to the world server, i.e. an instance of the Symbian class
RWorldServer, defined in
t32wld.h. Note that all class members can be kept private, except for the implementation of the virtual functions defined in
NativeModule:
#include "NativeModule.h"
#include <t32wld.h>
class WorldModule : public NativeModule {
private:
// @todo: some declarations
// a connection to the Symbian world server, and the current city id
RWorldServer server;
TWorldId id;
// destroying the module should close the server
~WorldModule() {
server.Close();
}
protected: // from NativeModule
TInt ExpectedRuntimeVersion() { /* @todo */ }
const char *ModuleVersion() { /* @todo */ }
void ConstructL() { /* @todo */ }
Runtime::Value ExecuteL(TInt index, Runtime::Value *params,
TInt paramCount, TRequestStatus &status)
{ /* @todo */ }
};
|
We will now fill the @todo sections step by step.
Module Versioning
Since neither C++ nor Symbian offer a proper interface or DLL versioning mechanism interface, we must somehow ensure that a DLL we load really is an
m module. This is achieved by implementing the following function defined in
NativeModule:
TInt ExpectedRuntimeVersion() { return Runtime::VERSION; }
|
Runtime::VERSION is guaranteed to change if the interface defined by the Runtime class changes. Modules relying on another version of the runtime system can therefore be identified and prevented from initializing before harming the m application.
Furthermore, there is a simple module versioning mechanism built into m: Each module has its ..version constant. For a native module, this version is the string returned by ModuleVersion(). If you manage your versions via CVS, you can automatically have CVS insert the current revision, for instance
const char *ModuleVersion() { return "$Revision: 887 $"; }
|
The m runtime system will trim non-numeric characters and just assign 887 to ..version.
Defining Module Members
As outlined above, each function is identified by its index (a unique integer). The simplest way to define those indices is to define an (anonymous) enumeration. The functions and any global module variables and constants are then added to m's internal tables in the 2nd phase constructor of the class, in its
ConstructL() function, using the following functions from
class Runtime:
- void AddNativeFunctionL(const TDesC &name, TInt minParamCount, TInt maxParamCount, TInt index);
Adds a function by linking it to its index, and specifies the minimal and maximal number of arguments permitted. The m compiler can thus perform some (minimal) checks when compiling a call to the function.
- Runtime::Value &GetVariableL(const TDesC &module, const TDesC &variable, TBool isConst = EFalse);
Adds a global variable to a module and returns a reference to it. If the variable does not exist, it is created and initialized to null.
- Runtime::Value &AddConstantL(const TDesC &name, const TDesC &value);
Runtime::Value &AddConstantL(const TDesC &name, TReal value);
void AddConstantL(const TDesC &name, Runtime::Value value);
Add a constant to the module, e.g.
runtime->AddConstantL(_L("answer"), 42);
|
World Server Example, Continued
For the functions and the
calls variable of our "world" module, defining module members looks as follows:
// the functions this module supports
// the enumeration values correspond to function indices
enum {
CountFunction, FindFunction, FirstFunction, HomeFunction, NextFunction
};
|
Since we use them more than once, we also declare the module and variable name descriptor constants for the world.calls variable:
// the names to access the calls variable
_LIT(WORLD, "world");
_LIT(CALLS, "calls");
|
void ConstructL() {
// the five functions this module supports
runtime->AddNativeFunctionL(_L("count"), 0, 0, CountFunction);
runtime->AddNativeFunctionL(_L("find"), 1, 2, FindFunction);
runtime->AddNativeFunctionL(_L("first"), 0, 0, FirstFunction);
runtime->AddNativeFunctionL(_L("home"), 0, 0, HomeFunction);
runtime->AddNativeFunctionL(_L("next"), 0, 0, NextFunction);
// initialize the variable counting the function calls
runtime->GetVariableL(WORLD, CALLS).SetNumber(0);
}
|
None of our functions take arguments except world.find, which can have one or two.
The value reference returned by runtime->GetVariableL() is set to (the number) zero (the C++ type of an m variables is Runtime::Value, which will be explained in detail in section * ()).
Implementing Functions
Each native module has only a single entry point for all the functions it declares: the virtual function
ExecuteL(). The actual function called is passed as a parameter, and corresponds to the index set when adding the function via
runtime->AddNativeFunctionL().
Besides the function index, ExecuteL() gets the following parameters passed by the m runtime system:
- params: a pointer to the parameter array, params[0] corresponds to the first parameter, params[1] to the second, an so on.
- paramCount: the number of parameters passed in the actual call. This is guaranteed to be in the bounds specified by the corresponding runtime->AddNativeFunctionL() call.
- status: a reference to the asynchronous request state. Only functions performing asynchronous requests will have to deal with this parameters; this is explained in section * ().
ExecuteL() returns the
Runtime::Value which is the result of the
m function.
World Server Example, Continued
The skeleton for the functions of our module
world might therefore look as follows:
Runtime::Value ExecuteL(TInt index, Runtime::Value *params,
TInt paramCount, TRequestStatus &status)
{
// if the world server is not connected, connect it
if (server.Handle() == 0) User::LeaveIfError(server.Connect());
// a variable which will contain the function result
Runtime::Value result;
// @todo: increment calls variable
switch (index) {
case CountFunction:
// @todo
case FindFunction:
// @todo
case FirstFunction:
// @todo
case HomeFunction:
// @todo
case NextFunction:
// @todo
}
return result;
}
|
Note that the server connection is not established until the first call of ExecuteL(), i.e. until a function of the module is called. The reasoning behind this is explained in section * ().
The next chapter will replace the @todo comments by actual code.
Next: DLL Entry Points© 2004-2011 airbit AG, CH-8008 Zürich
Document AB-M-NMI-887