C Interoperability
Embed C code directly in Strada programs and interface with C libraries.
Overview
Strada provides powerful C interoperability through __C__ blocks, which let you embed raw C code directly in your Strada programs. This enables:
Direct C Access
Call any C library function, use C data structures, and leverage existing C code.
High Performance
Write performance-critical code in C while keeping the rest of your program in Strada.
System Integration
Access operating system APIs, hardware interfaces, and low-level functionality.
__C__ Blocks
There are two types of __C__ blocks:
Top-Level Blocks
Place at file scope for includes, global variables, and C helper functions:
__C__ {
#include <stdio.h>
#include <math.h>
#include <openssl/ssl.h>
// Global state
static SSL_CTX *g_ssl_ctx = NULL;
static int g_initialized = 0;
// C helper function
static double fast_distance(double x1, double y1,
double x2, double y2) {
double dx = x2 - x1;
double dy = y2 - y1;
return sqrt(dx*dx + dy*dy);
}
}
Statement-Level Blocks
Use inside functions to write inline C code with access to Strada variables:
func calculate_sqrt(num $x) num {
__C__ {
// Access Strada variable 'x' (it's a StradaValue*)
double val = strada_to_num(x);
// Do the computation in C
double result = sqrt(val);
// Return a new StradaValue
return strada_new_num(result);
}
}
__C__ blocks, Strada variables are available by their name without the sigil. So $x becomes just x, and it's a StradaValue*.
Runtime Functions
The Strada runtime provides functions to convert between C types and StradaValue*:
Extracting Values (StradaValue* → C)
| Function | Returns | Description |
|---|---|---|
strada_to_int(sv) | int64_t | Extract integer value |
strada_to_num(sv) | double | Extract floating-point value |
strada_to_str(sv) | char* | Extract string (caller must free!) |
strada_to_bool(sv) | int | Extract boolean (0 or 1) |
Creating Values (C → StradaValue*)
| Function | Parameter | Description |
|---|---|---|
strada_new_int(i) | int64_t | Create integer value |
strada_new_num(n) | double | Create floating-point value |
strada_new_str(s) | const char* | Create string (copies the string) |
strada_new_str_len(s, len) | const char*, size_t | Create string with explicit length |
&strada_undef | - | Return undef value |
Reference Counting
| Function | Description |
|---|---|
strada_incref(sv) | Increment reference count |
strada_decref(sv) | Decrement reference count (may free) |
strada_to_str() allocates a new string that you must free(). Forgetting to free causes memory leaks!
Opaque Handle Pattern
C pointers can be stored in Strada int variables (which are 64-bit). This "opaque handle" pattern is the standard way to manage C resources:
__C__ {
#include <openssl/ssl.h>
static SSL_CTX *g_ctx = NULL;
static void ensure_ssl_init() {
if (!g_ctx) {
SSL_library_init();
g_ctx = SSL_CTX_new(TLS_client_method());
}
}
}
func ssl_connect(str $host, int $port) int {
__C__ {
ensure_ssl_init();
char *h = strada_to_str(host);
int p = (int)strada_to_int(port);
// Create connection...
SSL *ssl = SSL_new(g_ctx);
// ... connect to host:port ...
free(h);
// Store pointer as int64_t
return strada_new_int((int64_t)(intptr_t)ssl);
}
}
func ssl_write(int $handle, str $data) int {
__C__ {
// Retrieve pointer from int
SSL *ssl = (SSL*)(intptr_t)strada_to_int(handle);
char *d = strada_to_str(data);
int written = SSL_write(ssl, d, strlen(d));
free(d);
return strada_new_int(written);
}
}
func ssl_close(int $handle) void {
__C__ {
SSL *ssl = (SSL*)(intptr_t)strada_to_int(handle);
SSL_shutdown(ssl);
SSL_free(ssl);
return &strada_undef;
}
}
Usage
my int $conn = ssl_connect("example.com", 443);
ssl_write($conn, "GET / HTTP/1.1\r\nHost: example.com\r\n\r\n");
# ... read response ...
ssl_close($conn);
int is 64-bit, large enough to hold any pointer on modern systems. The cast pattern (int64_t)(intptr_t)ptr is portable and safe.
Working with Strings
String handling requires careful memory management:
func uppercase(str $s) str {
__C__ {
// Extract string (allocates memory!)
char *str = strada_to_str(s);
// Modify in place
for (char *p = str; *p; p++) {
if (*p >= 'a' && *p <= 'z') {
*p = *p - 32;
}
}
// Create new StradaValue (copies the string)
StradaValue *result = strada_new_str(str);
// Free the temporary string
free(str);
return result;
}
}
Binary Data
For binary data that may contain null bytes, use strada_new_str_len():
func read_binary(int $fd, int $len) str {
__C__ {
int fd = (int)strada_to_int(fd_arg);
size_t len = (size_t)strada_to_int(len_arg);
char *buf = malloc(len);
ssize_t n = read(fd, buf, len);
if (n <= 0) {
free(buf);
return &strada_undef;
}
// Preserve binary data including null bytes
StradaValue *result = strada_new_str_len(buf, n);
free(buf);
return result;
}
}
Shared Libraries
Creating a Shared Library
Compile your Strada code as a shared library:
# Create mylib.so from mylib.strada
./strada --shared mylib.strada
Your library can export functions that other Strada programs can call:
# lib/MathLib.strada
package MathLib;
version "1.0.0";
func add(int $a, int $b) int {
return $a + $b;
}
func multiply(int $a, int $b) int {
return $a * $b;
}
# Compile to shared library
./strada --shared lib/MathLib.strada
mv MathLib.so lib/
Using import_lib
Import shared libraries with import_lib for clean namespace access:
use lib "lib";
import_lib "MathLib";
func main() int {
my int $sum = MathLib::add(10, 32);
my int $product = MathLib::multiply(6, 7);
say("Sum: " . $sum); # 42
say("Product: " . $product); # 42
return 0;
}
LibName.so is required. Function signatures are embedded in the library via __strada_export_info().
Manual FFI
For more control, use the sys::dl_* functions directly:
my int $lib = sys::dl_open("./mylib.so");
my int $fn = sys::dl_sym($lib, "MathLib_add");
my scalar $result = sys::dl_call_sv($fn, [10, 32]);
say($result); # 42
sys::dl_close($lib);
Extern Functions (C-Callable)
Use the extern keyword to create functions with C-compatible calling conventions. These functions can be called directly from C code without wrappers or type conversions.
Defining Extern Functions
# extern_lib.strada - Functions callable from C
package MathExtern;
# C signature: int64_t add_integers(int64_t a, int64_t b)
extern func add_integers(int $a, int $b) int {
return $a + $b;
}
# C signature: double add_floats(double a, double b)
extern func add_floats(num $a, num $b) num {
return $a + $b;
}
# C signature: int64_t multiply(int64_t a, int64_t b)
extern func multiply(int $a, int $b) int {
return $a * $b;
}
Compiling as Shared Library
# Create shared library
./strada --shared extern_lib.strada
# Creates extern_lib.so with C-callable functions
Calling from C
The extern functions can be called directly from C without any Strada-specific wrappers:
/* main.c - C program that calls Strada extern functions */
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>
int main() {
// Load the Strada shared library
void *lib = dlopen("./extern_lib.so", RTLD_NOW);
if (!lib) {
fprintf(stderr, "Failed to load: %s\n", dlerror());
return 1;
}
// Get function pointers
int64_t (*add_integers)(int64_t, int64_t) = dlsym(lib, "add_integers");
double (*add_floats)(double, double) = dlsym(lib, "add_floats");
int64_t (*multiply)(int64_t, int64_t) = dlsym(lib, "multiply");
// Call them directly - no StradaValue* wrappers needed!
printf("add_integers(10, 32) = %ld\n", add_integers(10, 32)); // 42
printf("add_floats(3.14, 2.86) = %f\n", add_floats(3.14, 2.86)); // 6.0
printf("multiply(6, 7) = %ld\n", multiply(6, 7)); // 42
dlclose(lib);
return 0;
}
Compile and run the C program:
gcc -o main main.c -ldl
./main
Type Mapping
Extern functions use raw C types, not StradaValue*:
| Strada Type | C Type |
|---|---|
int | int64_t |
num | double |
str | char* |
void | void |
extern func when you need to call Strada code from C programs, embedded systems, or other languages with C FFI capabilities. For Strada-to-Strada library calls, regular functions with import_lib are preferred.
Extern vs Regular Functions
| Feature | Regular Function | Extern Function |
|---|---|---|
| Calling convention | StradaValue* parameters | Raw C types |
| Callable from C | Needs wrappers | Direct calls |
| Type safety | Runtime type checking | Compile-time only |
| Use case | Strada libraries | C interoperability |
Complete Example: Compression Library
Here's a complete example wrapping zlib for compression:
package Compress;
__C__ {
#include <zlib.h>
#include <stdlib.h>
#include <string.h>
}
func gzip(str $data) str {
__C__ {
char *input = strada_to_str(data);
size_t input_len = strlen(input);
// Allocate output buffer (worst case: slightly larger than input)
size_t output_len = compressBound(input_len);
char *output = malloc(output_len);
// Compress
int ret = compress((Bytef*)output, &output_len,
(Bytef*)input, input_len);
free(input);
if (ret != Z_OK) {
free(output);
return &strada_undef;
}
StradaValue *result = strada_new_str_len(output, output_len);
free(output);
return result;
}
}
func gunzip(str $data, int $max_size) str {
__C__ {
char *input = strada_to_str(data);
size_t input_len = strlen(input);
size_t output_len = (size_t)strada_to_int(max_size);
char *output = malloc(output_len);
int ret = uncompress((Bytef*)output, &output_len,
(Bytef*)input, input_len);
free(input);
if (ret != Z_OK) {
free(output);
return &strada_undef;
}
StradaValue *result = strada_new_str_len(output, output_len);
free(output);
return result;
}
}
Compile with the zlib library:
./strada --shared compress.strada -- -lz
Best Practices
Always Free Strings
strada_to_str() allocates memory. Always free() the result when done.
Check for NULL
Strada values can be undef. Check before extracting to avoid crashes.
Use Static Helpers
Put complex C logic in static helper functions in top-level blocks for cleaner code.
Document Handle Types
When using the opaque handle pattern, document what type of pointer each int handle represents.
Debugging Tips
- Compile with
-gflag for debug symbols:./strada -g program.strada - Use
-cto keep the generated C file for inspection:./strada -c program.strada - Add
printf()statements in your__C__blocks for debugging - Use GDB to debug the compiled executable
# Compile with debug info
./strada -g -c myprogram.strada
# Debug with GDB
gdb ./myprogram