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.
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.
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.
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.
There are several different ways of connecting your code to the Geosoft environment of your choice:
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.
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
This object can respond to the following events in the environment: