summaryrefslogtreecommitdiffhomepage
path: root/samples/12_c_extensions/README.md
blob: 8950e5358e3dc4a8dbdf1a9b9f8cd9da3b51be3c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# dragonruby-bind doc

This document describes how to use `dragonruby-bind` and also covers some
implementation details that will help to build the right mental model

### Hello World

Create a simple file `bridge.c` with the following content:

```c
double square(double d) {
  return d * d;
}
```

Now, generate bindings:

```bash
dragonruby-bind bridge.c --output=bindings.c
```

The output file `bindings.c` will contain something like the following:

```c
#include <mruby.h>
#include <string.h>
#include <assert.h>
#include <mruby/string.h>
#include <mruby/data.h>
#include <dragonruby.h>
#include "bridge.c"

// MRuby `typedef`s mrb_int in the mruby/value.h
// Then `#define`s mrb_int in mruby.h
// We need to undo the macro and avoid it's usage
// FIXME: I'm surely doing something wrong
#ifdef mrb_int
#undef mrb_int
#endif

void *(*drb_symbol_lookup)(const char *sym) = NULL;

static void (*drb_free_foreign_object_f)(mrb_state *, void *);
static struct RClass *(*mrb_module_get_f)(mrb_state *, const char *);
static mrb_int (*mrb_get_args_f)(mrb_state *, mrb_args_format, ...);
static struct RClass *(*mrb_module_get_under_f)(mrb_state *, struct RClass *, const char *);
static struct RClass *(*mrb_class_get_under_f)(mrb_state *, struct RClass *, const char *);
static struct RClass *(*mrb_define_module_under_f)(mrb_state *, struct RClass *, const char *);
static void (*mrb_define_module_function_f)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec);
static struct RClass *(*mrb_define_class_under_f)(mrb_state *, struct RClass *, const char *, struct RClass *);
static void (*mrb_define_method_f)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec);
static void (*mrb_define_class_method_f)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec);
static struct RData *(*mrb_data_object_alloc_f)(mrb_state *, struct RClass *, void *, const mrb_data_type *);
static mrb_value (*mrb_str_new_cstr_f)(mrb_state *, const char *);
static void (*mrb_raise_f)(mrb_state *, struct RClass *, const char *);
static struct RClass *(*mrb_exc_get_f)(mrb_state *, const char *);
static void drb_free_foreign_object_indirect(mrb_state *state, void *pointer) {
    drb_free_foreign_object_f(state, pointer);
}
static double drb_ffi__ZTSd_FromRuby(mrb_state *state, mrb_value self) {
    return mrb_float(self);
}
static mrb_value drb_ffi__ZTSd_ToRuby(mrb_state *state, double value) {
    return mrb_float_value(state, value);
}
static mrb_value drb_ffi_square_Binding(mrb_state *state, mrb_value value) {
    mrb_value *args = 0;
    mrb_int argc = 0;
    mrb_get_args_f(state, "*", &args, &argc);
    double d_0 = drb_ffi__ZTSd_FromRuby(state, args[0]);
    double ret_val = square(d_0);
    return drb_ffi__ZTSd_ToRuby(state, ret_val);
}
static int drb_ffi_init_indirect_functions(void *(*lookup)(const char *));
DRB_FFI_EXPORT
void drb_register_c_extensions(void *(*lookup)(const char *), mrb_state *state, struct RClass *FFI) {
    if (drb_ffi_init_indirect_functions(lookup))
        return;
    struct RClass *module = mrb_define_module_under_f(state, FFI, "CExt");
    struct RClass *object_class = state->object_class;
    mrb_define_module_function_f(state, module, "square", drb_ffi_square_Binding, MRB_ARGS_REQ(1));
}
static int drb_ffi_init_indirect_functions(void *(*lookup)(const char *fnname)) {
  drb_symbol_lookup = lookup;
  if (!(drb_free_foreign_object_f = (void (*)(mrb_state *, void *)) lookup("drb_free_foreign_object"))) return -1;
  if (!(mrb_class_get_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_class_get_under"))) return -1;
  if (!(mrb_data_object_alloc_f = (struct RData *(*)(mrb_state *, struct RClass *, void *, const mrb_data_type *)) lookup("mrb_data_object_alloc"))) return -1;
  if (!(mrb_define_class_method_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_class_method"))) return -1;
  if (!(mrb_define_class_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *, struct RClass *)) lookup("mrb_define_class_under"))) return -1;
  if (!(mrb_define_method_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_method"))) return -1;
  if (!(mrb_define_module_function_f = (void (*)(mrb_state *, struct RClass *, const char *, mrb_func_t, mrb_aspec)) lookup("mrb_define_module_function"))) return -1;
  if (!(mrb_define_module_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_define_module_under"))) return -1;
  if (!(mrb_exc_get_f = (struct RClass *(*)(mrb_state *, const char *)) lookup("mrb_exc_get"))) return -1;
  if (!(mrb_get_args_f = (mrb_int (*)(mrb_state *, mrb_args_format, ...)) lookup("mrb_get_args"))) return -1;
  if (!(mrb_module_get_f = (struct RClass *(*)(mrb_state *, const char *)) lookup("mrb_module_get"))) return -1;
  if (!(mrb_module_get_under_f = (struct RClass *(*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_module_get_under"))) return -1;
  if (!(mrb_raise_f = (void (*)(mrb_state *, struct RClass *, const char *)) lookup("mrb_raise"))) return -1;
  if (!(mrb_str_new_cstr_f = (mrb_value (*)(mrb_state *, const char *)) lookup("mrb_str_new_cstr"))) return -1;
  return 0;
}
```

Compile this as a shared library:

```c
# DRB_ROOT is the path to the gtk source directory
clang -shared \
  -isystem $DRB_ROOT/mruby/include/ \
  -isystem $DRB_ROOT/ \
  -o native/macos/bindings.dylib \
  bindings.c
```

Now, somewhere within `main.rb` do the following:

```rb
# Teach DRGTK about the bindings
$gtk.ffi_misc.dlopen("bindings")
# Use the `square` function seamlessly
puts FFI::CExt::square(42)
# yeilds: 1764.0
```

This is the bare minimum that is needed to start using the C extensions.

Now, more complex parts.

### DRB FFI

It is a good idea to include `dragonruby.h` into the C bridging file. It comes with
a number of helpful macros. As an example, you can specify for which functions
to generate bindings or change the name under which the function is available from
DRGTK:

```c
#include <dragonruby.h>

/// Binding for this function won't be generated
void something_useless() {
  /// ....
}

/// This one is accessible from Ruby as `square`
DRB_FFI
double square(double d) {
  return d * d;
}

/// This one is accessible from Ruby as `sqr_int`
DRB_FFI_NAME("sqr_int")
int ffi_square_int(int d) {
  return d * d;
}
```

_Note: It is always a good idea to mark your functions with `DRB_FFI` or
`DRB_FFI_NAME` explicitly. Support for unannotated functions is meant for
third-party libraries._

### Structs

It is possible to use C structs from DRGTK. Here is a C example and its
corresponding usage from Ruby.

```c
#include <dragonruby.h>

typedef struct Point {
  int x;
  int y;
} Point;

DRB_FFI
void printPoint(Point p) {
  printf("Point(%d, %d)\n", p.x, p.y);
}

DRB_FFI
Point fourtyPoint() {
  Point p;
  p.x = 42;
  p.y = 42;
  return p;
}
```

Here is how to use it in the Ruby code:

```ruby
$gtk.ffi_misc.gtk_dlopen("bindings")
include FFI::CExt
p = Point.new
p.x = 15
p.y = 22
printPoint(p)

p2 = fourtyPoint()
puts "Point(#{p2.x}, #{p2.y}) (from Ruby)"
p2.x = 152
puts "Point(#{p2.x}, #{p2.y}) (from Ruby)"
```

The output will be:

```
Point(15, 22)
Point(42, 42) (from Ruby)
Point(152, 42) (from Ruby)
```

### Pointers

Here is how to use pointers:

```c
#include <dragonruby.h>

DRB_FFI
int *createInts(int size) {
  int *ints = calloc(size, sizeof(int));
  return ints;
}

DRB_FFI
void printInts(int *ints, int size) {
  printf("int array: ");
  for (int i = 0; i < size; i++) {
    printf("%d ", ints[i]);
  }
  printf("\n");
}

DRB_FFI
void free_ints(int *ints) {
  free(ints);
}
```

The usage:

```rb
$gtk.ffi_misc.gtk_dlopen("bindings")
include FFI::CExt
ints = createInts(10)
10.times do |i|
  ints[i] = i
end
printInts(ints, 10)
freeInts(ints)

p = IntPointer.new
p[0] = 15
printInts(p, 1)
puts "print int from ruby: #{p[0]}"
```

The output:

```
print ints from C: 0 1 2 3 4 5 6 7 8 9
print ints from C: 15
print int from ruby: 15
```

Important part here is the memory ownership.
In the case of `createInts` the points is allocated in the C land, and therefore
it should be deallocated in the C land. It is a responsibility of developer to
take care of this memory.

In the case of `IntPointer.new` the pointer is allocated in the Ruby land, and
therefore it will be deallocated by DRGTK, without any need to worry about the
ownership.

### C Strings

Technically, the C string is a just a pointer, i.e.: `char *`. But we provide
some convenient sugar. Here is an example:

```c
#include <dragonruby.h>

DRB_FFI
char *allocateString() {
  char *str = calloc(6, sizeof(char));
  str[0] = 'h';
  str[1] = 'e';
  str[2] = 'l';
  str[3] = 'l';
  str[4] = 'o';
  str[5] = '\n';
  return str;
}

DRB_FFI
void freeString(char *s) {
  free(s);
}

DRB_FFI
void printString(char *s) {
  printf("hello from C: %s\n", s);
}

DRB_FFI
char *getStaticString() {
  return "Some static string";
}
```

Usage:

```rb
$gtk.ffi_misc.gtk_dlopen("bindings")
include FFI::CExt
s1 = allocateString()
printString(s1)
freeString(s1)

printString("or Ruby?")

s2 = getStaticString()
printString(s2)
puts "print C string from Ruby #{s2.str}"
```

Output:

```
hello from C: hello
hello from C: or Ruby?
hello from C: Some static string
print C string from Ruby: Some static string
```

General rules are the same as with any other pointers, but there are few more
additional notes:

 - `CharPointer`s have `str` method that returns a normal Ruby string
 - Ruby strings automatically converted to `char *`, as in the `printString("or Ruby?")`

### CExt

In order to avoid clashes and name collisions all the bridging functions are put
under a separate module (or namespace) under `FFI`. By default, the name `CExt`
is used, but it can be changed to anything else via the `--ffi-module` module, e.g.:

```bash
dragonruby-bind --ffi-module=CoolStuff bridge.c
```

Then one can use `include FFI::CoolStuff` instead.

### Pitfalls

There is no so-called marshalling when it comes to structs. When you read or
write to a struct field you are writing to the underlying C struct, which brings
some unexpected results. The following structs can be easily used from Ruby:

```c
typedef struct Point {
  int x;
  int y;
} Point;

typedef struct Size {
  int width;
  int height;
} Size;

typedef struct Rectangle {
  Point origin;
  Size size;
} Rectangle;
```

```rb
o = Point.new
o.x = 15
o.y = 25

s = Size.new
s.width = 150
s.height = 250

r = Rectangle.new
r.origin = o
r.size = s


puts "#{r.origin.x}, #{r.origin.y}"  #1

r.origin.x = 42                      #2

puts "#{r.origin.x}, #{r.origin.y}"  #3

p = r.origin
p.x = 42
r.origin = p
puts "#{r.origin.x}, #{r.origin.y}"  #4
```

In this example `15, 25` will be printed at line `#1`, after the assignment at `#2`
the same string will be printed `15, 25` at line `#3`.
That's because each `.` in Ruby returns a new object, in this case `p.origin`
returns a copy of the original `Point`. The right way to handle this case is right
before `#4`.

### Rough edges

Currently, there are no type checks and no checks on the number of arguments.
If you call a C function that expects an integer with a double - it may give some
garbage. If you call a C function with more or fewer arguments, then it may give
some garbage, or crash. This will come in later.