How to Use FFI
Last updated at 12:20 pm UTC on 28 October 2015
Squeak allows calling functions in the underlying platform's libraries. They are analogous to Primitive Methods, but they can call arbitrary functions.
Installation (Squeak 5.0)
(Installer repository: 'http://source.squeak.org/FFI')
Methods Invoking Functions
To call an external function, make a method (by convention the method selector should start with 'api' as in 'apiInvalRect:'). This method's structure is similar to a named primitive method. The first line should be the external function specification. Following the external function specification is normal Squeak smalltalk code that is executed if the external function fails.
Since API calls don't know how to communicate failure like Squeak primitives do, failure here means something went wrong in linking to and calling the external function, not that the function could not do its internal job. Thus, the most common code is simply '^self externalCallFailed.'
Sample method from the 3.1a-4303 image, in MacRect:
" ( void ) InvalRect (const Rect ☆ badRect ) ; "
< cdecl: void 'InvalRect' ( MacRect ) module: 'InterfaceLib'>
^ self externalCallFailed.
The external function specification is enclosed in angle brackets: < > Within the brackets, 'cdecl:' indicates an FFI call. 'cdecl:' is followed by the return type of the function, the name of the function as a literal string, a literal array of argument types, 'module:', and the literal string name of the shared library in which the function should be found. (JMM) Note this example uses InterfaceLib which is the shared library linked into the Squeak VM for Classic Applications. CarbonLib is the library for Carbon applications, however if you are using the mach-o based VMs under OS-X it's again different, you need to specify the framework the routine is in. Therefore you need to do a bit of research and more coding if you expect to run your FFI aware image under say os-8 and os-x. Note the mach-o VM has superceded the Carbon VMs so maybe you can ignore the CarbonLib issue.
Using 'apicall:' instead of 'cdecl:' indicates a different calling convention. I haven't been able to determine the actual intended difference. On Macintosh, the two forms are equivalent. On Unix, only 'cdecl:' is supported. I can't find the Windows source code, so I don't know what it does. [Jörn Eyrich: apicall specifies the Pascal calling convention (push arguments on stack left to right, callee removes arguments) which seems to be standard on Windows, and cdecl secifies the C calling conventions (push right to left, caller removes)]
Argument and return types must be names of ExternalTypes. These can be one of the atomic types (see ExternalType class>>initializeFFIConstants and ExternalType class>>initializeAtomicTypes):
- byte (unsigned)
- sbyte (signed)
- ushort (16-bit unsigned)
- short (16-bit signed)
- ulong (32-bit unsigned)
- long (32-bit signed)
- ulonglong (64-bit unsigned)
- longlong (64-bit signed)
- char (unsigned)
- schar (signed)
- float (single-precision float)
- double (double-precision float)
ExternalTypes can also be structure types, referred to by the name of a subclass of ExternalStructure with '*' appended to indicate that the argument or return is a pointer to that structure. See the next section.
If the function you want to call has a return or argument type that is a structure type that is not already present as a subclass of ExternalStructure, you'll need to create one. The most important bit is to write a class-side method 'fields' that returns an array of field descriptions. Each field description is a 2-element array (Or three but that does something else, I'm not sure what) the first element of which is the field name and the second is the type. Then send the class #defineFields to initialize it with your new implementation of #fields. Be sure to have for each subclass of ExternalStructure a class-side #initialize method that includes "self defineFields" or the class won't work after being loaded into another image.
For example, a color structur might implemented so:
maybe, like this:
- first, create a new class named color in the System Browser (subclass of ExternalStructure)
- add class method 'fields' to color
- in the end, just do a "color defineFields." in the Workspace
Mac Memory Allocation Issues
If you allocate external structures, those with memory outside the Squeak process space, you may need to increase the amount of memory that is reserved outside the object heap for such use. The Mac OS (9 and previous) needs this, other platforms may be able to dynamically get more memory from the OS. To see how much memory is currently reserved printIt 'Smalltalk extraVMMemory'. To change it, execute 'Smalltalk extraVMMemory: someNumberofBytes' Then save your image and quit. When you next start up, the amount of memory you requested will be reserved. (JMM) Note the OSX versions of the VM ignore extraVMMemory because the memory model for OS-X/unix applications is quite different.