Integration of (easy) C code, and Zig libraries, and Zig using C libraries
- 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:
[](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.