{"id":15395332,"url":"https://github.com/cocoa-xu/otter","last_synced_at":"2025-04-16T00:10:04.393Z","repository":{"id":43580857,"uuid":"457979550","full_name":"cocoa-xu/otter","owner":"cocoa-xu","description":"Call C functions in a shared library without writing a NIF.","archived":false,"fork":false,"pushed_at":"2022-02-28T03:05:05.000Z","size":210,"stargazers_count":15,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-16T00:09:57.897Z","etag":null,"topics":["elixir"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cocoa-xu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-02-10T23:21:04.000Z","updated_at":"2023-05-19T01:53:43.000Z","dependencies_parsed_at":"2022-07-22T18:32:55.280Z","dependency_job_id":null,"html_url":"https://github.com/cocoa-xu/otter","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cocoa-xu%2Fotter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cocoa-xu%2Fotter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cocoa-xu%2Fotter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cocoa-xu%2Fotter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cocoa-xu","download_url":"https://codeload.github.com/cocoa-xu/otter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249173086,"owners_count":21224483,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir"],"created_at":"2024-10-01T15:27:44.194Z","updated_at":"2025-04-16T00:10:04.367Z","avatar_url":"https://github.com/cocoa-xu.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Otter\n\n| OS               | arch    | Build Status |\n|------------------|---------|--------------|\n| Ubuntu 20.04     | x86_64  | [![CI](https://github.com/cocoa-xu/otter/actions/workflows/linux-x86_64.yml/badge.svg)](https://github.com/cocoa-xu/otter/actions/workflows/linux-x86_64.yml) |\n| macOS 11 Big Sur | x86_64  | [![CI](https://github.com/cocoa-xu/otter/actions/workflows/macos-x86_64.yml/badge.svg)](https://github.com/cocoa-xu/otter/actions/workflows/macos-x86_64.yml) |\n\n## Dependencies\n- pkg-config (for finding libffi)\n- libffi-dev\n\nFor macOS, libffi can be installed by HomeBrew\n```shell\nbrew install libffi\n```\n\nFor debian/ubuntu, libffi can be installed using the following command\n```shell\nsudo apt update\nsudo apt install libffi-dev\n```\n\n## Type Correspondences\nGenerally, we can extern a function using the following syntax\n\n```elixir\nextern func_name(:return_type)\nextern func_name(:return_type, arg_type, ...)\nextern func_name(:return_type, arg_name :: arg_type, ...)\n```\n\nNote that `arg_name` is optional, which means rule 2 and 3 can be rewritten as\n\n```elixir\nextern func_name(:return_type, [arg_name :: ]arg_type, ...)\n```\n\nand if we want to further simplify (or complify?) it, we have\n\n```elixir\nextern func_name(:return_type[, [arg_name :: ]arg_type, ...])\n```\n\n### Function return type\n| Syntax         | Example In C | Example in Otter | Description                                                                                         |\n|----------------|--------------|------------------|-----------------------------------------------------------------------------------------------------|\n| :return_type   | uint32_t     | :u32             | unsigned 32-bit integer. Return type should be the atom version of the basic types available below. |\n\n### Basic types\n\n| Syntax         | Example In C | Example in Otter | Description              |\n|----------------|--------------|------------------|--------------------------|\n| s8             | int8_t       | s8               | signed 8-bit integer.    |\n| s16            | int16_t      | s16              | signed 16-bit integer.   |\n| s32            | int32_t      | s32              | signed 32-bit integer.   |\n| s64            | int64_t      | s64              | signed 64-bit integer.   |\n| u8             | uint8_t      | u8               | unsigned 8-bit integer.  |\n| u16            | uint16_t     | u16              | unsigned 16-bit integer. |\n| u32            | uint32_t     | u32              | unsigned 32-bit integer. |\n| u64            | uint64_t     | u64              | unsigned 64-bit integer. |\n| f32            | float        | f32              | 32-bit single-precision floating-point numbers. |\n| f64            | double       | f64              | 64-bit double-precision floating-point numbers. |\n| c_ptr          | void *       | c_ptr            | Any C pointer.           |\n\n```elixir\ndefmodule Foo do\n  import Otter, except: [{:\u0026, 1}]\n\n  # see their implementations in test/test.cpp\n  extern pass_through_u8(:u8, val :: u8)\n  extern pass_through_u16(:u16, val :: u16)\n  extern pass_through_u32(:u32, val :: u32)\n  extern pass_through_u64(:u64, val :: u64)\n  extern pass_through_s8(:s8, val :: s8)\n  extern pass_through_s16(:s16, val :: s16)\n  extern pass_through_s32(:s32, val :: s32)\n  extern pass_through_s64(:s64, val :: s64)\n  extern pass_through_f32(:f32, val :: f32)\n  extern pass_through_f64(:f64, val :: f64)\n  extern pass_through_c_ptr(:u64, ptr :: c_ptr)\nend\n```\n\nWe'll use `{T}` to indicate any **basic types** from now on. For example, `{T}-size(42)` could be `u32-size(42)`.\n\n### ND-array types\n| Syntax                | Example In C      | Example in Otter    | Description                                      |\n|-----------------------|-------------------|---------------------|--------------------------------------------------|\n| {T}-size(d)           | `T [d]`           | u32-size(42)        | An array of 42 unsigned 32-bit integers.       |\n| {T}-size(d1, d2, ...) | `T [d1][d2][...]` | u8-size(100, 200)   | An array of 100-by-200 unsigned 8-bit integers. |\n\nND-array is not supported as a function argument yet. It can be only used in structs (see below) for now.\n\nWe'll use `{NDA}` to indicate any ND-array types from now on.\n\n### Struct types\nTo declare C structs, we'll have to use the `cstruct` macro. \n\nWe'll use `{FT}` to indicate the type of a field in the struct.\n\n```shell\n{FT} = {T}\n       | {NDA}\n```\n\nSay you have a struct named `name`,\n\n| Syntax                                       | Example In C                          | Example in Otter                  | Description                                                        |\n|----------------------------------------------|---------------------------------------|-----------------------------------|--------------------------------------------------------------------|\n| cstruct(name(field_name :: {FT}))          | `struct name { FT field_name; }`        | cstruct(name(val :: u32))         | A struct with a single field named `val` which type is `u32`       |\n| cstruct(name(field_name_1 :: {FT1}, ...))  | `struct name { FT1 field_name_1; ... }` | cstruct(name(x :: f32, y :: f32)) | A struct with fields `x` and `y` and they have the same type `f32` |\n\n## Todo\n- [ ] Create struct instances using [c_struct](https://github.com/cocoa-xu/c_struct). Maybe merge code in `c_struct` to\n  here?\n\n## Demo\n```elixir\ndefmodule Ctypes do\n  import Otter, except: [{:\u0026, 1}]\n\n  # module level default shared library name/path\n  @default_from (case :os.type() do\n                   {:unix, :darwin} -\u003e \"libSystem.B.dylib\"\n                   {:unix, _} -\u003e \"libc.so\"\n                   {:win32, _} -\u003e raise \"Windows is not supported yet\"\n                 end)\n  # module level default dlopen mode\n  @default_mode :RTLD_NOW\n\n  # specify shared library name and/or load mode for a single function\n  @load_from (case :os.type() do\n                {:unix, :darwin} -\u003e \"libSystem.B.dylib\"\n                {:unix, _} -\u003e \"libc.so\"\n                {:win32, _} -\u003e raise \"Windows is not supported yet\"\n              end)\n  @load_mode :RTLD_NOW\n  extern sin(:f64, f64)\n\n  # or using module level default shared library name and load mode\n  extern puts(:s32, c_ptr)\n  extern dlopen(:c_ptr, c_ptr, s32)\n  extern dlsym(:c_ptr, c_ptr, c_ptr)\n\n  # explict mark argument name and type\n  extern cos(:f64, theta :: f64)\n  \n  # also support functions with variadic arguments\n  extern printf(:u64, fmt :: c_ptr, args :: va_args)\nend\n\n# one extern will define two function, \n# - one returns ok-error tuple, \n# - the other is the bang version, which returns unwrapped value on :ok, and raise RuntimeError on :error  \n\niex\u003e CtypesDemo.puts(\"hello \\r\")\nhello\n{:ok, 10}\niex\u003e CtypesDemo.puts!(\"hello \\r\")\nhello\n10\niex\u003e CtypesDemo.printf!(\"world!\\r\\n\")\nworld!\n9\niex\u003e CtypesDemo.sin(3.1415926535)\n{:ok, 8.979318433952318e-11}\niex\u003e CtypesDemo.sin!(3.1415926535)\n8.979318433952318e-11\niex\u003e CtypesDemo.cos!(0)\n1.0\niex\u003e CtypesDemo.cos!(0.0)\n1.0\niex\u003e CtypesDemo.printf!(\"%s-%.5lf-0x%08x-%c\\r\\n\\0\", [\n...\u003e   as_type!(\"hello world!\\0\", :c_ptr),\n...\u003e   as_type!(123.456789, :f64),\n...\u003e   as_type!(0xdeadbeef, :u32),\n...\u003e   as_type!(65, :u8)\n...\u003e ])\nhello world!-123.45679-0xdeadbeef-A\n37\niex\u003e handle = CtypesDemo.dlopen!(\"/usr/lib/libSystem.B.dylib\", 2) # or \"libc.so\" for Linux\n20152781936\niex\u003e dlsym_addr = CtypesDemo.dlsym!(handle, \"dlsym\")\n7023526352\n```\n\nNote that we have `CtypesDemo.dlopen` and `CtypesDemo.dlsym` in the demo code above. They are declared in the `examples/ctypes_demo.ex`\nfile. \n\n```elixir\n  extern dlopen(:c_ptr, c_ptr, s32)\n  extern dlsym(:c_ptr, c_ptr, c_ptr)\n```\n\nAnd they are different from the ones in module Otter, namely, `Otter.dlopen` and `Otter.dlsym`. `CtypesDemo.dl*` are obtained\nby `Otter.dlopen` and `Otter.dlsym`. \n\nJust like the `sin` and `cos` functions in `CtypesDemo`, `dlopen` and `dlsym` are also C functions that can be `dlsym`'ed.\n\n`Otter.dl*` calls go to NIFs `otter_dl*` functions while `CtypesDemo.dl*` calls going to `Otter.invoke` which redirects to \nthe `otter_invoke` NIF.\n\n## Support for C struct\n### basic example\n```elixir\ndefmodule Foo do\n  import Otter\n  \n  @default_from Path.join([__DIR__, \"test.so\"])\n  @default_mode :RTLD_NOW\n  \n  # #pragma pack(push, 4)\n  # struct alignas(4) s_u8_u16 {\n  #     uint8_t u8;\n  #     uint16_t u16;\n  # };\n  # #pragma pack(pop)\n  cstruct(s_u8_u16(u8 :: u8, u16 :: u16))\n\n  # please see test/test.cpp for these extern functions\n  extern create_s_u8_u16(s_u8_u16())\n  extern receive_s_u8_u16(:u32, t :: s_u8_u16())\nend\n```\n\n### nd-array in C struct\n```elixir\ndefmodule Foo do\n  import Otter\n\n  @default_from Path.join([__DIR__, \"test.so\"])\n  @default_mode :RTLD_NOW\n  \n  # struct matrix16x16 {\n  #     uint32_t m[16][16];\n  # };\n  cstruct(matrix16x16(m :: u32-size(16, 16)))\n\n  # please see test/test.cpp for these extern functions\n  extern create_matrix16x16(matrix16x16())\n  extern receive_matrix16x16(:u32, t :: matrix16x16())\nend\n```\n\n## Installation\n\nIf [available in Hex](https://hex.pm/docs/publish), the package can be installed\nby adding `otter` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:otter, \"~\u003e 0.1.0\"}\n  ]\nend\n```\n\nDocumentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)\nand published on [HexDocs](https://hexdocs.pm). Once published, the docs can\nbe found at \u003chttps://hexdocs.pm/otter\u003e.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcocoa-xu%2Fotter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcocoa-xu%2Fotter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcocoa-xu%2Fotter/lists"}