summaryrefslogtreecommitdiffhomepage
path: root/samples/12_c_extensions/01_basics/README.md
blob: 6993173ee3cbd274bc36cbb6b472356e9ee8ab70 (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
# C Extensions introduction

This sample app serves as an introduction on how to write C extensions for the
DragonRuby Game Toolkit. You'll need a Pro License
which can be purchased at http://dragonruby.org. The sample app is provided in
the Standard license for those that are curious as to what implementing C Extensions
looks like.

## Requirements

In order to use C extensions you need a C compiler. We strongly recommend you
using [Clang](https://clang.llvm.org).

### Windows

To get Clang for Windows you can use the [official build](https://github.com/llvm/llvm-project/releases/download/llvmorg-10.0.0/LLVM-10.0.0-win64.exe).

During installation, make sure to choose "Add LLVM to the system PATH for all
users", as on the screenshot ![LLVM PATH](llvm-path.png)

On Windows, you also need to install MinGW-w64 toolchain from [here](http://mingw-w64.org/doku.php/download/mingw-builds)
Set the installation directory to "C:\mingw-w64": ![MinGW installation
path](ming2-path.png) and on the next step choose the right version and the
other setting as on the screenshot: ![MinGW config](mingw-version.png).


### Linux

On Debian-based systems you can install Clang via `apt`. It is recommended you
install `clang-10`, but any other 'reasonably' fresh version should work just
fine:

```
> apt-get install clang-10
> clang-10 --version
Apple clang version 10.0.0-4ubuntu1-18.04.2
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
```

Raspberry Pi instructions are mostly the same as x86-64 Linux. Out of the box,
Clang can cross-compile for the Pi on any platform, but you'll need to set up
a proper sysroot, which can be complicated, and beyond the scope of this
document. The bold--and patient--can always compile on the Pi device directly,
too.


### macOS

To get Clang on macOS it is recommended to install [Xcode](https://developer.apple.com/xcode/).
Once you've done that you should be able to use Clang from a terminal:

```
> clang --version
Apple clang version 11.0.3 (clang-1103.0.32.29)
Target: x86_64-apple-darwin19.5.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
```

### Android

For Android, you can [install the Android NDK](https://developer.android.com/ndk/)
on Windows, Linux, or macOS. This lets you compile C code for the platform. You
won't be using Java here.

DragonRuby ships with support for Android on arm32, arm64, x86, and x86-64. This might
be overkill, but it allows for maximum hardware compatibility. You should plan to
compile for all four architectures too, but of course this is optional.


## Hello World

Let's craft the simplest possible C extension: a simple function that
calculates square of an integer. Put the following code into a file named
`ext.c` under `mygame` directory:

```
int square(int x) {
  return x * x;
}
```

You need some glue code to connect this function to the GameToolKit. Good news
are that you don't need to craft this code yourself: it can be generated for
you by `dragonruby-bind` (or `dragonruby-bind.exe` if you are on Windows).

Run the following command from Linux/macOS terminal:

```
> ./dragonruby-bind --output=mygame/ext-bind.c mygame/ext.c
```

Or from Windows Powershell:

```
> .\dragobruby-bind.exe --output=mygame\ext-bind.c mygame\ext.c
```

With the `ext-bind.c` in place you can compile your C extension into a dynamic
or shared library. Here is how you do it:

On Windows:

```
clang -shared \
  --sysroot=C:\mingw-w64\mingw64^
  --target=x86_64-w64-mingw32 -fuse-ld=lld^
  -isystem include -I . -fPIC^
  -o mygame\native\windows-amd64\ext.dll mygame\ext-bind.c
```

On Linux:

```
> clang -shared \
  -isystem include -I . -fPIC \
  -o mygame/native/linux-amd64/ext.so mygame/ext-bind.c
```

On Raspberry Pi, on the device:

```
> clang -shared \
  -isystem include -I . -fPIC \
  -o mygame/native/linux-raspberrypi/ext.so mygame/ext-bind.c
```

On Raspberry Pi, with a cross-compiler:

```
> clang -shared --sysroot=/where/i/installed/a/sysroot \
  --target=arm-linux-gnueabihf -fuse-ld=lld \
  -isystem include -I . -fPIC \
  -o mygame/native/linux-raspberrypi/ext.so mygame/ext-bind.c
```

On macOS:

```
> clang -shared \
  -isystem include -I . -fPIC \
  -o mygame/native/macos/ext.dylib mygame/ext-bind.c
```

On Android:

(This is what it looks like on Linux, change the appropriate parts for Windows, etc)

```
> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \
  --target=armv7-none-linux-androideabi26 \
  --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \
  --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
  -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 -march=armv7-a \
  -mthumb -fno-limit-debug-info -fPIC -shared \
  -o mygame/native/android-arm32/ext.so mygame/ext-bind.c

> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \
  --target=aarch64-none-linux-android26 \
  --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \
  --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
  -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 \
  -fno-limit-debug-info -fPIC -shared \
  -o mygame/native/android-arm64/ext.so mygame/ext-bind.c

> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \
  --target=i686-none-linux-android26 \
  --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \
  --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
  -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 \
  -fno-limit-debug-info -fPIC -shared \
  -o mygame/native/android-x86/ext.so mygame/ext-bind.c

> android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/bin/clang \
  --target=x86_64-none-linux-android26 \
  --gcc-toolchain=android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64 \
  --sysroot=/home/icculus/projects/dragonruby/build-tools/host/Linux-amd64/android-ndk-r21d/toolchains/llvm/prebuilt/linux-x86_64/sysroot \
  -DANDROID -fdata-sections -ffunction-sections -funwind-tables -fstack-protector-strong -no-canonical-prefixes -D_FORTIFY_SOURCE=2 \
  -fno-limit-debug-info -fPIC -shared \
  -o mygame/native/android-amd64/ext.so mygame/ext-bind.c
```



Now, include the following snippet into the very beginning of `mygame/app/main.rb`:

```
$gtk.ffi_misc.gtk_dlopen("ext")
include FFI::CExt
puts square(11)
```

Now, simply run `dragonruby` (or `dragonruby.exe`) and you should see `121` on
the console.

Let's do a breakdown of each line!

1. `$gtk.ffi_misc.gtk_dlopen("ext")` - DragonRuby exposes a special function
    called `gtk_dlopen`, you can use it to load a dynamic that holds
    the C extension code. It looks for the shared library in
    "mygame/native/$PLATFORM/ext.$PLATFORM_DLL_EXTENSION"
2. `include FFI::CExt` - by default, DragonRuby puts all the code available in
   the C extension under `FFI::CExt` module. This line serves as a shortcut so
   that you don't need to write `FFI::CExt::square` any time you want to call
   a function.
3. `puts square(11)` - this line simply prints the value returned from C code.

Now, you can call the `square` function at any place in the code. Let's see
what's the square value of every pixel on the screen. Here is the full program:

```
$gtk.ffi_misc.gtk_dlopen("ext")
include FFI::CExt

def tick args
  args.outputs.labels  << [640, 500, "mouse.x = #{args.mouse.x.to_i}", 5, 1]
  args.outputs.labels  << [640, 460, "square(mouse.x) = #{square(args.mouse.x.to_i)}", 5, 1]
  args.outputs.labels  << [640, 420, "mouse.y = #{args.mouse.y.to_i}", 5, 1]
  args.outputs.labels  << [640, 380, "square(mouse.y) = #{square(args.mouse.y.to_i)}", 5, 1]
end
```

When you run the game now, you will see something like this:

![C basics Demo](c-basics-demo.png)