dwyl / dwyl/learn-zig

Integration of (easy) C code, and Zig libraries, and Zig using C libraries

Open
#22 2 comments 1 reaction 0 assignees View on GitHub
Dominant language
No language data
Stars
4
Forks
1
PR merge metrics
No merged PRs in 30d

Description

A few (easy) use cases below.
It demonstrates most (all?) of the possibilities that `Zigler` gives you to use FFI.

>[!NOTE]
>For larger `C` code base with opaque structs and self managed memory, you may encounter difficulties with dependency loops and unaccessible opaque structs (cf the `C`-HTML parser `lebor`) .

Nevertheless, some worked example as a reference that demonstrates how well thought `Zigler` is. ⭐️⭐️
⭐️
## Embed simple C code

I have a `C` file with 2 functions, _say_hello_ and _add_

- source file __/native/testing_simple_c/src/test.c__

```c
#include "test.h"

void say_hello(void)
{
printf("Hello from test!\n");
}

int add(int a, int b)
{
return a + b;
}
```

- headers file __/native/testing_simple_c/src/include/test.h__

```c
#ifndef TEST
#define TEST

#include

void say_hello(void);
int add(int a, int b);

#endif
```

### Read with `Zig` the `C` source

I have 2 public `Zig` functions that use the `C` code.

The function _hello_zig_ just calls the `C` _say_hello_ function.

The function _add_zig_ calls the `C` _add_ and returns a struct type marshalled as `{:ok, value}` in `Elxiir` for the sake of complicating things :)).

```zig
// native/testing_simple_c/test_source.zig
const std = @import("std");
const beam = @import("beam");

const t = @cImport(@cInclude("test.h"));

pub fn hello_zig() void {
return t.say_hello();
}

pub fn add_zig(a: i32, b: i32) beam.term {
return beam.make(.{ .ok, t.add(a, b) }, .{});
}
```

In `Elixir`, you declare the `Zig` source file above via `zig_code_path`, or directly write the ` Zig` source in the `Elixir` module using `~Z`.

In `use `Zigler`, declare the path to the headers and to the source code:

- use `c: [ include_dirs: ["relative_path_to_headers"}, src: ["relative_path_to_src"]]`

> my Mix.Project.app is `:ex_test`

```elixir
defmodule Source do
use Zig,
otp_app: :ex_test,
nifs: [:hello_zig, :add_zig],
zig_code_path: "../native/testing_simple_c/test_source.zig",
c: [
include_dirs: ["../native/testing_simple_c/include"],
src: ["../native/testing_simple_c/src/*"]
]

def test_hello do
hello_zig()
end

def test_add(x, y) when is_integer(x) and is_integer(y) do
{:ok, res} = add_zig(x, y)
IO.puts(res)
end
end
```

### Use `Zig`-compiled `C`-code

Use the `Zig` toolchain to produce a static file `.a` or a dynamic shared object (`.so` or `.dylib`) located in the "priv/lib" folder (a priori a `Zigler` convention) with the command:

```
zig cc -target native -shared -o priv/lib/testing_simple_c/libtest.dylib priv/lib/testing_simple_c/test.o
```

The `Zig` source code uses this shared library. You need to import the headers, but a priori using the full path(!?)

```zig
const std = @import("std");
const beam = @import("beam");

const c = @cImport(@cInclude("/Users/nevendrean/code/elixir/ex_mark/native/testing_simple_c/include/test.h"));

pub fn hello_lib_zig() void {
return c.say_hello();
}

