Skip to content
alexkelbo edited this page Dec 20, 2018 · 18 revisions

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.

Example

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.odin"

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. [!]

Foreign blocks

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 vs shared libraries

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

Calling conventions

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"

Types

C types don't map directly to Odin types typically, even if they both have types with the same name! There's a file in the core library (c.odin) 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.

Strings

C strings are pointers to chars that act as null-terminated arrays. The Odin equivalent to this is ^byte, which would be a pointer of unsigned 8-bit integers. To work with C strings in Odin, it's highly recommended that you convert them to an Odin string, which is a slice that has a pointer and a length.

The easiest way to do this is to import core:strings.odin, and then use the strings.to_odin_string(my_c_string) function to get an Odin string without any allocations. Then, to go back to a C string, you can use strings.new_c_string(my_odin_string) to get a ^byte again. Note that this does reallocate the string, so it must be freed after use.

Structs

Odin's structs are rearranged and packed in a different way than C compilers. In order to make an Odin struct that's compatible with C ABIs, you must add the #ordered flag to the struct. See this section of the Attributes and Tags page for more information.

Unions

Unions in Odin are tagged with the Type_Info of whatever type is being used in the union. To get a C-style union, use a struct with the #raw_union tag. See this section of the Attributes and Tags page for more information.

Clone this wiki locally