Introduction Common Elements Table of Contents

Compiling LightWave® Plug-ins

LightWave® plug-ins on all platforms are ordinary operating system objects (they're DLLs under Windows, for example), so building them is pretty straightforward. You'll need to define a couple of preprocessor symbols and export one variable name, and you'll need to compile and link with a bit of code supplied with the SDK. LightWave® plug-ins are ordinarily given a ".p" filename extension, although this isn't required.

Preprocessor Symbols

The SDK header files rely on preprocessor symbols to identify the operating system and CPU of the host system. These are currently used to define system-specific versions of the XCALL_ macro and the HostDisplayInfo structure (don't worry if you don't know what those are yet), and they determine what is stored in the system identification fields of the module descriptor. The operating system defines are

   _MSWIN   /* Microsoft Windows */
   _MACOS   /* Macintosh */
   _XGL     /* Unix */

and the CPU defines are.

   _X86_    /* Intel and Intel-compatible */
   _ALPHA_  /* Alpha AXP */
   _PPC_    /* PowerPC */
   _MIPS_   /* MIPS */

You need one symbol from the first list and one from the second, and you need to pass them to the compiler as preprocessor defines.

Exported Variable

The linker needs to export the symbol _mod_descrip. LightWave® looks for this module descriptor data structure by name when it attempts to load a plug-in. Symbol export is handled differently in different development environments, but it's often a linker command line option.

SDK Library

The SDK ships with source code files defining the _mod_descrip structure, default Startup and Shutdown functions, and a default names array. Each plug-in you create must include servmain.c and must be linked with server.lib.

Before compiling any plug-ins, you'll need to build server.lib for your system. Create a statically linked library containing servdesc.c, username.c, startup.c, and shutdown.c, all of which you should find in the SDK/source directory. Name the library server.lib. Define the operating system and CPU preprocessor symbols described previously when compiling the library source.

You may want to create more than one version of server.lib with different compiler settings (debug and release versions, for instance).

Debugging Notes

Don't forget that before you can run or debug your plug-in for the first time, you need to add it to the plug-in list in LightWave®.

In general, you must quit and restart LightWave® each time you rebuild your plug-in and want to run it. Your plug-in is cached in memory for the life of a LightWave® session, so LightWave® won't see the changes to your plug-in until it quits and restarts.

LightWave® Modeler supports a debug command line switch. When started with the -d switch, Modeler adds an "Unload Plug-ins" command. (This appears in the Modeler/Plug-ins menu with the default configuration, but may not appear in custom menu configurations.) Activating this command forces Modeler to unload all unlocked plug-in modules, so that when you execute your plug-in again it will be reloaded from disk.

Beginning in LightWave® 6.5, Modeler's -d switch can take an argument (-dfilename) which tells it where to write debug information. This can be useful for figuring out why plug-ins aren't loading, or for looking at trace information generated by XPanels.

Microsoft Windows

Plug-in modules under Windows are Win32 dynamic link libraries (DLLs). You don't need to create an import library (.lib) or an export file (.exp) for plug-in DLLs, but you will need to export _mod_descrip. One way to do this is to include a module definition (.def) file containing an EXPORT _mod_descrip directive. You can use the default source\serv.def file provided with the SDK for this.

Win32 DLLs have a standard entry point function named DllMain. You don't need to provide a DllMain for your LightWave® plug-ins unless, for example, the user interface is built with Windows interface components that require the DLL's instance handle. (But consider building your interface using the platform-independent components provided with the plug-in SDK.)

The alignment of structure members in your DLL must match LightWave®'s.

Data type Address must be...
char any
short (16-bit) even
int, long (32-bit) divisible by 4
float divisible by 4
double divisible by 8
structures aligned for the largest member
unions aligned for the first member

The recipes for specific compilers discuss what, if anything, you need to do to ensure that your plug-in's data is properly aligned.

If you decide to use makefiles to build your plug-ins, they should contain lines resembling the following:

   LWSDK_FLAGS = -D_X86_ -D_MSWIN

   .c.obj:
      $(CC) $(CFLAGS) $(LWSDK_FLAGS) $*.c $(LWSDK_SRC)servmain.c

   .obj.p:
      $(LINKER) -dll -out:$@ -def:$(LWSDK_INCL)serv.def $*.obj \
       $(LWSDK_LIB)server.lib $(OTHER_LIBS)

In other words, define the symbols _X86_ (or _ALPHA_) and _MSWIN, include servmain.c in the list of source code files, include the module definition file serv.def so that _mod_descrip is exported, and link with server.lib.

There are additional defines usually setup by the compiler for Windows 64bit support such as _AMD64_ (for AMD 64bit) or _IA64_ (for Intel Itanium). The LWSDK is compatible with 64bit compilers by Intel and Microsoft.

Microsoft Visual C++

To build an MSVC version of the SDK library,

  • Create a new project workspace, or insert a new project into an existing workspace. The project type should be "Static Library." Name the project "server".
  • Settings dialog, C/C++ tab, add _X86_ (or _ALPHA_) and  _MSWIN to the preprocessor definitions field.
  • In the field for additional include directories, type the path to the plug-in SDK include directory.
  • Add servdesc.c, username.c, startup.c, and shutdown.c to the project. These are located in the SDK\source directory.
  • Build server.lib.

To create a plug-in,

  • Create a new project workspace, or insert a new project into an existing workspace. The project type should be "Dynamic-Link Library."
  • Settings dialog, C/C++ tab, add _X86_ (or _ALPHA_) and  _MSWIN to the preprocessor definitions field.
  • In the field for additional include directories, type the path to the plug-in SDK include directory.
  • Add your source files to the project. Also add servmain.c, server.lib and serv.def.

Accept the default settings for the calling convention (__cdecl), alignment (8 byte) and runtime library (multithreaded or multithreaded debug, for the release and debug versions, respectively). If you've built both debug and release versions of server.lib (and this is recommended), make sure you list the appropriate one for the debug and release versions of your plug-in.

You're ready to build your plug-in. To debug it,

  • Settings dialog, Debug tab, enter the full path to lightwav.exe or modeler.exe, as appropriate, in the field labeled "Executable for debug session."
  • Set the working directory to the directory containing the LightWave® executables.
  • Build a debug version of your plug-in.

Hit F5 to begin debugging. The debugger will warn you that the LightWave® executable doesn't contain any debugging information, but that's okay. Your plug-in does have this information, which the debugger will find as soon as your plug-in is started by LightWave®.

Borland C++ 4.52
Information provided by Michal Koc.

Before creating any plug-ins, you'll need to build a Borland version of the SDK library.

  • Create a new project
  • Set the Target Type to Static Library (for .dll) [.lib]
  • In Standard Libraries, mark Static
  • Set the Platform to Win32
  • Set the Target Model to GUI
  • Add nodes with the files servdesc.c, username.c, startup.c, and shutdown.c
  • Options/Project/Directories, set the include and lib paths
  • Options/Project/Compiler/Defines, add _X86_ and _MSWIN
  • Options/Project/Compiler/Code Generation, set fastthis
  • Options/Project/32-bit Compiler, set Processor and Data alignment to 8 bytes
  • Compile

To build a plug-in,

  • Create a new project
  • Set the Target Type to Dynamic Library [.dll]
  • In Standard Libraries, mark Dynamic
  • Set the Platform to Win32
  • Set the Target Model to GUI
  • Add nodes with servmain.c, server.lib, and your source files; remove unused nodes
  • Options/Project/Directories, set include and lib paths
  • Options/Project/Compiler/Defines, add _X86_ and _MSWIN
  • Options/Project/Compiler/Code Generation, set fastthis
  • Options/Project/32-bit Compiler, set Processor and Data aligment to 8 bytes
  • Compile

GNU gcc/Mingw32
Information provided by Dan Maas

You can build LightWave® plug-ins with Win32 GNU distributions. The procedure given here was developed and tested with the Mingw32 distribution.

Before creating any plug-ins, you'll need to build a GNU version of the SDK library. (Some of the command lines below wrap to a second line here, but they should be entered on a single line.)

  • cd to the SDK source directory.
  • Compile the library sources.

    gcc -c -D_MSWIN -D_X86_ -O6 -I$(LWSDK_INCL) servmain.c servdesc.c username.c startup.c shutdown.c

  • Assemble the library.

    ar r libserver.a servdesc.o username.o startup.o shutdown.o

To build a plug-in,

  • Compile your source files.

    gcc -c -D_MSWIN -D_X86_ -O6 -I$(LWSDK_INCL) myplug.c

  • Link to the SDK library and generate a DLL.

    dllwrap -o myplug.p --export-all --dllname myplug.p myplug.o $(LWSDK_LIB)servmain.o $(LWSDK_LIB)libserver.a

An equivalent makefile would look like this:

   LWSDK_CFLAGS = -D_MSWIN -D_X86_ -O6

   %.o: %.c
   gcc $(LWSDK_CFLAGS) -I$(LWSDK_INCL) -c $<

   myplug.p: myplug.o
      dllwrap -o $@ --export-all --dllname $@ \
      myplug.o $(LWSDK_LIB)servmain.o $(LWSDK_LIB)libserver.a

Watcom C++ 10.0a

(On June 30, 1999, Sybase, Inc., sent an "end of life" letter to registered owners of Watcom C/C++ announcing that version 11.0 of the compiler would be its last. Watcom is therefore unlikely to play a role in future LightWave® plug-in development. This section remains useful, however, as an illustration of the incompatibilities you may encounter with some compilers.)

In Watcom terminology, plug-ins are NT DLLs, so that should be your target type. server.lib should also be built as an NT object.

To build a plug-in,

  • Add the SDK include directory to the include path [-i]
  • Disable stack depth checking [-s]
  • Add macro definitions [-d] _X86_ and _MSWIN
  • Change char default to signed [-j]
  • Use 8-byte structure alignment [-zp8]
  • Use the 32-bit flat memory model [-mf] (the default)
  • Use the stack-based calling convention [-5s]
  • Export [exp] _mod_descrip

There's an important mismatch in calling conventions that apparently can't be solved with a compiler switch or a pragma. When using the stack-based calling convention, which plug-ins must, Watcom 10.0a expects functions that return floating-point numbers to put them in specific registers, while LightWave®'s code leaves them at the top of the FPU stack. You'll encounter this whenever a plug-in compiled with Watcom needs to call a plug-in SDK function that returns a double.

What happens can be illustrated with a little assembly-ish pseudocode. Given

   double routine( void );
   double result;
   result = routine();

the different ways the function call is handled are

   Microsoft:  call routine
               fstp result          ; pop ST(0) into result

   Watcom:     call routine
               mov result,   eax    ; move edx:eax into result
               mov result+4, edx

When compiled in Microsoft Visual C++, routine leaves its return value at the top of the FPU stack, which is popped into result. In Watcom 10.0a, routine leaves its return value in the register pair edx:eax, which is then moved into result.

The workaround for this involves adding a bit of inline assembly language to each source file that contains a call to a LightWave® function returning a double. At the beginning of each such file, put

   static double fac;      /* floating point accumulator */
   extern void sdk_fstp( void );
   #pragma aux sdk_fstp = "fstp fac"
   #define SDK_DBLRTN( x ) \
      sdk_fstp();          \
      x = fac;

This uses Watcom inline assembly to load the contents of ST(0) into a fixed memory location, from which the value can be copied. Calls that would look like this

   result = objInfo->dissolve( objID, t );

must be changed to

   objInfo->dissolve( objID, t );
   SDK_DBLRTN( result );

The SDK function is called without assigning its return value. The assembly instruction fstp fac is inserted after the call to retrieve the return value, then fac is copied into result.

Macintosh

There are two binary formats of LightWave ® available: PEF (often refered to as CFM) and Mach-O (often refered to as a universal binary).  The binary format of plug-in modules must match the binary format of LightWave ® you wish to support; this means the CFM version of LightWave does not load Mach-O plug-ins, and the Mach-O version of LightWave does not currently load CFM plug-ins.

Mach-O:
To support the Mach-O version of LightWave ®, you must recompile your plug-in as a Mach-O '.plugin' bundle.  At its heart, the Mach-O plug-in binary is a dynamic library (dylib).  It is possible to create a non-bundled dylib plug-in that LightWave ® will load; but as of Mac OS Tiger (10.4), dylibs are never unloaded and your plugin may experience bizarre behavior.  It is best to 'wrap' your plug-in dylib in a bundle.  A bundle is really just a folder that uses a documented structure allowing you to package additional files inside your plug-in bundle, such as documentation, required frameworks, images, and more.  Plug-in bundles make use of the '.plugin' extension; therefore, this is the extension that should be used instead of the '.p' extension.  The reason is that the MacOS Finder will understand that the bundle is not just a folder and will view it correctly.  LightWave will understand bundles with the ".plugin" or ".p" extension. Because a Mach-O plug-in can be a universal binary, it is useful to package multiple CPU architecture support in the same plug-in bundle.  This is easily accomplished using XCode projects.  It is a good idea to also read the man pages for the 'lipo', 'otool', and 'libtool' command line utilities.  Currently, the only architectures supported for LightWave (and therefore plug-ins) is PowerPC 32bit and Intel 32bit. When 64bit support in LightWave for MacOS is available, your plug-in also needs to support the PowerPC 64bit and Intel 64bit architectures. Keep in mind that a 64bit Mach-O LightWave ® is not compatible with a 32bit Mach-O plug-in; but a universal binary plug-in with multiple architecture support will support all universal binary versions of LightWave that has matching architecture support.

Mach-O plug-ins should be created with the latest version XCode (which as of this writing is XCode 2.4).

PEF:
To support the PEF (CFM) version of LightWave ®, you must build your plug-in as a PEF (CFM) shared library (type 'shlb') for the PowerPC CPU architecture.

CFM plug-ins should be created with MetroWerks CodeWarrior (preferably version 10, the latest and last version available).

CodeWarrior (preferrably version 10)

Before compiling any plug-ins, you may want to build a CodeWarrior version of the SDK library.  Alternatively, you may add individual SDK files to your plug-in's project directly.

  • Create a new, empty project.
  • Target Settings/Linker, choose Mac OS PPC Linker.
  • PPC Target/Project Type, choose Library.
  • PPC Target, name the file server.lib.
  • PPC Processor, set alignment to PowerPC (the default).
  • Define the _MACOS and _PPC_ preprocessor symbols.
  • Access Paths/Systems Paths, add the SDK include directory.
  • Add servdesc.c, username.c, startup.c, and shutdown.c. These are located in the SDK source directory.
  • Build.

To create a plug-in project,

  • Create a new, empty project.
  • Target Settings/Linker, choose Mac OS PPC Linker.
  • PPC Target/Project Type, choose Shared Library.
  • Define the _MACOS and _PPC_ preprocessor symbols.
  • Access Paths/Systems Paths, add the SDK include directory.
  • PPC PEF/Export Symbols, choose the method you'll use to export _mod_descrip. See the CodeWarrior shared library documentation for an explanation of the available methods.
  • C/C++ Language, turn on Relaxed Pointer Type Rules and Enums Always Int.
  • Add your source code files to the project, along with servmain.c.
  • Add server.lib and MSL Runtime-PPC.Lib to the project. If the plug-in calls Mac Toolbox routines, you'll also need to add InterfaceLib (or CarbonLib if you're compiling for OS X), but consider building your interface using the platform-independent components provided with the plug-in SDK instead.

