summaryrefslogtreecommitdiffhomepage
path: root/samples/12_c_extensions/README.md
diff options
context:
space:
mode:
authorAmir Rajan <[email protected]>2021-01-18 12:08:34 -0600
committerAmir Rajan <[email protected]>2021-01-18 12:08:34 -0600
commita4b9c048a1d751f5226833bb0c527ba1a8ac5d09 (patch)
tree3f2535e7a6272e796d50e7f07c906d4c9eb1b14a /samples/12_c_extensions/README.md
parenta24a71805b1924ae7f80776c736f94575c171d2c (diff)
downloaddragonruby-game-toolkit-contrib-a4b9c048a1d751f5226833bb0c527ba1a8ac5d09.tar.gz
dragonruby-game-toolkit-contrib-a4b9c048a1d751f5226833bb0c527ba1a8ac5d09.zip
Synced with 2.3.
Diffstat (limited to 'samples/12_c_extensions/README.md')
-rw-r--r--samples/12_c_extensions/README.md414
1 files changed, 414 insertions, 0 deletions
diff --git a/samples/12_c_extensions/README.md b/samples/12_c_extensions/README.md
new file mode 100644
index 0000000..8950e53
--- /dev/null
+++ b/samples/12_c_extensions/README.md
@@ -0,0 +1,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.
+