Language Wrappers

From The Foundry MODO SDK wiki
Jump to: navigation, search

So you want to write a language wrapper for something other than C++? Good for you! This is an extremely challenging task, but can be very rewarding when it starts to actually work. This question doesn't come up very often and it's quite an advanced topic, so I won't be providing a lot of direct examples or technical help. This is more of an overview of the interesting features of the system and what's required to make a wrapper.

Background

COM

Objects in the SDK are all COM at their core. Object pointers passed from nexus to the plug-in are COM IUnknown interfaces, and object pointers passed back from the plug-in to nexus are exactly the same. The SDK ILxUnknown interface is identical to COM's IUnknown, and the SDK LxResult code is the same as COM's HRESULT.

That said, all the actual interfaces implemented in nexus are unique and don't reuse any other COM idioms. You will be required to translate your object format to use nexus interfaces.

Modules

Plug-in files get loaded as modules. That means that we look for a server of class Loader which can load objects of type Module. Module Objects are loaded opaquely and cached by the host subsystem. When we find a server that we want to use, the Module::Generate() method is used to spawn an instance, which will be -- of course -- a COM object.

The first time a module is loaded -- if the module is unknown at startup or the user uses "Add Plug-in" -- we query for a TagDescription Interface. We enumerate the tags for "server" tags that declare the contents of the module in terms of a class GUID and server name. Each newly-added server is then spawned and queried for its own TagDescription interface. This is enumerated to get the tags for the server itself. The server spawned for tags is then immediately destroyed.

Both the module and the server can present a NeedContext Interface. If present this will be called with the context object, which can be queried for services.

The built-in module loader will load any DLL on Windows or dyso on OSX which contains an entry point called _ILxModule_Create. That function is called with no argument and returns the module object for the DLL.

Caveats

There are some subtle differences between nexus COM and Microsoft COM. Our GUID for IUnknown is different. Fortunately you rarely query for IUnknown, and modo never does, so that's not much of an issue. Our error code defines are also different from standard HRESULT values. This is a bit more of a problem, and if you're using actual COM you'll need to provide a translation.

Stages

These are three stages for implementing your language wrapper.

Exported Objects

Exported objects are objects in modo that can be used by plug-ins. Services, for example, are objects implemented entirely in modo which provide methods that can be called by the client plug-in. You will need to create objects in your language that translate method calls so that they call into the service interface.

When a module is loaded it can present a NeedContext Interface. If that's present it gets called with the GUIDService Interface for the app. Any other service can be found by querying this object for that service interface GUID. The reason the GUIDService specifically is used is so that GUID pointers can be looked up using strings if that's necessary for your language.

Imported Objects

Imported objects are objects in your language which are exported as COM for nexus. You will need to provide base classes in your scripting language that match those defined for import. Your clients will then subclass these to create their own implementations, and you need to provide a way to wrap them as COM objects. When modo or any other client calls the method on the imported COM object, the corresponding method on the native object gets called and its result is returned back to the client.

Loading Plug-ins

In order to add plug-ins to nexus you will need a module. There are several ways to do this:

  1. If your language can be compiled into shared binaries, you can use our default loader. Just give it a standard entry point.
  2. If you can load individual scripts as modules, you can create your own loader. It will just be a Loader-class server that recognizes your script files and loads opaque Module Interface objects.
  3. If your scripting language is interactive then new servers might be added dynamically. In that case you need a dynamic module, which is a Module-class server. When the contents of the module changes, calling HostService::UpdateModule() will update the list of servers. An interactive scripting environment might also want to provide a LineInterpreter-class server.