Writing an ECOM Plugin

The resource file

Every interface may have several implementations (implementations can also have versions, but interfaces cannot). ECOM does not restrict how these implementations are packaged in DLLs: you might have a plug-in DLL with just one implementation of one interface or several DLLs each implementing several implementations of several interfaces. An ECOM-library user does not care, but as a plug-in writer your main job is to decide what this mapping of implementations to DLLs is going to look like. Are you providing just one DLL containing all the code, or would you rather have one DLL for each implementation? It is your choice.

Most of the mapping of implementations and interfaces to plug-in DLLs is done in a special resource file that is provided with each plug-in DLL:

// <dll uid>.RSS #include "RegistryInfo.rh"

RESOURCE REGISTRY_INFO theInfo {

interfaces = {

INTERFACE_INFO {

interface_uid = <uid for the interface>;

implementations = {

IMPLEMENTATION_INFO {

implementation_uid = <uid for the Jabber implementation>; version_no = 1;

display_name = "Jabber Protocol plug-in"; default_data = "Jabber"; opaque_data = ""; },

IMPLEMENTATION_INFO

implementation.

_uid = <uid for the AIM implementations

version_no = 1;

display_name =

"Aol IM Protocol plug-in";

default_data =

"AIM";

opaque_data = "

}

The resource file uses the array resource format to allow us to specify any number of interfaces and any number of implementations for each interface.

The interface sections are quite uninteresting as they contain just the UID needed to identify them in calls to CreateImplementationL() and similar functions. The implementation sections are more interesting; they are composed of:

• implementation_uid, which is used to identify the particular implementation; it can be used in some overloads of Createlmple-mentationL() and is returned by ListImplementationsL() but it should mostly be transparent to plug-in users

• version_no, which enables ECOM to support several different versions of the same implementation; ECOM transparently loads the most recent version of the implementation

• display_name, which is used indirectly in user interface elements; some GUIs allow the user to select a particular implementation

• default_data, which contains a text string used to help in deciding which implementation is the right one to use (our example uses 'Jabber' or 'AIM' to identify the IM protocol); its interpretation depends on what the plug-in is designed to do

• opaque_data is a binary field that can be used to provide more structured information about the capabilities of a plug-in; plug-in libraries that use opaque_data also provide a special type of plugin, a custom resolver, that implements the logic of selecting the right implementation based on some external information (such as the extension of the file to be converted) and the information in the opaque data.

The default_data item supports the meta-characters '||', meaning 'OR' (a||b means 'A' OR 'B'), and '*' indicating that the plug-in can handle various data types.12 Some examples of its various uses are:

12 This and the TEComResolverParams class show that ECOM originated in the Browsing/HTTP group within Symbian. The wildcard handling and the fact that default_data

• MIME types that a browser plug-in can handle: default_data= 'text/wml' or default_data='text/*'

• file extensions that an image converter can handle: default_data= 'PNG||JPEG'

• hash algorithms implemented by the plug-in to a cryptographic library: default_data='SHA-1'.

Now we are in a better position to understand what the CImPro-tocol::NewL() code does: the resource file for the Jabber plug-in has default_data='Jabber' and the client code calls something like CImProtocol::NewL('Jabber'). This is translated into a call to CreateImplementationL() that passes 'Jabber' to ECOM. ECOM searches through the plug-ins to find the one that has that string in the resource file and loads the DLL. It then calls the DLL's entry point to instantiate the object from the right class and passes a pointer to the instance back to the client. The client can then manipulate the instance through the base interface CImProtocol.

Low-level plug-in code

The basic MMP file for an ECOM plug-in is actually quite simple:

// Jabber.mmp TARGET jabber.dll TARGETTYPE PLUGIN

// First UID means it is an ECOM plugin

// Second UID has to match the dll_uid field in the plug-in resource file UID 0x10009D8D 0x<DLL UID>

SOURCEPATH \MyPlugin SOURCE Jabber.cpp

USERINCLUDE \Bar SYSTEMINCLUDE \epoc32\include

START RESOURCE <DLL UID>.rss TARGET jabber.rsc

LIBRARY euser.lib ECOM.lib

Plug-ins are polymorphic DLLs: instead of exporting a whole series of entry points in the same way as statically-linked DLLs, ECOM plug-ins are only expected to have one entry point: a function that returns a is at times called 'data type' (TEComResolverParams::DataType) indicates that its origin was oriented towards MIME-type handling.

pointer to TImplementationProxy, which gives a table of pairs of implementation UIDs and NewL() factory functions, and the size of the table so that when somebody tries to instantiate a certain implementation UID, ECOM knows which 'factory function' to call. It looks like this:

#include <e32std.h>

#include <ImplementationProxy.h>

#include "JabberProtocol.h"

const TImplementationProxy ImplementationTable[] = {

IMPLEMENTATION_PROXY_ENTRY(KJabberImplementationUID,

CJabberProtocol::NewL), IMPLEMENTATION_PROXY_ENTRY(KJabberEnhancedImplementationUID,

CJabberProtocolEnhanced::NewL)

EXPORT_C const TImplementationProxy* ImplementationGroupProxy(

TInt& aTableCount)

aTableCount = sizeof(ImplementationTable) / sizeof(TImplementationProxy);

return ImplementationTable; }

Providing a custom resolver

An important bit of ECOM functionality is resolving: taking a list of ECOM implementations for a certain interface and selecting a specific implementation to be loaded. The default behavior is known as the default resolver and you can see how it works by looking at the ExampleResolver.cpp code that comes with the SDKs. The default resolver goes through all candidate implementations, looks at their default_data and tries to match it against the TEComResolverParams provided in the ECOM creation call (CreateImplementationL()). The only complication is that it accepts the '||' and '*' meta-characters in the default_data field, which might be limiting for some uses.

Going back to our IM example, imagine that two competing Jabber plug-ins are provided. Which one should ECOM load? Perhaps some plug-ins are experimental and it should select only the ones marked as stable. Or perhaps some of them implement encryption of passwords that are saved on disk and we prefer those to the plug-ins that don't implement it. The plug-in library could mandate that plug-in implementers have to provide some more structured information in opaque_data and then write a custom resolver that is able to understand this information.

As designers of the IM library, we could mandate that the binary field must include a flag that indicates if it is a stable or experimental release of the plug-in and a flag that indicates if passwords are encrypted on disk or not. We can then write a custom resolver that uses some complicated selection logic, such as 'prefer plug-ins that support encryption and prefer stable releases over experimental ones'.

Writing a custom resolver is quite straightforward since it is just about implementing a special ECOM plug-in interface. You provide a plug-in that implements interface UID 0x10009DD0 (you have to use this in the plug-in resource file). You also have to fill out the implementation UID and the version number, but the other fields (display_name, default_data and opaque_data) can be left empty. The C++ class exported from the plug-in has to inherit from CResolver, which is declared in \epoc32\include\ecom\resolver.h.

The complicated part is coming up with a sensible binary format and writing the custom-resolver logic. There is a code example in the Symbian Developer Library's Symbian OS reference, at ^ C++ component reference ^ Multimedia MMF ^ CMMFFormatImplementationInformation.

After you've written the custom resolver, you need to make sure that any calls to CreateImplementationL() and ListImplementa-tionsL() use the resolver UID argument with the implementation UID of your custom resolver. You can do this by documenting it somewhere or by providing some header files that do it on behalf of the client.

+1 0

Average user rating: 5 stars out of 1 votes

Post a comment

  • Receive news updates via email from this site