You can globally define _MACOS and _PPC_ by putting them in a .h file and using that file as the C/C++ Language Prefix File (in Language Settings). To export _mod_descrip, you can create a text file containing simply "_mod_descrip". Call the file server.exp. In PPC PEF/Export Symbols, choose the "Use .exp file" method, and then add server.exp to the project.

In later versions of CodeWarrior, you may find that the compiler won't accept any sort of conversion between char * and const char *. To work around this, you can add the following pragma:

   #pragma old_argmatch on

Building plug-ins for OS X isn't much different from building them for previous operating systems on the Mac, but you must link with CarbonLib instead of InterfaceLib, and your code must be Carbon compliant. See the Carbon Porting Guide (an Adobe Acrobat PDF) at Apple's Developer website.

CodeWarrior up to version 6.2 cannot debug on OS X. Debugging in MacOS 9 is sufficient in most cases, but if you need to debug in OS X using the native debugger, you can use gdb in a terminal window.  CodeWarrior 10 debugs in OS X well.

When the CFM version of the LightWave ® application is run, OS X actually runs a Mach-O wrapper application that loads the program. The wrapper is called LaunchCFMApp, located in

   /System/Library/Frameworks/Carbon.framework/Versions/A/Support/
   LaunchCFMApp

To begin debugging, use gdb to run LaunchCFMApp.

   localhost% gdb /System.../LaunchCFMApp

