Table of Contents
Making a new plugin
IntroductionThis guide is intended to walk developers through the process of making a plugin for L3DT using version 11.08 of the Zeolite plugin API. In writing this guide I have assumed that you, the reader, have a working knowledge of the C programming language. You do not however require any experience in creating DLLs, or using any other APIs or libraries. Experience with C++ is not strictly required, but may be helpful in the long run.
If you are updating an existing plugin to the latest version of Zeolite, please refer to the upgrading a plugin to Zeolite v11.08 tutorial.
Required toolsZeolite API filesTo make a plugin you will of course need a copy of the Zeolite API, which you can download from here. The zip archive contains a folder called 'Zeolite vX.Y.Z' (where X, Y and Z are the version numbers). I have this folder sitting in my plugin projects directory (see below) with each plugin setup to share the same version of the plugin API. A compilerThe other required tool is a C/C++ compiler, and in this tutorial I will be using Microsoft Visual Studio 2008. The plugin API should be compatible with other compilers/IDEs, but for this tutorial I'll stick with what I know, and that's Visual Studio. An exampleYiu may find it helpful to refer to the source code of an example plugin when building your own. You may download the source for some selected plugins from the plugin examples page. The simplest example, TestDLL, is available here, and shows how to structure and configure an 'empty' plugin project. Creating your plugin projectTo create your new plugin project in Visual Studio, go to 'File→New→Project…' in the menu, and select the 'MFC DLL' option: You don't actually need to use the Microsoft Foundation Classes to make a plugin for L3DT, but again, I'm sticking with what I know best, and that happens to be MFC. Anyway, the next thing to do is enter your project name. If you intend to eventually share this plugin with other users, I recommend you add your initials to the start of the plugin name. For example, many of the plugins I make start with 'at' (for Aaron Torpy), so as to distinguish them from plugins made by other developers. After you click OK, you will be presented with a few more project settings. Here I recommend that you select the 'Regular DLL with MFC statically linked' option (see below). This saves you from having to deal with SxS assemblies or re-distributable MFC DLLs for your plugin, and also means you don't have to add the AFX_MANAGE_STATE macro to the start of all your function calls. The down-side is that your plugin will be slightly larger, and that the MFC libraries can't then be upgraded/updated by Microsoft because they're inside your DLL. Anyway, it's your choice. Configuring your DLLBefore we start to do anything interesting, we have to make three changes to the project configuration properties. To open the project properties window, go to 'Project→Properties' in the menu. Include pathsThe first configuration change you should make is on the 'C/C++→General' page. Here, click in the 'Additional Include directories' field: This will open the 'Addional Include Directories' window, shown below. In this window, add two directories:
Precompiled headersThe second configuration change is on the 'C/C++→Precompiled headers' page. Zeolite is not distributed with precompiled headers, so you'll either need to select 'Automatically generate' (if you want to use precompiled headers), or 'Not using precompiled headers' (if you don't). If you don't make this change, you'll get a 'missing precompiled header directive' error when compiling. Output file nameThe third configuration change is on the 'Linker→General' page. Here, we'll change the output file extension from DLL to ZEO (see below).
For 64-bit plugins, the file extension must be set to '.x64.zeo'.
Including files in your projectWith your brand-new plugin project, the files in the solution explorer should look something like this:
To make a Zeolite plugin, we have to include the following three files from the 'Zeolite vX.Y.Z' folder:
To add these files to the project, right-click on the 'Source Files' / 'Header files' folders, and select the 'Add→Existing items…' option, then select the three files to include. At the end, your solution explorer should look like this:
Note that 'Zeolite.cpp' is in 'Source files', and both 'Zeolite.h' and 'Zeolite_defines.h' are in 'Header files'. Optional wrapper classes and helper functionsZeolite also includes other header files for C++ wrapper classes and helper functions, some or all of which you may optionally add to your project. These files are:
Important includes for your source filesTo use the plugin API in your source code, you need to include the Zeolite header file somewhere near the top of your CPP file(s): #include <Zeolite.h>
If you use any of the wrapper classes or helper functions listed in the previous section, you will need to Creating the zeoInitPlugin function
Every plugin must define a function called zeoexport bool __stdcall zeoInitPlugin() { // do any custom initialisation steps here... // and return true (if false, plugin will be unloaded immediately) return true; }
Plugins must not call Zeolite API functions or declare instances of CzVar/etc classes until the
Later in this tutorial I'll explain some of the custom initialisation steps that you can perform within the
zeoInitPlugin function, such as registering file formats, extension functions and menu items.
Calling conventions and name decoration
In the preceding section the zeoexport bool __stdcall MyFuncName();
If you do not use The zeoShutdown function (optional)Plugins can - but are not required to - also export a function that is called just before the plugin is released by the application. This function is called zeoShutdown, and must have the following prototype: zeoexport void __stdcall zeoShutdown(void); This shutdown function is typically used by plugins to to release allocated memory, de-initialise other libraries, etc. Here is an example from the Free Image plugin, where ExtShutdown is used to de-initialise the FreeImage library. zeoexport void __stdcall zeoShutdown() { FreeImage_DeInitialise(); // kill FreeImage }
The plugin API instance of your plugin will be destroyed after the File I/O functions
For a plugin to load and/or save map files in a format other than those already supported by L3DT, it must do two things; the first of which is to register the format in the zeoexport bool __stdcall zeoInitPlugin() { // create a format handler for the "Heightfield" project map ZFORMAT hFormat = zformat_Create("Heightfield", "ESRI grid file (ASCII)", "asc", NULL); if(!hFormat) { // set the flags zformat_SetFlags(hFormat, ZFORMAT_FLAG_NATIVE|ZFORMAT_FLAG_SAVE|ZFORMAT_FLAG_LOAD); // set the option(s) bool UseProjGeoref = true; zformat_SetOptionValue(hFormat, "UseProjGeoref", VarID_bool, &UseProjGeoref); } // create a format handler for any map that is a heightfield hFormat = zformat_CreateGeneric(MAP_Heightfield, "ESRI grid file (ASCII)", "asc", NULL); if(hFormat) { // set the flags zformat_SetFlags(hFormat, ZFORMAT_FLAG_NATIVE|ZFORMAT_FLAG_SAVE|ZFORMAT_FLAG_LOAD); // set the option(s) bool UseProjGeoref = false; zformat_SetOptionValue(hFormat, "UseProjGeoref", VarID_bool, &UseProjGeoref); } return true; }
For more information on file format creation and configuration, please refer to the documentation for the file format functions in the Zeolite user-guide.
Now, the second thing a plugin must do to load/save map files is export one or more of the four recognised file I/O functions, which are: // save a map file zeoexport bool __stdcall zeoSaveMapFile(ZMAP hMap, const char* lpFileName, ZFORMAT hFormat, ZVAR hProgWnd); // load a map file zeoexport bool __stdcall zeoLoadMapFile(ZMAP hMap, const char* lpFileName, long MapTypeID, ZFORMAT hFormat, ZVAR hProgWnd); // save a tile in a mosaic map zeoexport bool __stdcall zeoSaveTileFile(ZMAP hMap, void* hTile, const char* lpFileName, ZFORMAT hFormat); // load a tile in a mosaic map zeoexport bool __stdcall zeoLoadTileFile(ZMAP hMap, void* hTile, const char* lpFileName, ZFORMAT hFormat); The sorts of things you do within these functions to actually load/save the map is perhaps best described by an example. I recommend you take a look at the source code for the HFF plugin, which you can find here. Source code for other example plugins are available here. You may also find this a good time to peruse the documentation for the Zeolite API functions, which explains the usage of all the API functions used in the above examples. Adding extension functionsThe Zeolite plugin API allows plugins to define extension functions, which are shared functions that can be called by the application, by other plugins, from scripts, or from L3DT's drop-down menu. To add an extension function, the plugins must do two things; define the function, and load the function 3). Loading an extension function
The first step in adding an extension function is to load the extension function using zfunc_Load during the In the below example I register an extension function called “HelloWorld”, which takes one argument (a string) and has no return value (void). zeoexport bool zeoInitPlugin() { // create a list to hold the argument list definition for my extension function ZLIST hArgList = zvar_Create(VarID_VarList); // create a string called 'TextStr' in the function argument list zlist_CreateItem(hArgList, VarID_string, "TextStr"); // now register the extension function zfunc_Load("MyPlugin.SomeFunctionNamespace.HelloWorld", "HelloWorld", VarID_void, hArgList); // ...and finally, release the argument list container zvar_Delete(hArgList); return true; } The above code tells the plugin API that:
Equivalently, the above code may be simplified using the CzList wrapper class: zeoexport bool zeoInitPlugin() { // declare a list to hold the argument list definition for my extension function CzList args; // create a string called 'TextStr' in the function argument list args.CreateItem(VarID_string, "TextStr"); // now register the extension function zfunc_Load("MyPlugin.SomeFunctionNamespace.HelloWorld", "HelloWorld", VarID_void, args); // no need to manually delete 'args'; it is destroyed when it goes out of scope return true; } Defining an extension functionFor the above zfunc_Load call to succeed, the named plugin function must be declared somewhere within the plugin. The prototype for all extension functions definitions looks like this: zeoexport bool __stdcall MyExtFunctionName(ZVAR hReturnVar, ZLIST hArgList) { // get the extension function arguments (from hArgList), and do something with them... // set the extension function return value (to hReturnVar)... // and return true (false indicates a serious error has occurred and error diagnostics should be logged) return true; }
All extension functions have a compiler return value of
bool . Functions should return true unless a serious error occurred, as returning false will throw messages for error diagnostics to the event log.
The hReturnVar and hArgList function arguments are handles to the return value and argument list objects, which may be manipulated/accessed using the various Zeolite API functions. Below I give an example of the “HelloWorld” extension function loaded in the previous example, which displays a text message passed to it as an string argument: zeoexport bool __stdcall HelloWorld(ZVAR hReturnVar, ZLIST hArgList) { // get the first (and only) function argument ZVAR hText = zlist_GetItemI(hArgList, 0); // check that the argument is indeed a string (just in case) if(!zvar_IsType(hText, VarID_string)) { zeoReportError("HelloWorld error:\r\n - invalid argument type"); return false; // bad error, let's get out now and log some diagnostics } // show the string in a message box AfxMessageBox(zstr_GetText(hText), MB_ICONINFORMATION); // the function has a void return value, so I don't need to do anything to 'hReturnVar' // everything went OK return true; } Calling extension functionsTo call an extension function, use the zfunc_GetFunc API function to get the function handle, and then execute it using the zfunc_Execute function. Alternatively, you may use the corresponding functions in the CzFunc wrapper class, as shown below: CzFunc funcShowMap; // declare a function wrapper // attach to the 'view.ShowMap' extension function if(!funcShowMap.GetFunc("view.ShowMap")) { return false; } // set the name of the map to be shown ("HF") in the first function argument zstr_SetText(funcShowMap.GetArgI(0), "HF"); // execute the function funcShowMap.Execute(); You can also use the script_Execute function to get and execute the function in a simple script call. For simple function calls (those with no or few arguments), this method is preferred. The example below performs the same actions as the example above, but more tersely: script_Execute("zs", "view.ShowMap \"HF\"", NULL); Adding menu options
You may add options to L3DT's menu using the zmenu_InsertItem function declared and defined in the
If successful, the return value of zmenu_InsertItem will be a non-negative integer that identifies the menu item. In the fullness of time, this integer may be used for modifying menu item states, setting icons, etc. Menu scripts and functions
The lpScript argument of For example, in the 'atFuncBrowser' plugin I have a function for showing the plugin's help webpage, which is called from the menu under 'Extensions→atFuncBrowser→Visit project page'. This function is defined like so: // this function takes no arguments, has no return value, and simply opens a webpage zeoexport bool __stdcall ShowWikiPage(ZVAR hReturnVar, ZLIST hArgList) { ShellExecute(NULL, "Open", "http://www.bundysoft.com/wiki/doku.php?id=plugins:general:atFuncBrowser", NULL, NULL, SW_NORMAL); return true; }
This function is loaded in zeoexport bool __stdcall zeoInitPlugin() { // load the extension function // ( note the function is declared in this plugin as 'ShowWikiPage' (above), but will be loaded // into the L3DT extension function namespace as 'UI.FunctionBrowser.ShowWikiPage'. ) zfunc_Load("UI.FunctionBrowser.ShowWikiPage", "ShowWikiPage", VarID_void, NULL); ... }
Finally, the menu item is loaded in zeoexport bool __stdcall zeoInitPlugin() { ... // add the menu option zmenu_InsertItem("UI.FunctionBrowser.ShowWikiPage", "Visit project page", 0, 0); return true; }
If the 'ShowWikiPage' function had arguments, they would be listed after the function name in the script, separated by spaces. However, this function has no arguments, so the script call to execute the function is simply the name of the function.
MenuContext valuesL3DT does not allow plugins to add items just anywhere in the application's menu. Rather, there are a defined set of sub-menus into which you may add items. These menus are identified by menu contexts, defined below:
Actually doing somethingIn this tutorial I have explained how to build the skeleton of a plugin, including how to create and configure the plugin project, include the Zeolite API files, and add file format handler functions, extension functions, and menu items. However, I have not shown you how to actually implement the detailed insides, such as how to read texture map values when saving files in a custom file format writer, or how to read the light map settings from the map project. These specifics are I think best illustrated by real examples, and for this reason, I have provided the complete source code for a number of plugins on the Zeolite examples page, ranging from simple file format plugins up to complex calculation and user-interface plugins. If you can't find an example that helps, please feel free to ask for advice in the plugins forum, and I will be more than happy to render any assistance possible. Compiling and testingCompilingTo compile your plugin in Visual Studio, select the 'Build→Build Solution' menu item, or press F7. At this point, if there are no compiler errors, the output window should finish with something like this: 1>YourDllName - 0 error(s), 0 warning(s) ========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ========== At this point, you have a complete Zeolite plugin that is ready to load in L3DT. Help! My plugin doesn't compile!If your compiler is throwing errors that prevents it from compiling your plugin, and you can't understand what's gone wrong, please report the problem in the plugins and scripts forum. Common errors and their fixes will be listed in this section if/when they are reported. Loading the plugin in L3DTTo load the plugin in L3DT, open the program and select the 'Extensions→Extension manager' menu option. In the extension manager, press the 'Load' button, and select your '.zeo' file, which should be under the 'release' or 'debug' sub-folder of your project directory (depending on whether you have compiled in release or debug configurations). Once your plugin has been loaded, you should restart L3DT to begin testing your plugin. Errors during startupIf L3DT begins throwing error messages during startup after loading your plugin, please report the problem in the plugins and scripts forum. Common errors and their fixes will be listed in this section if/when they are reported. Automatically launching L3DT after compilingTo launch L3DT automatically after you compile your plugin, select the 'Debug→Start without debugging' menu option, or press CTRL+F5. The first time you do this, Visual Studio will open the window shoen below: In this window, set the executable file name to the path to the L3DT executable. To do this, click the down arrow on the drop list, select the 'browse…' optino, then in the file dialog box, select the L3DT executable from he following path:
Every subsequent time you select 'Debug→Start without debugging' or press CTRL+F5, L3DT will launch and load your newly-compiled plugin. Advertising / sharing your pluginOnce you have built your plugin, please let us know about it by writing a short post to the plugins forum 4). Keeping up to dateFrom time to time, the Zeolite plugin API will change. New features will be added, and bugs will be fixed fixed. Usually, these changes are minor and don't affect binary or source compatibility. Rarely, these changes are major and require all plugins to be modified and recompiled. Whatever the magnitude of the change, I try to provide suitable advanced warning so that plugin developers know what to expect and have time to provide feedback. The primary channels for this communication are:
I would recommend plugin developers add the above feeds to their RSS reader to keep an eye on upcoming changes. Further questionsIf you have any questions or comments regarding this tutorial, please post them in this forum thread. Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Share Alike 3.0 Unported
|