Server (lx-server.hpp)
Contents
- 1 Plug-In System
- 2 Server Database Access
- 2.1 ILxFactory
- 2.1.1 (2) SDK: LXu_FACTORY define
- 2.1.2 (3) SDK: Factory::Name
- 2.1.3 (4) SDK: Factory::UserName
- 2.1.4 (5) SDK: Factory::ClassGUID
- 2.1.5 (6) SDK: Factory::Module
- 2.1.6 (7) SDK: Factory::InfoTag, etc.
- 2.1.7 (8) SDK: Factory::Spawn
- 2.1.8 (9) SDK: CLxUser_Factory::New method
- 2.1.9 (10) PY: empty Factory user class
- 2.2 ILxHostService interface
- 2.2.1 (11) SDK: LXu_HOSTSERVICE, etc. defines
- 2.2.2 (12) SDK: HostService::ScriptQuery
- 2.2.3 Servers
- 2.2.4 Examining the Server Table
- 2.2.5 Other Methods
- 2.2.5.1 (19) SDK: HostService::DefaultPath
- 2.2.5.2 (20) SDK: CLxUser_HostService::Lookup method
- 2.2.5.3 (21) SDK: HostService::SpawnForTagsOnly
- 2.2.5.4 (22) SDK: HostService::UpdateModule
- 2.2.5.5 (23) SDK: HostService::SaverVerify
- 2.2.5.6 (24) SDK: HostService::SaverSave
- 2.2.5.7 (25) PY: empty Service.Host user class
- 2.1 ILxFactory
- 3 Modules
- 3.1 (26) SDK: Module::Generate, etc.
- 3.2 (27) SDK: LXu_MODULE, etc. defines
- 3.3 (28) PY: empty Module user class
- 3.4 (29) SDK: LXtTagInfoDesc struct
- 3.5 (30) SDK: TagDescription::Count, etc.
- 3.6 (31) SDK: LXu_TAGDESCRIPTION, etc. defines
- 3.7 (32) PY: empty TagDescription user class
- 3.8 (33) SDK: NeedContext::SetContext
- 3.9 (34) SDK: LXu_NEEDCONTEXT define
- 3.10 (35) PY: empty NeedContext user class
- 4 Service Extensions
Plug-In System
The plug-in interface allows the nexus application to load extensions (in the form of objects) which provide services to the application. The plug-in API also defines the methods that the plug-ins use to access the state of the nexus application in order to perform their function. Loadable code files are called modules, and the individual standardized objects that loaded code can provide are called servers.
COM Interfaces
The plug-in API is built around COM, which is a limited but somewhat standard form of object-oriented interface. COM objects are opaque except for their first pointer which references a vector table of well-defined functions which operate on the object pointer. The vector table is laid out according to a plan described as an "interface" in COM terminology. The first few functions can be used for reference tracking and polymorphism.
Servers are the COM objects which happen to be of the type that are used by nexus to implement its extensible functionality. Except for that they are like any other COM object.
Server Identification
The nexus plug-in system maintains a database of the external servers which can be accessed. The servers in the system are referenced by a combination of class and name.
A Server Class is a GUID which determines the type of service which the server can perform. This is also the main interface provided by the server. There can be many servers of the same class, including many built-in to the nexus application.
A Server Name is a string which refers to a specific server within a given class. This might be something like "FractalNoise3D" or "JPEG". The name must be unique among the servers of the same class. A server name must be a byte string containing characters only in the ASCII range 33-127. It can contain no spaces and except for built-in servers must start with an alphabetic character. Case is significant unless it is ignored in a specific usage context, such as commands which are looked up in a case-insensitive manner.
Servers also have a set of tagged strings which define their fixed attributes, and which describe the kinds of services they perform within their class. These tags strings are read from the server and are stored in the database for reference while the server is not loaded. There are some tags common to all servers:
USERNAME | is the name of the server to be presented to the user in the UI. This can be a reference to a message table using "@table@msg@" syntax. |
LICENSE | indicates that the server needs a license in order to function. The license string is the product ID followed by a feature or build number. For example "iblend 12231" or "corel_X 300". Feature numbers should always be multiple digits for security. The string can also optionally contain a date for maintainance licenses, of the form "d:YYYY.MMDD". Example: "product 99999 d:2015.0130". |
OWNER, SERIAL | are set to the name of the license holder and the serial number for the license, for plug-ins that require licenses. The plug-in can read these values by looking up itself in the host service. |
(1) SDK: LXsSRV_USERNAME, etc. defines
#define LXsSRV_USERNAME "server.username" #define LXsSRV_LICENSE "server.license" #define LXsSRV_OWNER "server.owner" #define LXsSRV_SERIAL "server.serial"
Server Database Access
The server database can be accessed through the ILxHostService global, which provides a list of ILxFactory objects for reading server attributes or spawning servers of different types.
ILxFactory
An ILxFactory is an internal nexus object that can create servers of a specific type. Internally it has a handle to a plug-in module which will be loaded as needed.
(2) SDK: LXu_FACTORY define
#define LXu_FACTORY "2431A79E-3412-4B0D-987D-875489466C58"
Accessors for the server's name, username and class.
(3) SDK: Factory::Name
LXxMETHOD( LxResult, Name) ( LXtObjectID self, const char **name);
(4) SDK: Factory::UserName
LXxMETHOD( LxResult, UserName) ( LXtObjectID self, const char **userName);
(5) SDK: Factory::ClassGUID
LXxMETHOD( LxResult, ClassGUID) ( LXtObjectID self, LXtGUID *guid);
Get the module the server is in, or NULL if this is a built-in server.
(6) SDK: Factory::Module
LXxMETHOD( LxResult, Module) ( LXtObjectID self, const char **module);
This allow reading the tags for a factory. These do not need to be implemented by extension factories since the tags are read from the server instance.
(7) SDK: Factory::InfoTag, etc.
LXxMETHOD( LxResult, InfoTag) ( LXtObjectID self, const char *type, const char **value); LXxMETHOD( LxResult, TagCount) ( LXtObjectID self, unsigned int *count); LXxMETHOD( LxResult, TagByIndex) ( LXtObjectID self, unsigned int index, const char **type, const char **value);
Aditionally, servers can be spawned using these functions. The COM interface returned needs to be released by the caller using Release(), as usual.
(8) SDK: Factory::Spawn
LXxMETHOD( LxResult, Spawn) ( LXtObjectID self, void **ppvObj);
(9) SDK: CLxUser_Factory::New method
bool New ( CLxLocalizedObject &loc) { return Spawn (loc); }
Empty Factory Python user class.
(10) PY: empty Factory user class
pass
ILxHostService interface
This provides access to the list of factories. For convenience these methods all take the class GUID as a string, which is how they are defined in the API.
(11) SDK: LXu_HOSTSERVICE, etc. defines
#define LXu_HOSTSERVICE "525802A6-BF5F-46E9-9863-C03B54A3D908" #define LXa_HOSTSERVICE "hostservice"
As with all globals, the first method gets the ILxScriptQueryID interface for the system.
(12) SDK: HostService::ScriptQuery
LXxMETHOD( LxResult, ScriptQuery) ( LXtObjectID self, void **ppvObj);
Servers
This method can be used to lookup a factory it's class and server name. If allowLoad is true it will prompt the user to find a module if the server is not known. The factory must be released when no longer needed.
(13) SDK: HostService::LookupServer
LXxMETHOD( LxResult, LookupServer) ( LXtObjectID self, const char *className, const char *name, unsigned allowLoad, void **ppvObj);
This simply tests to see if a server exists, and skips the error reporting.
(14) SDK: HostService::TestServer
LXxMETHOD( LxResult, TestServer) ( LXtObjectID self, const char *className, const char *name);
Examining the Server Table
These methods allow the server database to be scanned. These first two allow the server list to walked by class. The factory returned must be released when no longer needed.
(15) SDK: HostService::NumServers
LXxMETHOD( unsigned, NumServers) ( LXtObjectID self, const char *className);
(16) SDK: HostService::ServerByIndex
LXxMETHOD( LxResult, ServerByIndex) ( LXtObjectID self, const char *className, unsigned index, void **ppvObj);
Servers can also be looked up by name to get their index.
(17) SDK: HostService::ServerGetIndex
LXxMETHOD( LxResult, ServerGetIndex) ( LXtObjectID self, const char *className, const char *name, unsigned *index);
Application hosts can add servers as factories. This should not be done by plug-ins becuase they can add servers through the module method.
(18) SDK: HostService::AddServer
LXxMETHOD( LxResult, AddServer) ( LXtObjectID self, LXtObjectID factory);
Other Methods
This returns the path to the plug-in directory.
(19) SDK: HostService::DefaultPath
LXxMETHOD( LxResult, DefaultPath) ( LXtObjectID self, const char **path);
The C++ service has methods to get a factory directly into a user object.
(20) SDK: CLxUser_HostService::Lookup method
bool Lookup ( CLxLoc_Factory &fac, const char *className, const char *name, bool allowLoad = true) { LXtObjectID obj; if (LXx_FAIL (LookupServer (className, name, allowLoad, &obj))) return false; return fac.take (obj); } bool ByIndex ( CLxLoc_Factory &fac, const char *className, unsigned int index) { LXtObjectID obj; if (LXx_FAIL (ServerByIndex (className, index, &obj))) return false; return fac.take (obj); }
We can also query things specific to our module. This return LXe_TRUE if the server is being spawned only to read tags.
(21) SDK: HostService::SpawnForTagsOnly
LXxMETHOD( LxResult, SpawnForTagsOnly) ( LXtObjectID self);
Dynamic modules can update - well, dynamically. Calling this method will force the server database to update the contents of the dynamic module.
(22) SDK: HostService::UpdateModule
LXxMETHOD( LxResult, UpdateModule) ( LXtObjectID self, const char *name);
Saver verification can be triggered directly for a given format and data object.
(23) SDK: HostService::SaverVerify
LXxMETHOD( LxResult, SaverVerify) ( LXtObjectID self, const char *format, LXtObjectID object, LXtObjectID msg);
This will perform a save using the filename, format and data object. The monitor is optional.
(24) SDK: HostService::SaverSave
LXxMETHOD( LxResult, SaverSave) ( LXtObjectID self, const char *filename, const char *format, LXtObjectID object, LXtObjectID monitor);
Empty Host service Python user class.
(25) PY: empty Service.Host user class
pass
Modules
Plug-in modules are loaded like any file, using an ILxLoader. The load is opaque, and the object type loaded is "module".
An ILxModule is a special type of factory capable of spawning servers of different classes which are the servers implemented by the module.
Generate | creates a new server instance given the class GUID and server name. |
GetTags | optional method returns a StringTag interface for a server based on the name and class ID. Allows tags to be read without creating a server inst. |
(26) SDK: Module::Generate, etc.
LXxMETHOD( LxResult, Generate) ( LXtObjectID self, const char *name, const LXtGUID *iid, void **ppvObj); LXxMETHOD( LxResult, GetTags) ( LXtObjectID self, const char *name, const LXtGUID *iid, void **ppvObj);
(27) SDK: LXu_MODULE, etc. defines
#define LXu_MODULE "4DB9C543-B192-4EDD-A65D-DD012FC27416" #define LXa_MODULE "module2"
Empty Module Python user class.
(28) PY: empty Module user class
pass
Modules are servers are self-describing by presenting a Tag Description interface. For a module the tags of "server" type give the name (as info) and the interface type (as GUID) for the servers in the module, and is used by the nexus to determine which servers are exported by an unknown module. For servers the tags can define various properties, including the user name.
(29) SDK: LXtTagInfoDesc struct
const char *type; const char *info; const LXtGUID *guid;
The Count() function returns the number of info tags for the object, and the Describe() method fills in the info for a given tag index.
(30) SDK: TagDescription::Count, etc.
LXxMETHOD( unsigned, Count) ( LXtObjectID self); LXxMETHOD( LxResult, Describe) ( LXtObjectID self, unsigned index, LXtTagInfoDesc *desc);
(31) SDK: LXu_TAGDESCRIPTION, etc. defines
#define LXu_TAGDESCRIPTION "5582E0EE-D682-47BC-BF3D-FB14D59948C1" #define LXsMOD_SERVER "server"
Empty TagDescription Python user class.
(32) PY: empty TagDescription user class
pass
The "need context" interface can be defined by any module or server that wants to be informed about the application context when it's first created. The application passed to the SetContext() method can be queried for globals, and is owned by the client and needs to be released. Note: this conflicts with the COM standard, so you cannot "set" this argument, you must "take" it.
(33) SDK: NeedContext::SetContext
LXxMETHOD ( LxResult, SetContext) ( LXtObjectID self, LXtObjectID app);
(34) SDK: LXu_NEEDCONTEXT define
#define LXu_NEEDCONTEXT "7D30408C-74AB-4d87-B71C-C6280883863C"
Empty NeedContext Python user class.
(35) PY: empty NeedContext user class
pass
Service Extensions
Global services can be extended by plug-ins. The extension interface itself is just a handle that lets lets us allocate a server object, which is then queried for the real service interface.
(36) SDK: ServiceExtension::Dummy
LXxMETHOD( void, Dummy) ( LXtObjectID self);
(37) SDK: LXu_SERVICEEXTENSION, etc. defines
#define LXu_SERVICEEXTENSION "E7C6F1A2-2F31-4FA5-B2EF-421BE159D0D8" #define LXa_SERVICEEXTENSION "serviceExtension" #define LXsSRVEXT_GUID "extension.guid"