Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch ffi Excluding Merge-Ins
This is equivalent to a diff from 3e1e2e713a to 4aab2109da
2023-10-19
| ||
20:06 | merge ffi check-in: 840780d4a4 user: patmaddox tags: trunk | |
2023-08-04
| ||
09:12 | ffi: add Lua as a language, since it's included in base Closed-Leaf check-in: 4aab2109da user: patmaddox tags: ffi | |
2023-08-03
| ||
20:30 | ffi: notes on C++ including headers check-in: 8e08e01286 user: patmaddox tags: ffi | |
14:04 |
merge ffi [f07e90b2c8]
- uclcmd reference implementation | |
2023-07-29
| ||
17:29 | ffi: initial draft of notes check-in: 32aa3d6a2b user: patmaddox tags: ffi | |
17:16 | www: home page links to drafts trunk instead of all checkins check-in: 3e1e2e713a user: patmaddox tags: trunk | |
17:15 | www: update link to drafts on home page check-in: 47312783ee user: patmaddox tags: trunk | |
Added drafts/ffi.md.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | # Comparing FFI in different languages - goals - call FreeBSD C libraries - build command line tools - write automated tests for operating system libraries - criteria - FFI ergonomics - general language ergonomics / toolchain - noteworthy, but non-criteria - performance - shared memory access - calling other language from C - memory control - preference - languages that compile to single binary - test cases - hello world - pass in an arg - mutate from C - upcase - intentional memory leak - concurrency - libucl - notes - all pretty much the same - most have you write some kind of header definition - sometimes you have to write a binding funtion also - strings are a pain - C strings have no length, are null-terminated - newer languages have length, not null-terminated - newer languages often use UTF encoded strings, C strings are a sequence of bytes with no specified encoding - almost always result in two memory copies - convert language string to C string, call C, convert C string back to language - can also create null pointer in calling language, and let C initialize it (sometimes) - automatic header import vs explicit bindings - Go, Zig, and C++ can use C headers directly, without needing to define bindings - D, Nim, and Rust need to explicitly define bindings - automatic header imports is very useful, especially as you have to reference more stuff - structs seem like a PITA with languages requiring bindings - explicit bindings sucks - you get into definition dependencies. Nim has C2Nim, but on ucl.h it used 30GB of RAM and then got OOM killed. - C++ calls C functions directly, so the library calling doesn't change at all - Supposedly there's a potential issue if the C header includes other headers - they end up including the C++ header with the same name. |
Changes to src/ffi-adventure/README.md.
1 2 3 4 | # FFI Adventure Explorations in calling C libraries from other languages. | | > | > > > > > > > | | 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 | # FFI Adventure Explorations in calling C libraries from other languages. ## Languages for consideration - C (reference) - D - Elixir - Go - Lua - `/usr/libexec/flua` - Nim - Pony - Rust - V - Zig ## Languages to investigate I know less about these, and may have to port some of them to FreeBSD. - C++ - Common Lisp - Crystal - Haxe - Jai - Nit - OCaml - Odin - Vale ## Examples - hello world (pass a string to C) - upcase (modify string in calling language) - intentional memory leak (how easy is it?) - concurrency (data races?) |
︙ | ︙ |
Added src/ffi-adventure/hello-world/hello-world-cpp/Makefile.
> > > > > > > > > | 1 2 3 4 5 6 7 8 9 | _build/hello: hello.cpp ../hello-world-c/_build/libhello.a @mkdir -p ${.TARGET:H} c++ -o ${.TARGET} -Wall -Werror -I../hello-world-c -L../hello-world-c/_build -lhello ${.ALLSRC:[1]} run: _build/hello ./_build/hello clean: rm -rf _build |
Added src/ffi-adventure/hello-world/hello-world-cpp/hello.cpp.
> > > > > > > > > | 1 2 3 4 5 6 7 8 9 | extern "C" { #include "libhello.h" } int main() { hello("C++"); return 0; } |
Added src/ffi-adventure/libucl/Justfile.
> > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 | test: #!/bin/sh mkdir -p _build for j in */Justfile; do impl=$(dirname $j) just -f $j run > _build/$impl cmp expected _build/$impl done @clean: rm -rf _build for j in */Justfile; do just -f $j clean; done |
Added src/ffi-adventure/libucl/data.ucl.
> > > > > | 1 2 3 4 5 | greeting = "hello world" vars { foo = "this is foo" bar = "this is bar" } |
Added src/ffi-adventure/libucl/expected.
> > > | 1 2 3 | hello world this is foo this is bar |
Added src/ffi-adventure/libucl/libucl-c/Justfile.
> > > > > > | 1 2 3 4 5 6 | @run: make > /dev/null ./_build/libucl-c @clean: rm -rf _build |
Added src/ffi-adventure/libucl/libucl-c/Makefile.
> > > | 1 2 3 | _build/libucl-c: libucl_c.c @mkdir -p ${.TARGET:H} cc -I/usr/local/include -L/usr/local/lib -lucl -Wall -Werror -o ${.TARGET} ${.ALLSRC} |
Added src/ffi-adventure/libucl/libucl-c/libucl_c.c.
> > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #include <stdio.h> #include <ucl.h> int main() { ucl_object_t* ucl = NULL; const char* greeting = NULL; const char* foo = NULL; const char* bar = NULL; struct ucl_parser* parser = ucl_parser_new(UCL_PARSER_DEFAULT); ucl_parser_add_file(parser, "../data.ucl"); if((ucl = ucl_parser_get_object(parser))) { ucl_object_tostring_safe(ucl_object_lookup(ucl, "greeting"), &greeting); ucl_object_tostring_safe(ucl_object_lookup_path(ucl, "vars.foo"), &foo); ucl_object_tostring_safe(ucl_object_lookup_path(ucl, "vars.bar"), &bar); } puts(greeting); puts(foo); puts(bar); } |
Added src/ffi-adventure/libucl/libucl-cpp/Justfile.
> > > > > > | 1 2 3 4 5 6 | @run: make > /dev/null ./_build/libucl-cpp @clean: rm -rf _build |
Added src/ffi-adventure/libucl/libucl-cpp/Makefile.
> > > | 1 2 3 | _build/libucl-cpp: libucl_cpp.cpp @mkdir -p ${.TARGET:H} c++ -I/usr/local/include -L/usr/local/lib -lucl -Wall -Werror -o ${.TARGET} ${.ALLSRC} |
Added src/ffi-adventure/libucl/libucl-cpp/libucl_cpp.cpp.
> > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <iostream> extern "C" { #include <ucl.h> } int main() { ucl_object_t* ucl = NULL; const char* greeting = NULL; const char* foo = NULL; const char* bar = NULL; struct ucl_parser* parser = ucl_parser_new(UCL_PARSER_DEFAULT); ucl_parser_add_file(parser, "../data.ucl"); if((ucl = ucl_parser_get_object(parser))) { ucl_object_tostring_safe(ucl_object_lookup(ucl, "greeting"), &greeting); ucl_object_tostring_safe(ucl_object_lookup_path(ucl, "vars.foo"), &foo); ucl_object_tostring_safe(ucl_object_lookup_path(ucl, "vars.bar"), &bar); } std::cout << greeting << "\n"; std::cout << foo << "\n"; std::cout << bar << "\n"; } |
Added src/ffi-adventure/libucl/libucl-go/Justfile.
> > > > > > > | 1 2 3 4 5 6 7 | @run: mkdir -p _build go build -o _build/libucl-go *.go ./_build/libucl-go @clean: rm -rf _build |
Added src/ffi-adventure/libucl/libucl-go/libucl_go.go.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | package main /* #cgo CFLAGS: -I/usr/local/include #cgo LDFLAGS: -L/usr/local/lib -lucl #include <ucl.h> */ import "C" import "fmt" //import "unsafe" func main() { var greeting *C.char var foo *C.char var bar *C.char var parser = C.ucl_parser_new(C.UCL_PARSER_DEFAULT) C.ucl_parser_add_file(parser, C.CString("../data.ucl")) var ucl = C.ucl_parser_get_object(parser) if(ucl != nil) { C.ucl_object_tostring_safe(C.ucl_object_lookup(ucl, C.CString("greeting")), &greeting) C.ucl_object_tostring_safe(C.ucl_object_lookup_path(ucl, C.CString("vars.foo")), &foo) C.ucl_object_tostring_safe(C.ucl_object_lookup_path(ucl, C.CString("vars.bar")), &bar) } fmt.Println(C.GoString(greeting)) fmt.Println(C.GoString(foo)) fmt.Println(C.GoString(bar)) } |
Added src/ffi-adventure/libucl/libucl-zig/Justfile.
> > > > > | 1 2 3 4 5 | @run: zig build run @clean: rm -rf _build |
Added src/ffi-adventure/libucl/libucl-zig/build.zig.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | const std = @import("std"); pub fn build(b: *std.build.Builder) void { // Standard target options allows the person running `zig build` to choose // what target to build for. Here we do not override the defaults, which // means any target is allowed, and the default is native. Other options // for restricting supported target set are available. const target = b.standardTargetOptions(.{}); // Standard release options allow the person running `zig build` to select // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. const mode = b.standardReleaseOptions(); const exe = b.addExecutable("libucl-zig", "src/main.zig"); exe.setTarget(target); exe.setBuildMode(mode); exe.addIncludePath("/usr/local/include"); exe.addLibraryPath("/usr/local/lib"); exe.linkSystemLibrary("ucl"); exe.install(); const run_cmd = exe.run(); run_cmd.step.dependOn(b.getInstallStep()); if (b.args) |args| { run_cmd.addArgs(args); } const run_step = b.step("run", "Run the app"); run_step.dependOn(&run_cmd.step); const exe_tests = b.addTest("src/main.zig"); exe_tests.setTarget(target); exe_tests.setBuildMode(mode); const test_step = b.step("test", "Run unit tests"); test_step.dependOn(&exe_tests.step); } |
Added src/ffi-adventure/libucl/libucl-zig/src/main.zig.
> > > > > > > > > > > > > > > > > > > > > > > > > > > | 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 | const std = @import("std"); const ucl = @cImport({ @cInclude("ucl.h"); }); pub fn main() !void { var parser: ?*ucl.ucl_parser = ucl.ucl_parser_new(ucl.UCL_PARSER_DEFAULT); var ucl_obj: ?*ucl.ucl_object_t = null; var greeting: [*c] const u8 = null; var foo: [*c] const u8 = null; var bar: [*c] const u8 = null; _ = ucl.ucl_parser_add_file(parser, "../data.ucl"); ucl_obj = ucl.ucl_parser_get_object(parser); if(ucl_obj != null) { _ = ucl.ucl_object_tostring_safe(ucl.ucl_object_lookup(ucl_obj, "greeting"), &greeting); _ = ucl.ucl_object_tostring_safe(ucl.ucl_object_lookup_path(ucl_obj, "vars.foo"), &foo); _ = ucl.ucl_object_tostring_safe(ucl.ucl_object_lookup_path(ucl_obj, "vars.bar"), &bar); } const stdout_file = std.io.getStdOut().writer(); var bw = std.io.bufferedWriter(stdout_file); const stdout = bw.writer(); try stdout.print("{s}\n{s}\n{s}\n", .{greeting, foo, bar}); try bw.flush(); } |
Added src/ffi-adventure/libucl/uclcmd/Justfile.
> > > > > > > | 1 2 3 4 5 6 7 | run: #!/bin/sh for v in greeting vars.foo vars.bar; do uclcmd get -q -f ../data.ucl $v done clean: |