pub fn add_lib_zig(a: i32, b: i32) beam.term {
return beam.make(.{ .ok, c.add(a, b) }, .{});
}
```

You declare in `use Zig` the shared object with: `c: [link_lib: {:priv, "relative_path_to_rpiv_to_dylib_or_so"]`.

```elixir
defmodule Compiled do
use Zig,
otp_app: :ex_test,
zig_code_path: "../native/testing_simple_c/test_lib.zig",
c: [
link_lib: {:priv, "lib/testing_simple_c/libtest.dylib"}
],
nifs: [:hello_lib_zig, :add_lib_zig]

def test_hello do
hello_lib_zig()
end

def test_add(a, b) when is_integer(a) and is_integer(b) do
{:ok, res} = add_lib_zig(a, b)
IO.puts(res)
end
end
```

## Embed a downloaded `Zig` library

As an example, I was looking for an HTML parser written in `Zig`. I tested the [rem library](https://github.com/chadwain/rem).

- produce a static file `.a` or shared object with the `Zig` toolchain of your library.
- in `use Zig`, use `packages: [package_name, {"path_to_package_main_file", [:beam]}]`

The `Elxiir` module is:

```elixir
defmodule Rem do
use Zig,
otp_app: :ex_html,
zig_code_path: "../native/rem/test.zig",
nifs: [test_fn: [:dirty_cpu]],
packages: [rem: {"priv/lib/rem-master/rem.zig", [:beam]}]

def test_main do
test_fn()
end
end
```

Now in the `Zig` source code, you can use the package (`rem` here)
```zig
const std = @import("std");
const beam = @import("beam");
const rem = @import("rem");
pub fn test_fn() !void {
const allocator = beam.allocator;
[...]
}
```

## Embed a "system" library

Example: `Ìmagemagick`.

We probably all have this package.
Let's use its `C` API:

[Image](https://imagemagick.org/api/magick-wand.php)

With `Elixir` and `Zigler`, the recipy is:

```elixir
defmodule Magick do
use Zig,
otp_app: :ex_test,
zig_code_path: "../native/magick/magick.zig",
nifs: [magick: [:dirty_cpu],
c: [
link_lib: {:system, "MagickWand"},
link_lib: {:system, "MagickCore"},
include_dirs: ["/opt/homebrew/include/ImageMagick-7"]
]

def img do
magick("native/magick/p404.jpg")
end
end
```

Take a look at `Zig` module below.
You see the power of `Zigler`: it returns `{:ok, "Image processed..."}` or `{:error, reason}`.

```zig
const std = @import("std");
const beam = @import("beam");
const m = @cImport({
@cInclude("MagickWand/MagickWand.h");
});

const Errors = error{
wandCreationFailed,
imageReadFailed,
SepaiToneFailed,
imageWriteFailed,
};

pub fn magick(path: []const u8) !beam.term {
m.MagickWandGenesis();
defer _ = m.MagickWandTerminus();

const wand = m.NewMagickWand();
if (wand == null) {
m.ClearMagickWand(wand);
return beam.make_error_pair(Errors.wandCreationFailed, .{});
}

defer _ = m.DestroyMagickWand(wand);

const image = m.MagickReadImage(wand, path.ptr);

if (image == 0) {
return beam.make_error_pair(Errors.imageReadFailed, .{});
}

const sepai = m.MagickSepiaToneImage(wand, 58000);
if (sepai == m.MagickFalse) {
return beam.make_error_pair(Errors.SepaiToneFailed, .{});
}

const write_result = m.MagickWriteImage(wand, "p404_sepia.jpg");
if (write_result == m.MagickFalse) {
return beam.make_error_pair(Errors.imageWriteFailed, .{});
}

const msg = "Image processed and written successfully";
return beam.make(.{ .ok, msg }, .{});
}

```

## Conclusion

The `Zigler` work is amazing. However, the `Zig` as a language is still _very_ unstable (a have a few example of "Ziggit" forum responses less than 6 months old that don't work anymore, and I using a "stable " __0.14.1__ version). Furthermore, its public ecosystem seems very tiny, and not only web stuff.

In comparison, I found tons of stuff in `Rust`.

The usage of `Zig` seems very limited to peculiar usages where you need speed or "easy" `C` interop.

So I will probably give up `Zig` as the hope of an "easy C integration" is more a dream than a reality.

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.