^title Calling C from Wren
When we are ensconced within the world of Wren, the external C world is
"foreign" to us. There are two reasons we might want to bring some foreign
flavor into our VM:
* We want to execute code written in C.
* We want to store raw C data.
Since Wren is object-oriented, behavior lives in methods, so for the former we
have **foreign methods**. Likewise, data lives in objects, so for the latter, we
define **foreign classes**. This page is about the first, foreign methods. The
[next page][] covers foreign classes.
[next page]: /embedding/storing-c-data.html
A foreign method looks to Wren like a regular method. It is defined on a Wren
class, it has a name and signature, and calls to it are dynamically dispatched.
The only difference is that the *body* of the method is written in C.
A foreign method is declared in Wren like so:
:::wren
class Math {
foreign static add(a, b)
}
The `foreign` tells Wren that the method `add()` is declared on `Math`, but
implemented in C. Both static and instance methods can be foreign.
## Binding Foreign Methods
When you call a foreign method, Wren needs to figure out which C function to
execute. This process is called *binding*. Binding is performed on-demand by the
VM. When a class that declares a foreign method is executed -- when the `class`
statement itself is evaluated -- the VM asks the host application for the C
function that should be used for the foreign method.
It does this through the `bindForeignMethodFn` callback you give it when you
first configure the VM. This callback isn't the foreign method itself. It's the
binding function your app uses to *look up* the foreign methods.
Its signature is:
:::c
WrenForeignMethodFn bindForeignMethodFn(
WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature);
Every time a foreign method is declared, the VM invokes this callback. It passes
in the module containing the class declaration, the name of the class containing
the method, the method's signature, and whether or not it's a static method. In
the above example, it would pass something like:
:::c
bindForeignMethodFn(vm, "main", "Math", true, "add(_,_)");
When you configure the VM, you give a pointer to a function that looks up the
appropriate C function for the given foreign method and returns a pointer to it.
Something like:
:::c
WrenForeignMethodFn bindForeignMethod(
WrenVM* vm,
const char* module,
const char* className,
bool isStatic,
const char* signature)
{
if (strcmp(module, "main") == 0)
{
if (strcmp(className, "Math") == 0)
{
if (!isStatic && strcmp(signature, "add(_,_)") == 0)
{
return mathAdd; // C function for Math.add(_,_).
}
// Other foreign methods on Math...
}
// Other classes in main...
}
// Other modules...
}
This implementation is pretty tedious, but you get the idea. Feel free to do
something more clever here in your host application.
The important part is that it returns a pointer to a C function to use for that
foreign method. Wren does this binding step *once* when the class definition is
first executed. It then keeps the function pointer you return and associates it
with that method. This way, *calls* to the foreign method are fast.
## Implementing a Foreign Method
All C functions for foreign methods have the same signature:
:::c
void foreignMethod(WrenVM* vm);
Arguments passed from Wren are not passed as C arguments, and the method's
return value is not a C return value. Instead -- you guessed it -- we go through
the [slot array][].
[slot array]: /embedding/slots-and-handles.html
When a foreign method is called from Wren, the VM sets up the slot array with
the receiver and arguments to the call. As in calling Wren from C, the receiver
object is in slot zero, and arguments are in consecutive slots after that.
You use the slot API to read those arguments, and then perform whatever work you
want to in C. If you want the foreign method to return a value, place it in slot
zero. that slot, the foreign method will implicitly return the receiver, since
that's what is already in there.) Like so:
:::c
void mathAdd(WrenVM* vm)
{
double a = wrenGetSlotDouble(vm, 1);
double b = wrenGetSlotDouble(vm, 2);
wrenSetSlotDouble(vm, 0, a + b);
}
While your foreign method is executed, the VM is completely suspended. No other
fibers will run until your foreign method returns.
This covers foreign behavior, but what about foreign *state*? For that, we need
a foreign *class*...
Storing C Data →
← Calling Wren from C