In gdb, type run followed by the name and path for the LightWave® component in which your plug-in runs.

XCode 2

XCode only will build plugins for the Mach-O (or universal binary) architecture, not CFM. You must bundle your dylib so that you do not experience unloading problems, which may not be very apparent during basic testing.  Also needed with your plug-in bundle is a proper "info.plist" property list file.  This file must properly identify the plug-in type identifiers.

To create an XCode 2 project for your plug-in:

  • From the XCode File menu, choose 'New Project...'
  • From the provided dialog, choose one of the Bundle templates such as 'Carbon Bundle'.
  • Choose the project directory and name. When complete, you will have an empty starter project.
  • Add lwsdk source files to your project as needed; these files are located in the LWSDK's 'source' folder.
  • Add your source files to your project and to the default target. (FYI: Each project can contain many targets; and, each target can produce a different executable/plug-in).
  • Modify the project settings for all configurations (both the Release and Debug) (do not add the quotes): Set 'Preprocessor Macros' to "_MACOS". Set 'Header Search Paths' to the project relative location to the LWSDK include folder. Many of the samples in the LWSDK use "../../../../include" for example.
  • Modify the target settings for all configurations: Set 'Wrapper Extension' to "plugin"; this will 'convince' the Finder to show your plugin bundle as a single package than as a folder. Optionally, set 'Product Name' to something other than your project name.
  • Modify the Info.plist file: set the CFBundleSignature to "LW3D".
  • The use of the default InfoPlist.strings resource is optional if only one language is to be supported in your plugin. Its contents can be integrated into Info.plist in that case.
  • Build your project.

