Writing FileMaker Plug-ins

I put this on my website a long time ago, maybe around 2002, as an HTML page. This is it moved to my blog.

Contents

Introduction

A FileMaker plug-in allows programmers to create new external functions to perform

calculations or other processing. These plug-ins must be written in C or C++. The following explanation assumes that you are using C++. There is very little difference when using C, though your code will be less easy to read.

The plug-in is a dynamically linked library, which integrates with FileMaker by means of some custom functions and data structures. The FileMaker Developer CD contains source code examples and a template which take care of most of the interaction with FileMaker – leaving you to write the external function code.

I would like to include full source code with this text. Unfortunately, large parts of the source code (in particular, the header files which are necessary to build plug-ins) are copyright protected by FileMaker Inc, who have decided that only people who have bought the Developer Edition should be allowed to write plug-ins. If you do not have access to the FileMaker Developer Edition then I suggest you contact one of the many FileMaker consultancy companies who will have it but will probably not have the C/C++ skills necessary to write Plug-ins.

How FileMaker interacts with the plug-in

FileMaker calls the FMExternCallProc() function (or FMExternCarbonCallProc() on the Mac) in the plug-in, passing it a pointer to a FMExternCallStruct data structure. The FMExternCallProc() function examines this struct to choose between one of the following operations:

  • Init: Allows the plug-in to do any special setup which it requires.
  • Idle: Allows the plug-in to make use of idle time.
  • External: Calculates an external function.
  • Shutdown: Allows the plug-in to do any special shutdown which it requires.
  • DoAppPreferences: Allows the user to set any plug-in options.

The Plug-in template project contains appropriately named functions to deal with each of these operations. You can modify these functions, but in most cases you will only need to change the Do_External() function.

The Do_External function is passed a function ID (see External Function Names), a Handle to a parameter, and a Handle to the result. A switch block in the Do_External function calls the appropriate function with the parameter and result Handles.

Handles

A Handle is a pointer to a pointer. The use of a Handle allows the actual data to move in memory as long as the pointer changes. We can still access the data as long as we do a fresh double dereference.

The following functions should be used to manage Handles:

  • FMX_NewHandle(size): Return Handle to new data allocation.
  • FMX_SetHandleSize(h, size):Set size of allocated data at the Handle.
  • FMX_GetHandleSize(h): Returns size of allocated data at the Handle.
  • FMX_DisposeHandle(h): Frees the data at the Handle.
  • FMX_LockHandle(h): Prevents the allocated data from moving in memory.
  • FMX_UnlockHandle(h): Allows the allocated data to move in memory.
  • FMX_MemoryError(): Checks for errors resulting from the use of these functions.

There are other memory management functions listed in FMExtern.h but their use is not demonstrated in the example source files and they are not documented.

Changing the Plug-in template to C++

I advise that you use C++ rather than C. Even if you do not understand the more advanced features of C++, the simpler improvements will make your code much easier to read.

To do so, you will need to change the template project from C to C++. For instance, with Visual C++ 5 simply change the file extensions of .c file to .cpp, and replace these files in the File tab of the project workspace. At this stage you may wish to rename the FMTemplate files to better reflect the name of your plug-in.

Mac

To use C++, you will need to add the following libraries to your Carbon target:

  • MSL_All_Carbon.Shlib
  • MSL_ShLibRuntime_PPC.lib

And you will need to specify the UseDLLPrefix.h prefix file in the
Language Settings / C/C++ Language part of the target’s settings.

And add these libraries to your PPC target:

  • MSL_All_PPC.Shlib
  • MSL_ShLibRuntime_PPC.lib

And you will need to specify the UseCarbonDLLPrefix.h prefix file in the
Language Settings / C/C++ Language part of the target’s settings.

Version String

In the FMTemplate.c (or, for instance, FMAbstractions.cpp) file you will find need to
change the #define statements which tell the plug-in what text to return from the Version
external function.

For instance, in the FMAbstractions.cpp I have used:

#define kVersionLength    24
#define kVersionString    "Abstractions Plug-in 1.1"

Note that you must count the number of characters manually.

