SDK Basics

From The Foundry MODO SDK wiki
Revision as of 08:14, 17 February 2012 by Shf (Talk | contribs) (Instances)

Jump to: navigation, search


The nexus SDK is implemented in COM, but as a C++ developer you mostly don't need to know any of that. Whew! That's a good thing because while COM is relatively simple, dealing with raw COM objects is quite complicated. Fortunately the C++ wrapper classes hide most of the complexity. There are, however, a few concepts that are important to understand at a high level.


The methods that you implement will take the ILxUnknownID datatype as argument for any object. This is the general object type, and can be transformed into specific interfaces based on your needs. This is a COM thing. All objects have multiple interfaces and need to be queried before they can be used. It's a little like dynamic casting in C++, except that the types are known at runtime.

Likewise some of the service methods you will be calling also take ILxUnknownID arguments. Fortunately the wrapper classes can cast to that type implicitly so there is very little that needs to be done on your side to support this. Just know that you can pass a wrapper object and it will be converted to the right type.

Reference Counting

COM objects manage their existence by reference counting. Again, this is mostly handled for you by the wrapper classes. When you initialize a wrapper with an ILxUnknownID object a reference is added, and when the wrapper changes or goes out of scope the reference is released.

User Wrappers

Objects in the SDK are mostly accessed through two kinds of wrappers.


are interfaces provided by nexus to access internal state. The wrappers are exceptionally easy to use; you just declare them and they are ready to go. The constructor does all the work of hooking the wrapper object to the real interface.
        CLxUser_SelectionService  sel_srv;

        now = sel_srv.GetTime ();

Localized Objects

Localized objects come into the plug-in from nexus, often as ILxUnknownID argument pointers. These need to be localized by initializing a class wrapper. For example, this method is passed an unknown object that has a StringTag Interface, which is then localized using the interface wrapper.

method (
        ILxUnknownID             thing)
        CLxUser_StringTag        tags (thing);

        n = tags.Count ();

The wrapper can also be initialized using the set() method, and its return value or the test() method can be used to determine if the initialization succeeded. (Lowercase methods operate on the wrapper, uppercase on the actual object.)

        if (!tags.set (thing))
                return LXe_NOTFOUND;


        if (tags.test ())
                tags.Get (tag_id, &value);

Using set() increments a reference to the object which is decremented when the wrapper releases the object, such as when it goes out of scope. There are cases when it's useful to "steal" the reference that's already been incremented by a previous call. For example, using raw allocation methods -- the ones that take an LXtObjectID as a ppvObj indirect argument -- add a reference to the returned object. The take() method transfers ownership of that reference to the wrapper.

        LXtObjectID               obj;
        LxResult                  rc;

        rc = source.Allocate (&obj);
        if (LXx_FAIL (rc))
                return false;

        return wrap.take (obj);

Export Wrappers

It's also necessary to export objects from the plug-in to nexus. In this case the plug-in creates objects using C++ and wraps them in COM interfaces. When nexus (or sometimes other plug-ins) use the COM methods those call through to the C++ methods for the base class. The C++ class starts by inheriting from the CLxImpl_ classes for the interfaces that it wants to present, and it then creates bodies for some subset of the inherited methods. Anything not implemented will have a default code body, if possible.

For example, a tool -- which needs to present Tool and ToolModel Interfaces -- could start like this:

class CMyClass :
        public CLxImpl_Tool,
        public CLxImpl_ToolModel
        void      tool_Evaluate (ILxUnknownID vts)                               LXx_OVERRIDE;
        void      tmod_Draw (ILxUnknownID vts, ILxUnknownID stroke, int flags)   LXx_OVERRIDE;

Each CLxImpl class has a unique prefix for all its methods, assuring that there is never a conflict as result of multiple inheritance.

Once the class implementation is created it's necessary to create the actual wrapper. This is called a polymorph -- because COM object are polymorphic -- and it manages the actual COM incarnations of your class instances. The polymorph must exist as long as their are COM instances. The code to create the polymorph is as follows:

        CLxGenericPolymorph     *srv;

        srv = new CLxPolymorph<CMyClass>;
        srv->AddInterface (new CLxIfc_Tool     <CMyClass>);
        srv->AddInterface (new CLxIfc_ToolModel<CMyClass>);

First we create a polymorph object using a template, which allows it to find the methods of your class. To that polymorph we add interfaces, which are the actual COM interfaces that will be presented by your COM object. Each CLxIfc_ template object serves as translator from the general COM API to the specifics of your class implementation. This polymorph object, once created, is capable of spawning many instances of your class. There are a few ways to do that.


In the case of the tool example, the class implements a server. This means that it gets installed into the module during initialization as one of the named services that the module can provide. The class of the object is given by the first interface, in this case Tool, which is what we want. The server should be given a name globally unique for all servers of this class by calling this function in initialize().

        lx::AddServer ("serverName", srv);


Some exported plug-in objects are created on demand, not as servers but as instances. In that case the best approach is to use a spawner. A typical example would be:

class CMySurfaceBin :
        public CLxImpl_SurfaceBin,
        public CLxImpl_TableauSurface,
        public CLxImpl_StringTags
                static CMySurfaceBin *
        Spawn (
                void              *ppvObj)
                CLxSpawnerCreate<CMySurfaceBin> sp ("mySurfBin");

                if (sp.created) {
                        sp.AddInterface (new CLxIfc_SurfaceBin    <CMySurfaceBin>);
                        sp.AddInterface (new CLxIfc_TableauSurface<CMySurfaceBin>);
                        sp.AddInterface (new CLxIfc_StringTags    <CMySurfaceBin>);

                return sp.spawn->Alloc (ppvObj);



Sometimes it would be overkill to create a spawner because you only ever need one instance of a given object, like a global listener for example. In that case it's best to inherit from CLxSingletonPolymorph. This encapsulates the polymorphic COM management into the single instance of the object. You just have to remember to add the special method macro.

class CMySelectionTracker :
        public CLxImpl_SelectionListener,
        public CLxSingletonPolymorph

       CMySelectionTracker ()
               AddInterface (new CLxIfc_SelectionListener<CMySelectionTracker>);