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

These instructions assume you will be writing a plug-in using C/C++.

Introduction

This 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 project 
 2.Make sure the project settings are correct 
 3.Add a new source file to the project 
 4.Include the Windows.h and PlugIn.h header files 
 5.Write the DllMain function 
 6.Write the basic ANYmazeExtension_Main function 
 7.Add processing of the AME_INITIALISE and AME_TERMINATE messages 
 8.Create a GUID for your plug-in 
 9.Add processing of the AME_ENUMERATE message 
 10.Add processing of the AME_GETNAME message 
 11.Add processing of the AME_GETRESULTTYPE message 
 12.Optionally add processing of the AME_GETHELPxxx messages 
 13.Optionally add processing of the AME_EDITSETTINGS message 
 14.Add processing of the AME_TESTSTART message 
 15.Add processing of the AME_POSN message 
 16.Add processing of the AME_TESTEND and AME_TESTABORT messages 
 17.You're done!  

1. Create a new DLL project

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

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

You should keep in mind that your code must be thread-safe. In fact this isn't too complex, as ANY-maze will help you with this (as we'll see later), but it does mean that you should avoid using static variables.

3. Add a new source file to the project

With 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 files

The 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' function

All 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' function

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

 The function is declared as __declspec(dllexport). This causes the routine to be exported from the DLL - which is required. The exact method of exporting a routine will depend on your development environment; the method used here is Microsoft-specific. Refer to your development environment help files for guidance.  
 The extern "C" line is included to tell the linker not to apply name decoration to the function name. Without this, the function will be exported with a name that has seemingly random numbers and symbols added to it, and it won't be recognised by ANY-maze. This method of avoiding name decoration is something else that's Microsoft-specific - again, refer to your development environment help files for guidance.  

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 messages

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

Even 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 message

Having 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 message

Using 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 message

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

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

 AME_GETHELPTITLE 
 AME_GETHELPINTRO 
 AME_GETHELPDETAILS  

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:

 \paraStarts a new paragraph
 \i1Switches italic on
 \i0Switches italic off
 \u1Switches underline on
 \u0Switches underline off
 \e1Switches bold on
 \e0Switches bold off
 \bulletStart a new bulleted item in a bullet 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 message

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

Now 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 message

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

 RT_NONE, which you should use when you don't have a result to return;  
 RT_ONOFF, which is used when your analysis determines when something starts and stops (like the animal being in the apparatus centre, or not);  
 RT_VALUE, which is used when your analysis determines a specific instantaneous value, for example, the animal's heart rate;  
 RT_CUMULATIVE, which is used when your analysis determines a value for the time between the previous position and this one, such as the distance the animal has travelled (in this case ANY-maze will sum all the values you pass it). 

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 messages

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

 The ANY-maze demo plug-in 
 Plug-in API reference 

© Copyright 2003-2026 Stoelting Co. All rights reserved

ANY-maze help topic T0999