The resource file

The example project contains these files, in the “Resources”
folder::

  • FMResource.h: constants used by the resources and the source code.
  • FMExample.r: A Macintosh resource file.
  • FMExample.rc: A Windows resource file.

The resources file must contain the following text resources:

  • 128: Plug-in Name (Note that the Plug-in Name must not begin with F)
  • 129: Description shown in Application Preferences
  • 130: Empty String
  • 131: Feature String (see below)
  • 145: External Function 0 Name
  • 146: External Function 1 Name
  • 147: etc

These resources are already present in the template project. You simply need to change them from their default entries.

Remember to modify the resource files in a text editor rather than your IDE (such as Visual C++’s resource editor), because the IDE may corrupt the file. FileMaker say this is because the resource file is intended to be “cross-platform”, but it’s not clear what they mean by that because they supply separate resource files for Macintosh and Windows. However, the IDE should compile the resource file without problems.

Feature String

The feature string gives FileMaker information about the plug-in and is made up as follows:

  • <4 char creator>: Registered Creator Code
  • <always 1>
  • <app prefs>: ‘Y’ if there are preferences, ‘n’ if not.
  • <externals>:  ‘Y’ if there are any external function, ‘n’ if not. Usually Y of course.
  • <always Y>
  • <idle> : ‘Y’ if you wish the plug-in to get FMXT_Idle messages, ‘n’ if not.
  • <always n>
  • <Win32s>: ‘Y’ if the plug-in only uses the Win32s API subset, ‘n’ if not.

For instance, the Abstractions Plug-in has “MurF1nYYnnY”:

External Function Names

The resources file should contain the names of your external functions, starting at
resource number 144. FileMaker will display these names to the user.When FileMaker calls
an external function it sends the function’s position in this list, starting at 0 for
resource number 144, then 1 for 145, 2 for 146, etc.

The function names should be prefixed with a Function Name Prefix. This prefix must be
4 or 5 characters and must not begin with FM.

For instance, the first 2 function names in the Abstractions Plug-in are:

144 "Abstr-Version"
145 "Abstr-RemoveMultipleReturns"

Adding an External Function

Add the function name

Add the function name to the resource file. e.g. 145  “Abstr-RemoveMultipleReturns” Add a case statement to the switch block in the Do_External() function so that it catches requests for the new function. e.g

/* Second function (RemoveMultipleReturns) */
case 1:
  RemoveMultipleReturns(parameter, result);
  break;

Add the function body

Enter the body of the function as static and void, in the FMTemplate.c (or, for example, FMAbstractions.cpp) file. e.g.

static void RemoveMultipleReturns(FHandle parameter, FHandle result) {

}

If you enter the body before the Do_External() function then you will not need to add a declaration prototype.

Get parameter size

Use FMX_GetHandleSize(parameter) to get the number of characters in the parameter. If
this equals 0 then there is no need to continue. e.g.

long lInSize = FMX_GetHandleSize(parameter);
if (lInSize > 0) {
  ...
}

Lock the parameter Handle

You should lock the parameter Handle to prevent the data moving during the lifetime of your function. e.g.

FMX_LockHandle(parameter);

Alternatively you could double-dereference this Handle every time you look at any piece of its data, but that would be tedious and inefficient.

Be sure to call FMX_UnlockHandle(parameter) at the end of the function.

Dereference the parameter Handle

You must use the * operator to dereference the Handle to get a pointer to the parameter
data. e.g.

unsigned char* pucIn = *parameter;

Note that I have used a puc prefix to remind myself that I am dealing with a pointer to an unsigned character.

Examine the parameter data

You may examine the data using array notation. e.g.

unsigned char ucTest = pucIn[4];

Or you could use the * operator to dereference:

unsigned char ucTest = *(pucIn + 4);

These two statements are equivalent.

You will probably use a loop to examine each character in turn. Make sure you do not go past the end of the parameter text. Remember that the data is not null-terminated, so many utility functions will not work.

Note that all parameter data is sent in text form, so you may need to convert text to a number. Be very careful when dealing with dates because the user must use the DateToText() function whose behaviour is not constant across different locales and which currently is not year-2000 compliant.

