{"id":21041031,"url":"https://github.com/chfoo/callfunc","last_synced_at":"2025-03-13T21:23:59.433Z","repository":{"id":145909399,"uuid":"191877167","full_name":"chfoo/callfunc","owner":"chfoo","description":"Foreign function interface Haxe library using libffi","archived":false,"fork":false,"pushed_at":"2020-05-08T02:20:01.000Z","size":419,"stargazers_count":21,"open_issues_count":1,"forks_count":0,"subscribers_count":5,"default_branch":"master","last_synced_at":"2025-01-23T12:42:48.911Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Haxe","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chfoo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-06-14T04:46:06.000Z","updated_at":"2024-12-11T22:45:53.000Z","dependencies_parsed_at":null,"dependency_job_id":"dc6e042e-a0b2-4d95-ad83-eaf57a3c1369","html_url":"https://github.com/chfoo/callfunc","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chfoo%2Fcallfunc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chfoo%2Fcallfunc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chfoo%2Fcallfunc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chfoo%2Fcallfunc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chfoo","download_url":"https://codeload.github.com/chfoo/callfunc/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243483179,"owners_count":20297952,"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":[],"created_at":"2024-11-19T13:50:03.806Z","updated_at":"2025-03-13T21:23:59.421Z","avatar_url":"https://github.com/chfoo.png","language":"Haxe","readme":"# Callfunc\n\nCallfunc is a foreign function interface (FFI) library for Haxe. It uses [libffi](https://github.com/libffi/libffi) for the actual native function execution. The use of libffi allows loading and calling arbitrary functions from dynamic libraries at runtime. If you have used Python, this is the same concept of the ctypes module.\n\nThere will be performance costs when using this library due to serialization and libffi overhead (described in a later section). As well, Callfunc can only operate on the [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) of a library. There will be a loss of safety such as C enums and typedefs. Regardless, Callfunc can be useful for easily calling native libraries or creating a library binding without having to maintain various wrappers for different targets.\n\nSupported targets:\n\n* CPP (little-endian only)\n* HashLink and HL/C (little-endian only)\n\nCallfunc can also be used as a interface for calling foreign functions in other targets:\n\n* JS + Emscripten (32-bit WASM, little-endian only)\n\n## Quick start\n\nCallfunc requires:\n\n* Haxe 4 or newer\n* libffi 3.3-rc0 or newer\n\nInstall Callfunc from Haxelib:\n\n    haxelib install callfunc\n\nOr install the latest from GitHub:\n\n    haxelib git callfunc https://github.com/chfoo/callfunc\n\nObtain libffi.{so,dylib,dll} (and callfunc.hdll for Hashlink) from the zip releases or see \"Compiling libraries\" to build them yourself.\n\n## Types\n\nThe `CoreDataType` enum contains the same data types as described in libffi. The `DataType` enum contains additional data types that are automatically aliased to core data types.\n\nInteger C data types that fit within 32 bits, such as `int16_t`, are converted to Haxe `Int`. C integers that are 64 bits wide are converted to Haxe `Int64`. As a consequence, `long int` can be either `Int` or `Int64` depending on the ABI.\n\nLikewise, **when converting to C data types, Haxe `Int` and `Int64` will be truncated (possible loss of data) when the data type is too small**. Otherwise, it there is no loss of data (either it fits or promoted).\n\n`float` and `double` are converted to Haxe `Float`.\n\n`void *` and alike are represented by the `Pointer` class.\n\n## Loading a library\n\nTo load a library, obtain a `Callfunc` instance and call the `newLibrary` method:\n\n```haxe\nimport callfunc.Callfunc;\n\nvar ffi = Callfunc.instance();\nvar library = ffi.openLibrary(\"libexample.so\");\n```\n\nThe name of the library is passed to `dlopen()` or `LoadLibrary()` on Windows.\n\n* On Windows, library names are usually \"example.dll\".\n* On MacOS, library names are usually \"libexample.123.dylib\" where 123 is the ABI version.\n* On Linux, library names are usually \"libexample.so.123\" where 123 is the ABI version.\n\n## Calling functions\n\nThe library object has a `s` field which lets you access functions using array access or field access syntax.\n\n### No parameters\n\nC:\n\n```c\nvoid do_something();\n```\n\nHaxe:\n\n```haxe\nlibrary.s[\"do_something\"].call();\n// or\nlibrary.s.do_something.call();\n```\n\n### Numeric parameters\n\nBy default, functions are automatically defined to accept no parameters and return no value. To pass arguments, you need to define the parameters. Once you define a function, you can call it as many times as you want.\n\nC:\n\n```c\nvoid do_something(int32_t a, int64_t b, double c);\n```\n\nHaxe:\n\n```haxe\nlibrary.define(\n    \"do_something\",\n    [DataType.SInt32, DataType.SInt64, DataType.Double]\n);\nlibrary.s.do_something.call(123, Int64.make(123, 456), 123.456);\n```\n\n### Numeric return\n\nC:\n\n```c\nint do_something();\n```\n\nHaxe:\n\n```haxe\nlibrary.define(\"do_something\", [], DataType.SInt);\nvar result = library.s.do_something.call();\ntrace(result); // Int on x86/x86-64\n```\n\n### Variadic functions\n\nFor C variadic functions (varargs), use `library.defineVariadic`:\n\nC:\n\n```c\nvoid printf(char * a, ...);\n```\n\nHaxe:\n\n```haxe\nlibrary.defineVariadic(\"printf\", [DataType.Pointer, DataType.SInt], 1, \"printf__int\");\nlibrary.defineVariadic(\"printf\", [DataType.Pointer, DataType.Double], 1, \"printf__double\");\nlibrary.defineVariadic(\"printf\", [DataType.Pointer, DataType.SInt, DataType.Double], 1, \"printf__int_double\");\n\nlibrary.s.printf__int.call(string, 123);\nlibrary.s.printf__double.call(string, 123.456);\nlibrary.s.printf__int_double.call(string, 123, 123.456);\n```\n\n## Pointers\n\nC pointers are represented by the `Pointer` class. They have two main methods which are `get()` and `set()`. By default, they have a data type of `SInt32` but you can change it as needed.\n\nC:\n\n```c\nvoid do_something(int32_t * a);\n```\n\nHaxe:\n\n```haxe\nlibrary.s.define(\"do_something\", [DataType.Pointer]);\n\nvar size = ffi.sizeOf(DataType.SInt32);\nvar p = ffi.alloc(size);\n\np.dataType = DataType.SInt32;\n\np.set(123);\nlibrary.s.do_something.call(p);\nvar result = p.get();\ntrace(result);\n```\n\nIf you need to free the allocated memory, use:\n\n```haxe\npointer.free();\n```\n\n### Arrays\n\nTo access array elements, use the array version of get/set:\n\n```haxe\nvar index = 10;\np.arraySet(index, 456);\nvar value = p.arrayGet(index); // =\u003e 456\n```\n\n### Interpreting pointers as Bytes\n\nCallfunc has methods for converting between `Bytes` and `Pointer` for targets that support it. The `Bytes` instance can be operated on directly which bypasses the `Pointer` class wrapper. Allocating `Bytes` to use a `Pointer` can also take advantage of the Haxe garbage collection.\n\nTo convert to `Bytes` assuming an array of 10 bytes:\n\n```haxe\nvar bytes = pointer.getBytes(10);\n```\n\nTo convert from `Bytes`:\n```haxe\nvar pointer = ffi.bytesToPointer(bytes);\n```\n\nHowever, for better portability between targets, the `DataView` interface (and `BytesDataView` implementation) is provided:\n\n```haxe\nvar view = pointer.getDataView(10);\n\nview.setUInt32(0, 123);\ntrace(view.getUInt32(0))\n```\n\n## Structures\n\nUnlike C arrays, the fields in C structures aren't necessarily next to each other. The way structs are packed depends on the ABI. To obtain the size and field offsets, build a `StructDef`.\n\nTo build this C struct:\n\n```c\nstruct {\n    int a;\n    char * b;\n};\n```\n\nCall `ffi.defineStruct()`:\n\n```haxe\nvar structDef = ffi.defineStruct(\n    [DataType.SInt, DataType.Pointer],\n    [\"a\", \"b\"]\n);\n```\n\nStructs can be accessed using the struct information:\n\n```haxe\nvar structPointer = ffi.alloc(structType.size);\n\nvar a = structPointer.get(DataType.SInt, structType.offsets[0]);\nvar b = structPointer.get(DataType.Pointer, structType.offsets[1]);\n```\n\nBut in most cases, you will access structs using the helper class `StructAccess`:\n\n```haxe\nvar struct = structDef.access(structPointer);\n\nstruct[\"a\"] = 123;\ntrace(struct[\"a\"]);\n// or\nstruct.a = 123;\ntrace(struct.a);\n```\n\n### Passing structs by value\n\nStructs are usually passed by reference using pointers, but passing structs by value is also supported. This is done by specifying the `Struct` data type to the function definition and pass `Pointer` arguments to populated structs. Copies of the structs will be made from the pointers during the function call.\n\nFor functions that return structs by value, a `Pointer` to a copied struct will be returned. This pointer should be freed by the caller.\n\n## Callback functions\n\nC code calling Haxe code is supported.\n\nThe following C function accepts a function pointer. The function pointer accepts two integers and returns an integer.\n\n```c\nvoid do_something(int32_t (*callback)(int32_t a, int32_t b));\n```\n\nIn Haxe, define the function parameters and return type and obtain a pointer to be passed to the C function.\n\n```haxe\nfunction myHaxeCallback(a:Int, b:Int):Int {\n    return b - a;\n}\n\nvar ffi = Callfunc.instance();\nvar callbackDef = ffi.wrapCallback(\n    myHaxeCallback,\n    [DataType.SInt32, DataType.SInt32],\n    DataType.SInt32\n);\n\nlibrary.define(\"do_something\", [DataType.Pointer]);\nlibrary.s.do_something.call(callbackDef.pointer);\n```\n\n## Strings\n\nTo quickly allocate a string:\n\n```haxe\nvar pointer = ffi.allocString(\"Hello world!\");\n\n// By default, UTF-8 is used.\n// To use UTF-16 use:\n\nvar pointer = ffi.allocString(\"Hello world!\", Encoding.UTF16LE);\n```\n\nLikewise, to decode a string:\n\n```haxe\nvar string = pointer.getString();\n\n// or\n\nvar string = pointer.getString(Encoding.UTF16LE);\n```\n\n## 32/64-bit integers\n\nSome C data types such as `size_t` don't have a fixed width and may require the use of `Int64`. Because of the mix of `Int` and `Int64`, Callfunc provides some abstracts to make things easier.\n\n`AnyInt` is an abstract over `Dynamic` which provides methods to convert values to `Int` or `Int64` at runtime. It encapsulates the if-else type checking. In function parameters, it can be used as a \"either\" type which accepts either `Int` or `Int64` integers.\n\n`AutoInt64` that is an abstract of `Int64` which automatically promotes `Int` to `Int64`. Likewise, `AutoInt` is an abstract of `Int` which truncates `Int64` to `Int`. These can be used for implicit casting between `Int64` if `Int64` methods are too verbose.\n\n## Emscripten\n\nTo use Callfunc's interface to Emscripten, you must create a context with the module object:\n\n```haxe\nvar context = new EmContext(Reflect.field(js.Browser.window, \"Module\"));\nCallfunc.setInstance(new Callfunc(context));\n```\n\nTo use exported functions, simply use the empty string `\"\"` as the library name. Opening other libraries is not supported at this time.\n\n## Garbage collection\n\nAny object with a `dispose()` method contains resources that cannot be automatically garbage collected. It is up to the user to call this method at the appropriate times.\n\nLikewise, `Pointer` objects hold C pointers which must be treated with care as usual in C.\n\n## Safety\n\nCallfunc does not provide any automatic protection against memory-unsafe conditions such as dangling pointers, out-of-bounds read/writes, type confusion, or integer overflows/underflows.\n\nFor targets that use libffi, the creation of `Function` or `StructType` instances is not thread safe.\n\n## Documentation\n\nA libcurl example is in the \"example\" directory.\n\nAPI docs: https://chfoo.github.io/callfunc/api/\n\n## Compiling the libraries\n\nPre-compiled libraries are included in the releases, but if you need to compile them yourself, see below.\n\n### libffi\n\n#### Windows\n\nvcpkg can be used to build libffi.\n\nIf you are compiling to HashLink, note that the HashLink binary from the website is 32-bit or 64-bit, so you will need to build and use correct versions of the libraries. For HashLink 1.11 and newer use 64-bit (\"x86-64\"). For HashLink 1.10 and older, use 32-bit (\"x86\").\n\n1. Download and set up vcpkg\n2. Install the Visual Studio C++ workload SDK in Tools, Get Tool and Features.\n3. Run `./vcpkg install libffi:x64-windows libffi:x86-windows`\n4. (Optional) Run `./vcpkg export --zip libffi:x64-windows libffi:x86-windows`\n\nAny exported zips are in the vcpkg folder. All installed packages are in the `installed` folder. The header and library will be in `include` and `bin` directories of the `x64-windows` (64-bit) and `x86-windows` (32-bit).\n\nFor the CPP target, you may optionally use MinGW-w64 if you have trouble compiling with the Haxe HXCPP and VS toolchain. In your `~/.hxcpp_config.xml` or `%HOMEPATH%/.hxcpp_config.xml`, under the \"VARS\" section, set `mingw` to `1`.\n\n#### MacOS\n\nYou can use homebrew to install libffi, but at the time of writing, it points to an outdated fork. You will need to run `brew edit libffi` to edit the brew recipe to use the official fork and install the head version.\n\nOn line 18, change:\n\n    head do\n        url \"https://github.com/atgreen/libffi.git\"\n\nTo:\n\n    head do\n        url \"https://github.com/libffi/libffi.git\"\n\nThen run `brew install libffi --HEAD` and `brew info libffi` to get the library path.\n\n#### Linux\n\nTypically libraries are provided your distribution's package manager, but only stable versions. In this case, the library can be built and installed following the instructions in the libffi readme file. Running the install step will install it to /usr/local/lib. On Debian-based distributions, you can replace the install step with `checkinstall` to create and install a deb package.\n\n### callfunc.hdll (HashLink)\n\nYou will need CMake. The following commands assumes a Bash shell.\n\n1. Create a build directory and change to it.\n\n        mkdir -p out/ \u0026\u0026 cd out/\n\n2. Run cmake to generate build files using a release config.\n\n        cmake .. -DCMAKE_BUILD_TYPE=Release\n\nTo optionally specify the include and linker paths, add (adjust paths as needed):\n\n* For libffi: `-DLIBFFI_INCLUDE_PATH:PATH=/usr/local/include/ -DLIBFFI_LIB_PATH:FILEPATH=/usr/local/lib/libffi.so`. For vcpkg, please add the toolchain define (`CMAKE_TOOLCHAIN_FILE`) as reported at the end of libffi install. (Use `vcpkg integrate install` to get the path.)\n* For HashLink: `-DHL_INCLUDE_PATH:PATH=/usr/local/include/ -DHL_LIB_PATH:FILEPATH=/usr/local/lib/libhl.so`.\n\nOn Linux and MacOS, this will be a makefile which you can run `make`.\n\nOn Windows, add `-A Win32` for 32-bit. CMake will generate a Visual Studio project file or nmake config by default. Consult documentation on CMake generators for other configs such as Mingw-w64.\n\nThe generated library will be in `out/callfunc/` suitable for development. To generate a library suitable for redistribution (rpath stuff), use\n\n        cmake --install . --config Release --prefix installed/\n\nto generate the library in `out/installed`.\n\nPlease see section \"Library paths\" for running without installing the libraries.\n\n### CPP target\n\nThe Callfunc binding library is statically built by hxcpp.\n\nBy default, the hxcpp build config (hxcpp_build.hxml) is configured to include libffi files only for a unit testing setup. You may need edit your `~/.hxcpp_config.xml` or `%HOMEPATH%/.hxcpp_config.xml` file to specify include and linking flags for libffi if your compiler cannot find the correct libffi.\n\nFor example:\n\n* To add the header include path `-I` flag, add `\u003cflag value=\"-I/usr/local/include\"/\u003e` to the `\u003ccompiler\u003e` section.\n* To add the dynamic library link path `-L` flag, add `\u003cflag value=\"-L/usr/local/lib\"/\u003e` to the `\u003clinker\u003e` section.\n\nAdjust the paths or create new sections for your platform/compiler as needed.\n\n### Troubleshooting compilation\n\nIf you have trouble getting the library or dependencies built, check the .travis.yml and azure-pipelines.yml files.\n\n## Library paths\n\nWhen running applications without installation on MacOS or Linux, the paths for searching for libraries is more restricted than Windows. That is, the system, by default, will not load libraries in the current directory or in the directory of the application.\n\nOn Linux, the `LD_LIBRARY_PATH` environment can be provided to the executable. For example:\n\n`LD_LIBRARY_PATH=\"./:/usr/local/lib/:$LD_LIBRARY_PATH\" hl myApplication.hl`\n\nOn MacOS, use `DYLD_FALLBACK_LIBRARY_PATH` (note this variable can't be exported for security reasons):\n\n`DYLD_FALLBACK_LIBRARY_PATH=\"./:/usr/local/lib:/lib:/usr/lib:$DYLD_FALLBACK_LIBRARY_PATH\" hl myApplication.hl`\n\nAdditionally on MacOS, `otool` can be used to show what your application or `callfunc.hdll` wants to load. `install_name_tool` can be used to change the location of where a binary expects to load dependant shared libraries.\n\nWhen using the precompiled libraries provided by this project on recent versions of MacOS, they need to be manually approved to load by deleting the quarantine attribute such as `xattr -d com.apple.quarantine callfunc.hdll`.\n\n### Library installation\n\nIf you want to manually install the libraries on Windows, the libraries can be placed in a folder that is in the PATH environment variable. For example, if you have HashLink executable's folder in PATH, you can put the hdll there too.\n\nIf you want to manually install the libraries on Linux/MacOS, it is standard practice to put the libraries in `/usr/local/lib`. (However, this may not work in all Linux distributions. You can put a symlink in `/usr/lib` to the hdll using `ln -i -s /usr/local/lib/callfunc.hdll /usr/lib/`) For more information, see the man page for dlopen(3).\n\nWhen distributing your application, you should be using a software toolkit to produce an installer, a package for a distro's package manager, or a self-contained executable.\n\n## Javascript\n\nThere are no C libraries needed to be compiled for the Javascript target.\n\n## Performance\n\nThis library comprises of two layers, the Haxe code and the libffi C wrapper library. Because Haxe representations of data types is not straightforward, the values passed between the layers are serialized and deserialized.\n\nThis serialization process involves creating an array for to hold all the function arguments and the return value, and packing the values into array. The C wrapper will create pointers to the array for libffi to process into the stack. libffi executes the function and the return value is serialized to the array. Finally, the Haxe code will deserialize the return value.\n\nWhether to use Callfunc depends on many factors including performance, maintainability, and ease-of-use.\n\n## Tests\n\nTo run the unit tests, please look at the .travis.yml file.\n\nFor developing the native library with gcc/clang compilers, add `-D CALLFUNC_ADD_SANITIZER_FLAGS:bool=true` to the cmake command to enable the address sanitizer.\n\n## Contributing\n\nIf you have a bug report, bug fix, or missing feature, please file an issue or pull request on GitHub.\n\n## License\n\nSee [LICENSE file](LICENSE). Note that you must also comply with the license of libffi too.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchfoo%2Fcallfunc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchfoo%2Fcallfunc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchfoo%2Fcallfunc/lists"}