The XCode/GCC build environment provides several preprocessor definitions you will find useful if you intend to write plugin code that is cross-platform compatible:

  • __MACH__ -- This indicates the Mach-O binary architecture, use this to distinguish CFM and Mach-O source code.
  • __LITTLE_ENDIAN__ -- This indicates the current cpu architecture uses little endian memory access, which is typical for Intel-based processors. This is useful to determine if byte swapping of your data is necessary.
  • __BIG_ENDIAN__ -- This indicates the current cpu architecture uses little endian memory access, which is typical for PowerPC processors used on the Mac. This is useful to determine if byte swapping of your data is necessary.
  • __LP64__ -- This indicates a 64bit MacOS cpu architecture. In this programming model, 'long' and pointers are 64bits.
    • Macintosh Programmer's Workshop
      Information provided by Mark Nutter

      Before building any plugins, you'll need to build an MPW version of the SDK library:

      • In the MPW worksheet, select "Set Directory..." from the Directory menu and set the working directory to the source directory of the SDK.
      • Select "Create Build Commands..." from the Build menu.
      • Enter server.lib as the Program Name.
      • Select "Static Library" as the Program Type.
      • Check "PowerPC Only" as the Target.
      • Add servdesc.c, startup.c, shutdown.c and username.c as the Source Files. These are in the SDK source directory.
      • Click the Include Search Paths button and Add the SDK include directory.
      • Click the PowerPC Options button and enter -d _MACOS -d _PPC_ in the "C Options" field.
      • Click the Create Make button to generate a makefile for server.lib.
      • Select Build (Cmd-B) from the Build menu. The program name should come up as server.lib by default. Click OK to build server.lib. You will get a warning that "serverData" is not used within function "Shutdown", which you can ignore.

      To build a plugin:

      • Select "Set Directory..." from the Directory menu and set the working directory to the directory containing your plug-in's source files.
      • Select "Create Build Commands..." from the Build menu.
      • Enter your plug-in's name as the Program Name.
      • Select "Shared Library" as the Program Type.
      • Check "PowerPC Only" as the Target.
      • Add the source files for your plug-in. Also add servmain.c (in the SDK source directory) and the server.lib you created.
      • Click the Include Search Paths button and Add the SDK include directory.
      • Click the PowerPC Options button and enter -d _MACOS -d _PPC_ -typecheck relaxed in the "C Options" field.
      • Click the Exported Symbols button and enter _mod_descrip in the Export Symbols box.
      • Click the CreateMake button to create your makefile.
      • Build (Cmd-B).

      Unix

      Plug-in modules under Unix are shared object modules, or DSO files. When compiling, remember to define both _XGL and the preprocessor symbol for your CPU. _mod_descrip must be exported from the DSO, and servmain.o must be among the objects passed to the linker. The link line should include any other libraries that the plug-in would need as a stand-alone program.

         .o.p:
            ld -shared -exported_symbol _mod_descrip -L$(SDK_LIB) \
             $(SDK_LIB)servmain.o $*.o -o $@ -lserver.lib $(OTHER_LIBS)