mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-12 14:48:40 +01:00
422 lines
17 KiB
HTML
422 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-type" content="text/html;charset=UTF-8" />
|
|
<title>Storing C Data – Wren</title>
|
|
<script type="application/javascript" src="../prism.js" data-manual></script>
|
|
<script type="application/javascript" src="../wren.js"></script>
|
|
<link rel="stylesheet" type="text/css" href="../prism.css" />
|
|
<link rel="stylesheet" type="text/css" href="../style.css" />
|
|
<link href='//fonts.googleapis.com/css?family=Source+Sans+Pro:400,700,400italic,700italic|Source+Code+Pro:400|Lato:400|Sanchez:400italic,400' rel='stylesheet' type='text/css'>
|
|
<!-- Tell mobile browsers we're optimized for them and they don't need to crop
|
|
the viewport. -->
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>
|
|
</head>
|
|
<body id="top" class="embedding">
|
|
<header>
|
|
<div class="page">
|
|
<div class="main-column">
|
|
<h1><a href="../">wren</a></h1>
|
|
<h2>a classy little scripting language</h2>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
<div class="page">
|
|
<nav class="big">
|
|
<a href="../"><img src="../wren.svg" class="logo"></a>
|
|
<ul>
|
|
<li><a href="../">Back to Wren</a></li>
|
|
</ul>
|
|
<section>
|
|
<h2>embedding</h2>
|
|
<ul>
|
|
<li><a href="./">Introduction</a></li>
|
|
<li><a href="slots-and-handles.html">Slots and Handles</a></li>
|
|
<li><a href="calling-wren-from-c.html">Calling Wren from C</a></li>
|
|
<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>
|
|
</ul>
|
|
</section>
|
|
</nav>
|
|
<nav class="small">
|
|
<table>
|
|
<tr>
|
|
<td><h2>embedding</h2></td>
|
|
<td><h2>?</h2></td>
|
|
<td><h2>?</h2></td>
|
|
</tr>
|
|
<tr>
|
|
<td>
|
|
<ul>
|
|
<li><a href="./">Introduction</a></li>
|
|
<li><a href="slots-and-handles.html">Slots and Handles</a></li>
|
|
<li><a href="calling-wren-from-c.html">Calling Wren from C</a></li>
|
|
<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>
|
|
</ul>
|
|
</td>
|
|
<td>
|
|
<ul>
|
|
</ul>
|
|
</td>
|
|
<td>
|
|
<ul>
|
|
</ul>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</nav>
|
|
<main>
|
|
<h1>Storing C Data</h1>
|
|
<p>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. You may want a Wren object
|
|
that represents a native resource like a file handle or database connection.</p>
|
|
<p>For those cases, you can define a <strong>foreign class</strong>, 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 <a href="calling-c-from-wren.html">foreign methods</a>
|
|
written in C. It produces real Wren objects that you can pass around, do <code>is</code>
|
|
checks on, etc. But it also wraps a blob of raw memory that is opaque to Wren
|
|
but accessible from C.</p>
|
|
<h2>Defining a Foreign Class <a href="#defining-a-foreign-class" name="defining-a-foreign-class" class="header-anchor">#</a></h2>
|
|
<p>You define one like so:</p>
|
|
<pre class="snippet">
|
|
foreign class Point {
|
|
// ...
|
|
}
|
|
</pre>
|
|
|
|
<p>The <code>foreign</code> keyword tells Wren to loop in the host application when it
|
|
constructs instances 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.</p>
|
|
<p>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 <a href="calling-c-from-wren.html#binding-foreign-methods">how foreign methods are bound</a>. When you <a href="configuring-the-vm.html">configure
|
|
the VM</a>, you set the <code>bindForeignClassFn</code> field in WrenConfiguration to point
|
|
to a C callback you define. Its signature must be:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
WrenForeignClassMethods bindForeignClass(
|
|
WrenVM* vm, const char* module, const char* className);
|
|
</pre>
|
|
|
|
<p>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:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
typedef struct
|
|
{
|
|
WrenForeignMethodFn allocate;
|
|
WrenFinalizerFn finalize;
|
|
} WrenForeignClassMethods;
|
|
</pre>
|
|
|
|
<p>It’s a pair of function pointers. The first, <code>allocate</code>, is called by Wren
|
|
whenever an instance of the foreign class is created. (We’ll get to the optional
|
|
<code>finalize</code> callback later.) The allocation callback has the same signature as a
|
|
foreign method:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
void allocate(WrenVM* vm);
|
|
</pre>
|
|
|
|
<h2>Initializing an Instance <a href="#initializing-an-instance" name="initializing-an-instance" class="header-anchor">#</a></h2>
|
|
<p>When you create an instance of a foreign class by calling one its
|
|
<a href="../classes.html#constructors">constructors</a>, Wren invokes the <code>allocate</code> 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:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
void* wrenSetSlotNewForeign(WrenVM* vm,
|
|
int slot, int classSlot, size_t size);
|
|
</pre>
|
|
|
|
<p>Like other <a href="slots-and-handles.html">slot manipulation functions</a>, 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:</p>
|
|
<ul>
|
|
<li>
|
|
<p>The <code>slot</code> 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.</p>
|
|
</li>
|
|
<li>
|
|
<p>The <code>classSlot</code> 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.</p>
|
|
</li>
|
|
<li>
|
|
<p>Finally, the <code>size</code> 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.</p>
|
|
</li>
|
|
</ul>
|
|
<p>So, for example, if you wanted to create a foreign instance that contains eight
|
|
bytes of C data, you’d call:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
void* data = wrenSetSlotNewForeign(vm, 0, 0, 8);
|
|
</pre>
|
|
|
|
<p>The value returned by <code>wrenSetSlotNewForeign()</code> 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.</p>
|
|
<p>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.</p>
|
|
<p>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.</p>
|
|
<h2>Accessing Foreign Data <a href="#accessing-foreign-data" name="accessing-foreign-data" class="header-anchor">#</a></h2>
|
|
<p>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.</p>
|
|
<p>Once you have a foreign instance in a slot, you can access the raw bytes it
|
|
stores by calling:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
void* wrenGetSlotForeign(WrenVM* vm, int slot);
|
|
</pre>
|
|
|
|
<p>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 <em>is</em> an instance of a foreign class and contains as much memory as you
|
|
access.</p>
|
|
<p>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.</p>
|
|
<h2>Freeing Resources <a href="#freeing-resources" name="freeing-resources" class="header-anchor">#</a></h2>
|
|
<p>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 there 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.</p>
|
|
<p>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.</p>
|
|
<p>Of course, you can (and usually should) add a method on your foreign class, like
|
|
<code>close()</code> 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.</p>
|
|
<p>To that end, you can also provide a <em>finalizer</em> 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.</p>
|
|
<p>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.</p>
|
|
<p>Instead, the finalize callback’s signature is only:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
void finalize(void* data);
|
|
</pre>
|
|
|
|
<p>Wren gives you the pointer to your foreign function’s memory, and that’s it. The
|
|
<em>only</em> thing you should do inside a finalizer is release any external resources
|
|
referenced by that memory.</p>
|
|
<h2>A Full Example <a href="#a-full-example" name="a-full-example" class="header-anchor">#</a></h2>
|
|
<p>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.</p>
|
|
<p>In Wren, the class we want looks like this:</p>
|
|
<pre class="snippet">
|
|
foreign class File {
|
|
construct create(path) {}
|
|
|
|
foreign write(text)
|
|
foreign close()
|
|
}
|
|
</pre>
|
|
|
|
<p>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.</p>
|
|
<h3>Setting up the VM <a href="#setting-up-the-vm" name="setting-up-the-vm" class="header-anchor">#</a></h3>
|
|
<p>Over in the host, first we’ll set up the VM:</p>
|
|
<pre class="snippet" data-lang="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, "my_module", "some code...");
|
|
|
|
return 0;
|
|
}
|
|
</pre>
|
|
|
|
<h3>Binding the foreign class <a href="#binding-the-foreign-class" name="binding-the-foreign-class" class="header-anchor">#</a></h3>
|
|
<p>We give the VM two callbacks. The first is for wiring up the foreign class
|
|
itself:</p>
|
|
<pre class="snippet" data-lang="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;
|
|
}
|
|
</pre>
|
|
|
|
<p>When our binding callback is invoked for the File class, we return the allocate
|
|
and finalize functions the VM should call. Allocation looks like:</p>
|
|
<pre class="snippet" data-lang="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");
|
|
}
|
|
</pre>
|
|
|
|
<p>First we create the instance by calling <code>wrenSetSlotNewForeign()</code>. We tell it to
|
|
add enough extra bytes to store a <code>FILE*</code> 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 <code>FILE**</code>. In
|
|
most cases, you’ll just have a single <code>*</code>.</p>
|
|
<p>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 <code>FILE*</code> – and we
|
|
store that back into the foreign instance using <code>*file</code>. Now we have a foreign
|
|
object that wraps an open file handle.</p>
|
|
<p>The finalizer simply casts the foreign instance’s data back to the proper type
|
|
and closes the file:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
void fileFinalize(void* data)
|
|
{
|
|
closeFile((FILE**) data);
|
|
}
|
|
</pre>
|
|
|
|
<p>It uses this little utility function:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
static void closeFile(FILE** file)
|
|
{
|
|
// Already closed.
|
|
if (*file == NULL) return;
|
|
|
|
fclose(*file);
|
|
*file = NULL;
|
|
}
|
|
</pre>
|
|
|
|
<p>This closes the file (if it’s not already closed) and also nulls out the file
|
|
handle so that we don’t try to use the file after it’s been closed.</p>
|
|
<h3>Binding the foreign methods <a href="#binding-the-foreign-methods" name="binding-the-foreign-methods" class="header-anchor">#</a></h3>
|
|
<p>That’s the foreign <em>class</em> part. Now we have a couple of foreign <em>methods</em> to
|
|
handle. The host tells the VM how to find them by giving Wren a pointer to this
|
|
function:</p>
|
|
<pre class="snippet" data-lang="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;
|
|
}
|
|
</pre>
|
|
|
|
<p>When Wren calls this, we look at the class and method name to figure out which
|
|
method it’s binding, and then return a pointer to the appropriate function. The
|
|
foreign method for writing to the file is:</p>
|
|
<pre class="snippet" data-lang="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);
|
|
}
|
|
</pre>
|
|
|
|
<p>We use <code>wrenGetSlotForeign()</code> 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 <em>itself</em> a pointer, we get a pointer to a
|
|
pointer.</p>
|
|
<p>We do a little sanity checking to make sure the user isn’t writing to a file
|
|
they already closed. If not, we call <code>fwrite()</code> to write to the file.</p>
|
|
<p>The other method is <code>close()</code> to let them explicitly close the file:</p>
|
|
<pre class="snippet" data-lang="c">
|
|
void fileClose(WrenVM* vm)
|
|
{
|
|
FILE** file = (FILE**)wrenGetSlotForeign(vm, 0);
|
|
closeFile(file);
|
|
}
|
|
</pre>
|
|
|
|
<p>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:</p>
|
|
<pre class="snippet">
|
|
var file = File.create("some/path.txt")
|
|
file.write("some text")
|
|
file.close()
|
|
</pre>
|
|
|
|
<p>Pretty neat, right? The resulting class looks and feels like a normal Wren
|
|
class, but it has the functionality and much of the performance of native C
|
|
code.</p>
|
|
<p><a class="right" href="configuring-the-vm.html">Configuring the VM →</a>
|
|
<a href="calling-c-from-wren.html">← Calling C from Wren</a></p>
|
|
</main>
|
|
</div>
|
|
<footer>
|
|
<div class="page">
|
|
<div class="main-column">
|
|
<p>Wren lives
|
|
<a href="https://github.com/wren-lang/wren">on GitHub</a>
|
|
— Made with ❤ by
|
|
<a href="http://journal.stuffwithstuff.com/">Bob Nystrom</a> and
|
|
<a href="https://github.com/wren-lang/wren/blob/main/AUTHORS">friends</a>.
|
|
</p>
|
|
<div class="main-column">
|
|
</div>
|
|
</footer>
|
|
</body>
|
|
</html>
|