Difference between revisions of "Hello World"

From The Foundry MODO SDK wiki
Jump to: navigation, search
(Total Code)
(Total Code)
Line 148: Line 148:
 
         lx::AddServer (name, srv);
 
         lx::AddServer (name, srv);
 
  }
 
  }
 +
 +
NOTE: I have not tried this! It would be nice if we had some confirmation that this works.

Revision as of 02:23, 1 February 2012

So you've decided to write a plug-in. Good for you! This "Hello World!" tutorial will cover the absolute minimum steps required to create a plug-in and get feedback inside the application.

Preparation

Build Environment Setup

You need to do three things to be ready to start coding a plug-in.

  1. Include headers. The lxsdk header directory needs to be on the compiler search path.
  2. Build common lib. The common code library archive needs to be built using your compiler and environment. You will link your plug-in to this library to get base implementations for the shared classes and objects.
  3. Empty .cpp file ready to compile and link.

You should be able to get started with this using the provided IDE projects for VC and XCode.

Decide on Your Server Type

A plug-in can't do anything unless it provides one of the proscribed server types for nexus to access. Is this a command, an item type, a tool? Perhaps the plug-in will have multiple servers that all work together. This is something for you to decide.

For the purpose of this tutorial we'll be making an image saver, which is one of the simplest types.

Writing Code

Headers

The first part of your plug-in source file will include the required headers. There are two types of headers you will typically want to use:

  • lx_<system>.hpp -- the user header for any given nexus system will have an L-X-underscore prefix and be of the hpp type. Check the documentation to see which interfaces are defined as part of a given system.
  • lxu_<name>.hpp -- utility headers have an L-X-U-underscore prefix, and contain helper classes of various kinds.

In this case we want the io system and image system headers. I/O gives us the definitions for savers and loaders, and Image defines the interfaces for image objects. Since we're going to say hello to the world we also need the log system.

#include <lxsdk/lx_io.hpp>
#include <lxsdk/lx_image.hpp>
#include <lxsdk/lx_log.hpp>

The Server Class

The heart of any plug-in is a C++ class that you write to implement a server interface. The interface is the set of standard methods that nexus uses to integrate your server into the application. This is done by inheriting from multiple implementation classes. In this case we're going to inherit from the Saver implementation:

class CHWSaver : public CLxImpl_Saver
{
    public:
        ...
};

The Saver super-class defines two methods: sav_Verify() and sav_Save(). The sav prefix is unique to the Saver implementation class, and allows multiple implementations with the same or similar methods to be inherited by the same server. We're going to implement the Save() method, adding this line to our class definition above:

        LxResult   sav_Save (ILxUnknownID source, const char *filename, ILxUnknownID monitor)  LXx_OVERRIDE;

The override keyword is optional but very useful. It declares to the compiler that you intend for your method to be derived from an identical method is a super-class. That means that if the method in the implementation class changes the compiler will throw an error. If you don't use the override keywork (or if your compiler doesn't support it -- it's not standard) then you get no error, but your method will never be called.

Server Methods

To flesh out the save method we'll declare a code body with the same arguments as above.

        LxResult
CHWSaver::sav_Save (
        ILxUnknownID            source,
        const char             *filename,
        ILxUnknownID            monitor)
{
        ...
}

The source is the object to be saved, in this case an image. This is an ILxUnknownID pointer type -- a general handle to a COM object -- and can be queried for any number of Image object interfaces. The filename is the full path of the file to be written in platform-specific format. The monitor is another object used for tracking the progress of the save and is discussed later.

The first thing we want to do is query the image object for an Image interface that will allow us to read the size for the "hello world" message. This is done simply by declaring a localized C++ image user class and initializing it to the source object.

        CLxUser_Image           image (source);

CLxUser_ classes are wrappers that allow COM objects to be accessed through common C++ syntax, with C++-friendly APIs. Once initialized with a COM object, they can be used like any other C++ object. In our case we're going to read the size of the image.

        unsigned                w, h;
        image.Size (&w, &h);

Instead of writing an image file as would be expected for a plug-in of this type we're going to generate test output just to alert the world that we exist. For illustration we'll do this two ways. Both will use the log service.

        CLxUser_LogService      logSvc;

Services are the inverse of servers -- they are interfaces exported by nexus to plug-in, allowing plug-in to access and manipulate the internal state of the nexus system. In this case the state of the log system. Unlike other user classes, they don't need to be initialized. Instead they are automatically initialized as soon as they are declared.

The simplest form of debug output just writes to the debug log or, inside the VC debugger, the output window. This is the same as debug output from nexus internal systems and respects the debug level specified on the command line.

        logSvc.DebugOut (LXi_DBLOG_NORMAL, "Hello world: image %s is %d x %d

", filename, w, h);

The second method uses a specific log, and displays output in the event log viewport inside a nexus app. This can be especially useful if you're not running inside the debugger. Without more comment the code is:

        CLxUser_Log             log;

        log.setByName ("status-io");
        ...blah...

Server Tags

Initialization

Plug-ins need to declare the servers they export, and the interface those servers support. This is done with the initialize() function which is called from the common lib code as the plug-in is loaded. This is largely boilerplate, but the exact sequence is important.

        void
initialize ()
{
        CLxGenericPolymorph     *srv;

        srv = new CLxPolymorph<CHWSaver>;
        srv->AddInterface (new CLxIfc_Saver<CHWSaver>);
        srv->AddInterface (new CLxIfc_StaticDesc<CHWSaver>);
        lx::AddServer (name, srv);
}

Total Code

Putting it all together, the final code looks like this:

#include <lxsdk/lx_io.hpp>
#include <lxsdk/lx_image.hpp>
#include <lxsdk/lx_log.hpp>

class CHWSaver : public CLxImpl_Saver
{
    public:
        LxResult   sav_Save (ILxUnknownID source, const char *filename, ILxUnknownID monitor)  LXx_OVERRIDE;
};

        LxResult
CHWSaver::sav_Save (
        ILxUnknownID            source,
        const char             *filename,
        ILxUnknownID            monitor)
{
        CLxUser_Image           image (source);
        CLxUser_LogService      logSvc;
        unsigned                w, h;

        image.Size (&w, &h);
        logSvc.DebugOut (LXi_DBLOG_NORMAL, "Hello world: image %s is %d x %d

", filename, w, h);

}

        void
initialize ()
{
        CLxGenericPolymorph     *srv;

        srv = new CLxPolymorph<CHWSaver>;
        srv->AddInterface (new CLxIfc_Saver<CHWSaver>);
        srv->AddInterface (new CLxIfc_StaticDesc<CHWSaver>);
        lx::AddServer (name, srv);
}

NOTE: I have not tried this! It would be nice if we had some confirmation that this works.