Foreign Function Interface (FFI)
The Foreign Function Interface (FFI) allows Twinkies programs to call functions from external libraries, such as Windows DLLs, Linux shared objects, or macOS dynamic libraries.
FFI Overview
FFI enables you to:
- Call system APIs (Windows, Linux, macOS)
- Use existing C libraries
- Access platform-specific functionality
- Integrate with legacy code
FFI Declaration Syntax
FFI functions are declared using the extern keyword:
extern "calling_convention" from "library" {
func function_name(params) -> return_type;
}
Basic Example
extern "cdecl" from "kernel32.dll" {
func GetTickCount() -> int;
func GetCurrentProcessId() -> int;
}
func main() -> int {
let ticks: int = GetTickCount();
print("System ticks:", ticks);
return 0;
}
Calling Conventions
Twinkies supports multiple calling conventions for compatibility:
cdecl (Default C)
extern "cdecl" from "msvcrt.dll" {
func strlen(str: string) -> int;
func printf(format: string, ...) -> int;
}
stdcall (Windows API)
extern "stdcall" from "kernel32.dll" {
func GetTickCount() -> int;
func Sleep(dwMilliseconds: int) -> int;
}
fastcall (Optimized)
extern "fastcall" from "library.dll" {
func optimized_function(x: int, y: int) -> int;
}
thiscall (C++ Member Functions)
extern "thiscall" from "library.dll" {
func member_function(this_ptr: int, param: int) -> int;
}
Library Types
The compiler automatically resolves library paths based on platform:
Windows (DLL)
extern "cdecl" from "kernel32.dll" {
func GetTickCount() -> int;
}
Linux (SO)
extern "cdecl" from "libc.so" {
func strlen(str: string) -> int;
}
macOS (DYLIB)
extern "cdecl" from "libSystem.dylib" {
func strlen(str: string) -> int;
}
Static Libraries
extern "cdecl" from "mylib.a" {
func my_function(x: int) -> int;
}
Common FFI Examples
Windows API
extern "stdcall" from "user32.dll" {
func MessageBoxA(hWnd: int, lpText: string, lpCaption: string, uType: int) -> int;
func GetSystemMetrics(nIndex: int) -> int;
}
extern "stdcall" from "kernel32.dll" {
func GetTickCount() -> int;
func Sleep(dwMilliseconds: int) -> int;
func GetCurrentProcessId() -> int;
}
func main() -> int {
// Show a message box
MessageBoxA(0, "Hello from Twinkies!", "FFI Test", 0);
// Get screen dimensions
let width: int = GetSystemMetrics(0); // SM_CXSCREEN
let height: int = GetSystemMetrics(1); // SM_CYSCREEN
print("Screen:", width, "x", height);
// Get system information
let ticks: int = GetTickCount();
let pid: int = GetCurrentProcessId();
print("Ticks:", ticks);
print("Process ID:", pid);
return 0;
}
C Standard Library
extern "cdecl" from "msvcrt.dll" {
func strlen(str: string) -> int;
func strcmp(str1: string, str2: string) -> int;
func atoi(str: string) -> int;
func rand() -> int;
func srand(seed: int) -> int;
}
extern "stdcall" from "kernel32.dll" {
func GetTickCount() -> int;
}
func main() -> int {
let str: string = "Hello, World!";
let len: int = strlen(str);
print("Length:", len);
let num: int = atoi("12345");
print("Number:", num);
srand(GetTickCount());
let random: int = rand();
print("Random:", random);
return 0;
}
File Operations
extern "stdcall" from "kernel32.dll" {
func CreateFileA(lpFileName: string, dwDesiredAccess: int,
dwShareMode: int, lpSecurityAttributes: int,
dwCreationDisposition: int, dwFlagsAndAttributes: int,
hTemplateFile: int) -> int;
func WriteFile(hFile: int, lpBuffer: string, nNumberOfBytesToWrite: int,
lpNumberOfBytesWritten: int, lpOverlapped: int) -> int;
func CloseHandle(hObject: int) -> int;
}
func main() -> int {
// Create a file
// Note: 0x40000000 = 1073741824 (GENERIC_WRITE)
let file: int = CreateFileA("test.txt", 1073741824, 0, 0, 2, 128, 0);
if (file != -1) {
let data: string = "Hello from Twinkies FFI!\n";
let data_length: int = strlen(data);
let written: int = 0;
WriteFile(file, data, data_length, written, 0);
CloseHandle(file);
print("File written successfully");
}
return 0;
}
FFI Function Parameters
FFI functions can have parameters of any Twinkies type:
extern "cdecl" from "library.dll" {
func function_with_int(x: int) -> int;
func function_with_string(s: string) -> int;
func function_with_bool(flag: bool) -> int;
func function_with_multiple(a: int, b: int, c: string) -> int;
}
FFI Return Types
FFI functions can return any Twinkies type:
extern "cdecl" from "library.dll" {
func returns_int() -> int;
func returns_bool() -> bool;
func returns_string() -> string;
func returns_void() -> void;
}
Error Handling
FFI calls may fail. Check return values and use error functions:
extern "stdcall" from "kernel32.dll" {
func GetLastError() -> int;
}
func main() -> int {
let result: int = SomeFunction();
if (result == 0) {
let error: int = GetLastError();
print("Error code:", error);
}
return 0;
}
Platform-Specific Considerations
Windows
- Use
.dllextension - Common libraries:
kernel32.dll,user32.dll,msvcrt.dll - Use
stdcallfor most Windows API functions - Use
cdeclfor C runtime functions
Linux
- Use
.soextension - Common libraries:
libc.so,libm.so - Most functions use
cdecl - Library paths may need to be specified
macOS
- Use
.dylibextension - Common libraries:
libSystem.dylib - Most functions use
cdecl - Framework paths may be needed
FFI Best Practices
1. Use Appropriate Calling Conventions
Match the calling convention of the target library:
// Windows API uses stdcall
extern "stdcall" from "kernel32.dll" {
func GetTickCount() -> int;
}
// C runtime uses cdecl
extern "cdecl" from "msvcrt.dll" {
func strlen(str: string) -> int;
}
2. Handle Errors
Always check return values and handle errors:
let handle: int = CreateFileA("file.txt", ...);
if (handle == -1) {
print("Failed to create file");
return 1;
}
3. Match Parameter Types
Ensure Twinkies types match the C types:
// C: int GetTickCount(void);
extern "stdcall" from "kernel32.dll" {
func GetTickCount() -> int; // ✓ Correct
}
// C: int MessageBox(HWND hWnd, LPCTSTR lpText, ...);
extern "stdcall" from "user32.dll" {
func MessageBoxA(hWnd: int, lpText: string, lpCaption: string, uType: int) -> int;
}
4. Document FFI Functions
Comment FFI functions with their C signatures:
// C: DWORD GetTickCount(void);
extern "stdcall" from "kernel32.dll" {
func GetTickCount() -> int;
}
// C: int MessageBox(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType);
extern "stdcall" from "user32.dll" {
func MessageBoxA(hWnd: int, lpText: string, lpCaption: string, uType: int) -> int;
}
FFI Limitations
- No Struct Support - Cannot pass or return structs directly
- No Function Pointers - Cannot use function pointers as parameters
- No Variadic Functions - Limited support for variadic functions
- Type Mapping - Some C types may not map perfectly to Twinkies types
- Memory Management - Must manually manage memory for some FFI calls
Complete FFI Example
// Complete FFI example demonstrating multiple features
extern "stdcall" from "kernel32.dll" {
func GetTickCount() -> int;
func Sleep(dwMilliseconds: int) -> int;
func GetCurrentProcessId() -> int;
}
extern "stdcall" from "user32.dll" {
func MessageBoxA(hWnd: int, lpText: string, lpCaption: string, uType: int) -> int;
func GetSystemMetrics(nIndex: int) -> int;
}
extern "cdecl" from "msvcrt.dll" {
func strlen(str: string) -> int;
func strcmp(str1: string, str2: string) -> int;
}
func main() -> int {
print("=== FFI Demonstration ===");
// System information
let ticks: int = GetTickCount();
let pid: int = GetCurrentProcessId();
print("Process ID:", pid);
print("System ticks:", ticks);
// Screen information
let width: int = GetSystemMetrics(0);
let height: int = GetSystemMetrics(1);
print("Screen:", width, "x", height);
// String operations
let test: string = "Hello, FFI!";
let len: int = strlen(test);
print("String length:", len);
// Timing
let start: int = GetTickCount();
Sleep(1000); // Sleep for 1 second
let end: int = GetTickCount();
let elapsed: int = end - start;
print("Elapsed time (ms):", elapsed);
// User interaction
MessageBoxA(0, "FFI test complete!", "Twinkies FFI", 0);
return 0;
}
Next Steps
- Learn about Inline Assembly for low-level operations
- Read Modules for organizing FFI code
- Check Examples Guide for more FFI examples
