GX Based DLL Development
In order for your DLL code to interact via a GX all of the functionality must be wrapped in a function which will be called within GXC code. The function must be exported from the DLL using the "C" calling convention in order to be accessible from within GXC. The first parameter in the exported function MUST BE the Geosoft handle, and is the only required parameter. If your function is providing it's own user interface that will typically be all that is passed.
// In this example taken from the callfunc example the user interface // is being provided by the GX and all parameters are being passed in. // GX_OBJECT_PTR is a void pointer and is defined in geoengine.core.gxlib.h __declspec(dllexport) long __cdecl iSum_CALLFUNC( // returns sum of two numbers GX_OBJECT_PTR pGeo, // geosoft handle const long *pl1, // first number const long *pl2) // second number // Strings are a special case, you need to pass the length as well __declspec(dllexport) long __cdecl iStringFunction( GX_OBJECT_PTR pGeo, // geosoft handle char * myString, const long * pmyStringLen)
The exported function will not be visible to your GXC code without being declared either in a GXH file or within the GXC itself. The function declaration does not include the geosoft handle. Note also that the integer pointers expected by the DLL are declared simply as type int within the header. There are no pointers in the GXC language. Strings are a special case. The length of the string must always be passed along with the string.
//---------------------------------------------------------- // iSum_CALLFUNC Return the sum of two numbers [callfunc] // --- this is the name of the DLL (callfunc.dll) --- int iSum_CALLFUNC( int, // first number int ); // second number [MyDLL] // name of DLL containing iStringFunction int iStringFunction( string, int); // length of string;
The alternative to declaring the function in a GXH is to declare it within the GXC file. See the iMyFunction example below.
Finally, the last step is to call the function from within a GX.
Error Handling
It is good practice for your DLL function to return a code to the calling GX indicating succesful completion or an error code. For example if the user presses Cancel on your DLL controlled form it would be advantageous to capture that information in the GX so that the GX and any script calling the GX can be halted.
__declspec(dllexport) long __cdecl iMyFunction( GX_OBJECT_PTR pGeo){ const GX_RESULT_OK = 0; const GX_RESULT_CANCEL = 1; const GX_RESULT_ERROR = -1; if ( .. something that returns an error...) { return GX_RESULT_ERROR; } else if ( .. user pressed cancel...) { return GX_RESULT_CANCEL; } else { return GX_RESULT_OK; } }
//================================================= NAME ="MyFunction" VERSION ="v1.00 " DESCRIPTION ="Does something wonderful, I'm sure " //=========================================================================== #include <all.gxh> [MyDLL] int iMyFunction(); //=========================================================================== { switch (iMyFunction()) { case -1: Abort_SYS(); case 1: Cancel_SYS(); } }
NOTE: The actual effect of Cancel_SYS is to cause the GX to end without logging it in the event log or displaying an error message, which is sufficient in an interactive sense. However when the user Cancels the execution of a GX during a script they generally want the script to end as well. This can be accomplished by beginning the script with the "CANCEL_AS_ERROR ON" command as the first item. Terminating your GX with Abort_SYS will always terminate a script and show an error message.
Using the C++ API
To use the C++ API one has to simply construct a single GXContext object to wrap the pGeo pointer and maintain it in thread local storage for the duration of the code that will uses the API. To illustrate here is a simple example that uses the C++ AP to open a GDB with its filename (it is assumed that a GX was written to browse for the file and pass it into the entry point):
using namespace geosoft::gx::geogx; __declspec(dllexport) long __cdecl iProcessDB(GX_OBJECT_PTR pGeo, const char *file) { auto ctx = GXContext::create_internal(pGeo); try { auto db = GXDB.open(name, 'SUPER', ''); // Do stuff with it } catch (GXCancel&) { return 1; } catch (GXExit &) { return 0; } catch (GXError & e) { MessageBox(NULL, e.message(), _L("GXError"), MB_OK | MB_ICONERROR)); return -1; } catch (GXAPIError &api_e) { MessageBox(NULL, api_e.message(), _L("GXError"), MB_OK | MB_ICONERROR)); return 0; }
The exception handlers shown are all required since unhandled exceptions will lead to montaj crashing. The way the exceptions are handled can change of course. Depending on your code you might need to handle other C++ exceptions. Do however beware of using GX C++ API calls that might throw inside exception handlers.
Installing your custom DLL
Third party applications are usually placed in the <geosoft>\bin or the <geosoft>\resourcefiles\bin directory but can be placed in a different location if desired. A registry setting is required to locate each DLL that is not placed in a \bin directory. Add the following registry key to the system running the application:
HKEY_LOCAL_MACHINE\SOFTWARE\Geosoft\{KEY}\3rdPartyDLLs.
The {KEY} is the version of oasis montaj that will be running. In the standard Oasis montaj version this key is “Oasis montaj” and for the Viewer it is “Oasis montaj Viewer”. However, some special versions of montaj for our CS customers do have unique key names. Note that in this case version doesn't refer to the version number, but rather the name of the product.
Once this key is created, populate it with the names of each DLL needing to be redirected to a different location. For example, if a custom GX called a method in my geocustom.dll file installed in c:\program files\custom, I would create a new string in the registry:
Name: geocustom.dll
Data: <Program Files>\custom\geocustom.dll
The first time your GX is run, montaj will try to load the DLL from <geosoft>\bin first. If it is not found there, it will check this registry setting for any matches of the DLL name and load it from the specified path instead. Until montaj has been restarted, the DLL will always be loaded from the alternate location.
Connecting your code to the Geosoft environment
There are several different ways of connecting your code to the Geosoft environment of your choice:
- GX based - In this scenario, which is the simplest, you create a GX which calls an exported function or functions in your DLL. That function receives a "handle" to the Geosoft object which is necessary to interact with the API functions. Upon completion the DLL function returns control to the GX. In this scenario your function behaves similarly to an API function, in that it's lifespan is limited to the GX execution.
- .NET based - A function exported from a .NET assembly can be called directly from a menu item without wrapping it in a GX, but is otherwise the same as above.
- External application - A standalone program can be built which creates a separate instance of the Geosoft executable in memory and accesses databases or maps. Some parts of the API are not available using this method. Furthermore the external application can not interact with a running instance of the Geosoft program. However, a GX can be created that will prepare your Geosoft project so that it can be opened and processed by the external application, which can be run using the GX iShellExecute_SYS() method. The Python package also allows you to create fully 64-bit external applications that access the GX API.
- GeoXTool application - This is a DLL based program that becomes part of the running instance of Geosoft. A GeoXTool is able to interact with the program and 'react' to user actions such as changing line, selecting data or zooming/ panning a map. This type of tool is most difficult to create but very powerful.
GeoXTool Application
A GeoXTool is a DLL based application that integrates itself into the environment. Once launched from a GX the process stays resident and responds to events within the environment. If the tool has a user interface, the exposed form will become part of the sidebar, or will be displayed as stay-on-top if undocked. Currently the C and GXNET APIs may be used to create a GeoXTOOL, and examples are provided of each.
Several steps are necessary to create a GeoXTool.
- Create an exported function that initializes the GeoXTool.
- Create an object that IS the GeoXTool. This object can respond to the events in the GeoXTool Interface and can trigger events in the GeoXTool API via callbacks. Typically it will display a form that gets parented (ie embedded into) the sidebar. Once initialized the interface method callbacks will be called whenever appropriate. The API callback functions can be triggered by user actions or within the interface methods.
- Create a GX to launch the GeoXTool.
Creating a GeoXTool
For C developers all of the function prototypes are provided in "geoxtool.h". All of the functions in the API and Interface need to be separately created and assigned to their respective callbacks during the creation of the tool. The GeoXTool MFC example is a complete example.
extern "C" __declspec(dllexport) void * _cdecl hCreate_GEOXTOOL(void *hGEO, // Geosoft Handle HWND hParent, // Parent Window long lMETA, // META object to get info from HWND *phWND, // Filled with new HWND of your tool const GEOXTOOL_API *pAPI, // API Functions GEOXTOOL_INTERFACE *pFuncs, // Filled with function pointers char *pcToolName, // Buffer for the name of the tool long lToolNameSize) // Size of Tool Name Buffer
For .Net developers the process is somewhat simpler. By creating a control descending from ToolControl most of the plumbing is already taken care of. ToolControl is implemented in the geoengine.core.gxnetx assembly
GeoXTool API
This object can respond to the following events in the environment:
- General Info
- ChangeArea
- ChangeLocation
- ChangeProjection