server (lx_server.hpp)

From The Foundry MODO SDK wiki
Jump to: navigation, search
There are security restrictions on this page


Contents

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: Declarations
 #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: Declarations
 #define LXu_FACTORY             "2431A79E-3412-4B0D-987D-875489466C58"

Accessors for the server's name, username and class.

(3) SDK: ILxFactory interface
         LXxMETHOD(  LxResult,
 Name) (
         LXtObjectID               self,
         const char              **name);

(4) SDK: ILxFactory interface
         LXxMETHOD(  LxResult,
 UserName) (
         LXtObjectID               self,
         const char              **userName);

(5) SDK: ILxFactory interface
         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: ILxFactory interface
         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: ILxFactory interface
         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: ILxFactory interface
         LXxMETHOD(  LxResult,
 Spawn) (
         LXtObjectID               self,
         void                    **ppvObj);

(9) User Class: Factory method
         bool
 New (
         CLxLocalizedObject      &loc)
 {
         return Spawn (loc);
 }

Empty Factory Python user class.

(10) PY: Factory method
 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: Declarations
 #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: ILxHostService interface
         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: ILxHostService interface
         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: ILxHostService interface
         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: ILxHostService interface
         LXxMETHOD(  unsigned,
 NumServers) (
         LXtObjectID              self,
         const char              *className);

(16) SDK: ILxHostService interface
         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: ILxHostService interface
         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: ILxHostService interface
         LXxMETHOD(  LxResult,
 AddServer) (
         LXtObjectID              self,
         LXtObjectID              factory);

Other Methods

This returns the path to the plug-in directory.

(19) SDK: ILxHostService interface
         LXxMETHOD(  LxResult,
 DefaultPath) (
         LXtObjectID               self,
         const char              **path);

The C++ service has methods to get a factory directly into a user object.

(20) User Service Class: HostService 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: ILxHostService interface
         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: ILxHostService interface
         LXxMETHOD(  LxResult,
 UpdateModule) (
         LXtObjectID              self,
         const char              *name);

Empty Host service Python user class.

(23) PY: HostService method
 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 StringTags interface for a server based on the name and class ID. Allows tags to be read without creating a server inst.

(24) SDK: ILxModule interface
         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);

(25) SDK: Declarations
 #define LXu_MODULE      "4DB9C543-B192-4EDD-A65D-DD012FC27416"
 #define LXa_MODULE      "module2"

Empty Module Python user class.

(26) PY: Module method
 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.

(27) 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.

(28) SDK: ILxTagDescription interface
         LXxMETHOD( unsigned,
 Count) (
         LXtObjectID              self);
 
         LXxMETHOD( LxResult,
 Describe) (
         LXtObjectID              self,
         unsigned                 index,
         LXtTagInfoDesc          *desc);

(29) SDK: Declarations
 #define LXu_TAGDESCRIPTION      "5582E0EE-D682-47BC-BF3D-FB14D59948C1"
 #define LXsMOD_SERVER           "server"

Empty TagDescription Python user class.

(30) PY: TagDescription method
 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.

(31) SDK: ILxNeedContext interface
         LXxMETHOD ( LxResult,
 SetContext) (
         LXtObjectID              self,
         LXtObjectID              app);

(32) SDK: Declarations
 #define LXu_NEEDCONTEXT         "7D30408C-74AB-4d87-B71C-C6280883863C"

Empty NeedContext Python user class.

(33) PY: NeedContext method
 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.

(34) SDK: ILxServiceExtension interface
         LXxMETHOD( void,
 Dummy) (
         LXtObjectID              self);

(35) SDK: Declarations
 #define LXu_SERVICEEXTENSION    "E7C6F1A2-2F31-4FA5-B2EF-421BE159D0D8"
 #define LXa_SERVICEEXTENSION    "serviceExtension" 
 #define LXsSRVEXT_GUID          "extension.guid"