mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-16 20:28:04 +01:00
Finish off the rest of the embedding docs.
A first draft of them, at least. They probably need some editing. Remove the "Application Lifecycle" page for now. I do intend to add some docs about how fibers interact with the host app, but I can do that later.
This commit is contained in:
@ -1,9 +0,0 @@
|
||||
^title Application Lifecycle
|
||||
|
||||
**TODO: Write these docs.**
|
||||
|
||||
Until these are written, you can read the docs in [wren.h][].
|
||||
|
||||
[wren.h]: https://github.com/munificent/wren/blob/master/src/include/wren.h
|
||||
|
||||
<a href="configuring-the-vm.html">← Configuring the VM</a>
|
||||
@ -37,8 +37,10 @@ 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.
|
||||
first [configure the VM][config]. This callback isn't the foreign method itself.
|
||||
It's the binding function your app uses to *look up* foreign methods.
|
||||
|
||||
[config]: configuring-the-vm.html
|
||||
|
||||
Its signature is:
|
||||
|
||||
@ -112,8 +114,7 @@ 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:
|
||||
zero. Like so:
|
||||
|
||||
:::c
|
||||
void mathAdd(WrenVM* vm)
|
||||
@ -124,7 +125,9 @@ that's what is already in there.) Like so:
|
||||
}
|
||||
|
||||
While your foreign method is executed, the VM is completely suspended. No other
|
||||
fibers will run until your foreign method returns.
|
||||
fibers will run until your foreign method returns. You should *not* try to
|
||||
resume the VM from within a foreign method by calling `wrenCall()` or
|
||||
`wrenInterpret()`. The VM is not re-entrant.
|
||||
|
||||
This covers foreign behavior, but what about foreign *state*? For that, we need
|
||||
a foreign *class*...
|
||||
|
||||
@ -7,7 +7,7 @@ compiler, but that's still a good bit of work.
|
||||
|
||||
It's also not an effective way to communicate. You can't pass arguments to
|
||||
Wren—at least, not without doing something nasty like converting them to
|
||||
literals in a string of source code—and we can't get a result value back.
|
||||
literals in a string of source code—and you can't get a result value back.
|
||||
|
||||
`wrenInterpret()` is great for loading code into the VM, but it's not the best
|
||||
way to execute code that's already been loaded. What we want to do is invoke
|
||||
@ -26,7 +26,7 @@ C, first we need a few things:
|
||||
|
||||
[signature]: ../method-calls.html#signature
|
||||
|
||||
* **The receiver to invoke the method on.** It's the receiver's class that
|
||||
* **The receiver object to invoke the method on.** The receiver's class
|
||||
determines which method is actually called.
|
||||
|
||||
* **The arguments to pass to the method.**
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
When you create a Wren VM, you tweak it by passing in a pointer to a
|
||||
WrenConfiguration structure. Since Wren has no global state, you can configure
|
||||
each VM them differently if your application happens to run multiple.
|
||||
each VM differently if your application happens to run multiple.
|
||||
|
||||
The struct looks like:
|
||||
|
||||
@ -20,7 +20,8 @@ The struct looks like:
|
||||
int heapGrowthPercent;
|
||||
} WrenConfiguration;
|
||||
|
||||
Most fields have useful defaults, which you can (and should) set by calling:
|
||||
Most fields have useful defaults, which you can (and should) initialize by
|
||||
calling:
|
||||
|
||||
:::c
|
||||
wrenInitConfiguration(&configuration);
|
||||
@ -51,7 +52,7 @@ host should return the source code for that module. Memory for the source should
|
||||
be allocated using the same allocator that the VM uses for other allocation (see
|
||||
below). Wren will take ownership of the returned string and free it later.
|
||||
|
||||
The module loader will only be called once for any given module name. Wren caches
|
||||
The module loader is only be called once for any given module name. Wren caches
|
||||
the result internally so subsequent imports of the same module use the
|
||||
previously loaded code.
|
||||
|
||||
@ -63,21 +64,19 @@ If you don't use any `import` statements, you can leave this `NULL`.
|
||||
### `bindForeignMethodFn`
|
||||
|
||||
The callback Wren uses to find a foreign method and bind it to a class. See
|
||||
[this page][foreign method] for details.
|
||||
[this page][foreign method] for details. If your application defines no foreign
|
||||
methods, you can leave this `NULL`.
|
||||
|
||||
[foreign method]: /embedding/calling-c-from-wren.html
|
||||
|
||||
If your application defines no foreign methods, you can leave this `NULL`.
|
||||
|
||||
### `bindForeignClassFn`
|
||||
|
||||
The callback Wren uses to find a foreign class and get its foreign methods. See
|
||||
[this page][foreign class] for details.
|
||||
[this page][foreign class] for details. If your application defines no foreign
|
||||
classes, you can leave this `NULL`.
|
||||
|
||||
[foreign class]: /embedding/storing-c-data.html
|
||||
|
||||
If your application defines no foreign classes, you can leave this `NULL`.
|
||||
|
||||
## Diagnostics
|
||||
|
||||
These let you wire up some minimal output so you can tell if your code is doing
|
||||
@ -108,7 +107,7 @@ is:
|
||||
int line,
|
||||
const char* message)
|
||||
|
||||
The type parameter is one of:
|
||||
The `type` parameter is one of:
|
||||
|
||||
:::c
|
||||
typedef enum
|
||||
@ -197,5 +196,4 @@ frequent garbage collections.
|
||||
|
||||
If set to zero, the VM uses a default of 50.
|
||||
|
||||
<a class="right" href="application-lifecycle.html">Application Lifecycle →</a>
|
||||
<a href="storing-c-data.html">← Storing C Data</a>
|
||||
|
||||
@ -92,15 +92,10 @@ have to explicitly link to.
|
||||
[libm]: https://en.wikipedia.org/wiki/C_mathematical_functions#libm
|
||||
|
||||
If your program is in C++ but you are linking to the Wren library compiled as C,
|
||||
then you'll need to handle the calling convention differences like so:
|
||||
this header handles the differences in calling conventions between C and C++:
|
||||
|
||||
:::c
|
||||
extern "C" {
|
||||
#include "wren.h"
|
||||
}
|
||||
|
||||
(Wren's source can be compiled as either C or C++, so you can skip this by
|
||||
compiling the whole thing yourself as C++. Whatever floats your boat.)
|
||||
#include "wren.hpp"
|
||||
|
||||
## Creating a Wren VM
|
||||
|
||||
@ -115,7 +110,7 @@ This gives you a basic configuration that has reasonable defaults for
|
||||
everything. If you don't need to tweak stuff, you can leave it at that. We'll
|
||||
[learn more][configuration] about what you can configure later.
|
||||
|
||||
[configuration]: configuration.html
|
||||
[configuration]: configuring-the-vm.html
|
||||
|
||||
With this ready, you can create the VM:
|
||||
|
||||
@ -128,7 +123,7 @@ a WrenVM. You can have multiple Wren VMs running independently of each other
|
||||
without any problems, even concurrently on different threads.
|
||||
|
||||
`wrenNewVM()` stores its own copy of the configuration, so after calling it, you
|
||||
can discard the `WrenConfiguration` struct you filled in. Now you have a live
|
||||
can discard the WrenConfiguration struct you filled in. Now you have a live
|
||||
VM, waiting to run some code!
|
||||
|
||||
## Executing Wren code
|
||||
|
||||
@ -14,8 +14,8 @@ of the VM. These functions are based on two fundamental concepts: **slots** and
|
||||
|
||||
When you want to send data to Wren, read data from it, or generally monkey
|
||||
around with Wren objects from C, you do so by going through an array of slots.
|
||||
You can think of it as a shared message board that both the VM and your C code
|
||||
leave notes on for the other side to process.
|
||||
Think of it as a shared message board that both the VM and your C code leave
|
||||
notes on for the other side to process.
|
||||
|
||||
The array is zero-based, and each slot can hold a value of any type. It is
|
||||
dynamically sized, but it's your responsibility to ensure there are enough slots
|
||||
@ -58,8 +58,9 @@ enough slots for the objects it is sending you.
|
||||
|
||||
### Writing slots
|
||||
|
||||
Once you have some slots, you can store data in them using a number of functions all named `wrenSetSlot<type>()` where `<type>` is the kind of data.
|
||||
We'll start with the simple ones:
|
||||
Once you have some slots, you store data in them using a number of functions all
|
||||
named `wrenSetSlot<type>()` where `<type>` is the kind of data. We'll start with
|
||||
the simple ones:
|
||||
|
||||
:::c
|
||||
void wrenSetSlotBool(WrenVM* vm, int slot, bool value);
|
||||
@ -231,15 +232,13 @@ A handle is an opaque wrapper around an object of any type, but just as
|
||||
important, it's a *persistent* one. When Wren gives you a pointer to a
|
||||
WrenHandle, it guarantees that that pointer remains valid. You can keep it
|
||||
around as long as you want. Even if a garbage collection occurs, Wren will
|
||||
ensure all of the handle and the objects they wrap are kept safely in memory.
|
||||
ensure the handle and the object it wraps are kept safely in memory.
|
||||
|
||||
Internally, Wren keeps a list of all of the WrenHandles that have been created.
|
||||
That way, during garbage collection, it can find them all and make sure their
|
||||
objects aren't freed.
|
||||
|
||||
But what if you don't want it to be kept around any more? Since C relies on
|
||||
manual memory management, WrenHandle does too. When you are done with one, you
|
||||
must explicitly release it by calling:
|
||||
objects aren't freed. But what if you don't want it to be kept around any more?
|
||||
Since C relies on manual memory management, WrenHandle does too. When you are
|
||||
done with one, you must explicitly release it by calling:
|
||||
|
||||
:::c
|
||||
void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle);
|
||||
|
||||
@ -1,66 +1,366 @@
|
||||
^title Storing C Data
|
||||
|
||||
**TODO: Write these docs.**
|
||||
An embedded language often needs to work with native data. You may want a
|
||||
pointer to some memory managed in the C heap, or maybe you want to store a chunk
|
||||
of data more efficiently than Wren's dynamism allows. Often, you want to have a
|
||||
Wren object that manages an external resource like a file handle.
|
||||
|
||||
<!--
|
||||
For those cases, you can define a **foreign class**, a chimera whose state is
|
||||
half Wren and half C. It is a real Wren class with a name, constructor, and
|
||||
methods. You can define methods on it written in Wren, or [foreign methods][]
|
||||
written in C. It produces real Wren objects that you can pass around, do `is`
|
||||
checks on, etc. But it also wraps a blob of raw memory that is opaque to Wren
|
||||
but accessible from C.
|
||||
|
||||
- foreign classes
|
||||
- embedded language often need to work with native c data
|
||||
- maybe want to refer to pointer to memory in c heap
|
||||
- maybe want more dense efficient encoding c provides
|
||||
- may want to refer to resource otherwise managed outside of wren, like file
|
||||
handle
|
||||
- wrap in foreign class
|
||||
- hybrid of wren and c
|
||||
- foreign class has methods defined in wren (though can be foreign)
|
||||
- each instance is instance of wren class, knows type, etc.
|
||||
- but also stores bytes of data opaque to wren
|
||||
- defining:
|
||||
- "foreign class"
|
||||
- declaring class foreign says "when construct call into c to fill in
|
||||
opaque bytes"
|
||||
- need c fn pointer to do that work
|
||||
- like method, bound when class is declared
|
||||
- bindForeignClassFn
|
||||
- takes two pointers, ctor and finalizer
|
||||
- initializing:
|
||||
- whenever construct instance of foreign class, calls fn
|
||||
- call wrenSetSlotNewForeign() to create instance and tell it how many
|
||||
bytes to allocate
|
||||
- returns void* to opaque data
|
||||
- initialize as see fit
|
||||
- accessing:
|
||||
- data opaque, can't be too opaque, need to use it!
|
||||
- cannot access from within wren, only c
|
||||
- if have instance of foreign class in slot, wrenGetSlotForeign() returns
|
||||
void* to raw data
|
||||
- typically, used if foreign method defined on foreign class
|
||||
- remember receiver in slot zero
|
||||
- freeing:
|
||||
- memory for foreign objects managed like all mem in wren
|
||||
- may be gc'd
|
||||
- usually ok
|
||||
- but foreign obj may point to resource whose lifetime should be tied to
|
||||
life of obj
|
||||
- for ex: file handle
|
||||
- if foreign obj is collected, no longer have way to get to file handle
|
||||
- if didn't close it first, leak
|
||||
- when object is gc'd, want to close file
|
||||
- define finalizer fn
|
||||
- other thing set in bindForeignClassFn
|
||||
- if provided, gc calls this before obj is collected
|
||||
- provides raw bits of obj
|
||||
- nothing else!
|
||||
- called from right inside gc, so vm is in brittle state
|
||||
- don't mess with wren, use stack, etc.
|
||||
- just free resource
|
||||
- cannot fail
|
||||
[foreign methods]: http://localhost:8000/embedding/calling-c-from-wren.html
|
||||
|
||||
-->
|
||||
## Defining a Foreign Class
|
||||
|
||||
Until these are written, you can read the docs in [wren.h][].
|
||||
You define one like so:
|
||||
|
||||
[wren.h]: https://github.com/munificent/wren/blob/master/src/include/wren.h
|
||||
:::wren
|
||||
foreign class Point {
|
||||
// ...
|
||||
}
|
||||
|
||||
The `foreign` keyword tells Wren to loop in the host application when it
|
||||
constructs an instance of the class. The host tells Wren how many bytes of extra
|
||||
memory the foreign instance should contain and in return, Wren gives the host
|
||||
the opportunity to initialize that data.
|
||||
|
||||
To talk to the host app, Wren needs a C function it can call when it constructs
|
||||
an instance of the foreign class. This function is found through a binding
|
||||
process similar to how foreign methods are bound. When you [configure the VM][],
|
||||
you set the `bindForeignClassFn` field in WrenConfiguration to point to a C
|
||||
callback you define. Its signature must be:
|
||||
|
||||
[configure the vm]: configuring-the-vm.html
|
||||
|
||||
:::c
|
||||
WrenForeignClassMethods bindForeignClass(
|
||||
WrenVM* vm, const char* module, const char* className);
|
||||
|
||||
Wren invokes this callback once when a foreign class declaration is executed.
|
||||
Wren passes in the name of the module containing the foreign class, and the name
|
||||
of the class being declared. The host's responsibility is to return one of these
|
||||
structs:
|
||||
|
||||
:::c
|
||||
typedef struct
|
||||
{
|
||||
WrenForeignMethodFn allocate;
|
||||
WrenFinalizerFn finalize;
|
||||
} WrenForeignClassMethods;
|
||||
|
||||
It's a pair of function pointers. The first, `allocate`, is called by Wren
|
||||
whenever an instance of the foreign class is created. (We'll get to the optional
|
||||
`finalize` callback later.) The allocation callback has the same signature as a
|
||||
foreign method:
|
||||
|
||||
:::c
|
||||
void allocate(WrenVM* vm);
|
||||
|
||||
## Initializing an Instance
|
||||
|
||||
When you create an instance of a foreign class by calling one its
|
||||
[constructors][], Wren invokes the `allocate` callback you gave it when binding
|
||||
the foreign class. Your primary responsibility in that callback is to tell Wren
|
||||
how many bytes of raw memory you need. You do that by calling:
|
||||
|
||||
[constructors]: ../classes.html#constructors
|
||||
|
||||
:::c
|
||||
void* wrenSetSlotNewForeign(WrenVM* vm,
|
||||
int slot, int classSlot, size_t size);
|
||||
|
||||
Like other [slot manipulation functions][slot], it both reads from and writes to
|
||||
the slot array. It has a few parameters to make it more general purpose since it
|
||||
can also be used in other foreign methods:
|
||||
|
||||
[slot]: slots-and-handles.html
|
||||
|
||||
* The `slot` parameter is the destination slot where the new foreign object
|
||||
should be placed. When you're calling this in a foreign class's allocate
|
||||
callback, this should be 0.
|
||||
|
||||
* The `classSlot` parameter is the slot where the foreign class being
|
||||
constructed can be found. When the VM calls an allocate callback for a
|
||||
foreign class, the class itself is already in slot 0, so you'll pass 0 for
|
||||
this too.
|
||||
|
||||
* Finally, the `size` parameter is the interesting one. Here, you pass in the
|
||||
number of extra raw bytes of data you want the foreign instance to store.
|
||||
This is the memory you get to play with from C.
|
||||
|
||||
So, for example, if you wanted to create a foreign instance that wraps an extra
|
||||
eight bytes of data, you'd call:
|
||||
|
||||
:::c
|
||||
void* data = wrenSetSlotNewForeign(vm, 0, 0, 8);
|
||||
|
||||
The value returned by `wrenSetSlotNewForeign()` is the raw pointer to the
|
||||
requested bytes. You can cast that to whatever C type makes sense (as long as it
|
||||
fits within the requested number of bytes) and initialize it as you see fit.
|
||||
|
||||
Any parameters passed to the constructor are also available in subsequent slots
|
||||
in the slot array. That way you can initialize the foreign data based on values
|
||||
passed to the constructor from Wren.
|
||||
|
||||
After the allocate callback returns, the class's constructor in Wren is run and
|
||||
execution proceeds like normal. From here on out, within Wren, it appears you
|
||||
have a normal instance of a class. It just happens to have some extra bytes
|
||||
hiding inside it that can be accessed from foreign methods.
|
||||
|
||||
## Accessing Foreign Data
|
||||
|
||||
Typically, the way you make use of the data stored in an instance of a foreign
|
||||
class is through other foreign methods. Those are usually defined on the same
|
||||
foreign class, but can be defined on other classes as well. Wren doesn't care.
|
||||
|
||||
If you call a foreign method on a foreign instance, or you pass in a foreign
|
||||
instance as a parameter to the method, then it will be in the slot array. If the
|
||||
foreign instance is the receiver of the method, it's in slot zero. Parameters go
|
||||
in order after that. Given a foreign instance in a slot, you can access the raw
|
||||
bytes it stores by calling:
|
||||
|
||||
:::c
|
||||
void* wrenGetSlotForeign(WrenVM* vm, int slot);
|
||||
|
||||
You pass in the slot index containing the foreign object and it gives you back a
|
||||
pointer to the raw memory the object wraps. As usual, the C API doesn't do any
|
||||
type or bounds checking, so it's on you to make sure the object in that slot
|
||||
actually *is* an instance of a foreign class and contains as much memory as you
|
||||
access.
|
||||
|
||||
Given that void pointer, you can now freely read and modify the data it points
|
||||
to. They're your bits, Wren just holds them for you.
|
||||
|
||||
## Freeing Resources
|
||||
|
||||
If your foreign instances are just holding memory and you're OK with Wren's
|
||||
garbage collector managing the lifetime of that memory, then you're done. Wren
|
||||
will keep the bytes around as long as their is still a reference to them. When
|
||||
the instance is no longer reachable, eventually the garbage collector will do
|
||||
its thing and free the memory.
|
||||
|
||||
But, often, your foreign data refers to some resource whose lifetime needs to
|
||||
be explicitly managed. For example, if you have a foreign object that wraps an
|
||||
open file handle, you need to ensure that handle doesn't get left open when the
|
||||
GC frees the foreign instance.
|
||||
|
||||
Of course, you can (and usually should) add a method on your foreign class, like
|
||||
`close()` so the user can explicitly release the resource managed by the object.
|
||||
But if they forget to do that and the object is no longer reachable, you want to
|
||||
make sure the resource isn't leaked.
|
||||
|
||||
To that end, you can also provide a *finalizer* function when binding the
|
||||
foreign class. That's the other callback in the WrenForeignClassMethods struct.
|
||||
If you provide that callback, then Wren will invoke it when an instance of your
|
||||
foreign class is about to be freed by the garbage collector. This gives you one
|
||||
last chance to clean up the object's resources.
|
||||
|
||||
Because this is called during the middle of a garbage collection, you do not
|
||||
have unfettered access to the VM. It's not like a normal foreign method where
|
||||
you can monkey around with slots and other stuff. Doing that while the GC is
|
||||
running could leave Wren in a weird state.
|
||||
|
||||
Instead, the finalize callback's signature is only:
|
||||
|
||||
:::c
|
||||
void finalize(void* data);
|
||||
|
||||
Wren gives you the pointer to your foreign function's memory, and that's it. The
|
||||
*only* thing you should do inside a finalizer is release any external resources
|
||||
referenced by that memory.
|
||||
|
||||
## A Full Example
|
||||
|
||||
That's a lot to take in, so let's walk through a full example of a foreign class
|
||||
with a finalizer and a couple of methods. We'll do a File class that wraps the
|
||||
C standard file API.
|
||||
|
||||
In Wren, the class we want looks like this:
|
||||
|
||||
:::wren
|
||||
foreign class File {
|
||||
construct create(path) {}
|
||||
|
||||
foreign write(text)
|
||||
foreign close()
|
||||
}
|
||||
|
||||
So you can create a new file given a path. Once you have one, you can write to
|
||||
it and then explicitly close it if you want. We also need to make sure the file
|
||||
gets closed if the user forgets to and the GC cleans up the object.
|
||||
|
||||
### Setting up the VM
|
||||
|
||||
Over in the host, first we'll set up the VM:
|
||||
|
||||
:::c
|
||||
#include "wren.h"
|
||||
|
||||
int main(int argc, const char* argv[])
|
||||
{
|
||||
WrenConfiguration config;
|
||||
wrenInitConfiguration(&config);
|
||||
|
||||
config.bindForeignClassFn = bindForeignClass;
|
||||
config.bindForeignMethodFn = bindForeignMethod;
|
||||
|
||||
WrenVM* vm = wrenNewVM(&config);
|
||||
wrenInterpret(vm, "some code...");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
### Binding the foreign class
|
||||
|
||||
We give the VM two callbacks. The first is for wiring up the foreign class
|
||||
itself:
|
||||
|
||||
:::c
|
||||
WrenForeignClassMethods bindForeignClass(
|
||||
WrenVM* vm, const char* module, const char* className)
|
||||
{
|
||||
WrenForeignClassMethods methods;
|
||||
|
||||
if (strcmp(className, "File") == 0)
|
||||
{
|
||||
methods->allocate = fileAllocate;
|
||||
methods->finalize = fileFinalize;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Unknown class.
|
||||
methods->allocate = NULL;
|
||||
methods->finalize = NULL;
|
||||
}
|
||||
|
||||
return methods;
|
||||
}
|
||||
|
||||
When our binding callback is invoked for the File class, we return the allocate
|
||||
and finalize functions the VM should call. Allocation looks like:
|
||||
|
||||
:::c
|
||||
#include <stdio.h>
|
||||
#include "wren.h"
|
||||
|
||||
void fileAllocate(WrenVM* vm)
|
||||
{
|
||||
FILE** file = (FILE**)wrenSetSlotNewForeign(vm, 0, 0, sizeof(FILE*));
|
||||
const char* path = wrenGetSlotString(vm, 1);
|
||||
*file = fopen(path, "w");
|
||||
}
|
||||
|
||||
First we create the instance by calling `wrenSetSlotNewForeign()`. We tell it to
|
||||
add enough extra bytes to store a `FILE*` in it, which is C's representation of
|
||||
a file handle. We're given back a pointer to the bytes. Since the file handle is
|
||||
itself a pointer, we end up with a double indirection, hence the `FILE**`. In
|
||||
most cases, you'll just have a single `*`.
|
||||
|
||||
We also pull the file path from the slot array. Then we tell C to create a new
|
||||
file at that path. That gives us back a new file handle -- a `FILE*` -- and we
|
||||
store that back into the foreign instance using `*file`. Now we have a foreign
|
||||
object that wraps an open file handle.
|
||||
|
||||
The finalizer simply casts the foreign instance's data back to the proper type
|
||||
and closes the file:
|
||||
|
||||
:::c
|
||||
void fileFinalize(void* data)
|
||||
{
|
||||
closeFile((FILE**)file);
|
||||
}
|
||||
|
||||
It uses this little utility function:
|
||||
|
||||
:::c
|
||||
static void closeFile(FILE** file)
|
||||
{
|
||||
// Already closed.
|
||||
if (*file == NULL) return;
|
||||
|
||||
fclose(*file);
|
||||
*file = NULL;
|
||||
}
|
||||
|
||||
This closes the file (if it's not already closed) and also nulls out the pointer
|
||||
so that we don't try to use the file after it's been closed.
|
||||
|
||||
### Binding the foreign methods
|
||||
|
||||
That's the foreign *class* part. Now we have a couple of foreign *methods* to
|
||||
handle. The host tells the VM how to find them by giving Wren a pointer to this
|
||||
function:
|
||||
|
||||
:::c
|
||||
WrenForeignMethodFn bindForeignMethod(WrenVM* vm, const char* module,
|
||||
const char* className, bool isStatic, const char* signature)
|
||||
{
|
||||
if (strcmp(className, "File") == 0)
|
||||
{
|
||||
if (!isStatic && strcmp(signature, "write(_)") == 0)
|
||||
{
|
||||
return fileWrite;
|
||||
}
|
||||
|
||||
if (!isStatic && strcmp(signature, "close()") == 0)
|
||||
{
|
||||
return fileClose;
|
||||
}
|
||||
}
|
||||
|
||||
// Unknown method.
|
||||
return NULL;
|
||||
}
|
||||
|
||||
When Wren calls this, we look at the class and method name to figure out which
|
||||
method its binding, and then return a pointer to the appropriate function. The
|
||||
foreign method for writing to the file is:
|
||||
|
||||
:::c
|
||||
void fileWrite(WrenVM* vm)
|
||||
{
|
||||
FILE** file = (FILE**)wrenGetSlotForeign(vm, 0);
|
||||
|
||||
// Make sure the file is still open.
|
||||
if (*file == NULL)
|
||||
{
|
||||
wrenSetSlotString(vm, 0, "Cannot write to a closed file.");
|
||||
wrenAbortFiber(vm, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const char* text = wrenGetSlotString(vm, 1);
|
||||
fwrite(text, sizeof(char), strlen(text), *file);
|
||||
}
|
||||
|
||||
We use `wrenGetSlotForeign()` to pull the foreign data out of the slot array.
|
||||
Since this method is called on the file itself, the foreign object is in slot
|
||||
zero. We take the resulting pointer and cast it to a pointer of the proper type.
|
||||
Again, because our foreign data is *itself* a pointer, we get a pointer to a
|
||||
pointer.
|
||||
|
||||
We do a little sanity checking to make sure the user isn't writing to a file
|
||||
they already closed. If not, we call `fwrite()` to write to the file.
|
||||
|
||||
The other method is `close()` to let them explicitly close the file:
|
||||
|
||||
void fileClose(WrenVM* vm)
|
||||
{
|
||||
closeFile(file);
|
||||
}
|
||||
|
||||
It uses the same helper we defined above. And that's it, a complete foreign
|
||||
class with a finalizer and a couple of foreign methods. In Wren, you can use it
|
||||
like so:
|
||||
|
||||
:::wren
|
||||
var file = File.create("some/path.txt")
|
||||
file.write("some text")
|
||||
file.close()
|
||||
|
||||
<a class="right" href="configuring-the-vm.html">Configuring the VM →</a>
|
||||
<a href="calling-c-from-wren.html">← Calling C from Wren</a>
|
||||
|
||||
@ -29,7 +29,6 @@
|
||||
<li><a href="calling-c-from-wren.html">Calling C from Wren</a></li>
|
||||
<li><a href="storing-c-data.html">Storing C Data</a></li>
|
||||
<li><a href="configuring-the-vm.html">Configuring the VM</a></li>
|
||||
<li><a href="application-lifecycle.html">Application Lifecycle</a></li>
|
||||
</ul>
|
||||
</section>
|
||||
</nav>
|
||||
@ -49,7 +48,6 @@
|
||||
<li><a href="calling-c-from-wren.html">Calling C from Wren</a></li>
|
||||
<li><a href="storing-c-data.html">Storing C Data</a></li>
|
||||
<li><a href="configuring-the-vm.html">Configuring the VM</a></li>
|
||||
<li><a href="application-lifecycle.html">Application Lifecycle</a></li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
Reference in New Issue
Block a user