Series 60 Developer Platform 2.0: Ecom Plug-In Architecture: January 23, 2004
Series 60 Developer Platform 2.0: Ecom Plug-In Architecture: January 23, 2004
S E R I E S
60
P L A T F O R M
Legal Notice
Copyright 2004 Nokia Corporation. All rights reserved. Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. Java and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. Other product and company names mentioned herein may be trademarks or trade names of their respective owners. Disclaimer The information in this document is provided as is, with no warranties whatsoever, including any warranty of merchantability, fitness for any particular purpose, or any warranty otherwise arising out of any proposal, specification, or sample. Furthermore, information provided in this document is preliminary, and may be changed substantially prior to final release. This document is provided for informational purposes only. Nokia Corporation disclaims all liability, including liability for infringement of any proprietary rights, relating to implementation of information presented in this document. Nokia Corporation does not warrant or represent that such use will not infringe such rights. Nokia Corporation retains the right to make changes to this specification at any time, without notice. License A license is hereby granted to download and print a copy of this specification for personal use only. No other license to any other intellectual property rights is granted herein.
Contents
1.
1.1 1.2 1.3
Introduction........................................................................................................ 5
Purpose and Scope.........................................................................................................5 Series 60 Developer Platform .........................................................................................5 What Is ECom? ...............................................................................................................5
2. 3.
3.1 3.2 3.3
Resolution .......................................................................................................................8
4.
4.1
5.
5.1 5.2
6.
6.1 6.2 6.3 6.4 6.5
7. 8. 9.
Change History
January 23, 2004 Version 1.0 Initial document release
1.
1.1
Introduction
Purpose and Scope
This document describes the ECom framework and discusses why and how it should be used. Each component of the ECom plug-in framework is explained and an example application, EComShape, is used to illustrate the main concepts.
1.2
The ECom plug-in framework was introduced in Series 60 Developer Platform 2.0. Series 60 Developer Platform 1.0 was built on Symbian OS v6.1, which does not contain the ECom framework. This framework was first provided in Symbian OS v7.0s, the underlying OS for Series 60 Developer Platform 2.0.
1.3
What Is ECom?
ECom is a client/server framework that provides a service to instantiate, resolve, and destroy plug-ins. Essentially, it means that functionality that is required by a number of applications can be encapsulated in a DLL and accessed by multiple clients through an interface class. Previously, developers used polymorphic interface DLLs to interact with other services. This allowed software developers to extend their applications using a defined abstract interface. Developers were not concerned about the underlying implementation at compile time. When the application was being executed it would only then select the correct implementation to use. However, developers had to create their own framework to: Manage the locating, loading, and unloading of DLLs. Determine which concrete interface to instantiate. Determine which instantiation function to call, etc.
For every instance of a polymorphic interface DLL being used, programmers would have to provide code that would satisfy the above requirements needed to successfully instantiate the correct instance. This produces huge amounts of repeated functionality. ECom removes the complexity and repeated functionality because it is a generic framework that provides consistent mechanisms to address these shortfalls. To ensure that the service integrates seamlessly into the application, users of the service are not made aware that they are dealing with a requesting service. The service remains "hidden" by making use of instantiation functions, which will construct the required type of object.
2.
Client Overview
The ECom framework provides a seamless interoperation between applications and plug-ins. This means that clients are not made aware that they are employing the ECom framework when using the plug-in. The clients' only concern is how the plug-in behaves and how to interact with it. This is very important, as users want to easily extend their applications without worrying about loading and configuring plug-ins they might want to use. The interaction between ECom and plug-ins is hidden through the use of abstract interfaces and instantiation functions. These are combined to create an interface definition. This will be discussed in much greater detail later, but below is an example of an interface definition (CShape): class CShape : public CBase { public: // Instantiation function will load explicit shape static CShape* NewL(); // Instantiation function requires some sort of identifier static CShape* NewL(const TDesC& aIdentifier); // Destructor virtual ~CShape(); // Draws the shape to screen virtual void Draw(CWindowGc& aGraphicsContext) = 0; private: // Private details... };
The function SomeFunctionBeingCalled(), shown below, demonstrates how the interface definition is used. The client requests an instance of an object of type CShape, by calling the CShape::NewL() instantiation function. The instantiation function will return the default implementation of CShape. The user then proceeds to Draw() the shape to the screen. Once the operations between the application and CShape object have been completed, the CShape object is destroyed: void CClientApplication::SomeFunctionBeingCalled() { ... CShape* shape = CShape::NewL(); CleanupStack::PushL(shape); shape->DrawL(gc); CleanupStack::PopAndDestroy(shape); ... }
The client is not exposed to the ECom framework and is given the appearance that this is a conventional object being created, used, and destroyed. The only noticeable difference is that the CShape::Draw() is a pure abstract method. The CShape class definition will be discussed in more detail in Chapter 5.
3.
ECom Architecture
This section provides a general introduction to the various components required by the ECom framework to function correctly. Each item will be identified and briefly discussed. The ECom architecture consists of the following components: Interface definition Interface implementation ECom framework Resolution
3.1
Interface Definition
The interface definition, which makes use of ECom, serves two purposes: It defines the behavior of the plug-in. The functions implemented will define the services provided by the plug-in. These functions are generally implemented as pure abstract functions, which allows the interface definition to have multiple concrete implementations installed on the device. It defines instantiation functions. These functions are used to determine what concrete implementation to instantiate using the ECom framework. Examples of instantiation functions include NewL(), OpenL(), etc.
Note: Instantiation functions dont explicitly instantiate the object, but issue a request to the ECom framework, which returns a TAny (void*) object that must be reinterpreted (cast) to the interface definition. To implicitly determine which implementation function to use, ECom is given, at runtime, a cue. This cue is used in a process called resolution (described in Section 3.4) to identify the concrete implementation to be constructed.
3.2
Interface Implementation
An interface implementation is a concrete class that implements the interface definition. This provides the actual service defined by the interface definition. It is important to note that it is possible, indeed usual, to implement more than one concrete implementation. Where more than one concrete implementation is provided, they are referred to as an interface implementation collection. In order to interact with the concrete classes, the ECom framework requires the following: An array of instantiation functions; for more information, see Section 6.3. A published compiled registry information resource file that list its implementation and properties; for more information, see Section 6.4.
3.3
ECom Framework
The ECom framework provides the ability to resolve and instantiate the correct implementation of the interface definition at runtime. It uses the conventional client/server architecture. 3.3.1 ECom client
The ECom client provides a well-defined interface that controls the resolution/identification, instantiation, and destruction of the requested interface using REComSession. Once the request to instantiate an implementation occurs, all of the interface definition calls will use the instantiated implementation. When the instantiated object is destroyed it must notify the ECom framework.
Note: The RECommSession does not allow more than one connection to the ECom server per thread. 3.3.2 ECom server
The ECom server manages the clients request to instantiate, resolve, and destroy the requested plug-in. The server consists of a repository of registered implementations. This includes metadata used to resolve the correct implementation requested by the client. The implementations of the ECom plug-ins are stored in the following directory: \systems\libs\plugins. The server is constantly monitoring which implementations have been registered or removed. An implementation can be removed at any time, for example if a drive is physically removed or swapped. The server is notified via the file server if there has been some sort of change. The server then determines what changes have been made by scanning through the appropriate directories using a non-intrusive search that does not affect processing. If it notices any changes, it notifies the repository and performs the necessary modifications.
3.4
Resolution
Each concrete implementation of the interface definition provides metadata (name, description, version, etc.), which is used to describe and identify the interface; this is known as registry information. (Further information can be found in Section 6.4.) This data provides the necessary information for the ECom server to locate the correct concrete implementation by looking up the interfaces UID or a text cue. If a text cue is used, the ECom framework uses a resolver to determine which implementation to use by comparing the cue to the registered metadata. The ECom framework provides a default resolver for selecting the most appropriate implementation.
Note: The default resolver does a case-sensitive search against the cue provided.
Apart from finding the best-fit implementation, the user can also request a list of implementations. This will allow the client to iterate through the list and determine which implementation is most suited for the application needs. It is also possible to create a custom resolver, this will allow developers to specify their own selection algorithm when determining which interface to use. More information can be found in Chapter 7.
4.
Example Application
Use of the ECom framework is best explained by means of an example implementation. In this section, the structure and functions of the accompanying example EComShape are discussed. The example application is based on the classic object-oriented shape example. An application will issue a request for a particular instance of a shape and for a picture of that shape to be displayed on the screen. Once the application has an instance of a shape object, it is not concerned about how it has been implemented but rather how it behaves. EComShape is a trivial example of the use of ECom. However, it does demonstrate all the key features and will therefore be used to help explain the various ECom components as they are discussed throughout the remainder of this document. It is important to note that, in practice, ECom would not be employed for such a simple application; it should only be used where there are multiple clients wanting to access the functionality provided by a plug-in. The EComShape application illustrates how to: Create a plug-in interface definition Create implementations of the plug-in interface Create a custom resolver Create a client using the plug-in
4.1
The application has been split into three different directories: \ecomshape Interface definition and custom resolver \shapeimplementation Implementations of the interface definition \shaperesolver Application that interacts with the definition
4.1.1
This directory provides the interface definition CShape and the custom resolver CShapeResolver. The CShape interface definition is the ECom plug-in interface used by all client applications, and all concrete implementations must derive from this. It provides two instantiation functions and one abstract method. The first instantiation method explicitly requests an interface by passing the interfaces UID. The second instantiation method accepts a cue that will be used by a custom resolver to acquire the correct interface. The abstract method accepts a CWindowGc object that is used to draw the object. A custom resolver called CShapeResolver is also implemented. Since a resolver is a plug-in, it must also provide a registration information resource file (101F614D.rss) and an exported factory table (Proxy.cpp).
Version 1.0 | January 23, 2004
4.1.2
There are three implementations of the CShape interface definition: CCirlce, CSquare, and CTriangle. Each implements the abstract function Draw(), which draws the shape of the object to the screen. The implementations also contain a registration information resource file (101F614F.rss) and an exported factory table (Proxy.cpp). 4.1.3 Client application
4.2
Developers should ensure that the Series 60 SDK 2.0 for Symbian OS has been installed, and then copy/uncompress the source example and place it in a directory located on the same drive where the SDK has been installed. Once this has been completed, they should follow the Building and running examples chapter in the SDK documentation.
Note: When installing the ECom plug-in/custom resolver DLL and ECom plugin/custom resolver compiled resource file for a device, developers should ensure that they are placed in the \system\libs\plugins directory.
5.
Interface Definition
An interface definition defines the behavior of the plug-in. Accordingly, when declaring the interface definition, the following features must be provided: Functionality provided by the service. These methods are generally pure abstract methods. Instantiation mechanisms.
Note: The definition must always contain a TUid data member; this is used to identify the instantiated object for destruction. Below is an example of an ECom-enabled definition, taken from the EComShape example application. This example interface definition is very simple and just enables the client to request that a particular shape be drawn to the screen. This service is offered through the const abstract Draw() method. The instantiation mechanism required is provided through the NewL() methods. // Foward declared... class CWindowGc; class CShape : public CBase { public: static CShape* NewL(); static CShape* NewL(const TDesC8& aMatch); virtual ~CShape(); virtual void Draw(CWindowGc& aGraphicsContext) const = 0; private: TUid iDtor_ID_Key; }; The interface definition is derived from CBase, but this is not mandatory; for example, the class could be derived from CActive, RHandleBase, etc. If the interface definition is derived from CBase, then, in keeping with convention, a static factory method called NewL()would be implemented for creation, and a destructor for cleanup. If the interface class derives from RHandleBase, then OpenL() and Close() methods should be implemented. Generally, abstract classes do not contain instantiation functions, but, in the case of an ECom interface definition, this is required. The reason for the instantiation function is to issue a request to the ECom framework to create an instance of the correct concrete plug-in. The abstract method(s) defined in the interface, i.e. Draw(), have no explicit implementation. The concrete plug-in implements the actions of the method call.
5.1
Instantiation Functions
The interface definition class, CShape, contains two instantiation methods. The first instantiation method requests the default implementation of CShape in the
Version 1.0 | January 23, 2004
EComShape, CCircle is defined as the default implementation. To ensure that the correct instance is retrieved, the CCircle UID is passed to the ECom service. (The CCircle UID is defined in a resource file; this is discussed further in Section 6.4.) CShape* CShape::NewL() { const TUid KCCircleInterfaceUid = {0x101F6150}; TAny* interface = REComSession::CreateImplementationL ( KCCircleInterfaceUid, _FOFF (CShape, iDtor_ID_Key)); return reinterpret_cast <CShape*> (interface); } The instantiation function makes use of ECom's REComSession static method CreateImplementationL(). CreateImplementationL has several overloaded methods that allow for: Selection of a specific interface. Passing of parameters to the implementation creation function. A cue to be passed to indicate which interface to use.
This particular use of CreateImplementationL passes two parameters, a UID to identify the requested implementation and the offset used to pass the Idtor_ID_Key, which is used to identify the created instance. The IDtor_ID_Key is used to notify the ECom framework once the object is destroyed; more information can be found in Section 5.2. The _FOFF macro is used to calculate the offset of IDtor_ID_Key from CShape. REComSession::CreateImplementationL() returns a TAny pointer that will be reinterpreted (cast) into the definition type. It also populates Idtor_ID_Key. The example above returns an instance of the CCircle implementation by specifying the UID of CCircle. The second instantiation function defined by the interface, NewL(const TDesC& aMatch), makes use of a custom resolver to determine which interface the client requires. To use the custom resolver, a cue (aMatch) is passed to enable the framework to determine which interface to select. In the CEComShape example, a client could request a CTriangle object using a cue, for example: LIT8 (KTriangleText,"TRIANGLE"); CShape* triangle = CShape::NewL (KTriangleText); The implementation of the overloaded NewL() provided by CShape takes this cue as a parameter: CShape* CShape::NewL (const TDesC8& aMatch) { const TUid KCShapeInterfaceUid = {0x101F614C}; const TUid KCShapeResolverUid = {0x101F614E}; TEComResolverParams resolverParams; resolverParams.SetDataType (aMatch); resolverParams.SetWildcardMatch (ETrue); TAny* interface = REComSession::CreateImplementationL (KCShapeInterfaceUid, _FOFF (CShape, iDtor_ID_Key), resolverParams, KCShapeResolverUid); return reinterpret_cast <CShape*> (interface); }
The overloaded NewL() method creates a TEComResolverParams object. This encapsulates the resolvers cue, which is set by calling the resolverParams.SetDataType() method. In the EComShape application all the cues are simply the name of the type of shape required, that is SQUARE, CIRCLE,, and TRIANGLE. The ability to include wildcard characters in the cue text is supported. The resolver parameter object must be set to use wild cards through a call to resolverParams.SetWildcardMatch() with ETrue passed through. Once the TEComResolverParams object has been configured properly, it is passed, along with the UID of the interface definition, to the REComSession::CreateImplementationL() method. The requested interface is then located and constructed. A TAny pointer is then returned and the iDtor_ID_Key populated as a means of identification. The TAny pointer being returned is reinterpreted into the definition type. If no interface implementation is found, the ECom framework leaves. The error code returned is KErrNotFound. The above examples are taken from the EComShape project and can be found at /ecomexample/shaperesolver/inc/shapeinterface.inl.
5.2
Destroying
The interface definition implements a virtual destructor. It is important that when an object is going to be destroyed it notifies the ECom framework by calling REComSession::DestroyedImplementation() and passing through the iDtor_ID_Key for identification. CShape::~CShape() { REComSession::DestroyedImplementation (iDtor_ID_Key); }
6.
An interface implementation collection is a set of concrete plug-ins that uses the interface definition to define its behavior. The interface implementation collection must provide the following: Standard DLL entry point Implementing interfaces Implementation factory container: defining and exporting Registry information Implementation collection project file
In the EComShape application, the interface implementation collection comprises three implementations, namely those for circle, square, and triangle. (The implementation classes are defined in /ecomexample/shapereimplementation.)
6.1
Since a plug-in is a DLL, it must provide the standard requirements defined by Symbian OS and provide a standard entry point for the DLL. The example below is all that is required: TInt E32DLL() { return KErrNone; }
6.2
Implementing Interfaces
When creating a concrete implementation of the defined interface it is necessary to: Derive from the interface definition Implement the pure abstracted methods Create conventional instantiation functions, i.e., NewL() and ConstructL()
The EComShape example provides three concrete implementations of the interface definition, namely CCircle, CSquare, and CTriangle. The class definitions can be found in the directory /ecomexample/shapereimplementation/inc.
6.3
The ECom framework needs to determine the factory function required to instantiate the correct instance. This is handled by providing a function that returns a pointer to an array of TImplementationProxy. The TImplenetationProxy is a structure that contains a TUid used to identify the implementation and a TAny pointer that points to the instantiation function. The array of TImplementationProxy values defined for EComShape is:
{ {{0x101F6150}, CCircle::NewL}, {{0x101F6151}, CSquare::NewL}, {{0x101F6152}, CTriangle::NewL} }; The UIDs in the array must be the same as the implementation_uids defined in the registration resource file; this is discussed in Section 6.4. To export the container, the following exported function must be called: const TImplementationProxy* ImplementationGroupProxy(TInt& aTableCount); It returns a pointer to the first item in the array and the amount of elements contained in the array, for example: EXPORT_C const TImplementationProxy* ImplementationGroupProxy (TInt& aTableCount) { aTableCount = sizeof (implementationTable) / sizeof (TImplementationProxy); return implementationTable; } The implementation factory container and export function for the EComShape application can be found at /ecomexample/shapereimplementation/src/proxy.cpp.
6.4
Registry Information
For the plug-in to register with the ECom framework, a resource file must be created and compiled to list all of the plug-in properties. Thus, when creating an interface collection resource file (/ecomexample/shapereimplementation/data/101F614F.rss), the following must occur: The resource file must be given the same name as the third UID of the DLL. In the EComShape example, the third UID is 0x101F614F, therefore the resource file would be named 101F614F.rss. The resource structures needed can be found in RegistryInfo.rh. A single REGISTRY_INFO structure is required. This declares the interface implementations available in the interface collection. It contains the dll_uid and an array of INTERFACE_INFO structures. INTERFACE_INFO declares the implementations available for a particular interface. It contains an interface_uid and an array of IMPLEMENTATION_INFO structures. The interface_uid is a UID that identifies the interface definition. IMPLEMENTATION_INFO declares details of an interface implementation. This structure stores information that is used to identify the implementation explicitly or by resolution. It also contains general metadata information about the interface.
Note: The default resolver uses the default_data attribute to resolve plug-in interface lookups, but a custom resolver can also use opaque_data to search against. #include "RegistryInfo.rh" RESOURCE REGISTRY_INFO theInfo {
Version 1.0 | January 23, 2004
// UID for the DLL dll_uid = 0x101F614F; // Declare array of interface info interfaces = { INTERFACE_INFO { // UID of interface that is implemented interface_uid = 0x101F612C; implementations = { // Info for CCircle IMPLEMENTATION_INFO { implementation_uid = 0x101F6150; version_no = 1; display_name = "Circle shape"; default_data = "CIRCLE"; opaque_data = ""; }, // Info for CSquare IMPLEMENTATION_INFO { implementation_uid = 0x101F6151; version_no = 1; display_name = "Square shape"; default_data = "SQUARE"; opaque_data = ""; }, // Info for CTriangle IMPLEMENTATION_INFO { implementation_uid = 0x101F6152; version_no = 1; display_name = "Triangle shape"; default_data = "TRIANGLE"; opaque_data = ""; } }; } }; }
6.5
When writing a project file (*.mmp file) for the implementation collection, it must contain the following details and build options. TARGET Specifies the targets name. TARGETTYPE Specifies the targets build. For an ECom implementation collection, ECOMIIC must be specified. UID Specifies the ECom recognition DLL UID (0x101F614F) and the UID for the DLL. RESOURCE Specifies the implementation registration file. SYSTEMINCLUDE Specifies the include path for ECom. LIBRARY Specifies ecom.lib to link against. For example:
Version 1.0 | January 23, 2004
TARGET ecomshape.dll TARGETTYPE ECOMIIC // ECom Dll recognition UID followed by the unique UID for this dll UID 0x10009D8D 0x101F614F SOURCEPATH SOURCE SOURCE SOURCE SOURCE SOURCE . main.cpp proxy.cpp circle.cpp square.cpp triangle.cpp . \epoc32\include \epoc32\include\ecom
7.
Custom Resolver
As previously mentioned, it is possible for a developer to create his or her own custom resolver. A custom resolver can be created if a different selection algorithm is required, for example the EComShape application implements a case-insensitive selection. Custom resolvers are themselves interface implementations. They are concrete implementations of the ECom CResolver interface definition. As when implementing normal ECom interface definition plug-ins, the developer must provide registration resource information, a UID, and a factory container and function. When implementing the concrete implementation of CResolver, the following must be defined and implemented: A factory function that takes MPublicRegistry object. An implementation of IdentifyImplementationL() and ListAllL().
The factory method NewL() is responsible for creating an instance of the customer resolver. The MPublicRegistry object passed by the factory function gives the custom resolver access to a list of implementations of a specified interface. The IdentifyImplementationL() method identifies the most appropriate implementation of a specified interface definition. To do this, the MPublicRegistry object iRegistry is queried for a list of CImplementationInformation objects. The CImplementationInformation objects contain information provided by the registry information resource file (the registry information is discussed in Section 6.4). The list is then iterated through and compared against the data provided by aAdditionalParameters. Once a successful match has been found, the UID of the matching interface is returned; otherwise, KnullUid is returned. The ListAllL() functions method returns a list of all of the matching implementations. The same concept applies as in IdentifyImplementationL(), except it will return a list of CImplementationInformation objects back to the ECom server framework. The custom resolver defined for the EComShape example is shown here: class CShapeResolver : public CResolver { public: static CShapeResolver* NewL(MPublicRegistry& aRegistry); virtual ~CShapeResolver(); virtual TUid IdentifyImplementationL(TUid aInterfaceUid, const TEComResolverParams& aAdditionalParameters) const; virtual RImplInfoArray* ListAllL(TUid aInterfaceUid, const TEComResolverParams& aAdditionalParameters) const; private: CShapeResolver(MPublicRegistry& aRegistry); TUid Resolve(const RImplInfoArray& aImplementationsInfo, const TEComResolverParams& aAdditionalParameters) const; TBool Match(const TDesC8& aImplementationType, const TDesC8& aMatchType, TBool aUseWildcards) const; };
Version 1.0 | January 23, 2004
The Resolve() and Match() functions in the CShapeResolver class contain the algorithms used to determine which implementation to use. Resolve() cycles through the implementations list and calls Match() to determine if that implementation can be used (the method of determination in this case is by descriptor comparison). When creating the registration resource, the interface_uid must always be defined as 0x10009D90. This value associates it to the CResolver interface. The resolver registration resource defined for EComShape (/ecomexample/shaperesolver/data/101F614D.rss) is shown: #include "RegistryInfo.rh" RESOURCE REGISTRY_INFO theInfo { dll_uid = 0x101F6130; interfaces = { INTERFACE_INFO { // Interface UID of resolvers interface_uid = 0x10009D90; implementations = { IMPLEMENTATION_INFO { implementation_uid = 0x101F614E; version_no = 1; display_name = ""; default_data = ""; opaque_data = ""; } }; } }; } Note that display_name, default_data, and opaque_date contain no data, as they are not used.
8.
Summary
This document provides an overview of what is required when using the ECom framework. To recap, the ECom framework is a client/server architecture that provides a service to instantiate, resolve, and destroy plug-in instances. Users using the interface definition are unaware that they are interacting with the ECom framework because the interface provides instantiation functions. The instantiation functions implement the request to the framework, which will return an instance of the requested implementation. When the request is issued, it uses EComs client REComSession to interact with the server. The ECom server constantly monitors to see what plug-ins are available on the device. It stores information that is used to resolve interfaces explicitly and implicitly. The framework provides a default resolver, which is used to resolve the best fit implementations. The user can implement a customized resolver used to determine the best fit implementations. When implementing a customize resolver, it must derive from CResolver (interface definition).
9.
Te r m s a n d A b b r e v i a t i o n s
Meaning Dynamic Link Library