Set the result data

As soon as you know how many characters will be in the result data, you can use FMX_SetHandleSize() to allocate space for the result data.You should then check that the
call succeeded. e.g.

FMX_SetHandleSize(result, lOutLength);
if (FMX_MemoryError() == 0) {
  * pucOut = *result;
  ...

You should not dereference the result Handle to a pointer until just before you use it,
because the data may move between the time you get the pointer and the time you use it.
Alternatively you could use FMX_LockHandle().

You might set the result data as you examine the parameter data. e.g.

pucOut[x] = pucIn[y];

However you are more likely to copy the data into the result from an intermediate location. e.g.

lstrcpy((LPSTR)*result, (const char*)pucTemp);

You should have an appropriate #ifdef block for Mac code because the Mac deals with strings differently.

Be sure to delete (or free) any data which you have previously allocated dynamically using new (or malloc), to prevent memory leaks.

Accepting multiple parameters

Unfortunately a FileMaker external function can only be sent one text parameter. In order to receive more than one piece of data you must demand that the user separates the data with a special character.

It is becoming commonplace to use the ‘|’ (pipe) character as the dividing character with external functions. For instance, with the Abstractions Plug-in, a user would enter:

External(Abstr-KeyTriggeredBy, KeyField & "|" & TriggerField).

Of course this significantly complicates your external function because you must separate the parameters within your code.

Registering your plug-in

You must register your 4 character Creator Name with Apple at http://developer.apple.com/dev/cftype/ even if you are not creating a Mac plug-in.

You must then register your plug-in details at www.filemaker.com. They will then send you a registration code. You must email FileMaker Inc a copy of your plug-in, quoting the registration code.

Debugging with Visual C++

To step through your code with the Visual C++ debugger you must build your plugin, place a copy in the FileMaker System folder, then edit the Debug tab of the Project Settings. Choose ‘Additional DLLs’ from the Category drop-down, click on a new line under Local Name, then click the ‘…’ button to browse for the plugin in the FileMaker system folder.

Of course, be sure to choose Debug in Choose Active Configuration under the Build menu.

Developing on MacOS

CodeWarrior only

Although Apple provides development tools with MacOS X, including Project Builder, these can only be used to build Mach-O format executables, which run only under MacOS X. But at the moment FileMaker is not fully MacOS X native so it’s in the old PEF format (also known as CFM) and requires PEF format plug-ins. It seems that only CodeWarrior can build PEF executables on MacOS X, so you must buy it to develop plug-ins for FileMaker on MacOS X. In the future, FileMaker might change to Mach-O format.

CodeWarrior is also the best choice for C/C++ development on previous versions of MacOS, and there is a version which runs on both Mac and Windows platforms, making it easier to build cross-platform plug-ins.

MacOS X Target

You’ll need to use the “Carbon” target on Mac OS X, or the FAT target, which includes that target. See the next section, about MacOS Classic.

MacOS Classic Target

For “Classic” MacOS 8 or 9, you need to use the “FAT” target, which includes the PPC target. FAT was used in the past to contain both 68k and PowerPC executables, but in this case both executables seem to be the same apart from some “Version Info” settings in the Linker/PPC PEF part of the target’s settings.

You will need to remove the 68k target from the FAT target, because recent versions of CodeWarrior (for instance, 8 and 9) can not build 68k executables.

Note that FileMaker does not seem to be Carbonised on MacOS Classic, so you will not be able to link to CarbonLib (use InterfaceLib, AppearanceLib, UrlAccessLib, etc instead) or use any of the Carbon-only functions such as CEPreferencesSetAppValue(). Yes, this is very annoying.

Updating the CodeWarrior 6 example to CodeWarrior 8

In Edit| <target> Settings:
  • In Target/Access Paths/System Paths: Add (Compiler)MSL to avoid the “extras.h not found” error.
  • In Target/PPC Target: Change the Type to FMXT.
Re-add the resource file

Select the Project|Add Files menu item, then choose the FMExample.r file. It should now appear in the “Link Order” project tab.

Further Reading