L3DT users' wiki
Large 3D terrain generator

 

Making a new plugin

Author Aaron Torpy
Last update 19th of August 2011

Introduction

This 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 tools

Zeolite API files

To 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.

:tutorials:zeolite:aaron:plugindir.png

A compiler

The 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 example

Yiu 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 project

To create your new plugin project in Visual Studio, go to 'File→New→Project…' in the menu, and select the 'MFC DLL' option:

:tutorials:zeolite:aaron:newproject.png

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.

:tutorials:zeolite:aaron:newproject.png

Configuring your DLL

Before 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 paths

The first configuration change you should make is on the 'C/C++→General' page. Here, click in the 'Additional Include directories' field:

:tutorials:zeolite:aaron:includedirectories.png

This will open the 'Addional Include Directories' window, shown below. In this window, add two directories:

  1. the base directory for your plugin project's source code, and;
  2. the base directory for the Zeolite plugin API.

:tutorials:zeolite:aaron:includedirectories2.png

Precompiled headers

The 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.

:tutorials:zeolite:aaron:precompiled_headers.png

Output file name

The third configuration change is on the 'Linker→General' page. Here, we'll change the output file extension from DLL to ZEO (see below).

:tutorials:zeolite:aaron:output_filename.png

For 64-bit plugins, the file extension must be set to '.x64.zeo'.

Including files in your project

With your brand-new plugin project, the files in the solution explorer should look something like this:

:tutorials:zeolite:aaron:sln_files1.png

To make a Zeolite plugin, we have to include the following three files from the 'Zeolite vX.Y.Z' folder:

Filename What is it?
Zeolite.cpp Main source file for the plugin API.
Zeolite.h Main header file for the plugin API.
Zeolite_defines.h Header file for some additional definitions.

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:

:tutorials:zeolite:aaron:sln_files2.png

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 functions

Zeolite 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:

Source/header file What does it do?
zVar.cpp/h Implements the CzVar wrapper class for manipulating generic Zeolite variables.
zStr.cpp/h Implements the CzStr wrapper class for manipulating Zeolite string variables.
zList.cpp/h Implements the CzList wrapper class for manipulating Zeolite dynamic list variables.
zMap.cpp/h Implements the CzMap wrapper class for manipulating Zeolite map variables.
zMapTile.cpp/h Implements the CzMapTile wrapper class for manipulating Zeolite mosaic map tiles.
zFormat.cpp/h Implements the CzFormat wrapper class for manipulating Zeolite file format handler variables.
zFunc.cpp/h Implements the CzFunc wrapper class for loading and executing Zeolite extension functions.
zColour.cpp/h Implements the CzColour wrapper class for manipulating Zeolite colour variables.
zClimate.cpp/h Implements the CzClimate wrapper class for manipulating Zeolite climate variables.
zBuffer.cpp/h Implements the CzBuffer wrapper class for manipulating Zeolite buffer variables (static arrays).
zComboSel.cpp/h Implements the CzComboSel wrapper class that provides a user interface for selecting one or more items from a list.
zFileSel.cpp/h Implements the CzFileSel wrapper class that provides a user interface for selecting a file on the file system.
zDirSel.cpp/h Implements the CzDirSel wrapper class that provides a user interface for selecting a directory on the file system.
zProgBox.cpp/h Implements the CzProgBox wrapper class for displaying a progress window.
zFile.cpp/h Provides utility functions for manipulating file and directory path names, finding files, etc.
zMenu.cpp/h Provides functions for adding items to L3DT's menu.
zProj.cpp/h Provides functions for interacting with the map project, including storing and retrieving maps and settings.
zView.cpp/h Provides functions for interacting with the main map display in L3DT (the 'view'), such as setting the active map, refreshing the screen, getting section tool coordinates, etc.
helper/zApp.cpp/h Provides various functions for accessing features and settings of the L3DT application, such as getting path settings, icon handles, etc.
helper/zBackup.cpp/h Provides functions for creating and restoring to backup points, and creating and using 'undo' points.
helper/zBin.cpp/h Provides functions for serialising/de-serialising Zeolite variables and structures to/from binary buffers.
helper/zCalcHF.cpp/h Provides functions for performing heightfield calculations.
helper/zCalcMan.cpp/h Provides functions for accessing L3DT's threaded calculation manager, including aborting calculations, setting progress display data, etc.
helper/zCalcMap.cpp/h Provides functions for performing generic map calculations, such as resizing.
helper/ZeoScriptEx.cpp/h Provides an extended function for calling ZeoScript scripts.
helper/zMapTypeID.cpp/h Provides functions for converting map type identifiers to/from text strings.
helper/zSettings.cpp/h Provides functions for accessing persistent application settings, including user preferences, window settings, etc.
helper/zVarCast.cpp/h Provides functions for casting Zeolite variables from one data type to another.
helper/zXML.cpp/h Provides functions for loading and saving Zeolite variables and structures to/from XML files (using L3DT's dialect of XML).

Important includes for your source files

To 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 #include them also.

Creating the zeoInitPlugin function

Every plugin must define a function called zeoInitPlugin. This function notifies the plugin that the Zeolite API instance has been loaded and that it may now proceed to call API functions or create/manipulate Zeolite objects. The function should look like this:

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 zeoInitPlugin function is called. The Zeolite API for each plugin is initialised after the plugin is loaded into memory and the InitInstance or dllmain functions are called, and before the zeoInitPlugin function is called.

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 zeoInitPlugin function was defined using the zeoexport keyword and the stdcall calling convention. Any and all functions exported from your DLL that are to be used by the plugin API must be similarly defined with zeoexport 1) and stdcall 2). An example function called MyFuncName with a return type of bool is given below:

zeoexport bool __stdcall MyFuncName();

If you do not use stdcall, your plugin will crash after the function returns. If you do not include the zeoexport keyword, you will need to explicitly include this function in the exports table for your DLL so that it is available to the application.

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 zeoShutdown function is called, but before the DLL itself is released from memory. Thus, plugins must not call API functions after zeoShutdown is called.

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 zeoInitPlugin function in your plugin. We do this using the zformat_Create/zformat_CreateGeneric API functions. Here is an example from the L3DTio_ASC plugin, which exports the heightfield in the ERSI ASCII grid file format:

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 functions

The 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 zeoInitPlugin function call.

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:

  • an extension function should be loaded into the extension function namespace as “MyPlugin.SomeFunctionNamespace.HelloWorld”,
  • the function is declared with a name of “HelloWorld” within the plugin,
  • the function has a return type of void, and;
  • the function takes one argument; a string called “TextStr”.

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 function

For 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 functions

To 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 zmenu.h and zmenu.cpp files. This function takes four arguments:

Argument Data type What is it?
lpScript const char* A script, in the ZeoScript language, that defines the action to be taken when the user clicks on the menu item. This may be a single function call, or a long set of instructions. See below for a discussion and example.
lpItemName const char* The text to be displayed in the menu link. Note that the dot '.' character is used to delimit sub-menus. Consequently, dots for ellipses (…) are not permitted.
MenuContext unsigned long An integer identifying where the menu item should be placed. Valid MenuContext values are given below. Zero is default.
Flags unsigned long Reserved for future use. Value must be zero.

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 zmenu_InsertItem allows developers to assign complex scripted actions to menu items. However, for plugins, the easiest way to add a menu item is to define a function to handle the menu event, and call that function in the menu script.

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 zeoInitPlugin with no arguments, and a void return value, like so:

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 zeoInitPlugin using zmenu_InsertItem. The script is simply the function's name, which in this case was “UI.FunctionBrowser.ShowWikiPage”.

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 values

L3DT 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:

Symbol Value What does it mean?
ZMENU_CONTEXT_EXT_CUR 0 The item will be inserted into the calling plugin's sub-menu within the 'Extensions' top-level menu (this is default).
ZMENU_CONTEXT_EXT_ROOT 1 The item will be added to the 'Extensions' top-level menu.
ZMENU_CONTEXT_FILE_IMPORT 2 The item will be added to the 'File→Import' menu.
ZMENU_CONTEXT_FILE_EXPORT 3 The item will be added to the 'File→Export' menu.

Actually doing something

In 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 testing

Compiling

To 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 L3DT

To 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 startup

If 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 compiling

To 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:

C:\Program files\Bundysoft\L3DT [version]\L3DT.exe

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 plugin

Once you have built your plugin, please let us know about it by writing a short post to the plugins forum 4).

Keeping up to date

From 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 questions

If you have any questions or comments regarding this tutorial, please post them in this forum thread.

1) This disables name mangling and enforces predictable name decoration so that you don't need to include the function in the exports table.
3) These actions may be done in either order within the plugin source code.
4) …or a long post, if you prefer.
 
tutorials/zeolite/aaron.txt · Last modified: 2017/08/31 07:27 (external edit)
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki