
Calling functions in other packages.
In version 1.7.1.0, we have included the feature of calling functions in other ACF packages from the ACF package you are currently working with. As it always has been possible to use eval (ACF_run("other acf function in a different package";params)), it is not very effective and introduces a significant overhead. The new efficient way to do it is our new inter-package calling scheme that makes us able to have libraries of ACF functions that can be used from new packages. This simplifies the implementation of new features using standard ACF libraries, for example, an EXCEL library that makes it much simpler to produce EXCEL reports.
The USE statement
Right below the package declaration (and before any function declaration), write:
USE package_name;
The other package needs to be loaded when you compile your source code. In this way, the compiler can syntax-check your function call against the function in the other package.
After that, you can simply call the functions in that package as if they were local functions. Just write the name and the parameter list. In this way, the calling scheme induces very little overhead.
Let's say, in the bootstrap library that comes pre-installed, there is a function to return the filename from a full path:
package bootstrap "bootstrap library";
...
...
...
function GetFilenameFromPath ( string path )
FunctionID 205;
path = substitute ( path, ":", "/");
path = substitute ( path, "\\", "/");
return regex_replace("^(.+)/(.+\..+)$", path, "\2");
end
...
...
...
Now we are working on a different package, so we need this function.
package testnewstuff "test some new stuff";
USE bootstrap; // tell us that all functions in that package are available here...
Function test_ext_functions ( string path )
string cc = GetFilenameFromPath ( path );
return cc;
end
In the FileMaker Calculation:
Set Variable [$fn; ACF_Run("test_ext_functions";"/Users/peter/Desktop/MyFile.txt")]
Now, the $fn will contain MyFile.txt
Performance metrics
To see how the inter-package calls perform compared to local functions, I did a small test. I wrote some functions to trim away white space left, right, and both sides of a given string. Like this:
Function leftTrim (string s)
FunctionID 240;
return regex_replace ("^\s+", s, "");
end
function rightTrim (string s)
FunctionID 241;
return regex_replace ("\s+$", s, "");
end
function Trim (string s)
FunctionID 242;
return leftTrim(rightTrim(s));
end
Then I made a test function to see the performance:
function testTrim (string s, int z)
int i;
string x;
for (i=1,z)
x = Trim(s);
end for
return x;
end
When called from FileMaker calculation, with a given string with some spaces on each side, and the z-value of 10,000; the time including the call from FileMaker was about 155 mS.
Then I moved the trim functions to a different package and used the "USE" statement. With the same string and z-value, the time increased to 165 mS.
The difference was also 10 mS for a loop with 10,000 rounds, which tells that the overhead with inter-package calls is only 1 µS.
An overhead of 1 µS per call for inter-package communication is typically considered negligible in most application contexts, especially considering the benefits of modularity, code reuse, and separation of concerns that come with organizing functionality across packages.
Compared to the standard custom function
Brian Dunning's CF site has a Trim4 standard custom function. I did the same test with this, repeating the call to it 10,000 times: The execution time increased to 1718 ms (or 1.718 seconds). This is 0.1718 mS for each call. Compared to the ACF trim function that runs 10,000 Trim calls in 155 mS/10,000 = 0.0155 mS or 15.5 µS for each call. That makes our function 11 times faster. Our Trim function uses two regex operations that are probably not the fastest. Later we will have a trim function as an optimized plugin function.
I tried both as a calculation with a while statement, a script with a loop, and an ACF function using the FileMaker calculation engine with the @....@ notation. They all executed in the range 1708 to 1730 ms.
function testTrim4 (string s, int z)
int i;
string x;
$$val = s;
for (i=1,z)
x = @Trim4($$val)@;
end for
return x;
end
Can we optimize it some more?
Well, I actually did implement the trim functions into the ACF language as standard native functions to see the effect of using this instead. I created three native functions for this that are now standard functions in the ACF language:
- TrimLeft - Trims away white space on the left side of the string
- TrimRight - Trims away white space on the right side of the string
- TrimBoth - Trims away white space on both sides of the string.
Then I changed the function in this way:
function testTrim (string s, int z)
int i;
string x;
for (i=1,z)
x = TrimBoth(s);
end for
return x;
end
And the result was really impressive, with 11.64 mS for all 10,000 operations altogether. This means that each trim operation on both sides used only 1.164 µS. That is 148 times faster than the standard FileMaker Custom Function implementation.
Some points to notice
- When compiling or loading binary for a package that uses other packages, the other packages need to be loaded first. The compiler checks the existence of the functions in the other package and will issue an error if they are not available, or have different parameters. Likewise, when loading the binary for a pre-compiled package, the link stage in the loading will issue an error and refuse to load if the dependencies are not met.
- Two packages cannot reference each other. Having cross-dependencies makes it impossible to load the first package.
- The automatic link stage when loading or compiling a package will update references both for references to other packages and other packages referencing the current package.
- If you change the parameters - or the return type of a library function that is called from some other loaded packages, you will have a broken dependency issue. You will get a linker error and the plugin refuses to load the package.
- The change must be performed in all packages that use the library package. Do not compile them yet.
- Clear the packages using the
ACF_ClearPackagesplugin command. This will automatically install the bootstrap library after clearing so the dev-tool functions work. - Compile the changed library.
- Finally, compile the other packages that use this library package.
- If you just added new functions to the library, or changed the code for the existing functions without changing the parameters or the return type, there will be no dependency issue and the automatic linking will handle the linking.
A new version of the development tool
We have created a new version of the development tool that better supports this new function. The new tool will soon be released.
Easier implementation of the development tool into any target application project to make it easy to develop ACF functions and libraries.
Instead of implementing fields in a preference table, we have a brand new table that comes with one layout. Each record in this table is the home of an ACF library. In this way, you can have smaller more manageable libraries and use the inter-package calls instead of having it all in a huge library.
At the start of FileMaker, you can just call the script "InitACF", which loops records in this table and loads the binary packages.
This implementation makes it unnecessary to alter any of the existing tables. This new table does not need any relations and is thus a stand-alone service table.
The new dev-tool has functions to assist with dependency issue solving, making it possible to deactivate the packages that use a library temporarily, then run the initACF button, and do the changes necessary to the library package. Then you can compile and activate the other packages after changing the parameters or calls.
