-
-
Notifications
You must be signed in to change notification settings - Fork 652
Binding to C
Odin makes it fairly easy to bind to C code (but not C++ code, unless wrapped) that is compiled as a local static or dynamic/shared library, or to a system library.
This is primarily done in two parts, first by using foreign import
to create an import name and link to the library file, and foreign
blocks to link to declare individual exported functions and variables.
Make a file called foo.c
containing the following:
foo.c:
int foo_add_int(int a, int b) {
return a + b;``
}
int foo_add_double(double a, double b) {
return a + b;
}
then, depending on your platform:
gcc -c foo.c
ar rcs foo.a foo.o
cl -TC -c foo.c
lib -nologo foo.obj -out:foo.lib
And finally, to use it in Odin, you could do:
import "core:fmt"
when ODIN_OS=="windows" do foreign import foo "foo.lib";
when ODIN_OS=="linux" do foreign import foo "foo.a";
foreign foo {
foo_add_int :: proc(a, b: i32) -> i32 ---;
foo_add_double :: proc(a, b: f64) -> f64 ---;
}
main :: proc() {
fmt.println(foo_add_int(2, 2));
fmt.println(foo_add_double(2.71828, 3.14159));
}
Note that the procedure declaration has to end in ---
inside the foreign
block to denote that the body is found elsewhere. This is to prevent a parsing ambiguity with procedure variables and types. [!]
Odin supports multiple ways of making binding to C easier.
By default, the foreign block links to a function of the same name as the name you specify inside the foreign
block. This can be explicitly changed by using the @(link_name=<string>)
and @(link_prefix=<string>)
attributes. The following are all equivalent:
foreign foo {
foo_add_int :: proc(a, b: i32) -> i32 ---;
foo_add_double :: proc(a, b: f64) -> f64 ---;
}
foreign foo {
@(link_name="foo_add_int")
add_int :: proc(a, b: i32) -> i32 ---;
@(link_name="foo_add_double")
add_double :: proc(a, b: f64) -> f64 ---;
}
@(link_prefix="foo_");
foreign foo {
add_int :: proc(a, b: i32) -> i32 ---;
add_double :: proc(a, b: f64) -> f64 ---;
}
Static libraries work the same in on every platform: you specify the relative paths of the library file in the .odin file you are importing it to.
Dynamic/shared libraries are slightly different depending on which operating system you are using:
- In Windows you link to an import lib (.lib) relative to the file you're importing from, and the user have to make sure the corresponding .dll is visible to the exectutable.
- In Linux you link directly to the shared object (.so) that is visible to the executable.
Note that for local libraries and for system libraries in Windows you need to specify the full filename, including file extension. For system libraries in Linux you can omit the extension, and it will link to the appropriate library file available in the system library search paths.
To summarize, if the current file is /path/to/file/foo.odin
foreign import foo "foo.a" // static lib in linux, links to /path/to/file/foo.a
foreign import foo "foo.so" // shared lib in linux links to /path/to/executable/foo.so
foreign import foo "foo.lib" // static/import library in windows, links to /path/to/file/foo.lib
foreign import foo "system:foo" // system library (static or shared) in linux. Links to /usr/lib/libfoo.a
foreign import foo "system:foo.lib" // system library (static or import) in windows. Links to foo.lib
C supports a plethora of calling conventions, most of them for Windows. Procedure calling conventions are added after the proc
in the signature:
foo :: proc "c" () {}
bar :: proc "std" () {}
Procedure declarations in foreign
blocks default to the "c" calling convention, but this can be changed by adding the @(default_calling_convention=<string>)
attribute prior to the foreign
block.
@(default_calling_convention="std")
foreign lib {
...
}
Possible calling conventions are:
"odin"
"contextless"
"c", "cdecl"
-
"std"
,"stdcall"
-
"fast"
,"fastcall"
C types don't map directly to Odin types typically, even if they both have types with the same name!
There's a package in the core library (core:c
) that you can import to get some Odin equivalents of some of the basic C types.
Some types don't fit in this file, though. There are three big ones: strings, structs, and unions.
C strings are pointers to the first byte in an array of bytes, who's last byte is zero, or NUL. (a.k.a "the null terminator.)
The Odin equivalent to this is cstring
, which is just a ^u8
.
To work with C strings in Odin, it's highly recommended that you use this type, and, as required, convert them to string
with a cast: string(my_cstring)
or cast(string) my_cstring
, which creates a string that just points to the cstring
's data. No allocation or copy is performed.
Despite being just a cast, this takes O(N)
time as it will scan the memory, looking for the null terminator, in order to determine the length of the string, since Odin strings keep track of the length.
To go the other way, you may import core:strings
and use strings.clone_to_cstring(my_cstring)
, which makes a copy of the underlying string, and puts a null terminator on the end.
The returned cstring
is allocated and so therefore should be freed once you no longer need it.
Rather than a dynamic allocation, you might alternatively want to use strings.clone_to_cstring(my_cstring, context.temp_allocator)
to use temporary storage instead.
Odin structs with fields that are the same size as their C equivalents, (see core:c
) Odin structs have the same layout as the equivalent C struct.
Packed structs in Odin work the same as C, with the #packed
tag: S :: struct #packed { x: i64, y: u8 }
[1]
Unions in Odin do not have a compatible data layout to their equivalent C union.
You may still declare such a union however by using a struct with the #raw_union
tag. [1]
[1] See this section of the Attributes and Tags page for more information.