|
ANY-maze Help > The ANY-maze reference > ANY-maze plug-ins > Step-by-step instructions for writing an ANY-maze plug-in Step-by-step instructions for writing an ANY-maze plug-in
IntroductionThis topic takes you through the steps required to create an ANY-maze plug-in. To do this, a minimal example plug-in is developed based on the demo plug-in provided with ANY-maze. However, it should be easy to see where you will need to substitute your own code to implement a truly useful plug-in.
1. Create a new DLL projectThe first step is to create a new DLL project. Most development environments include a 'New project' command, which can be used to create projects of various types. You should create a Win32 DLL project. I suggest you avoid any options which will include 'standard' code, as we'll add all the code necessary as we progress through these instructions. By the way, you can call your project (and thus your DLL) anything you like; the name is not important to ANY-maze. 2. Make sure the project settings are correctYou should use your development environment to ensure that the project settings are correct for the plug-in DLL to run correctly with ANY-maze. Firstly, you must ensure that the project is being built as 32-bit or 64-bit, depending on the current version of ANY-maze that is running. Usually, if you're using a 64-bit version of the Windows operating system, then ANY-maze will also be the 64-bit version. If you're not sure which version of ANY-maze you're using, you can find out from the Help page under About ANY-maze -> Program information -> Application version. Secondly, you should ensure that your project will be compiled/linked with multi-threaded runtime libraries (we recommend using the statically linked libraries rather than the DLLs - i.e. the This is important because ANY-maze uses a different thread for each piece of apparatus on which a test is being performed, and it's these threads that will call the plug-in code. Thus, in a protocol with two apparatus, your plug-in will be called from both threads.
3. Add a new source file to the projectWith your project created and set to be multi-threaded, you're ready to actually write some code. But first, you will need to add a source file to your project. You can call the file anything you like. In these instructions we'll use a single source file for the entire plug-in, but of course you could break it into different files if you prefer. 4. Include the 'Windows.h' and 'PlugIn.h' header filesThe next step is to include the Windows.h and PlugIn.h header files. The former defines Windows functions, structures and constants, while the latter defines the structures and constants used to interface a plug-in to ANY-maze. Of course you may want to include other headers too - for example, if you plan to use some maths functions, you'll probably want to include math.h. The path to windows.h will almost certainly be defined already within your development environment, whereas the path of PlugIn.h won't be. An easy solution is to copy PlugIn.h from the demo plug-in folder into your project folder, or you can use a fully qualified path name to the demo plug-in folder, whichever you prefer.
#include "windows.h" #include "PlugIn.h"
5. Write the 'DllMain' functionAll DLLs must include a DllMain function, which Windows will call when the DLL is loaded. In a plug-in, the DllMain has no particular tasks to perform, so a minimal implementation is fine.
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { //BEGIN switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH : //Note the module handle hInstance = hModule; break; case DLL_THREAD_ATTACH : case DLL_THREAD_DETACH : case DLL_PROCESS_DETACH : //Nothing to do break; }
//Done return TRUE; }
Here the routine does just one thing - it notes the module handle, as this is required to load resources. Of course, you may want your plug-in to perform additional processing, but do bear in mind that the DLL must be thread-safe and that there are special rules about what can and cannot be done during DLL_PROCESS_ATTACH processing. 6. Write the basic 'ANYmazeExtension_Main' functionThe ANYmazeExtension_Main function is the only routine you have to include in your DLL to make it into an ANY-maze plug-in. This routine acts as the link between ANY-maze and the plug-in, and all communication passes through it. For now, we're going to add a skeleton implementation of ANYmazeExtension_Main which we'll then add to in the following steps.
extern "C" __declspec(dllexport) int ANYmazeExtension_Main (LPGUID guid, DWORD Mesg, WPARAM wParam, LPARAM lParam) { //BEGIN //Action depends on the Mesg switch (Mesg) { .... we'll add some case statements in a minute ....
default : //What's this message? return AME_NOTIMPLEMENTED; } }
There are some important things to note about this routine:
As is probably apparent, this routine processes messages, which are passed to it from ANY-maze. Note that these are NOT Windows messages, although the routine does look very like a WNDPROC - this similarity is intentional, as it should mean anyone with even a passing familiarity with Windows programming can quickly understand how this routine should work. The salient point is that ANY-maze will call this routine passing it a message (usually with some parameters in wParam and lParam); the routine will process the message and return a code indicating the outcome of the processing. ALL communication between ANY-maze and the plug-in is performed this way. The GUID parameter is used to identify which plug-in a message is being addressed to - remember that a single DLL can implement multiple plug-ins if it wants to. If your DLL implements just a single plug-in, then you can often ignore the GUID - although not always, as we'll see in the next two steps! 7. Add processing of the AME_INITIALISE and AME_TERMINATE messagesThe first messages to process are AME_INITIALISE and AME_TERMINATE. In most cases, you won't actually need to do any processing of these messages - but you must, at least, return either AME_NOTIMPLEMENTED or AME_OK. ANY-maze will send an AME_INITIALISE message to your DLL when it (ANY-maze) starts up. This is your opportunity to perform any initialisation your plug-in requires. For example, if your plug-in needs to create a thread, perhaps to manage communication with some other program, then this is the time to do it. You should always return AME_OK to this message - returning any other value will mean that your plug-in is unloaded by ANY-maze, and it will then be entirely ignored by the system. As you would expect, the AME_TERMINATE message is used to terminate your plug-in. This message is sent when ANY-maze is closing down, and gives you an opportunity to clean-up - for example, if you create a thread to communicate with another program, then this would be the time to tell the other program that you are closing down and to end the thread.
case AME_INITIALISE : case AME_TERMINATE : //Nothing to do return AME_OK;
8. Create a GUID for your plug-inEven if your DLL only implements a single plug-in, the plug-in still needs an ID and this should be a Windows Globally Unique Identifier, or GUID. You can create a GUID using the free GUIDGEN.EXE tool, which comes with Visual Studio. (It can also be downloaded, for free, from Microsoft's web-site.) GUIDGEN can provide you with the new ID it generates in a variety of formats - choose the static const... format. Having created the GUID, you should paste it into your source file:
//GUIDs for our plug-ins. These can be created using guidgen.exe static const GUID GUID_MYFIRSTPLUGIN = {0xf5c18c7b,0x5a39,0x47f8,{0x99,0x2b,0x13,0x37,0xfe,0x0f,0x90,0xd5}}; static const GUID GUID_MYSECONDPLUGIN = {0xb6d62e2c,0xf888,0x40e8,{0xb8,0x87,0xac,0x1f,0x45,0x14,0xb7,0x70}};
You can call your GUID anything you like - the name will only be used within your plug-in. If your DLL will implement multiple plug-ins, you should generate one GUID for each one. 9. Add processing of the AME_ENUMERATE messageHaving created a GUID for the plug-in (or plug-ins), you are ready to process the AME_ENUMERATE message. ANY-maze will send AME_ENUMERATE messages to your DLL to ask it what plug-ins it includes. This works as follows: The first time ANY-maze passes an AME_ENUMERATE message, the guid parameter (of ANYmazeExtension_Main) will be GUID_NULL. Your code should detect this and return the GUID of the first plug-in it implements. The next time ANY-maze passes an AME_ENUMERATE message, the GUID parameter will be the same as the GUID you just returned; your code should detect this and return the GUID of the next plug-in it implements. You should continue in this way until you have returned the GUIDs of all the plug-ins in your DLL. You should then return AME_NOTIMPLEMENTED to indicate to ANY-maze that the enumeration is complete. Here's an example:
case AME_ENUMERATE : //Simply enumerate the GUIDs of the analysis we can perform, //returning the next GUID in the buffer pointed at by lParam. //When enumeration starts we are passed a null GUID if (*guid == GUID_NULL) { //Enumeration is starting - return GUID of the first plug-in //we implement *((LPGUID)lParam) = GUID_MYFIRSTPLUGIN; return AME_OK; } else if (*guid == GUID_MYFIRSTPLUGIN) { //We've now returned the GUID of the first plug-in, so now //return the GUID of the second one *((LPGUID)lParam) = GUID_MYSECONDPLUGIN; return AME_OK; } else if (*guid == GUID_MYSECONDPLUGIN) { //We've now returned the GUID of the second plug-in, so that's //all the plug-ins we implement. Return AME_NOTIMPLEMENTED to //end enumeration return AME_NOTIMPLEMENTED; } else { //We should never be passed GUIDs that we don't recognise return AME_ERROR; } break;
This code can just be dropped into the ANYmazeExtension_Main routine where it says '.... we'll add some case statements in a minute ....' in the above example. Here, I've included two plug-ins to show how the enumeration works, but for the rest of these instructions I'll just use a single plug-in. If you want to see more details on how to implement multiple plug-ins in a single DLL, refer to the ANY-maze demo plug-in listing. 10. Add processing of the AME_GETNAME messageUsing the AME_ENUMERATE message, ANY-maze can learn what plug-ins your DLL implements - but it still needs to know what the plug-ins are called, so it can display their names to the user. To this end, it will send AME_GETNAME messages. This message is very simple; you just need to return the name of the plug-in whose GUID is passed (and if you only implement a single plug-in, then you can even ignore the GUID - which is what the code below does).
case AME_GETNAME : //Return the name of the GUID in the buffer pointed to by lParam. The //buffer's size is in wParam StringCbCopy (lParam, wParam, "My first plug-in"); return AME_OK;
Note that in this example, the plug-in name is meaningless - you should try to use more descriptive names for your plug-ins. By the way, a better approach than the example would be to add the name to the DLL's resource file and then load it using LoadString. Also note that here I didn't check the passed GUID, because my DLL only implements one plug-in, but it would still be good practice to check the GUID and return AME_ERROR if you don't recognise it. 11. Add processing of the AME_GETRESULTTYPE messageFor all plug-ins, you need to decide what result type you will return. There are five types, including one for 'None', so you don't actually have to return a result at all. The other types are RT_ONOFF, RT_VALUE, RT_CUMULATIVE and RT_ACTION. For this example, we're going to use a result type of RT_ONOFF. ANY-maze will query your plug-in to find out what type of result it returns, by sending it an AME_GETRESULTTYPE message. The processing is very simple:
case AME_GETRESULTTYPE : //Return the result type in the unsigned char pointed to by lParam *((unsigned char*)lParam) = RT_ONOFF; return AME_OK;
Like for the AME_GETNAME message processing, I've not checked the passed GUID, which it would be better to do. By the way, we have now performed enough steps for our plug-in to be recognised by ANY-maze and to work - well kind of - of course, it won't actually do anything. But if you paste the above 'cases' into the ANYmazeExtension_Main routine, build your DLL, copy it to the ANY-maze.exe folder, start ANY-maze and look at the plug-ins element of the protocol, you should see your plug-in listed! 12. Optionally add processing of the AME_GETHELPxxx messagesUnless your plug-in is just for your own use, it's strongly recommended that you provide some help (even if it's minimal). This is very easy to do, as you simply have to write some text and pass it back to ANY-maze when it sends AME_GETHELPxxx messages. There are three AME_GETHELPxxx messages:
The only difference between the messages is that they're asking for different text: either the Title for the plug-in's help topic, the Introductory text or the Details text. How you choose to divide the text between Introduction and Details is up to you, but be aware that the Introductory text you return will appear in a section of the help topic titled 'Introduction', and the details text will appear in a section titled 'Details'. Also, the introductory text is limited to 1024 characters, whereas the details text can be up to 64K in size. Like plug-in names, it's a good idea to define your text in the DLL's resource file, rather than embedding strings in the source code, and this is the approach adopted below:
case AME_GETHELPTITLE : case AME_GETHELPINTRO : case AME_GETHELPDETAILS : //These all return strings, which we load from the DLL's resources if (Mesg == AME_GETHELPTITLE) RscID = IDS_HELP_TITLE; else if (Mesg == AME_GETHELPINTRO) RscID = IDS_HELP_INTRO; else RscID = IDS_HELP_DETAILS;
//Load the resource return ((LoadString (hInstance, RscID, (LPSTR)lParam, (int)wParam) != 0) ? AME_OK : AME_ERROR);
The actual text of the help message is not necessarily formatted in the way that you would probably expect. Specifically, you need to use special mark-up within the text to end paragraphs, this mark-up being \para. So, for example, returning the string 'Hello\para World' would write the words 'Hello' and 'World' on separate lines. In fact, there is more mark-up available to you - here's a full list:
Note that the '\' character is the C/C++ escape character, so if you type it into a string in C/C++ code, it will be taken to mean that you're escaping the following character - so when typing in '\para' you should actually enter '\\para', as the escaped '\' character is itself. Also note that CR or LF characters will not cause new paragraphs in the help message - use \para to do this. Finally, if you don't want to include help then you should simply return AME_NOTIMPLEMENTED to all of the AME_GETHELPxxx messages. 13. Optionally add processing of the AME_EDITSETTINGS messageIn some cases, a plug-in might have user definable settings. For example, the demo plug-in detects when the animal is within a certain distance of the centre of the apparatus - but how far that distance is, is something the user can define using the plug-in's settings. The settings for a plug-in are typically set using a Windows dialogue box, but you can use a different user interface if you wish. The actual settings are treated by ANY-maze as a simple buffer (i.e. a contiguous block of bytes), which it stores on behalf of the plug-in as part of the experiment's protocol. When the user clicks the Alter the settings... link for a plug-in, ANY-maze sends an AME_EDITSETTINGS message to the plug-in, with the lParam pointing at the settings buffer and the wParam set to the buffer's size. The size of the buffer is fixed, but you don't have to use the entire buffer if you don't need to; you just can't use more than the size passed in wParam. The first time ANY-maze passes a settings buffer to your plug-in, the buffer will be filled with zeros - you should detect this and set the buffer to hold some suitable default values. For example, you might choose to define the settings in your plug-in as:
typedef struct { bool Initialised; int DistanceFromCentre; }MYPLUGIN_SETTINGS, *MYPLUGIN_SETTINGS;
When ANY-maze first passes you these settings, you could use some code like this to detect that the settings are uninitialised and set the DistanceFromCentre to a default value:
LPMYPLUGIN_SETTINGS MyPlugInSettings;
switch (Mesg) { case AME_EDITSETTINGS : //Check our settings are not bigger than the buffer assert (wParam >= sizeof(MYPLUGIN_SETTINGS); if (wParam >= sizeof(MYPLUGIN_SETTINGS) return AME_ERROR;
//Access the settings MyPlugInSettings = (LPMYPLUGIN_SETTINGS)lParam;
//Check for uninitialised settings if (!MyPlugInSettings->Initialised) { //Set default values MyPlugInSettings->DistanceFromCentre = 10;
//Now the settings are initialised MyPlugInSettings->Initialised = true; }
....continue processing the message
break; }
The settings for a plug-in are typically set using a Windows dialogue box. I don't intend to go into the details of how to display and process a dialogue box here, but you'll find that the demo plug-in code can act as a good basis for your own implementation. Nevertheless, there are a couple of points worth mentioning: first, you will probably want to use DialogBoxParam as this will allow you to pass a pointer to the settings buffer to the DialogProc; second, in the DialogProc you can use a static variable to store the pointer, as ANY-maze will only pass AME_EDITSETTINGS messages from its main thread, so there are no multi-threaded issues for you to contend with. To close your settings window, the user will probably have two choices - OK and Cancel. In the former case, you will want to update the settings buffer with the new settings entered by the user and then have ANY-maze save the settings in the protocol; in the latter case, you will want any new settings to be ignored and you won't want the settings buffer to be saved. ANY-maze will use the value returned from the AME_EDITSETTINGS message to determine what it should do - return AME_OK if you want the settings to be saved, or AME_CANCELLED if you don't want them saved. Of course, some plug-ins may not have any settings - in which case you should simply return AME_NOTIMPLEMENTED to the AME_EDITSETTINGS message. 14. Add processing of the AME_TESTSTART messageNow we get to the real work of a plug-in - to actually analyse data during a test. This is handled through four messages: AME_TESTSTART, AME_POSN, AME_TESTEND and AME_TESTABORT. Here, I'll describe the AME_TESTSTART message. At the moment that a test starts, ANY-maze will send all active plug-ins (i.e. plug-ins the user has selected to use in the protocol) an AME_TESTSTART message. This provides certain test-specific data to the plug-in, and acts as the moment for the plug-in to initialise itself so it's ready to analyse the test. Specifically, the lParam of the AME_TESTSTART message will point at an AME_TESTDATA record, and the wParam will be set to the size of the AME_TESTDATA record. This record is defined in the PlugIn.h file as follows:
typedef struct { DWORD AnimalNum; char AnimalID[80]; DWORD TrialNum; RECT ApparatusRect; double Scaling; LPVOID Settings; DWORD SettingsSize; LPVOID Context; }AME_TESTDATA, *LPAME_TESTDATA;
First, a brief note about the wParam. As mentioned above, this specifies the size of the AME_TESTDATA record. Seemingly, this is unnecessary - as the record is defined in PlugIn.h and so you could get the size using sizeof(AME_TESTDATA). However, the reason the size is passed is to allow us (the ANY-maze development team) to add new fields to this record in the future. For example, at some time in the future we might define an AME_TESTDATA2 record with additional fields. In this case, you will be able to tell whether the record being passed is an AME_TESTDATA or AME_TESTDATA2 record by comparing the wParam to the size of these two record types. The fields of the AME_TESTDATA record are described in the record's help topic (click the record name to view this topic), but I will mention two of them now. The Settings field points at a Settings buffer, which is just the same as the Settings buffer passed in the AME_EDITSETTINGS message (see above). So, if the user has actually changed the settings, these will be provided in this buffer - otherwise the buffer will be all zeros and you should use default values. The other field which deserves attention is the Context field, which you will use to return a context value to ANY-maze. The Context can be any value you want, but typically it will be a pointer to a record that you define within your plug-in and which you allocate as part of your AME_TESTSTART processing. The idea is as follows: ANY-maze can run multiple tests simultaneously, so your plug-in may be sent messages about, say, three tests (all of which are being performed at the same time). If you allocate a Context record during AME_TESTSTART processing, then you can fill it with useful fields related to the analysis that your plug-in performs and return it to ANY-maze by setting the Context field of the AME_TESTDATA record to point at it. ANY-maze will then pass this value back to your plug-in as part of the AME_POSN message, so you can use the fields it contains to update your analysis. If there are three tests running simultaneously, you will simply end up allocating three records (because you'll be sent three AME_TESTSTART messages) and ANY-maze will pass a reference to the appropriate record with each AME_POSN message. This means you don't need to worry (too much) about the fact that your plug-in may be called by multiple threads, because you can simply keep all analysis variables in your context record which will automatically be thread-specific. Bearing the above in mind, you may very well want to copy some of the data provided in the AME_TESTDATA record to your Context record, so it's available to you during AME_POSN processing - for example, you'll almost certainly want to do this with any Settings that you have. In fact, the explanation is more complicated than the code - here's the AME_TESTSTART processing performed by the demo plug-in, which detects when the animal is in the centre of the apparatus:
typedef struct { double Distance; int AppCentreX; int AppCentreY; bool InCentre; }INCENTREANALYSISRECORD, *LPINCENTREANALYSISRECORD;
....
case AME_TESTSTART : //The lParam points at a AME_TESTDATA record and the wParam is the //size of the record. Check the size assert (wParam >= sizeof(AME_TESTDATA)); if (wParam < sizeof(AME_TESTDATA)) return AME_ERROR;
//Access the AME_TESTDATA record AME_TestData = (LPAME_TESTDATA)lParam;
//Allocate a InCentreAnalysisRecord - if this fails return AME_ERROR InCentreAnalysisRec = new INCENTREANALYSISRECORD; if (InCentreAnalysisRec == NULL) return AME_ERROR;
//Calculate and note the centre point of the apparatus InCentreAnalysisRec->AppCentreX = (AME_TestData->ApparatusRect.left + AME_TestData->ApparatusRect.right) / 2; InCentreAnalysisRec->AppCentreY = (AME_TestData->ApparatusRect.top + AME_TestData->ApparatusRect.bottom) / 2;
//The passed AME_TestData record includes a pointer to the settings //for this analysis. The settings define the distance from the centre //that we consider to be the "centre" of the apparatus. Convert this //distance to pixels and note it in the InCentreAnalysisRec. If the //distance is zero then the user has not specified a distance so //use a default of 10cm Settings = (LPINCENTREANALYSISSETTINGS)AME_TestData->Settings; if (!Settings->Initialised) Settings->Distance = 10; d = (double)Settings->Distance;
//Convert from cm to mm d = d * 10;
//Convert from mm to pixels InCentreAnalysisRec->Distance = d * AME_TestData->Scaling;
//Start by assuming the animal is not in the centre of the apparatus InCentreAnalysisRec->InCentre = false;
//Set Context to the address of the InCentreAnalysisRec AME_TestData->Context = (LPVOID)InCentreAnalysisRec; return AME_OK;
As can be seen in the above example, if an error occurs during AME_TESTSTART processing you can just return AME_ERROR - ANY-maze will then flag your plug-in as inactive (for this test) and won't send any further messages to it. 15. Add processing of the AME_POSN messageThe AME_POSN message is the heart of the plug-in system, as this is the message which actually tells you where the animal is. To do this, ANY-maze passes you an AME_POSNDATA record. Specifically, the lParam of the AME_POSN message points at the AME_POSNDATA record, and the wParam tells you the record size. The size of the record is passed for the same reasons that the AME_TESTSTART message passed the size of the AME_TESTDATA record (see above) - i.e. so we (the ANY-maze development team) can add fields to the record in the future, and plug-in code will be able to tell which version of the record is being passed.
typedef struct { LPVOID Context; DWORD Time_xy; int x; int y; DWORD Time_HeadTail; int hx; int hy; int tx; int ty; int cx; int cy; bool Hidden; RECT AnimalArea; int ResultType; int ResultValue; }AME_POSNDATA, *LPAME_POSNDATA;
The first field of the AME_POSNDATA record is the Context, which is the Context value you passed back to ANY-maze as part of your AME_TESTSTART processing (see above). The other fields are defined in the AME_POSNDATA topic, so, except for the two 'Result' fields, I won't describe them here. The ResultType and ResultValue fields are where you will return the result of your analysis. Specifically, you will return an RT_xxx value in ResultType and, unless the ResultType is RT_NONE, the actual value of the result in ResultValue. The result types are described in their respective topics, but briefly they are:
So, here's an example of the processing performed by the demo plug-in which determines when the animal is in the centre of the apparatus:
case AME_POSN : //The lParam points at a AME_POSNDATA record and the wParam is the //size of the record. Check the size assert (wParam >= sizeof(AME_POSNDATA)); if (wParam < sizeof(AME_POSNDATA)) return AME_ERROR;
//Access the AME_POSNDATA record AME_PosnData = (LPAME_POSNDATA)lParam;
//Access the InCentreAnalysisRec if (AME_PosnData->Context == NULL) return AME_ERROR; InCentreAnalysisRec = (LPINCENTREANALYSISRECORD)AME_PosnData->Context;
//Calculate the distance from the animal's posn to the apparatus centre dx = AME_PosnData->x - InCentreAnalysisRec->AppCentreX; dy = AME_PosnData->y - InCentreAnalysisRec->AppCentreY; d = sqrt((dx * dx) + (dy * dy));
//Is the animal within the required distance of the apparatus centre NowInCentre = (d < InCentreAnalysisRec->Distance);
//Has the animal's "InCentre" state changed if (NowInCentre != InCentreAnalysisRec->InCentre) { //Yes, so return a suitable result AME_PosnData->ResultType = RT_ONOFF; AME_PosnData->ResultValue = NowInCentre ? 1 : 0;
//Update the in centre state InCentreAnalysisRec->InCentre = NowInCentre; } else { //We're not returning a result AME_PosnData->ResultType = RT_NONE; }
//Processed successfully return AME_OK;
Note that you may not ever return a result. For example, your plug-in might simply pass the coordinates on to some other piece of software (perhaps via a pipe or DDE), in which case you will just return RT_NONE as the ResultType for every AME_POSN message you receive. 16. Add processing of the AME_TESTEND and AME_TESTABORT messagesAs you'd probably expect, when a test ends, ANY-maze will send you a message to tell you this. In fact it sends one of two messages, either AME_TESTEND or AME_TESTABORT. As the names imply, the former is sent when a test ends normally, whereas the latter is sent when a test is aborted and its results are not going to be saved. In either case, the parameters of the message are the same - wParam is unused (set to zero) and lParam points at your Context record for the test. You will typically use this to perform any clean-up operations and to deallocate the Context record (assuming you allocated one in the first place). It's important to note that ANY-maze will not deallocate this record - your plug-in allocates it, so your plug-in must deallocate it. Here's the code for the demo plug-in's processing of AME_TESTEND and AME_TESTABORT; as you'll see, it's the same in either case:
case AME_TESTEND : case AME_TESTABORT : //The lParam is our context value if (lParam == NULL) return AME_OK; InCentreAnalysisRec = (LPINCENTREANALYSISRECORD)lParam;
//Delete the InCentreAnalysisRec delete InCentreAnalysisRec;
//Done return AME_OK;
A final point about processing errors. If your plug-in encounters an error during the test (i.e. when processing an AME_POSN message) then you will return AME_ERROR. This will ensure that no result is recorded, but it won't stop further AME_POSN messages from being sent. So, if you encounter a serious error which will prevent your analysis from continuing, you will need to flag this in your Context record. Then on each AME_POSN message you can check the flag and return AME_ERROR if it's set. This is fine, but it leaves one problem - what if you had returned some results to ANY-maze before the error occurred? These results will have been saved by ANY-maze, but because of the error they may now be incorrect (for example, if the demo plug-in simply stopped detecting when the animal is in the centre, then all the time from that moment on would be counted as either in-centre or not-in-centre, depending on the state when the error occurred). Clearly this is not satisfactory. To overcome this problem, you can return AME_CANCELLED from your AME_TESTEND processing. This will cause ANY-maze to delete any results it had recorded for your plug-in in the test, so it'll be like the plug-in simply wasn't active for the test. So, updating the above code to include this:
case AME_TESTEND : case AME_TESTABORT : //The lParam is our context value if (lParam == NULL) return AME_OK; InCentreAnalysisRec = (LPINCENTREANALYSISRECORD)lParam;
//Did some serious error occur during analysis? If so //we want to return AME_CANCELLED, else we will return AME_OK retcode = InCentreAnalysisRec->SeriousErrorDetected ? AME_CANCELLED : AME_OK;
//Delete the InCentreAnalysisRec delete InCentreAnalysisRec;
//Done return retcode;
17. You're done!If you've read this far, then you should now know enough to get started writing a plug-in for yourself. In fact, I don't recommend you start from scratch - instead you may find it easier to make a copy of the ANY-maze demo plug-in source code, and edit it to suit your needs. You can do this a little at a time, and test things as you go along. And, of course, ANY-maze Support are on hand to help you, so do contact us if there's anything you're unsure of. By the way, we're very aware that most people who use ANY-maze are not programmers, so don't worry about contacting us with simple questions - we'll keep our answers as un-technical as possible, and we'll gladly write sections of code for you if you get stuck. Indeed we'll even write entire plug-ins (for free) provided you can supply us with a detailed specification of what you'd like the plug-in to do. See also:
© Copyright 2003-2026 Stoelting Co. All rights reserved ANY-maze help topic T0999 |