├── .gitattributes ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── azure-pipelines.yml ├── example ├── curlexample │ ├── Curl.hx │ └── CurlExample.hx └── randomexample │ └── RandomExample.hx ├── haxelib.json ├── hxcpp_build.xml ├── hxml ├── docs-gen.hxml ├── docs-xml.hxml ├── example_curl.cpp.hxml ├── example_curl.hl.hxml ├── example_curl.hxml ├── example_random.cpp.hxml ├── example_random.hl.hxml ├── example_random.hxml ├── test.cpp.hxml ├── test.hl.c.hxml ├── test.hl.hxml └── test.js.hxml ├── libs.hxml ├── script ├── azure-pipeline-templates │ └── install_cmake.yml ├── curl_opts.sh ├── export_ld_path.sh ├── get_chromedriver.sh ├── get_emsdk.sh ├── get_hashlink.sh ├── get_hashlink_bin.sh ├── get_haxe_bin.sh ├── get_libffi.sh ├── get_mingw_bin.sh ├── get_neko_bin.sh ├── hashlink_sdl.patch ├── template │ └── bin.md ├── test_build_emscripten_module.sh ├── test_js_headless.py └── zip_release.py ├── src ├── c │ ├── CMakeLists.txt │ ├── callfunc.c │ ├── callfunc.h │ └── callfunc_hxcpp.cpp └── haxe │ └── callfunc │ ├── AnyInt.hx │ ├── AutoInt.hx │ ├── AutoInt64.hx │ ├── BytesDataView.hx │ ├── BytesTools.hx │ ├── Callback.hx │ ├── Callfunc.hx │ ├── CoreDataType.hx │ ├── DataType.hx │ ├── DataView.hx │ ├── Debug.hx │ ├── Disposable.hx │ ├── Function.hx │ ├── Library.hx │ ├── Pointer.hx │ ├── StructAccess.hx │ ├── StructDef.hx │ ├── core │ ├── BasicPointer.hx │ ├── CallbackHandle.hx │ ├── Context.hx │ ├── CoreDataTypeTable.hx │ ├── DataTypeTools.hx │ ├── FunctionHandle.hx │ ├── LibraryHandle.hx │ ├── StructTypeHandle.hx │ ├── dummy │ │ └── DummyContext.hx │ ├── emscripten │ │ ├── EmCallback.hx │ │ ├── EmContext.hx │ │ ├── EmDataType.hx │ │ ├── EmFunction.hx │ │ ├── EmLibrary.hx │ │ ├── EmPointer.hx │ │ ├── EmStructType.hx │ │ ├── EmscriptenModule.hx │ │ └── ModuleTools.hx │ ├── impl │ │ ├── CallbackImpl.hx │ │ ├── ContextImpl.hx │ │ ├── ExternDef.hx │ │ ├── FunctionImpl.hx │ │ ├── LibraryImpl.hx │ │ ├── NativeUtil.hx │ │ ├── PointerImpl.hx │ │ └── StructTypeImpl.hx │ └── serialization │ │ ├── ArgSerializer.hx │ │ ├── DataValueSerializer.hx │ │ ├── NumberUtil.hx │ │ └── StructTypeSerializer.hx │ └── string │ ├── Encoder.hx │ ├── Encoding.hx │ └── StringUtil.hx ├── test.html ├── test.hxml └── test ├── c └── examplelib │ ├── CMakeLists.txt │ ├── examplelib.c │ └── examplelib.h └── haxe └── callfunc ├── ListDataTypes.hx ├── PerformanceTest.hx ├── TestAll.hx └── test ├── TestAnyInt.hx ├── TestCairoMatrix.hx ├── TestCairoSurface.hx ├── TestCallfunc.hx ├── TestDataView.hx ├── TestExamplelib.hx ├── TestPointer.hx └── TestStructAccess.hx /.gitattributes: -------------------------------------------------------------------------------- 1 | script/hashlink_sdl.patch text eol=crlf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio Code 2 | .vscode/ 3 | 4 | # Compiled output files 5 | out/ 6 | 7 | # Libraries for zip release 8 | bin/ 9 | 10 | # CMake 11 | CMakeLists.txt.user 12 | CMakeCache.txt 13 | CMakeFiles 14 | CMakeScripts 15 | Testing 16 | Makefile 17 | cmake_install.cmake 18 | install_manifest.txt 19 | compile_commands.json 20 | CTestTestfile.cmake 21 | _deps 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | 3 | language: haxe 4 | 5 | haxe: 6 | - "4.0.5" 7 | 8 | before_install: 9 | # Cmake 1.13 minimum 10 | - wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add - && 11 | sudo apt-add-repository 'deb https://apt.kitware.com/ubuntu/ xenial main' && 12 | sudo apt-get update && 13 | sudo apt-get install cmake 14 | # libraries used for testing 15 | - sudo apt-get install -y libcairo2 libpng-dev libjpeg-turbo8-dev libturbojpeg libvorbis-dev libopenal-dev libsdl2-dev libmbedtls-dev libuv1-dev 16 | - sudo ln -s /usr/lib/x86_64-linux-gnu/libturbojpeg.so.0.1.0 /usr/lib/x86_64-linux-gnu/libturbojpeg.so 17 | # dependencies 18 | - script/get_libffi.sh download linux-x86-64 19 | - script/get_libffi.sh install linux-x86-64 20 | - script/get_hashlink.sh download linux-x86-64 21 | - script/get_hashlink.sh install linux-x86-64 22 | 23 | install: 24 | - yes | haxelib install hxcpp 25 | - yes | haxelib install hashlink 26 | - yes | haxelib install test.hxml 27 | 28 | script: 29 | - mkdir -p out/ && cd out/ && /usr/bin/cmake .. -DCMAKE_BUILD_TYPE=Debug -DLIBFFI_INCLUDE_PATH:PATH=/usr/local/include/ -DLIBFFI_LIB_PATH:FILEPATH=/usr/local/lib/libffi.so 30 | - make && make examplelib 31 | - cd ../ 32 | - haxe hxml/test.cpp.hxml -D callfunc_unit_test 33 | - haxe hxml/test.hl.hxml -D callfunc_unit_test 34 | - haxe hxml/test.hl.c.hxml -D callfunc_unit_test 35 | - gcc -g -o out/hl/test/test out/hl/test/test.c -I out/hl/test/ -std=c11 -lhl -lm out/callfunc/callfunc.hdll 36 | - source script/export_ld_path.sh && ./out/cpp/TestAll-debug 37 | - source script/export_ld_path.sh && hl out/hl/test.hl 38 | - source script/export_ld_path.sh && out/hl/test/test 39 | - haxe hxml/example_curl.cpp.hxml -D callfunc_unit_test 40 | - haxe hxml/example_curl.hl.hxml -D callfunc_unit_test 41 | - haxe hxml/example_random.cpp.hxml -D callfunc_unit_test 42 | - haxe hxml/example_random.hl.hxml -D callfunc_unit_test 43 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.1.0 (2020-04-06) 4 | 5 | * Fixed: `Library.dispose()` now disposes both the functions and library to fix memory leak of the library. 6 | * Fixed: Disposing functions would leak memory if they returned structures by value. 7 | * Added: `Library.disposeFunctions()` for use when you only want to dispose the functions. 8 | 9 | * For HashLink, using the latest callfunc.hdll is recommended to get all of the bug fixes above. 10 | 11 | ## 1.0.1 (2020-03-31) 12 | 13 | * Fixed: Calling foreign functions with callbacks more than once may cause segfault because an internal Callfunc function was garbage collected. 14 | 15 | ## 1.0.0 (2020-03-27) 16 | 17 | * Added: `CoreDataTypeTable` for internal use to improve performance. 18 | * Changed: Documentation and examples were updated from `var callfunc = Callfunc.instance()` to `var ffi = Callfunc.instance()` to avoid shadowing the callfunc package. 19 | * Version bumped to 1.0.0 to indicate stable API as for SemVer. 20 | 21 | ## 0.5.1 (2019-12-23) 22 | 23 | * Fixed: removed mistakenly added `trace()` in `Pointer.getString()`. 24 | 25 | ## 0.5.0 (2019-12-06) 26 | 27 | * Fixed: Strings not properly null-terminated when passing them to the Callfunc C library. 28 | * Changed: The API was overhauled. 29 | * A new high level API was written and the low level API was moved to the `core` package. 30 | * Defined library functions are now stored in the library object so you don't need to keep a reference to each one to dispose them later. 31 | * Functions are now accessed by array access or field access syntax on the `library.s` field. 32 | * Most static extensions are now part of the high level API so that syntax is more natural. That is, `call()` is used instead of `callVA()` or `getCallable()`. 33 | * The high level API is more object-oriented, for example, `pointer.free()`, `pointer.getString()`, etc. 34 | * Class `Callfunc` no longer implements `Context`; `Callfunc` wraps `Context` now. For unsupported targets, `Callfunc` wraps `DummyContext`. 35 | * Emscripten users still need to make `Callfunc` wrap `EmContext` and set that as the singleton. 36 | * String methods now have an explicit null-terminator parameter. 37 | * Please review the readme to update your code to the new API. 38 | * Added: Support for calling variadic (varargs) functions in Emscripten 39 | * Added: `AnyInt` abstract for better handling of `Int` and `Int64` at runtime. 40 | 41 | ## 0.4.0 (2019-08-27) 42 | 43 | * Changed: [Backwards incompatible] `Pointer`: The parameters in `arrayGet()` and `arraySet()` were reordered to be closer with standard array or map methods. 44 | * Added: `dataType` field to `Pointer` for default data type. 45 | 46 | ## 0.3.0 (2019-08-23) 47 | 48 | * Fixed: Emscripten pointer get() for unsigned int data types returning signed values. 49 | * Added: StructAccess 50 | * Added: Support for pass by value structs 51 | 52 | ## 0.2.1 (2019-07-03) 53 | 54 | * Fixed: Support for HL/C 55 | * Fixed: Double free when closing library and memory leak when error thrown opening library/function. 56 | 57 | ## 0.2.0 (2019-06-26) 58 | 59 | * Fixed: Signed int8 and int16 conversion from int32 or unsigned int8. 60 | * Changed `Memory.pointerToBytes()` to never free the pointer. 61 | * Added: `Pointer.arrayGet()` and `Pointer.arraySet()`. 62 | * Added: `Callfunc.newCallback()`. 63 | * Added: JS+Emscripten interface support. 64 | * Added: `DataView` and `Memory.pointerToDataView()`. 65 | * Added: More data types. 66 | * Added: `Library.newVariadicFunction()`. 67 | * Added: `FunctionTools` and `PointerTools`. 68 | 69 | ## 0.1.0 (2019-06-19) 70 | 71 | * First release 72 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(callfunc) 3 | add_subdirectory(src/c/ callfunc/) 4 | add_subdirectory(test/c/examplelib/ examplelib/) 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2020 Christopher Foo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/curlexample/Curl.hx: -------------------------------------------------------------------------------- 1 | package curlexample; 2 | 3 | import callfunc.Function; 4 | import callfunc.AnyInt; 5 | import callfunc.Callback; 6 | import callfunc.Callfunc; 7 | import callfunc.DataType; 8 | import callfunc.Library; 9 | import callfunc.Pointer; 10 | 11 | // This class provides a few methods to the libcurl C functions. 12 | // It demonstrates how to convert Dynamic functions into typed functions. 13 | // In a real libcurl binding, you would wrap most of libcurl functionality 14 | // into object oriented manner. 15 | 16 | typedef CurlWriteFunction = (buffer:Pointer, size:AnyInt, count:AnyInt)->Int; 17 | 18 | class Curl { 19 | // Constants from libcurl header file 20 | public static final GLOBAL_SSL = 1 << 0; 21 | public static final GLOBAL_WIN32 = 1 << 1; 22 | public static final GLOBAL_ALL = GLOBAL_SSL | GLOBAL_WIN32; 23 | public static final OPT_URL = 10000 + 2; 24 | public static final OPT_VERBOSE = 0 + 41; 25 | public static final OPT_WRITE_FUNCTION = 20000 + 11; 26 | 27 | // Functions available for calling 28 | public final globalInit:Int->Void; 29 | public final globalCleanup:Void->Void; 30 | public final easyInit:Void->Pointer; 31 | public final easySetOptPointer:(Pointer, Int, Pointer)->Int; 32 | public final easySetOptLong:(Pointer, Int, Int)->Int; 33 | public final easyPerform:Pointer->Int; 34 | 35 | final ffi:Callfunc; 36 | final library:Library; 37 | 38 | public function new() { 39 | ffi = Callfunc.instance(); 40 | library = ffi.openLibrary(getLibraryName()); 41 | 42 | globalInit = library.define( 43 | "curl_global_init", 44 | [DataType.SInt] 45 | ).call; 46 | globalCleanup = library.define( 47 | "curl_global_cleanup" 48 | ).call; 49 | easyInit = library.define( 50 | "curl_easy_init", 51 | [], 52 | DataType.Pointer 53 | ).call; 54 | easySetOptPointer = library.defineVariadic( 55 | "curl_easy_setopt", 56 | [DataType.Pointer, DataType.SInt, DataType.Pointer], 57 | 2, 58 | DataType.SInt, 59 | "curl_easy_setopt:pointer" 60 | ).call; 61 | easySetOptLong = library.defineVariadic( 62 | "curl_easy_setopt", 63 | [DataType.Pointer, DataType.SInt, DataType.SLong], 64 | 2, 65 | DataType.SInt, 66 | "curl_easy_setopt:long" 67 | ).call; 68 | easyPerform = library.define( 69 | "curl_easy_perform", 70 | [DataType.Pointer], 71 | DataType.SInt 72 | ).call; 73 | } 74 | 75 | function getLibraryName():String { 76 | switch Sys.systemName() { 77 | case "Windows": 78 | return "libcurl.dll"; 79 | case "Mac": 80 | return "libcurl.dylib"; 81 | default: 82 | return "libcurl.so"; 83 | } 84 | } 85 | 86 | public function newWriteFunction(callback:CurlWriteFunction):Callback { 87 | var handle = ffi.wrapCallback( 88 | callback, 89 | [DataType.Pointer, DataType.Size, DataType.Size, DataType.Pointer], 90 | DataType.Size 91 | ); 92 | 93 | return handle; 94 | } 95 | 96 | public function dispose() { 97 | library.dispose(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /example/curlexample/CurlExample.hx: -------------------------------------------------------------------------------- 1 | package curlexample; 2 | 3 | import callfunc.AnyInt; 4 | import callfunc.Callfunc; 5 | import callfunc.Pointer; 6 | import haxe.io.Bytes; 7 | import haxe.io.BytesBuffer; 8 | 9 | using haxe.Int64; 10 | 11 | // This example shows how to interact with libcurl to download a web page. 12 | // We have a Curl class that helps define types for some type safety. 13 | class CurlExample { 14 | public static function main() { 15 | var ffi = Callfunc.instance(); 16 | var curl = new Curl(); 17 | 18 | curl.globalInit(Curl.GLOBAL_ALL); 19 | 20 | var handle = curl.easyInit(); 21 | curl.easySetOptLong(handle, Curl.OPT_VERBOSE, 1); 22 | 23 | // Allocate a ASCII/UTF8 string on the heap, pass the pointer, 24 | // libcurl will copy the string, then we free it. 25 | var url = ffi.allocString("https://haxe.org/"); 26 | curl.easySetOptPointer(handle, Curl.OPT_URL, url); 27 | url.free(); 28 | 29 | // This section below demonstrates how to use callback functions. 30 | // 31 | // Since size_t is dependent on the CPU, Callfunc will use either 32 | // Int or Int64. But we don't want to write two functions, so we 33 | // accept Int64 but return Int. Callfunc provides AnyInt 34 | // which is an abstract over the Dynamic type. It provides methods 35 | // for checking the type at runtime and converting as needed. 36 | 37 | var receiveBuffer = new BytesBuffer(); 38 | 39 | function writeCallback(buffer:Pointer, size:AnyInt, 40 | count:AnyInt):Int { 41 | 42 | // size is guaranteed to be 1 byte from libcurl. 43 | // We choose an AND value that is guaranteed to not be negative 44 | // and won't lose data in 32/64-bit truncation and promotion. 45 | // 16 MB is more than enough as kernel receive sizes are near 4 KB. 46 | var processedCount = (size.toInt64() * (count.toInt64() & 0xffffff)).toInt(); 47 | var view = buffer.getDataView(processedCount); 48 | 49 | receiveBuffer.addBytes(view.buffer, view.byteOffset, view.byteLength); 50 | 51 | return processedCount; 52 | } 53 | 54 | var writeCallbackInfo = curl.newWriteFunction(writeCallback); 55 | curl.easySetOptPointer(handle, Curl.OPT_WRITE_FUNCTION, 56 | writeCallbackInfo.pointer); 57 | 58 | // Make libcurl do things 59 | var error = curl.easyPerform(handle); 60 | var receivedBytes = receiveBuffer.getBytes(); 61 | 62 | writeCallbackInfo.dispose(); 63 | 64 | // Print out some info 65 | trace('Perform error: $error'); 66 | trace('Got ${receivedBytes.length} byte(s)'); 67 | trace(' ${receivedBytes.sub(0, 100)}'); 68 | 69 | // Delete the library and function handles. 70 | // Normally this isn't necessary in an application as you want to 71 | // keep functions around and use them repeatedly. 72 | curl.globalCleanup(); 73 | curl.dispose(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /example/randomexample/RandomExample.hx: -------------------------------------------------------------------------------- 1 | package randomexample; 2 | 3 | import callfunc.AnyInt; 4 | import callfunc.Callfunc; 5 | import callfunc.DataType; 6 | import haxe.ds.Option; 7 | import haxe.io.Bytes; 8 | 9 | // This example shows how to obtain random bytes from the OS using system 10 | // libraries. 11 | class RandomExample { 12 | static final bufferLength = 16; 13 | static final ffi = Callfunc.instance(); 14 | 15 | public static function main() { 16 | final systemName = Sys.systemName(); 17 | var result; 18 | 19 | quickLog('Operating System: $systemName'); 20 | 21 | switch systemName { 22 | case "Windows": 23 | result = getWindowsBytes(); 24 | case "Linux": 25 | result = getLinuxBytes(); 26 | case "Mac": 27 | result = getMacBytes(); 28 | default: 29 | quickLog("Unsupported OS."); 30 | Sys.exit(1); 31 | return; 32 | } 33 | 34 | switch result { 35 | case Some(bytes): 36 | quickLog("OK"); 37 | final stdOut = Sys.stdout(); 38 | stdOut.writeString(bytes.toHex()); 39 | stdOut.writeString("\n"); 40 | quickLog("Done. Goodbye!"); 41 | case None: 42 | quickLog("OS is unable to provide random bytes."); 43 | Sys.exit(1); 44 | } 45 | } 46 | 47 | static function quickLog(text:String) { 48 | final stdErr = Sys.stderr(); 49 | stdErr.writeString(text); 50 | stdErr.writeString("\n"); 51 | } 52 | 53 | static function getWindowsBytes():Option { 54 | final STDCALL = 2; 55 | final library = ffi.openLibrary("Advapi32.dll"); 56 | 57 | library.define( 58 | "SystemFunction036", 59 | [DataType.Pointer, DataType.ULong], 60 | DataType.SInt, 61 | STDCALL 62 | ); 63 | 64 | final buffer = ffi.alloc(bufferLength); 65 | final result = library.s.SystemFunction036.call(buffer, bufferLength); 66 | var bytes; 67 | 68 | if (result == 1) { 69 | bytes = Some(buffer.getDataView(bufferLength).toBytes()); 70 | } else { 71 | bytes = None; 72 | } 73 | 74 | buffer.free(); 75 | library.dispose(); 76 | return bytes; 77 | } 78 | 79 | static function getLinuxBytes():Option { 80 | final library = ffi.openLibrary("libc.so.6"); 81 | 82 | library.define( 83 | "getrandom", 84 | [DataType.Pointer, DataType.Size, DataType.UInt], 85 | DataType.Size 86 | ); 87 | 88 | final buffer = ffi.alloc(bufferLength); 89 | final result:AnyInt = library.s.getrandom.call(buffer, bufferLength, 0); 90 | var bytes; 91 | 92 | if (result.toInt() == bufferLength) { 93 | bytes = Some(buffer.getDataView(bufferLength).toBytes()); 94 | } else { 95 | bytes = None; 96 | } 97 | 98 | buffer.free(); 99 | library.dispose(); 100 | return bytes; 101 | } 102 | 103 | static function getMacBytes():Option { 104 | final library = ffi.openLibrary("libSystem.dylib"); 105 | 106 | library.define( 107 | "arc4random_buf", 108 | [DataType.Pointer, DataType.Size] 109 | ); 110 | 111 | final buffer = ffi.alloc(bufferLength); 112 | library.s.arc4random_buf.call(buffer, bufferLength); 113 | 114 | final bytes = Some(buffer.getDataView(bufferLength).toBytes()); 115 | 116 | buffer.free(); 117 | library.dispose(); 118 | return bytes; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /haxelib.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "callfunc", 3 | "url" : "https://github.com/chfoo/callfunc/", 4 | "license": "MIT", 5 | "tags": ["native", "ffi"], 6 | "description": "Foreign function interface library using libffi", 7 | "version": "1.1.0", 8 | "classPath": "src/haxe", 9 | "releasenote": "Fixed library dispose memory leak. (See changelog)", 10 | "contributors": ["chfoo"], 11 | "dependencies": { 12 | "safety": "", 13 | "unifill": "" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /hxcpp_build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /hxml/docs-gen.hxml: -------------------------------------------------------------------------------- 1 | -cmd haxelib run dox -i out/xml/ -o out/docs/ --include "callfunc" --title "Callfunc API Documentation" 2 | -------------------------------------------------------------------------------- /hxml/docs-xml.hxml: -------------------------------------------------------------------------------- 1 | -cp src/haxe 2 | libs.hxml 3 | -D doc-gen 4 | --macro include('callfunc', false) 5 | --no-output 6 | 7 | --each 8 | 9 | -xml out/xml/hl.xml 10 | -hl out/dummy.hl 11 | --macro include('callfunc.impl') 12 | 13 | --next 14 | 15 | -xml out/xml/cpp.xml 16 | -cpp out/cpp/ 17 | --macro include('callfunc.impl') 18 | 19 | --next 20 | 21 | -xml out/xml/js.xml 22 | -js out/dummy.js 23 | --macro include('callfunc.emscripten') 24 | -------------------------------------------------------------------------------- /hxml/example_curl.cpp.hxml: -------------------------------------------------------------------------------- 1 | hxml/example_curl.hxml 2 | 3 | -cpp out/cpp/ 4 | -------------------------------------------------------------------------------- /hxml/example_curl.hl.hxml: -------------------------------------------------------------------------------- 1 | hxml/example_curl.hxml 2 | 3 | -hl out/hl/example_curl.hl 4 | -------------------------------------------------------------------------------- /hxml/example_curl.hxml: -------------------------------------------------------------------------------- 1 | libs.hxml 2 | -cp src/haxe/ 3 | -cp example/ 4 | -main curlexample.CurlExample 5 | -------------------------------------------------------------------------------- /hxml/example_random.cpp.hxml: -------------------------------------------------------------------------------- 1 | hxml/example_random.hxml 2 | 3 | -cpp out/cpp/ 4 | -------------------------------------------------------------------------------- /hxml/example_random.hl.hxml: -------------------------------------------------------------------------------- 1 | hxml/example_random.hxml 2 | 3 | -hl out/hl/example_random.hl 4 | -------------------------------------------------------------------------------- /hxml/example_random.hxml: -------------------------------------------------------------------------------- 1 | libs.hxml 2 | -cp src/haxe/ 3 | -cp example/ 4 | -main randomexample.RandomExample 5 | -------------------------------------------------------------------------------- /hxml/test.cpp.hxml: -------------------------------------------------------------------------------- 1 | test.hxml 2 | # -lib hxcpp-debug-server 3 | -cpp out/cpp 4 | -------------------------------------------------------------------------------- /hxml/test.hl.c.hxml: -------------------------------------------------------------------------------- 1 | test.hxml 2 | -hl out/hl/test/test.c 3 | -------------------------------------------------------------------------------- /hxml/test.hl.hxml: -------------------------------------------------------------------------------- 1 | test.hxml 2 | -hl out/hl/test.hl 3 | -------------------------------------------------------------------------------- /hxml/test.js.hxml: -------------------------------------------------------------------------------- 1 | test.hxml 2 | -js out/js/test.js 3 | -------------------------------------------------------------------------------- /libs.hxml: -------------------------------------------------------------------------------- 1 | -lib safety 2 | -lib unifill 3 | -------------------------------------------------------------------------------- /script/azure-pipeline-templates/install_cmake.yml: -------------------------------------------------------------------------------- 1 | # cmake is installed in /usr/local at version 1.12 which is too old 2 | steps: 3 | - script: | 4 | curl -L -s -S -f -m 60 -o /tmp/cmake.sh https://github.com/Kitware/CMake/releases/download/v3.16.1/cmake-3.16.1-Linux-x86_64.sh 5 | chmod +x /tmp/cmake.sh 6 | sudo /tmp/cmake.sh --skip-license --prefix=/usr/local/ 7 | condition: and( 8 | succeeded(), 9 | eq(variables['Agent.OS'], 'Linux') 10 | ) 11 | -------------------------------------------------------------------------------- /script/curl_opts.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export CURL_OPTS="-L -s -S -f -m 60" 4 | -------------------------------------------------------------------------------- /script/export_ld_path.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | SCRIPT_DIR=$(dirname "$BASH_SOURCE") 4 | 5 | NEW_PATHS="$SCRIPT_DIR/../out/callfunc/" 6 | NEW_PATHS+=":$SCRIPT_DIR/../out/examplelib/" 7 | NEW_PATHS+=":/usr/local/lib/" 8 | 9 | LD_LIBRARY_PATH="$NEW_PATHS:$LD_LIBRARY_PATH" 10 | 11 | export LD_LIBRARY_PATH 12 | -------------------------------------------------------------------------------- /script/get_chromedriver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | SCRIPT_DIR="$PWD"/$(dirname "$BASH_SOURCE") 5 | 6 | mkdir -p "$SCRIPT_DIR/../out" 7 | cd "$SCRIPT_DIR/../out" 8 | 9 | source "$SCRIPT_DIR/curl_opts.sh" 10 | 11 | curl $CURL_OPTS -O https://chromedriver.storage.googleapis.com/78.0.3904.105/chromedriver_linux64.zip 12 | unzip chromedriver_linux64.zip 13 | 14 | chmod +x chromedriver 15 | -------------------------------------------------------------------------------- /script/get_emsdk.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | SCRIPT_DIR="$PWD"/$(dirname "$BASH_SOURCE") 5 | 6 | mkdir -p "$SCRIPT_DIR/../out" 7 | cd "$SCRIPT_DIR/../out" 8 | 9 | git clone --depth 10 https://github.com/emscripten-core/emsdk emsdk 10 | 11 | cd emsdk 12 | 13 | ./emsdk install latest 14 | ./emsdk activate latest 15 | 16 | -------------------------------------------------------------------------------- /script/get_hashlink.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | SCRIPT_DIR="$PWD"/$(dirname "$BASH_SOURCE") 5 | 6 | function download { 7 | mkdir -p "$SCRIPT_DIR/../out" 8 | cd "$SCRIPT_DIR/../out" 9 | source "$SCRIPT_DIR/curl_opts.sh" 10 | 11 | curl $CURL_OPTS -o hashlink-1.11.tar.gz "https://github.com/HaxeFoundation/hashlink/archive/1.11.tar.gz" 12 | 13 | tar -xf hashlink-1.11.tar.gz 14 | mv hashlink-1.11 hashlink 15 | } 16 | 17 | function install { 18 | cd "$SCRIPT_DIR/../out/hashlink" 19 | 20 | make 21 | sudo make install 22 | } 23 | 24 | function install_msbuild { 25 | local PLATFORM=$1 26 | 27 | find_msbuild 28 | cd "$SCRIPT_DIR/../out/hashlink" 29 | 30 | case $PLATFORM in 31 | windows-x86) 32 | BUILD_PLATFORM=Win32 33 | ;; 34 | windows-x86-64) 35 | BUILD_PLATFORM=x64 36 | ;; 37 | esac 38 | 39 | "$MSBUILD" hl.sln \ 40 | /p:Configuration=Release \ 41 | /p:Platform=$BUILD_PLATFORM \ 42 | /p:WindowsTargetPlatformVersion=10.0 \ 43 | /p:PlatformToolset=v142 44 | 45 | mkdir -p /c/hl 46 | cp -R -p -P -v Release/* /c/hl/ 47 | 48 | set +x 49 | echo "##vso[task.prependpath]c:/hl/" 50 | set -x 51 | } 52 | 53 | function find_msbuild { 54 | MSBUILD=`"c:/Program Files (x86)/Microsoft Visual Studio/Installer"/vswhere.exe -latest -requires Microsoft.Component.MSBuild -find "MSBuild/**/Bin/MSBuild.exe"` 55 | MSBUILD=${MSBUILD//\\/\//} # replace backslash to forward slash 56 | } 57 | 58 | COMMAND=$1 59 | PLATFORM=$2 60 | 61 | case $COMMAND in 62 | download) 63 | download $PLATFORM 64 | ;; 65 | install) 66 | case $PLATFORM in 67 | macos|linux-x86-64) 68 | install $PLATFORM 69 | ;; 70 | windows-x86|windows-x86-64) 71 | install_msbuild $PLATFORM 72 | ;; 73 | *) 74 | echo "Unknown platform $PLATFORM" 75 | ;; 76 | esac 77 | ;; 78 | *) 79 | echo "Unknown command $COMMAND" 80 | exit 2 81 | esac 82 | -------------------------------------------------------------------------------- /script/get_hashlink_bin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | SCRIPT_DIR="$PWD"/$(dirname "$BASH_SOURCE") 5 | 6 | function download { 7 | local PLATFORM=$1 8 | mkdir -p "$SCRIPT_DIR/../out" 9 | cd "$SCRIPT_DIR/../out" 10 | source "$SCRIPT_DIR/curl_opts.sh" 11 | 12 | case $PLATFORM in 13 | windows-x86) 14 | VERSION="1.10" 15 | FILE="hl-1.10.0-win.zip" 16 | ARCHIVE_DIR="hl-1.10.0-win" 17 | ;; 18 | windows-x86-64) 19 | VERSION="1.11" 20 | FILE="hl-1.11.0-win.zip" 21 | ARCHIVE_DIR="hl-1.11.0-win" 22 | ;; 23 | *) 24 | echo "Unknown platform $PLATFORM" 25 | exit 2 26 | ;; 27 | esac 28 | 29 | curl $CURL_OPTS -O "https://github.com/HaxeFoundation/hashlink/releases/download/$VERSION/$FILE" 30 | 31 | case $FILE in 32 | *.tar.gz) tar -xf "$FILE" ;; 33 | *.zip) unzip "$FILE" ;; 34 | esac 35 | 36 | mv "$ARCHIVE_DIR" hashlink 37 | } 38 | 39 | function install { 40 | local PLATFORM=$1 41 | cd "$SCRIPT_DIR/../out/hashlink" 42 | 43 | case $PLATFORM in 44 | linux-x86-64|macos) install_unix ;; 45 | windows-x86|windows-x86-64) install_windows ;; 46 | esac 47 | } 48 | 49 | function install_unix { 50 | for name in hl; do 51 | sudo cp -p -P $name /usr/local/bin/ 52 | done 53 | 54 | for name in libhl.*; do 55 | sudo cp -p -P $name /usr/local/lib/ 56 | done 57 | 58 | for name in *.hdll; do 59 | sudo cp -p -P $name /usr/local/lib/ 60 | done 61 | 62 | for name in include/*; do 63 | sudo cp -p -P $name /usr/local/include/ 64 | done 65 | 66 | } 67 | 68 | function install_windows { 69 | mkdir -p /c/hl 70 | cp -R -p -P -v * /c/hl/ 71 | 72 | set +x 73 | echo "##vso[task.prependpath]c:/hl/" 74 | set -x 75 | } 76 | 77 | COMMAND=$1 78 | PLATFORM=$2 79 | 80 | case $COMMAND in 81 | download) 82 | download $PLATFORM 83 | ;; 84 | install) 85 | install $PLATFORM 86 | ;; 87 | *) 88 | echo "Unknown command $COMMAND" 89 | exit 2 90 | esac 91 | -------------------------------------------------------------------------------- /script/get_haxe_bin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | SCRIPT_DIR="$PWD"/$(dirname "$BASH_SOURCE") 5 | 6 | # install paths based on https://github.com/travis-ci/travis-build/blob/2d9a70b31e1669c4126994561a395a08b65ad737/lib/travis/build/script/haxe.rb 7 | 8 | function download { 9 | local PLATFORM=$1 10 | mkdir -p "$SCRIPT_DIR/../out" 11 | cd "$SCRIPT_DIR/../out" 12 | source "$SCRIPT_DIR/curl_opts.sh" 13 | 14 | ARCHIVE_DIR="haxe_20191217082701_67feacebc" 15 | 16 | case $PLATFORM in 17 | linux-x86-64) 18 | FILE="haxe-4.0.5-linux64.tar.gz" 19 | ;; 20 | macos) 21 | FILE="haxe-4.0.5-osx.tar.gz" 22 | ;; 23 | windows-x86) 24 | FILE="haxe-4.0.5-win.zip" 25 | ;; 26 | windows-x86-64) 27 | FILE="haxe-4.0.5-win64.zip" 28 | ;; 29 | *) 30 | echo "Unknown platform $PLATFORM" 31 | exit 2 32 | ;; 33 | esac 34 | 35 | curl $CURL_OPTS -O "https://github.com/HaxeFoundation/haxe/releases/download/4.0.5/$FILE" 36 | 37 | case $FILE in 38 | *.tar.gz) tar -xf "$FILE" ;; 39 | *.zip) unzip "$FILE" ;; 40 | esac 41 | 42 | mv "$ARCHIVE_DIR" haxe 43 | } 44 | 45 | function install { 46 | local PLATFORM=$1 47 | cd "$SCRIPT_DIR/../out/haxe" 48 | 49 | case $PLATFORM in 50 | linux-x86-64|macos) install_unix ;; 51 | windows-x86|windows-x86-64) install_windows ;; 52 | esac 53 | } 54 | 55 | function install_unix { 56 | for name in haxe haxelib; do 57 | sudo cp -p -P $name /usr/local/bin/ 58 | done 59 | 60 | sudo mkdir -p /usr/local/lib/haxe/ 61 | sudo cp -R -p -P std /usr/local/lib/haxe/ 62 | 63 | export HAXE_STD_PATH=/usr/local/lib/haxe/std 64 | 65 | mkdir -p ./lib 66 | haxelib setup ./lib 67 | } 68 | 69 | function install_windows { 70 | mkdir -p /c/haxe 71 | cp -R -p -P -v * /c/haxe/ 72 | 73 | HAXE_STD_PATH="c:/haxe/std/" 74 | set +x 75 | echo "##vso[task.setvariable variable=HAXE_STD_PATH]$HAXE_STD_PATH" 76 | echo "##vso[task.prependpath]c:/haxe/" 77 | set -x 78 | 79 | mkdir -p /c/haxe/lib 80 | /c/haxe/haxelib setup c:/haxe/lib 81 | } 82 | 83 | COMMAND=$1 84 | PLATFORM=$2 85 | 86 | case $COMMAND in 87 | download) 88 | download $PLATFORM 89 | ;; 90 | install) 91 | install $PLATFORM 92 | ;; 93 | *) 94 | echo "Unknown command $COMMAND" 95 | exit 2 96 | esac 97 | -------------------------------------------------------------------------------- /script/get_libffi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | SCRIPT_DIR="$PWD"/$(dirname "$BASH_SOURCE") 5 | 6 | function download { 7 | mkdir -p "$SCRIPT_DIR/../out" 8 | cd "$SCRIPT_DIR/../out" 9 | source "$SCRIPT_DIR/curl_opts.sh" 10 | 11 | curl $CURL_OPTS -O "https://github.com/libffi/libffi/releases/download/v3.3/libffi-3.3.tar.gz" 12 | 13 | tar -xf libffi-3.3.tar.gz 14 | mv libffi-3.3 libffi 15 | } 16 | 17 | function install { 18 | cd "$SCRIPT_DIR/../out/libffi" 19 | 20 | ## Running autogen is only required for git sources, 21 | ## not needed for tarball releases 22 | # ./autogen.sh 23 | ./configure --disable-docs 24 | make 25 | sudo make install 26 | } 27 | 28 | function install_vcpkg_windows { 29 | local PLATFORM=$1 30 | 31 | pushd $VCPKG_INSTALLATION_ROOT 32 | 33 | if [[ ! -f "./vcpkg.exe" ]]; then 34 | echo "vcpkg.exe not found in this directory" 35 | exit 1 36 | fi 37 | 38 | case $PLATFORM in 39 | windows-x86) 40 | ./vcpkg.exe install libffi:x86-windows 41 | ;; 42 | windows-x86-64) 43 | ./vcpkg.exe install libffi:x64-windows 44 | ;; 45 | esac 46 | 47 | popd 48 | } 49 | 50 | COMMAND=$1 51 | PLATFORM=$2 52 | 53 | case $COMMAND in 54 | download) 55 | case $PLATFORM in 56 | linux-x86-64|macos) 57 | download 58 | ;; 59 | *) 60 | echo "Uknown platform $PLATFORM" 61 | exit 2 62 | ;; 63 | esac 64 | ;; 65 | install) 66 | case $PLATFORM in 67 | linux-x86-64|macos) 68 | install 69 | ;; 70 | windows-x86|windows-x86-64) 71 | install_vcpkg_windows $PLATFORM 72 | ;; 73 | *) 74 | echo "Uknown platform $PLATFORM" 75 | exit 2 76 | ;; 77 | esac 78 | ;; 79 | *) 80 | echo "Unknown command $COMMAND" 81 | exit 2 82 | esac 83 | -------------------------------------------------------------------------------- /script/get_mingw_bin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | SCRIPT_DIR="$PWD"/$(dirname "$BASH_SOURCE") 5 | 6 | function get_vars { 7 | case $PLATFORM in 8 | windows-x86) 9 | URL="https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win32/Personal%20Builds/mingw-builds/8.1.0/threads-posix/dwarf/i686-8.1.0-release-posix-dwarf-rt_v6-rev0.7z/download" 10 | FILE="i686-8.1.0-release-posix-dwarf-rt_v6-rev0.7z" 11 | ARCHIVE_DIR="mingw32" 12 | RELEASE_NAME="i686-8.1.0-release-posix-dwarf-rt_v6-rev0" 13 | ;; 14 | windows-x86-64) 15 | URL="https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/8.1.0/threads-posix/seh/x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z/download" 16 | FILE="x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z" 17 | ARCHIVE_DIR="mingw64" 18 | RELEASE_NAME="x86_64-8.1.0-release-posix-seh-rt_v6-rev0" 19 | ;; 20 | *) 21 | echo "Unknown platform $PLATFORM" 22 | exit 2 23 | ;; 24 | esac 25 | } 26 | 27 | function download { 28 | local PLATFORM=$1 29 | mkdir -p "$SCRIPT_DIR/../out" 30 | cd "$SCRIPT_DIR/../out" 31 | source "$SCRIPT_DIR/curl_opts.sh" 32 | 33 | get_vars $PLATFORM 34 | 35 | curl $CURL_OPTS -o $FILE "$URL" 36 | 37 | case $FILE in 38 | *.tar.gz) tar -xf "$FILE" ;; 39 | *.zip) unzip "$FILE" ;; 40 | *.7z) 7z x "$FILE" ;; 41 | esac 42 | 43 | mkdir -p "$RELEASE_NAME" 44 | mv "$ARCHIVE_DIR" "$RELEASE_NAME"/ 45 | } 46 | 47 | function install { 48 | local PLATFORM=$1 49 | cd "$SCRIPT_DIR/../out/" 50 | 51 | case $PLATFORM in 52 | windows-x86|windows-x86-64) 53 | install_windows 54 | ;; 55 | *) 56 | echo "Unknown platform $PLATFORM" 57 | exit 2 58 | ;; 59 | esac 60 | } 61 | 62 | 63 | function install_windows { 64 | get_vars $PLATFORM 65 | 66 | mkdir -p /c/mingw-w64 67 | cp -R -p -P -v "$RELEASE_NAME" /c/mingw-w64/ 68 | 69 | echo "##vso[task.prependpath]c:/mingw-w64/$RELEASE_NAME/$ARCHIVE_DIR/bin" 70 | } 71 | 72 | COMMAND=$1 73 | PLATFORM=$2 74 | 75 | case $COMMAND in 76 | download) 77 | download $PLATFORM 78 | ;; 79 | install) 80 | install $PLATFORM 81 | ;; 82 | *) 83 | echo "Unknown command $COMMAND" 84 | exit 2 85 | esac 86 | -------------------------------------------------------------------------------- /script/get_neko_bin.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | SCRIPT_DIR="$PWD"/$(dirname "$BASH_SOURCE") 5 | 6 | # install paths based on https://github.com/travis-ci/travis-build/blob/2d9a70b31e1669c4126994561a395a08b65ad737/lib/travis/build/script/haxe.rb 7 | 8 | function download { 9 | PLATFORM=$1 10 | mkdir -p "$SCRIPT_DIR/../out" 11 | cd "$SCRIPT_DIR/../out" 12 | source "$SCRIPT_DIR/curl_opts.sh" 13 | 14 | case $PLATFORM in 15 | macos) 16 | FILE="neko-2.3.0-osx64.tar.gz" 17 | ARCHIVE_DIR="neko-2.3.0-osx64" 18 | ;; 19 | linux-x86-64) 20 | FILE="neko-2.3.0-linux64.tar.gz" 21 | ARCHIVE_DIR="neko-2.3.0-linux64" 22 | ;; 23 | windows-x86) 24 | FILE="neko-2.3.0-win.zip" 25 | ARCHIVE_DIR="neko-2.3.0-win" 26 | ;; 27 | windows-x86-64) 28 | FILE="neko-2.3.0-win64.zip" 29 | ARCHIVE_DIR="neko-2.3.0-win64" 30 | ;; 31 | *) 32 | echo "Unknown platform $PLATFORM" 33 | exit 2 34 | ;; 35 | esac 36 | 37 | curl $CURL_OPTS -O "https://github.com/HaxeFoundation/neko/releases/download/v2-3-0/$FILE" 38 | 39 | case $FILE in 40 | *.tar.gz) tar -xf "$FILE" ;; 41 | *.zip) unzip "$FILE" ;; 42 | esac 43 | 44 | mv "$ARCHIVE_DIR" neko 45 | } 46 | 47 | function install { 48 | local PLATFORM=$1 49 | cd "$SCRIPT_DIR/../out/neko" 50 | 51 | case $PLATFORM in 52 | linux-x86-64|macos) install_unix $PLATFORM ;; 53 | windows-x86|windows-x86-64) install_windows ;; 54 | esac 55 | } 56 | 57 | function install_unix { 58 | local PLATFORM=$1 59 | 60 | for name in neko nekoc nekoml nekotools; do 61 | sudo cp -p -P $name /usr/local/bin/ 62 | done 63 | 64 | for name in libneko.*; do 65 | sudo cp -p -P $name /usr/local/lib/ 66 | done 67 | 68 | for name in include/*; do 69 | sudo cp -p -P $name /usr/local/include/ 70 | done 71 | 72 | sudo mkdir -p /usr/local/lib/neko/ 73 | 74 | for name in *.ndll; do 75 | sudo cp -p -P $name /usr/local/lib/neko/ 76 | done 77 | 78 | sudo cp -p -P nekoml.std /usr/local/lib/neko/ 79 | 80 | if [[ $PLATFORM =~ ^linux ]]; then 81 | sudo ldconfig 82 | fi 83 | 84 | export NEKOPATH=/usr/local/lib/neko/ 85 | } 86 | 87 | function install_windows { 88 | mkdir -p /c/neko 89 | cp -R -p -P -v * /c/neko/ 90 | 91 | NEKOPATH=c:/neko/ 92 | 93 | set +x 94 | echo "##vso[task.setvariable variable=NEKOPATH]$NEKOPATH" 95 | echo "##vso[task.prependpath]$NEKOPATH" 96 | set -x 97 | } 98 | 99 | COMMAND=$1 100 | PLATFORM=$2 101 | 102 | case $COMMAND in 103 | download) 104 | download $PLATFORM 105 | ;; 106 | install) 107 | install $PLATFORM 108 | ;; 109 | *) 110 | echo "Unknown command $COMMAND" 111 | exit 2 112 | esac 113 | -------------------------------------------------------------------------------- /script/hashlink_sdl.patch: -------------------------------------------------------------------------------- 1 | diff --git a/libs/sdl/gl.c b/libs/sdl/gl.c 2 | index 2917b35..7a72655 100644 3 | --- a/libs/sdl/gl.c 4 | +++ b/libs/sdl/gl.c 5 | @@ -13,7 +13,7 @@ 6 | # define glDispatchCompute(...) hl_error("Not supported on OSX") 7 | # define glMemoryBarrier(...) hl_error("Not supported on OSX") 8 | #elif defined(_WIN32) 9 | -# include 10 | +# include 11 | # include 12 | # include 13 | #elif defined(HL_CONSOLE) 14 | diff --git a/libs/sdl/sdl.c b/libs/sdl/sdl.c 15 | index dc5c331..89f1c0c 100644 16 | --- a/libs/sdl/sdl.c 17 | +++ b/libs/sdl/sdl.c 18 | @@ -3,8 +3,8 @@ 19 | #include 20 | 21 | #if defined(_WIN32) || defined(__ANDROID__) 22 | -# include 23 | -# include 24 | +# include 25 | +# include 26 | #else 27 | # include 28 | #endif 29 | -------------------------------------------------------------------------------- /script/template/bin.md: -------------------------------------------------------------------------------- 1 | # Binary distributions 2 | 3 | For hdll files, don't be alarmed at the outdated verions. They are compatible with this release of Callfunc. They are rebuilt and redistributed only when there are changes in the C code. As well, Callfunc will raise an error if the hdll is an incompatible version. 4 | 5 | Don't forget the licenses for Callfunc and libffi when bundling the libraries. 6 | -------------------------------------------------------------------------------- /script/test_build_emscripten_module.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -x 4 | SCRIPT_DIR="$PWD"/$(dirname "$BASH_SOURCE") 5 | 6 | mkdir -p $SCRIPT_DIR/../out/js/ 7 | cd $SCRIPT_DIR/../test/c/examplelib 8 | 9 | emcc -Wall -Werror -O3 examplelib.c -o $SCRIPT_DIR/../out/js/em.js \ 10 | -s EXPORTED_FUNCTIONS='["_malloc", "_calloc", "_free", "_examplelib_ints", "_examplelib_string", "_examplelib_variadic", "_examplelib_callback"]' \ 11 | -s EXTRA_EXPORTED_RUNTIME_METHODS='["ccall", "cwrap", "addFunction", "getValue", "setValue"]' \ 12 | -s RESERVED_FUNCTION_POINTERS=10 \ 13 | -s ASSERTIONS=2 14 | -------------------------------------------------------------------------------- /script/test_js_headless.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import subprocess 3 | import sys 4 | import os 5 | import atexit 6 | import signal 7 | 8 | from selenium import webdriver 9 | from selenium.webdriver.chrome.options import Options 10 | from selenium.common.exceptions import NoSuchElementException 11 | from selenium.webdriver.support.ui import WebDriverWait 12 | from selenium.webdriver.support import expected_conditions as EC 13 | from selenium.webdriver.common.by import By 14 | 15 | 16 | def main(): 17 | print('Starting server...', file=sys.stderr) 18 | 19 | script_dir = os.path.dirname(__file__) 20 | root_dir = os.path.join(script_dir, '..') 21 | html_path = os.path.join(root_dir, 'test.html') 22 | server_process = subprocess.Popen(['emrun', '--no_browser', '--no_emrun_detect', html_path], preexec_fn=os.setsid) 23 | 24 | print('Starting Chrome...', file=sys.stderr) 25 | 26 | chrome_options = Options() 27 | chrome_options.add_argument("--headless") 28 | 29 | if "CHROMEWEBDRIVER" in os.environ: 30 | driver_path = os.path.join(os.environ["CHROMEWEBDRIVER"], 'chromedriver') 31 | else: 32 | driver_path = os.path.join(root_dir, 'out', 'chromedriver') 33 | 34 | browser = webdriver.Chrome(options=chrome_options, executable_path=driver_path) 35 | test_result = False 36 | 37 | def cleanup_server(): 38 | os.killpg(os.getpgid(server_process.pid), signal.SIGTERM) 39 | server_process.wait() 40 | 41 | def cleanup_browser(): 42 | browser.quit() 43 | 44 | atexit.register(cleanup_server) 45 | atexit.register(cleanup_browser) 46 | 47 | print('Loading page...', file=sys.stderr) 48 | browser.get('http://localhost:6931/test.html') 49 | print('Loaded.', file=sys.stderr) 50 | 51 | try: 52 | header_element = WebDriverWait(browser, 10).until( 53 | EC.presence_of_element_located((By.CSS_SELECTOR, ".header")) 54 | ) 55 | print(header_element.text, file=sys.stderr) 56 | 57 | summary_element = browser.find_element_by_css_selector(".headerinfo") 58 | print(summary_element.text, file=sys.stderr) 59 | except NoSuchElementException: 60 | print('Failed to find element', file=sys.stderr) 61 | body_element = browser.find_element_by_tag_name('body') 62 | print(body_element.text[:1000], file=sys.stderr) 63 | 64 | if header_element.text == 'TEST OK': 65 | test_result = True 66 | 67 | print('Cleanup', file=sys.stderr) 68 | cleanup_browser() 69 | cleanup_server() 70 | 71 | print('Done', file=sys.stderr) 72 | 73 | if not test_result: 74 | sys.exit(1) 75 | 76 | 77 | if __name__ == '__main__': 78 | main() 79 | -------------------------------------------------------------------------------- /script/zip_release.py: -------------------------------------------------------------------------------- 1 | '''Create a zip file of the latest commit in the git repository. 2 | 3 | Used to create consistently named zip files that don't include undesired or 4 | uncommited files. 5 | ''' 6 | import json 7 | import os 8 | import os.path 9 | import shutil 10 | import subprocess 11 | import sys 12 | import time 13 | import zipfile 14 | 15 | NAME = 'callfunc' 16 | 17 | def main(): 18 | project_dir = os.path.join(os.path.dirname(__file__), '..') 19 | json_path = os.path.join(project_dir, 'haxelib.json') 20 | 21 | with open(json_path) as file: 22 | doc = json.load(file) 23 | version = doc['version'] 24 | 25 | timestamp = time.strftime('%Y%m%d-%H%M%S', time.gmtime()) 26 | name = '{name}-{version}-{timestamp}.zip'.format( 27 | name=NAME, version=version, timestamp=timestamp) 28 | output_dir = os.path.join(project_dir, 'out', 'release') 29 | output_path = os.path.join(output_dir, name) 30 | 31 | print('Outputting to', output_path) 32 | 33 | os.makedirs(output_dir, exist_ok=True) 34 | subprocess.run(['git', 'archive', 'HEAD', '-o', output_path], cwd=project_dir) 35 | 36 | if '--bin' in sys.argv: 37 | shutil.copy2( 38 | os.path.join(project_dir, 'script', 'template', 'bin.md'), 39 | os.path.join(project_dir, 'bin', 'README.md') 40 | ) 41 | 42 | add_bin(output_path, project_dir) 43 | 44 | if '--sign' in sys.argv: 45 | sign(output_path) 46 | 47 | print('Done') 48 | 49 | def add_bin(zip_filename: str, project_dir: str): 50 | print('Adding binaries') 51 | 52 | filenames = ( 53 | 'bin/README.md', 54 | 'bin/libffi.txt', 55 | 'bin/linux-x86-64/callfunc-1.1.0_4f22b89/callfunc.hdll', 56 | 'bin/linux-x86-64/libffi-3.3/libffi.so', 57 | 'bin/linux-x86-64/libffi-3.3/libffi.so.7', 58 | 'bin/macos/callfunc-1.1.0_d8de8bc/callfunc.hdll', 59 | 'bin/macos/libffi-3.3/libffi.dylib', 60 | 'bin/macos/libffi-3.3/libffi.7.dylib', 61 | 'bin/windows-x86/callfunc-1.1.0_4f22b89/callfunc.hdll', 62 | 'bin/windows-x86/libffi-3.3/libffi.dll', 63 | 'bin/windows-x86-64/callfunc-1.1.0_4f22b89/callfunc.hdll', 64 | 'bin/windows-x86-64/libffi-3.3/libffi.dll', 65 | ) 66 | 67 | zip_file = zipfile.ZipFile(zip_filename, mode='a') 68 | 69 | for filename in filenames: 70 | print('Adding', filename) 71 | zip_file.write(os.path.join(project_dir, filename), filename) 72 | 73 | def sign(filename: str): 74 | print('Sign release:') 75 | subprocess.run(['gpg', '--armor', '--detach-sign', filename]) 76 | 77 | if __name__ == '__main__': 78 | main() 79 | -------------------------------------------------------------------------------- /src/c/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | project(callfunc) 3 | 4 | set(LIBFFI_INCLUDE_PATH "" CACHE PATH "Include directory for libffi (ffi.h)") 5 | set(LIBFFI_LIB_PATH "" CACHE FILEPATH "Filename of libffi shared library (libffi.{so,dll,dylib})") 6 | set(HL_INCLUDE_PATH "" CACHE PATH "Include directory for HashLink (hl.h)") 7 | set(HL_LIB_PATH "" CACHE FILEPATH "Filename HashLink shared library (libhl.{so,dll,dylib})") 8 | 9 | add_library(callfunc_hdll SHARED callfunc.c) 10 | 11 | if(NOT LIBFFI_INCLUDE_PATH) 12 | find_path(LIBFFI_INCLUDE_PATH_AUTO ffi.h) 13 | endif() 14 | 15 | if(NOT HL_INCLUDE_PATH) 16 | find_path(HL_INCLUDE_PATH_AUTO hl.h) 17 | endif() 18 | 19 | target_include_directories(callfunc_hdll PRIVATE "${LIBFFI_INCLUDE_PATH}" "${LIBFFI_INCLUDE_PATH_AUTO}") 20 | target_include_directories(callfunc_hdll PRIVATE "${HL_INCLUDE_PATH}" "${HL_INCLUDE_PATH_AUTO}") 21 | 22 | if(NOT LIBFFI_LIB_PATH) 23 | if(NOT WIN32) 24 | find_library(LIBFFI_LIB_PATH_AUTO ffi) 25 | else() 26 | find_library(LIBFFI_LIB_PATH_AUTO libffi) 27 | endif() 28 | endif() 29 | 30 | target_link_libraries(callfunc_hdll PRIVATE "${LIBFFI_LIB_PATH}" "${LIBFFI_LIB_PATH_AUTO}") 31 | 32 | if(NOT HL_LIB_PATH) 33 | if(NOT WIN32) 34 | find_library(HL_LIB_PATH_AUTO hl) 35 | else() 36 | find_library(HL_LIB_PATH_AUTO libhl) 37 | endif() 38 | endif() 39 | 40 | target_link_libraries(callfunc_hdll PRIVATE "${HL_LIB_PATH}" "${HL_LIB_PATH_AUTO}") 41 | 42 | if(NOT WIN32) 43 | target_link_libraries(callfunc_hdll PRIVATE dl) 44 | endif() 45 | 46 | if(MSVC) 47 | target_compile_options(callfunc_hdll PRIVATE /W4) 48 | else() 49 | target_compile_options(callfunc_hdll PRIVATE -Wall -Wextra -pedantic) 50 | endif() 51 | 52 | if(CALLFUNC_ADD_SANITIZER_FLAGS) 53 | target_compile_options(callfunc_hdll PRIVATE -fno-omit-frame-pointer -fsanitize=address ) 54 | target_link_options(callfunc_hdll PRIVATE -fno-omit-frame-pointer -fsanitize=address ) 55 | endif() 56 | 57 | set_target_properties(callfunc_hdll PROPERTIES 58 | PREFIX "" 59 | OUTPUT_NAME "callfunc" 60 | SUFFIX ".hdll" 61 | C_STANDARD 99 62 | ) 63 | target_compile_definitions(callfunc_hdll PRIVATE CALLFUNC_HL) 64 | 65 | install(TARGETS callfunc_hdll DESTINATION lib) 66 | -------------------------------------------------------------------------------- /src/c/callfunc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #ifdef _WIN32 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | #ifdef CALLFUNC_HL 14 | // we've already prefixed our names 15 | #define HL_NAME(n) n 16 | #include 17 | #define CALLFUNC_API HL_PRIM 18 | #else 19 | #define CALLFUNC_API 20 | #endif 21 | 22 | #define CALLFUNC_API_VERSION (0x03) 23 | 24 | #define CALLFUNC_SUCCESS (0) 25 | #define CALLFUNC_FAILURE (1) 26 | 27 | #define CALLFUNC_MAX_RETURN_SIZE (8) 28 | 29 | #define CALLFUNC_VOID (0) 30 | #define CALLFUNC_UINT8 (1) 31 | #define CALLFUNC_SINT8 (2) 32 | #define CALLFUNC_UINT16 (3) 33 | #define CALLFUNC_SINT16 (4) 34 | #define CALLFUNC_UINT32 (5) 35 | #define CALLFUNC_SINT32 (6) 36 | #define CALLFUNC_UINT64 (7) 37 | #define CALLFUNC_SINT64 (8) 38 | #define CALLFUNC_FLOAT (9) 39 | #define CALLFUNC_DOUBLE (10) 40 | #define CALLFUNC_UCHAR (11) 41 | #define CALLFUNC_SCHAR (12) 42 | #define CALLFUNC_USHORT (13) 43 | #define CALLFUNC_SSHORT (14) 44 | #define CALLFUNC_SINT (15) 45 | #define CALLFUNC_UINT (16) 46 | #define CALLFUNC_SLONG (17) 47 | #define CALLFUNC_ULONG (18) 48 | #define CALLFUNC_POINTER (19) 49 | #define CALLFUNC_STRUCT (20) 50 | 51 | #define CALLFUNC_DEFAULT_ABI (-999) 52 | 53 | #define _CALLFUNC_ABORT_NULL(pointer) { assert(pointer != NULL); if (pointer == NULL) { abort(); } } 54 | 55 | #ifdef __cplusplus 56 | extern "C" { 57 | #endif 58 | 59 | typedef int CallfuncError; 60 | 61 | #ifdef CALLFUNC_CPP 62 | typedef Dynamic CallfuncHaxeFunc; 63 | #elif CALLFUNC_HL 64 | typedef vclosure * CallfuncHaxeFunc; 65 | #else 66 | typedef void (*CallfuncHaxeFunc)(uint8_t *); 67 | #endif 68 | 69 | 70 | struct CallfuncLibrary { 71 | void * library; 72 | }; 73 | 74 | struct CallfuncFunction { 75 | struct CallfuncLibrary * library; 76 | ffi_cif cif; 77 | void(*function)(void); 78 | void ** arg_pointers; 79 | bool * arg_pointer_allocated; 80 | }; 81 | 82 | struct CallfuncStructType { 83 | ffi_type type; 84 | }; 85 | 86 | struct CallfuncCallback { 87 | ffi_cif cif; 88 | ffi_closure * closure; 89 | void * code_location; 90 | uint8_t * arg_buffer; 91 | CallfuncHaxeFunc haxe_function; 92 | }; 93 | 94 | CALLFUNC_API 95 | int32_t callfunc_api_version(); 96 | 97 | CALLFUNC_API 98 | const char * callfunc_get_error_message(); 99 | 100 | CALLFUNC_API 101 | void callfunc_get_sizeof_table(uint8_t * buffer); 102 | 103 | CALLFUNC_API 104 | void * callfunc_alloc(size_t size, bool zero); 105 | 106 | CALLFUNC_API 107 | void callfunc_free(void * pointer); 108 | 109 | CALLFUNC_API 110 | struct CallfuncLibrary * callfunc_new_library(); 111 | 112 | CALLFUNC_API 113 | void callfunc_del_library(struct CallfuncLibrary * library); 114 | 115 | CALLFUNC_API 116 | CallfuncError callfunc_library_open(struct CallfuncLibrary * library, 117 | const char * name); 118 | 119 | CALLFUNC_API 120 | void callfunc_library_close(struct CallfuncLibrary * library); 121 | 122 | CALLFUNC_API 123 | CallfuncError callfunc_library_get_address(struct CallfuncLibrary * library, 124 | const char * name, void ** dest_pointer); 125 | 126 | CALLFUNC_API 127 | struct CallfuncFunction * callfunc_new_function( 128 | struct CallfuncLibrary * library); 129 | 130 | CALLFUNC_API 131 | void callfunc_del_function(struct CallfuncFunction * function); 132 | 133 | CALLFUNC_API 134 | CallfuncError callfunc_function_define(struct CallfuncFunction * function, 135 | void * target_function, int abi, uint8_t * definition); 136 | 137 | CALLFUNC_API 138 | void callfunc_function_call(struct CallfuncFunction * function, 139 | uint8_t * argument_buffer); 140 | 141 | CALLFUNC_API 142 | struct CallfuncStructType * callfunc_new_struct_type(); 143 | 144 | CALLFUNC_API 145 | void callfunc_del_struct_type(struct CallfuncStructType * struct_type); 146 | 147 | CALLFUNC_API 148 | CallfuncError callfunc_struct_type_define( 149 | struct CallfuncStructType * struct_type, uint8_t * definition, 150 | uint8_t * resultInfo); 151 | 152 | CALLFUNC_API 153 | struct CallfuncCallback * callfunc_new_callback(); 154 | 155 | CALLFUNC_API 156 | void callfunc_del_callback(struct CallfuncCallback * callback); 157 | 158 | CALLFUNC_API 159 | CallfuncError callfunc_callback_define(struct CallfuncCallback * callback, 160 | uint8_t * definition); 161 | 162 | CALLFUNC_API 163 | CallfuncError callfunc_callback_bind(struct CallfuncCallback * callback, 164 | uint8_t * arg_buffer, CallfuncHaxeFunc haxe_function); 165 | 166 | CALLFUNC_API 167 | void * callfunc_callback_get_pointer(struct CallfuncCallback * callback); 168 | 169 | CALLFUNC_API 170 | int64_t callfunc_pointer_to_int64(void * pointer); 171 | 172 | CALLFUNC_API 173 | void * callfunc_int64_to_pointer(int64_t address); 174 | 175 | CALLFUNC_API 176 | void callfunc_pointer_get(void * pointer, uint8_t data_type, uint8_t * buffer, 177 | int32_t offset); 178 | 179 | CALLFUNC_API 180 | void callfunc_pointer_set(void * pointer, uint8_t data_type, uint8_t * buffer, 181 | int32_t offset); 182 | 183 | CALLFUNC_API 184 | void callfunc_pointer_array_get(void * pointer, uint8_t data_type, 185 | uint8_t * buffer, int32_t index); 186 | 187 | CALLFUNC_API 188 | void callfunc_pointer_array_set(void * pointer, uint8_t data_type, 189 | uint8_t * buffer, int32_t index); 190 | 191 | size_t _callfunc_data_type_size(uint8_t data_type); 192 | 193 | const char * _callfunc_get_dll_error_message(); 194 | 195 | ffi_type * _callfunc_constant_to_ffi_type(int constant); 196 | 197 | CallfuncError _check_ffi_status(ffi_status status); 198 | 199 | void _callfunc_parse_parameter_definition(uint8_t * definition, 200 | size_t * num_params, int32_t * num_fixed_params, 201 | ffi_type *** parameter_types, 202 | ffi_type ** return_type); 203 | 204 | void _callfunc_parse_struct_definition(uint8_t * definition, 205 | size_t * num_fields, ffi_type *** field_types); 206 | 207 | ffi_type * _callfunc_parse_type(uint8_t * buffer, size_t offset, 208 | size_t * bytes_read); 209 | 210 | void * _callfunc_get_buffer_struct_copy(uint8_t * buffer, size_t offset, 211 | size_t struct_size); 212 | 213 | void _callfunc_rescursive_free_types(ffi_type ** types, size_t length); 214 | 215 | void _callfunc_rescursive_free_type(ffi_type * type); 216 | 217 | void * _callfunc_get_aligned_pointer(void * pointer, uint8_t data_type, 218 | int32_t index); 219 | 220 | void _callfunc_closure_handler(ffi_cif * cif, void * return_value, 221 | void ** args, void * user_data); 222 | 223 | int32_t _callfunc_array_get_int(uint8_t * buffer, size_t offset); 224 | 225 | void _callfunc_array_set_int(uint8_t * buffer, size_t offset, int32_t value); 226 | 227 | void _callfunc_closure_impl(struct CallfuncCallback * callback); 228 | 229 | #ifdef __cplusplus 230 | } 231 | #endif 232 | -------------------------------------------------------------------------------- /src/c/callfunc_hxcpp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void _callfunc_closure_impl(struct CallfuncCallback * callback) { 5 | callback->haxe_function(); 6 | } 7 | -------------------------------------------------------------------------------- /src/haxe/callfunc/AnyInt.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import haxe.Int64; 4 | 5 | /** 6 | * Abstract over Dynamic for integer types not known at compile time. 7 | * 8 | * This abstract contains convenient methods to convert 9 | * dynamic objects to `Int` and `Int64` at runtime with optional data loss 10 | * checking. It encapsulates the if-else type checking. 11 | * 12 | * It is used in places where you need to check the type of the value at 13 | * runtime or as a substitute for an "either" type in functions that accept 14 | * either `Int` or `Int64`. 15 | */ 16 | abstract AnyInt(Dynamic) from Dynamic to Dynamic { 17 | static final WRONG_TYPE_ERROR = "Not an Int or Int64"; 18 | 19 | /** 20 | * Converts value to `Int`. 21 | * 22 | * If the value is `Int`, it is returned unchanged. If it is `Int64`, the 23 | * value will be converted or truncated with data loss. 24 | * 25 | * @param checkTruncation If `true`, an exception will be thrown if the 26 | * value cannot be converted with data loss. 27 | * @throws String If the value is not `Int` or `Int64`, or value cannot 28 | * be converted from `Int64` without data loss. 29 | */ 30 | public function toInt(checkTruncation:Bool = false):Int { 31 | final value:Dynamic = this; 32 | 33 | if (Std.is(value, Int)) { 34 | return value; 35 | } else if (Int64.is(value)) { 36 | if (!checkTruncation) { 37 | return (value:Int64).low; 38 | } else { 39 | return Int64.toInt((value:Int64)); 40 | } 41 | } else { 42 | throw WRONG_TYPE_ERROR; 43 | } 44 | } 45 | 46 | /** 47 | * Converts value to `Int64`. 48 | * 49 | * If the value is `Int` it is promoted to `Int64`. If the value is 50 | * `Int64`, it is returned unchanged. 51 | * 52 | * @throws String If the value is neither `Int` or `Int64`. 53 | */ 54 | public function toInt64():Int64 { 55 | final value:Dynamic = this; 56 | 57 | if (Int64.is(value)) { 58 | return value; 59 | } else if (Std.is(value, Int)) { 60 | return Int64.make(0, value); 61 | } else { 62 | throw WRONG_TYPE_ERROR; 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/haxe/callfunc/AutoInt.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import haxe.Int64; 4 | 5 | /** 6 | * `Int` abstract with automatic truncation of `Int64`. 7 | * 8 | * `Int64` will be converted to `Int` with possible data loss. 9 | * 10 | * This abstract can be used for implicit casing between `Int64` 11 | * as "syntactic sugar". 12 | * 13 | * @see `AnyInt` for a runtime version. 14 | */ 15 | @:forward 16 | @:forwardStatics 17 | abstract AutoInt(Int) from Int to Int { 18 | inline public function new(value:Int) { 19 | this = value; 20 | } 21 | 22 | @:from 23 | inline public static function fromInt64(value:Int64):AutoInt { 24 | return new AutoInt(value.low); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/haxe/callfunc/AutoInt64.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import haxe.Int64; 4 | 5 | /** 6 | * `Int64` abstract with automatic promotion of `Int`. 7 | * 8 | * `Int` will be converted to `Int64`. 9 | * 10 | * This abstract can be used for implicit casing between `Int64` 11 | * as "syntactic sugar". 12 | * 13 | * @see `AnyInt` for a runtime version. 14 | */ 15 | @:forward 16 | @:forwardStatics 17 | abstract AutoInt64(Int64) from Int64 to Int64 { 18 | inline public function new(value:Int64) { 19 | this = value; 20 | } 21 | 22 | @:from 23 | inline public static function fromInt(value:Int):AutoInt64 { 24 | return new AutoInt64(Int64.make(0, value)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/haxe/callfunc/BytesDataView.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.string.Encoder; 4 | import haxe.Int64; 5 | import haxe.io.ArrayBufferView; 6 | import haxe.io.Bytes; 7 | import haxe.io.Encoding; 8 | 9 | using callfunc.BytesTools; 10 | 11 | /** 12 | * Data view using Bytes as the underlying buffer array. 13 | */ 14 | class BytesDataView implements DataView { 15 | public var buffer(get, never):Any; 16 | public var byteLength(get, never):Int; 17 | public var byteOffset(get, never):Int; 18 | 19 | public final bytes:Bytes; 20 | final _bytes:Bytes; 21 | final _byteLength:Int; 22 | final _byteOffset:Int; 23 | 24 | /** 25 | * @param bytes Underlying byte array 26 | * @param byteOffset Absolute index position of the buffer in which this 27 | * view will represent as index 0. 28 | * @param byteLength Number of bytes this view will represent. 29 | */ 30 | public function new(bytes:Bytes, byteOffset:Int = 0, ?byteLength:Int) { 31 | _bytes = this.bytes = bytes; 32 | _byteOffset = byteOffset; 33 | _byteLength = byteLength != null ? byteLength : bytes.length; 34 | } 35 | 36 | /** 37 | * Create a new buffer and return a view to it. 38 | * @param size Number of bytes. 39 | */ 40 | public static function alloc(size:Int):DataView { 41 | return new BytesDataView(Bytes.alloc(size)); 42 | } 43 | 44 | /** 45 | * Convert from ArrayBufferView. 46 | */ 47 | public static function fromArrayBufferView(view:ArrayBufferView):DataView { 48 | return new BytesDataView(view.buffer, view.byteOffset, view.byteLength); 49 | } 50 | 51 | function get_buffer():Any { 52 | return _bytes; 53 | } 54 | 55 | function get_byteOffset():Int { 56 | return _byteOffset; 57 | } 58 | 59 | function get_byteLength():Int { 60 | return _byteLength; 61 | } 62 | 63 | public function get(position:Int):Int { 64 | return _bytes.get(_byteOffset + position); 65 | } 66 | 67 | public function set(position:Int, value:Int) { 68 | _bytes.set(_byteOffset + position, value); 69 | } 70 | 71 | public function getUInt8(position:Int):Int { 72 | return get(position); 73 | } 74 | 75 | public function setUInt8(position:Int, value:Int) { 76 | set(position, value); 77 | } 78 | 79 | public function getInt8(position:Int):Int { 80 | return _bytes.getSInt8(_byteOffset + position); 81 | } 82 | 83 | public function setInt8(position:Int, value:Int) { 84 | set(position, value); 85 | } 86 | 87 | public function getUInt16(position:Int):Int { 88 | return _bytes.getUInt16(_byteOffset + position); 89 | } 90 | 91 | public function setUInt16(position:Int, value:Int) { 92 | _bytes.setUInt16(_byteOffset + position, value); 93 | } 94 | 95 | public function getInt16(position:Int):Int { 96 | return _bytes.getSInt16(_byteOffset + position); 97 | } 98 | 99 | public function setInt16(position:Int, value:Int) { 100 | _bytes.setUInt16(_byteOffset + position, value); 101 | } 102 | 103 | public function getUInt32(position:Int):UInt { 104 | return _bytes.getInt32(_byteOffset + position); 105 | } 106 | 107 | public function setUInt32(position:Int, value:UInt) { 108 | _bytes.setInt32(_byteOffset + position, value); 109 | } 110 | 111 | public function getInt32(position:Int):Int { 112 | return _bytes.getInt32(_byteOffset + position); 113 | } 114 | 115 | public function setInt32(position:Int, value:Int) { 116 | _bytes.setInt32(_byteOffset + position, value); 117 | } 118 | 119 | public function getInt64(position:Int):Int64 { 120 | return _bytes.getInt64(_byteOffset + position); 121 | } 122 | 123 | public function setInt64(position:Int, value:Int64) { 124 | _bytes.setInt64(_byteOffset + position, value); 125 | } 126 | 127 | public function getFloat(position:Int):Float { 128 | return _bytes.getFloat(_byteOffset + position); 129 | } 130 | 131 | public function setFloat(position:Int, value:Float) { 132 | _bytes.setFloat(_byteOffset + position, value); 133 | } 134 | 135 | public function getDouble(position:Int):Float { 136 | return _bytes.getDouble(_byteOffset + position); 137 | } 138 | 139 | public function setDouble(position:Int, value:Float) { 140 | _bytes.setDouble(_byteOffset + position, value); 141 | } 142 | 143 | public function blit(position:Int, source:DataView, sourcePosition:Int = 0, 144 | ?sourceLength:Int) { 145 | sourceLength = sourceLength != null ? sourceLength : source.byteLength; 146 | _bytes.blit( 147 | _byteOffset + position, 148 | source.buffer, 149 | source.byteOffset + sourcePosition, 150 | sourceLength); 151 | } 152 | 153 | 154 | public function blitBytes(position:Int, source:Bytes, sourcePosition:Int = 0, 155 | ?sourceLength:Int) { 156 | sourceLength = sourceLength != null ? sourceLength : source.length; 157 | _bytes.blit( 158 | _byteOffset + position, 159 | source, 160 | sourcePosition, 161 | sourceLength); 162 | } 163 | 164 | 165 | public function fill(pos:Int, len:Int, value:Int) { 166 | _bytes.fill(_byteOffset + pos, len, value); 167 | } 168 | 169 | 170 | public function sub(position:Int, length:Int):DataView { 171 | return new BytesDataView(buffer, _byteOffset + position, length); 172 | } 173 | 174 | public function toBytes(position:Int = 0, ?length:Int):Bytes { 175 | length = length != null ? length : byteLength; 176 | return _bytes.sub(_byteOffset + position, length); 177 | } 178 | 179 | public function getString(pos:Int, len:Int, ?encoding:Encoding):String { 180 | return _bytes.getString(_byteOffset + pos, len, encoding); 181 | } 182 | 183 | public function setString(position:Int, string:String, ?encoding:Encoding) { 184 | var stringBytes = Bytes.ofString(string, encoding); 185 | blitBytes(position, stringBytes); 186 | } 187 | 188 | public function getStringFull(pos:Int, ?len:Int, 189 | encoding:callfunc.string.Encoding = UTF8):String { 190 | return Encoder.decodeFromBytes(_bytes, _byteOffset + pos, len, encoding); 191 | } 192 | 193 | public function setStringFull(pos:Int, text:String, 194 | encoding:callfunc.string.Encoding = UTF8, 195 | terminator:Bool = false) { 196 | return Encoder.encodeToBytes(_bytes, _byteOffset + pos, text, encoding, terminator); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/haxe/callfunc/BytesTools.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import haxe.io.Bytes; 4 | 5 | /** 6 | * Static method extensions to `haxe.io.Bytes`. 7 | */ 8 | class BytesTools { 9 | /** 10 | * Interpret and return a signed 8-bit integer 11 | */ 12 | public static function getSInt8(bytes:Bytes, position:Int):Int { 13 | var value = bytes.get(position); 14 | 15 | if (value & 0x80 == 0) { 16 | return value; 17 | } else { 18 | return -((~value & 0xff) + 1); 19 | } 20 | } 21 | 22 | /** 23 | * Interpret and return a signed, little-endian 16-bit integer. 24 | */ 25 | public static function getSInt16(bytes:Bytes, position:Int):Int { 26 | var value = bytes.get(position) | (bytes.get(position + 1) << 8); 27 | 28 | if (value & 0x8000 == 0) { 29 | return value; 30 | } else { 31 | return -((~value & 0xffff) + 1); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/haxe/callfunc/Callback.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.core.Context; 4 | import callfunc.core.CallbackHandle; 5 | 6 | /** 7 | * Allows C code to call Haxe functions. 8 | */ 9 | class Callback implements Disposable { 10 | final callbackHandle:CallbackHandle; 11 | 12 | /** 13 | * A function pointer which can be called by C code. 14 | */ 15 | public final pointer:Pointer; 16 | 17 | public function new(context:Context, callbackHandle:CallbackHandle) { 18 | this.callbackHandle = callbackHandle; 19 | pointer = new Pointer(context, callbackHandle.getPointer()); 20 | } 21 | 22 | public function dispose() { 23 | callbackHandle.dispose(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/haxe/callfunc/Callfunc.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.string.StringUtil; 4 | import callfunc.string.Encoding; 5 | import callfunc.core.Context; 6 | import haxe.Int64; 7 | import haxe.io.Bytes; 8 | 9 | /** 10 | * Main class for interfacing with foreign functions. 11 | */ 12 | class Callfunc { 13 | static var _instance:Null; 14 | public final context:Context; 15 | 16 | public function new(context:Context) { 17 | this.context = context; 18 | } 19 | 20 | /** 21 | * Returns a singleton instance. 22 | */ 23 | public static function instance():Callfunc { 24 | if (_instance == null) { 25 | _instance = new Callfunc( 26 | #if (cpp || hl) 27 | new callfunc.core.impl.ContextImpl() 28 | #else 29 | new callfunc.core.dummy.DummyContext() 30 | #end 31 | ); 32 | } 33 | 34 | return _instance; 35 | } 36 | 37 | /** 38 | * Replace the singleton instance with the given instance. 39 | * @param instance 40 | */ 41 | public static function setInstance(instance:Callfunc) { 42 | _instance = instance; 43 | } 44 | 45 | /** 46 | * Allocates memory on the heap. 47 | * 48 | * This calls standard C `malloc()` or `calloc()`. 49 | * 50 | * @param size Number of bytes. 51 | * @param initZero Whether to initialize the array to 0. 52 | * @throws String Allocation failed. 53 | */ 54 | public function alloc(size:Int, initZero:Bool = false):Pointer { 55 | return new Pointer(context, context.alloc(size, initZero)); 56 | } 57 | 58 | /** 59 | * Returns the width of the data type. 60 | * 61 | * @return Number of bytes. 62 | */ 63 | public function sizeOf(type:DataType):Int { 64 | return context.sizeOf(type); 65 | } 66 | 67 | /** 68 | * Returns a pointer from the given address. 69 | * 70 | * @param address An address in memory. 71 | */ 72 | public function getPointer(address:Int64):Pointer { 73 | return new Pointer(context, context.getPointer(address)); 74 | } 75 | 76 | /** 77 | * Returns C struct information from the given definition. 78 | * 79 | * The returned information then can be used to access pointers in an 80 | * easier manner. 81 | * 82 | * @param dataTypes Data types for each field of the struct. 83 | * @param names Name of each field. 84 | * @throws String An error message if the data type is invalid. 85 | */ 86 | public function defineStruct(dataTypes:Array, names:Array):StructDef { 87 | return new StructDef(context.newStructType(dataTypes), names); 88 | } 89 | 90 | /** 91 | * Returns a library object to access a dynamic library's symbols. 92 | * 93 | * @param name A filename to the dynamic library as accepted by `dlopen()` 94 | * or `LoadLibrary()` on Windows. 95 | * @throws String An error message if the library could not be found or 96 | * any other error. 97 | */ 98 | public function openLibrary(name:String):Library { 99 | return new Library(context, context.newLibrary(name)); 100 | } 101 | 102 | /** 103 | * Returns a Callback instance for passing Haxe functions to C code. 104 | * 105 | * Availability depends on the libffi platform support for closures. 106 | * 107 | * @param haxeFunction Callback function to be wrapped which accepts the 108 | * same number of arguments and returns the same data type as defined. 109 | * @param params Data types corresponding to the function parameters 110 | * exposed to the C code. 111 | * @param returnType Data type of the return value of the exposed function. 112 | * @throws String An error message if the data type is invalid or any 113 | * other error. 114 | */ 115 | public function wrapCallback( 116 | haxeFunction:Dynamic, 117 | ?params:Array, 118 | ?returnType:DataType):Callback { 119 | 120 | @:nullSafety(Off) 121 | var arrayArgCallback:Array->Any = 122 | (args:Array) -> Reflect.callMethod(null, haxeFunction, args); 123 | 124 | return wrapArrayCallback(arrayArgCallback, params, returnType); 125 | } 126 | 127 | /** 128 | * Returns a Callback instance for passing Haxe functions to C code. 129 | * 130 | * This is the implementation method which uses arrays for arguments. 131 | * `warpCallback` is the convenience method that accepts any function. 132 | * 133 | * @see `Callback.wrapCallback` 134 | */ 135 | public function wrapArrayCallback( 136 | haxeFunction:Array->Any, 137 | ?params:Array, 138 | ?returnType:DataType) { 139 | 140 | function argPointerWrapper(args:Array):Any { 141 | if (params != null) { 142 | for (index in 0...args.length) { 143 | if (params[index] == DataType.Pointer) { 144 | args[index] = new Pointer(context, args[index]); 145 | } 146 | } 147 | } 148 | 149 | final result = haxeFunction(args); 150 | 151 | if (returnType == DataType.Pointer) { 152 | return Pointer.unwrap(returnType); 153 | } else { 154 | return result; 155 | } 156 | } 157 | 158 | final handle = context.newCallback(argPointerWrapper, params, returnType); 159 | return new Callback(context, handle); 160 | } 161 | 162 | #if sys 163 | /** 164 | * Exposes a pointer to the underlying C array of a Haxe `Bytes`. 165 | * 166 | * This method is used to share from a Haxe array to a C library. 167 | * 168 | * Care must be ensured that the `Bytes` instance has not been garbage 169 | * collected when using the pointer. 170 | * 171 | * This method is not portable across targets. 172 | * 173 | * @param bytes 174 | */ 175 | public function bytesToPointer(bytes:Bytes):Pointer { 176 | return new Pointer(context, context.bytesToPointer(bytes)); 177 | } 178 | #end 179 | 180 | /** 181 | * Converts a Haxe string to a new C string and returns its pointer. 182 | * 183 | * The C string will be encoded with the given encoding with a null 184 | * terminator. 185 | * 186 | * This is equivalent of determining the number of bytes in the encoded 187 | * string, allocating memory, and writing the encoded string with the 188 | * pointer. 189 | * 190 | * The caller is responsible for freeing the string. 191 | * 192 | * @param text String to be encoded, 193 | * @param encoding Encoding such as UTF-8. 194 | * @param terminator Whether to include a null-terminator. 195 | * @param lengthCallback A callback that accepts the number of bytes 196 | * allocated 197 | */ 198 | public function allocString(text:String, 199 | encoding:Encoding = UTF8, 200 | terminator:Bool = true, 201 | ?lengthCallback:Int->Void):Pointer { 202 | final length = StringUtil.getEncodedLength(text, encoding, terminator); 203 | final pointer = alloc(length, true); 204 | pointer.setString(text, encoding, terminator); 205 | 206 | if (lengthCallback != null) { 207 | lengthCallback(length); 208 | } 209 | 210 | return pointer; 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /src/haxe/callfunc/CoreDataType.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import haxe.ds.Option; 4 | 5 | /** 6 | * Represents primitive C data types supported by libffi. 7 | * 8 | * Note that `Struct` is not true primitive data type; it is an indicator 9 | * for composite data types in the serialization process. 10 | * 11 | * @see `DataType` 12 | */ 13 | @:enum 14 | abstract CoreDataType(Int) { 15 | var Void = 0; 16 | var UInt8 = 1; 17 | var SInt8 = 2; 18 | var UInt16 = 3; 19 | var SInt16 = 4; 20 | var UInt32 = 5; 21 | var SInt32 = 6; 22 | var UInt64 = 7; 23 | var SInt64 = 8; 24 | var Float = 9; 25 | var Double = 10; 26 | var UChar = 11; 27 | var SChar = 12; 28 | var UShort = 13; 29 | var SShort = 14; 30 | var SInt = 15; 31 | var UInt = 16; 32 | var SLong = 17; 33 | var ULong = 18; 34 | var Pointer = 19; 35 | var Struct = 20; 36 | 37 | inline public function toInt():Int { 38 | return this; 39 | } 40 | 41 | public static function fromDataType(dataType:DataType):CoreDataType { 42 | switch dataType { 43 | case DataType.UChar: return UChar; 44 | case DataType.UShort: return UShort; 45 | case DataType.UInt: return UInt; 46 | case DataType.ULong: return ULong; 47 | case DataType.SChar: return SChar; 48 | case DataType.SShort: return SShort; 49 | case DataType.SInt: return SInt; 50 | case DataType.SLong: return SLong; 51 | case DataType.UInt8: return UInt8; 52 | case DataType.SInt8: return SInt8; 53 | case DataType.UInt16: return UInt16; 54 | case DataType.SInt16: return SInt16; 55 | case DataType.UInt32: return UInt32; 56 | case DataType.SInt32: return SInt32; 57 | case DataType.UInt64: return UInt64; 58 | case DataType.SInt64: return SInt64; 59 | case DataType.Float: return Float; 60 | case DataType.Double: return Double; 61 | case DataType.Pointer: return Pointer; 62 | case DataType.Void: return Void; 63 | case DataType.Struct(_): return Struct; 64 | default: throw 'Invalid type $dataType'; 65 | } 66 | } 67 | 68 | public static function getName(value:CoreDataType):String { 69 | switch value { 70 | case Void: return "Void"; 71 | case UInt8: return "UInt8"; 72 | case SInt8: return "SInt8"; 73 | case UInt16: return "UInt16"; 74 | case SInt16: return "SInt16"; 75 | case UInt32: return "UInt32"; 76 | case SInt32: return "SInt32"; 77 | case UInt64: return "UInt64"; 78 | case SInt64: return "SInt64"; 79 | case Float: return "Float"; 80 | case Double: return "Double"; 81 | case UChar: return "UChar"; 82 | case SChar: return "SChar"; 83 | case UShort: return "UShort"; 84 | case SShort: return "SShort"; 85 | case SInt: return "SInt"; 86 | case UInt: return "UInt"; 87 | case SLong: return "SLong"; 88 | case ULong: return "ULong"; 89 | case Pointer: return "Pointer"; 90 | case Struct: return "Struct"; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/haxe/callfunc/DataType.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | /** 4 | * Represents common C data types. 5 | * 6 | * The following types are not supported: 7 | * 8 | * - LongDouble 9 | * - ComplexFloat 10 | * - ComplexDouble 11 | * - ComplexLongDouble 12 | * 13 | * The `Pointer` data type is a pointer to a data type (object pointer) or 14 | * a function (function pointer). libffi and Callfunc assumes that the 15 | * architecture treats object pointers and function pointers as the 16 | * same thing. It is up to the user to dereference the pointer to the correct 17 | * data type or function. 18 | * 19 | * Note that `Struct` is a composite of data types. It is intended to define 20 | * pass-by-value struct C function signatures and nested struct definitions. 21 | * Not to be confused with a `Pointer` which can point to a struct. 22 | * 23 | * @see `CoreDataType` 24 | */ 25 | enum DataType { 26 | Void; 27 | UInt8; 28 | SInt8; 29 | UInt16; 30 | SInt16; 31 | UInt32; 32 | SInt32; 33 | UInt64; 34 | SInt64; 35 | Float; 36 | Double; 37 | UChar; 38 | SChar; 39 | UShort; 40 | SShort; 41 | SInt; 42 | UInt; 43 | SLong; 44 | ULong; 45 | Pointer; 46 | LongDouble; 47 | ComplexFloat; 48 | ComplexDouble; 49 | ComplexLongDouble; 50 | Size; 51 | PtrDiff; 52 | WChar; 53 | Struct(fields:Array); 54 | } 55 | -------------------------------------------------------------------------------- /src/haxe/callfunc/DataView.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import haxe.io.Encoding; 4 | import haxe.Int64; 5 | import haxe.io.ArrayBufferView; 6 | import haxe.io.Bytes; 7 | 8 | using callfunc.BytesTools; 9 | 10 | /** 11 | * Provides an abstract view to Bytes. 12 | * 13 | * The get and set integer methods use signed/unsigned N-bit values. 14 | * Float and double numbers are 32- and 64-bit values respectively. 15 | * The position is defined as the offset from the start of the view. 16 | * 17 | * Protection against out-of-bounds is not guaranteed and is not mandatory 18 | * for implementations. 19 | */ 20 | interface DataView { 21 | /** 22 | * Underlying byte array 23 | */ 24 | public var buffer(get, never):Any; 25 | 26 | /** 27 | * Number of bytes this view represents. 28 | */ 29 | public var byteLength(get, never):Int; 30 | 31 | /** 32 | * Absolute index position of the buffer in which this view represents 33 | * as index 0. 34 | */ 35 | public var byteOffset(get, never):Int; 36 | 37 | // TODO: endian support 38 | 39 | /** 40 | * @see `DataView.getUInt8()` 41 | */ 42 | public function get(position:Int):Int; 43 | 44 | /** 45 | * @see `DataView.setUInt8()` 46 | */ 47 | public function set(position:Int, value:Int):Void; 48 | 49 | public function getUInt8(position:Int):Int; 50 | 51 | public function setUInt8(position:Int, value:Int):Void; 52 | 53 | public function getInt8(position:Int):Int; 54 | 55 | public function setInt8(position:Int, value:Int):Void; 56 | 57 | public function getUInt16(position:Int):Int; 58 | 59 | public function setUInt16(position:Int, value:Int):Void; 60 | 61 | public function getInt16(position:Int):Int; 62 | 63 | public function setInt16(position:Int, value:Int):Void; 64 | 65 | public function getUInt32(position:Int):UInt; 66 | 67 | public function setUInt32(position:Int, value:UInt):Void; 68 | 69 | public function getInt32(position:Int):Int; 70 | 71 | public function setInt32(position:Int, value:Int):Void; 72 | 73 | public function getInt64(position:Int):Int64; 74 | 75 | public function setInt64(position:Int, value:Int64):Void; 76 | 77 | public function getFloat(position:Int):Float; 78 | 79 | public function setFloat(position:Int, value:Float):Void; 80 | 81 | public function getDouble(position:Int):Float; 82 | 83 | public function setDouble(position:Int, value:Float):Void; 84 | 85 | /** 86 | * Copy data from another view into this view. 87 | * 88 | * @param position Number of bytes offset from the start of the view. 89 | * @param source The view in which data is copied from. 90 | * @param sourcePosition Number of bytes offset from the start of the source 91 | * view. 92 | * @param sourceLength Number of bytes to be copied from the source. If not 93 | * given, defaults to length of source. 94 | */ 95 | public function blit(position:Int, source:DataView, sourcePosition:Int = 0, 96 | ?sourceLength:Int):Void; 97 | 98 | /** 99 | * Copies data from Bytes to this view. 100 | * 101 | * @see `DataView.blit()`. 102 | */ 103 | public function blitBytes(position:Int, source:Bytes, sourcePosition:Int = 0, 104 | ?sourceLength:Int):Void; 105 | 106 | /** 107 | * Sets bytes to given value. 108 | */ 109 | public function fill(pos:Int, len:Int, value:Int):Void; 110 | 111 | /** 112 | * Returns a subsection of the current view. 113 | */ 114 | public function sub(position:Int, length:Int):DataView; 115 | 116 | /** 117 | * Returns a copy of data to Bytes. 118 | */ 119 | public function toBytes(position:Int = 0, ?length:Int):Bytes; 120 | 121 | /** 122 | * Returns a decoded string. 123 | * 124 | * Depending on the target, null bytes may stop the decoder. 125 | * 126 | * @see DataView.getStringFull 127 | */ 128 | public function getString(pos:Int, len:Int, ?encoding:Encoding):String; 129 | 130 | /** 131 | * Encodes a string. 132 | * 133 | * Null-terminator is not written. 134 | * 135 | * @see DataView.setStringFull 136 | */ 137 | public function setString(position:Int, string:String, ?encoding:Encoding):Void; 138 | 139 | /** 140 | * Returns a decoded string. 141 | * 142 | * @param pos Position of the view to start reading. 143 | * @param len Amount of bytes (not code units or code points) to decode. 144 | * If not provided, a null-terminator is searched. 145 | * @param encoding Encoding of the string. 146 | */ 147 | public function getStringFull(pos:Int, ?len:Int, 148 | encoding:callfunc.string.Encoding = UTF8):String; 149 | 150 | /** 151 | * Encodes a string. 152 | * 153 | * @param pos Position of the view to start writing. 154 | * @param text The string to be written. 155 | * @param terminator Whether to include a null-terminator. 156 | * @param encoding Encoding of the string. 157 | * @return Number of bytes written. 158 | */ 159 | public function setStringFull(pos:Int, text:String, 160 | encoding:callfunc.string.Encoding = UTF8, 161 | terminator:Bool = false):Int; 162 | } 163 | -------------------------------------------------------------------------------- /src/haxe/callfunc/Debug.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import haxe.macro.Expr; 4 | 5 | using haxe.macro.ExprTools; 6 | 7 | class Debug { 8 | macro static public function assert(condition:Expr, ?message:Expr) { 9 | if (!haxe.macro.Context.defined("debug")) { 10 | return macro {}; 11 | } 12 | 13 | var error = 'Assertion error: ${condition.pos}: ${condition.toString()}, '; 14 | 15 | return macro { 16 | if (!($condition)) { 17 | throw $v{error} + $message; 18 | }; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/haxe/callfunc/Disposable.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | /** 4 | * Classes that hold resources which can't be garbage collected automatically. 5 | */ 6 | interface Disposable { 7 | /** 8 | * Release any resources held by the instance. 9 | * 10 | * Calling any methods or properties, including `dispose()`, after this 11 | * method is undefined behavior. 12 | */ 13 | public function dispose():Void; 14 | } 15 | -------------------------------------------------------------------------------- /src/haxe/callfunc/Function.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.core.Context; 4 | import callfunc.core.FunctionHandle; 5 | import callfunc.core.LibraryHandle; 6 | import haxe.ds.Option; 7 | 8 | /** 9 | * Represents a symbol in an opened dynamic library. 10 | */ 11 | class Function { 12 | /** 13 | * Library of which this function belongs to. 14 | */ 15 | public final library:Library; 16 | 17 | final libraryHandle:LibraryHandle; 18 | final context:Context; 19 | var functionHandle:Null; 20 | 21 | /** 22 | * Symbol name of the function. 23 | */ 24 | public final name:String; 25 | 26 | /** 27 | * Data types of the function parameters. 28 | * 29 | * If the function accepts no parameters, it is an empty array. 30 | */ 31 | public var params(get, set):Array; 32 | var _params:Array; 33 | 34 | /** 35 | * Data type of the return value. 36 | * 37 | * If the function has no return value, `DataType.Void` is used. 38 | */ 39 | public var returnType(get, set):DataType; 40 | var _returnType:DataType; 41 | 42 | /** 43 | * If a variadic C function, the number of fixed parameters. 44 | */ 45 | public var fixedParamCount(get, set):Option; 46 | var _fixedParamCount:Option; 47 | 48 | /** 49 | * If supported, the libffi ABI function type. 50 | */ 51 | public var abi(get, set):Option; 52 | var _abi:Option; 53 | 54 | /** 55 | * Executes function in the library. 56 | * 57 | * This method is a variadic version of `arrayCall`. This method is 58 | * provided as convenience and should be used in most cases. 59 | * 60 | * Example: `myLibrary.s.myFunction.call(a, b, c);` 61 | * 62 | * @see `Function.arrayCall` for description of function behavior. 63 | */ 64 | public final call:Dynamic; 65 | 66 | public function new(name:String, context:Context, library:Library, libraryHandle:LibraryHandle) { 67 | this.name = name; 68 | this.context = context; 69 | this.library = library; 70 | this.libraryHandle = libraryHandle; 71 | _params = []; 72 | _returnType = DataType.Void; 73 | _fixedParamCount = Option.None; 74 | _abi = Option.None; 75 | call = Reflect.makeVarArgs(cast arrayCall); 76 | } 77 | 78 | function get_params():Array { 79 | return _params; 80 | } 81 | 82 | function set_params(value:Array):Array { 83 | if (value != _params) { 84 | reset(); 85 | } 86 | 87 | return _params = value; 88 | } 89 | 90 | function get_returnType():DataType { 91 | return _returnType; 92 | } 93 | 94 | function set_returnType(value:DataType):DataType { 95 | if (value != _returnType) { 96 | reset(); 97 | } 98 | 99 | return _returnType = value; 100 | } 101 | 102 | function get_fixedParamCount():Option { 103 | return _fixedParamCount; 104 | } 105 | 106 | function set_fixedParamCount(value:Option):Option { 107 | if (!value.equals(_fixedParamCount)) { 108 | reset(); 109 | } 110 | 111 | return _fixedParamCount = value; 112 | } 113 | 114 | function get_abi():Option { 115 | return _abi; 116 | } 117 | 118 | function set_abi(value:Option):Option { 119 | if (!value.equals(_abi)) { 120 | reset(); 121 | } 122 | 123 | return _abi = value; 124 | } 125 | 126 | /** 127 | * Returns a pointer to the symbol in the library. 128 | * 129 | * If the symbol is not a function, do not define any function parameters 130 | * or return type. Simply use this method. 131 | * 132 | * @throws String An error message if the symbol was not found or any 133 | * other error. 134 | */ 135 | public function pointer():Pointer { 136 | return new Pointer(context, libraryHandle.getPointer(name)); 137 | } 138 | 139 | /** 140 | * Executes the function with the given array of arguments. 141 | * 142 | * (Internally, this is the implementation method. `call` is the 143 | * convenience method.) 144 | * 145 | * @param args Arguments that correspond the parameter data types. 146 | * Arguments can be `Int`, `haxe.io.Int64`, `Float`, or `Pointer`. 147 | * Numeric types will be promoted and casted 148 | * (with possible truncation or loss of precision) appropriately. 149 | * @return If `returnType` is not `DataType.Void`, the return value 150 | * will be converted to either `Int`, `haxe.io.Int64`, `Float`, or 151 | * `Pointer`. 152 | * 153 | * Integer data types that fit within 32 bits will be 154 | * promoted to `Int` while wider integers will be promoted 155 | * to `haxe.io.Int64`. 156 | * 157 | * If `returnType` is `DataType.Struct`, the return value will be 158 | * a `Pointer` which the caller should free. 159 | * 160 | * @throws String An error message if the argument list is invalid (such 161 | * as wrong size or wrong type. 162 | * 163 | * @see `Function.call` for easier function call. 164 | */ 165 | public function arrayCall(?args:Array):Any { 166 | if (functionHandle == null) { 167 | var abi:Null; 168 | 169 | switch _abi { 170 | case Some(abi_): 171 | abi = abi_; 172 | case None: 173 | abi = null; 174 | } 175 | 176 | 177 | switch _fixedParamCount { 178 | case None: 179 | functionHandle = libraryHandle.newFunction(name, _params, _returnType, abi); 180 | case Some(fixedParamCount): 181 | functionHandle = libraryHandle.newVariadicFunction(name, _params, fixedParamCount, _returnType, abi); 182 | } 183 | } 184 | 185 | if (args != null) { 186 | final numArgs = args.length; 187 | for (index in 0...numArgs) { 188 | args[index] = Pointer.unwrap(args[index]); 189 | } 190 | } 191 | 192 | final returnValue = functionHandle.call(args); 193 | 194 | return Pointer.wrap(returnValue, context); 195 | } 196 | 197 | function reset() { 198 | if (functionHandle != null) { 199 | functionHandle.dispose(); 200 | functionHandle = null; 201 | } 202 | } 203 | 204 | @:allow(callfunc.Library) 205 | function dispose() { 206 | if (functionHandle != null) { 207 | functionHandle.dispose(); 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/haxe/callfunc/Library.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.core.Context; 4 | import callfunc.core.LibraryHandle; 5 | 6 | 7 | /** 8 | * Loaded dynamic library symbols and functions. 9 | */ 10 | class Library implements Disposable { 11 | final context:Context; 12 | final libraryHandle:LibraryHandle; 13 | final functions:Map; 14 | 15 | /** 16 | * Array or field access to functions and symbols. 17 | * 18 | * This object allows accessing functions and symbols using the array 19 | * syntax or field access syntax. 20 | * 21 | * If the symbol name exists in the library, a `Function` will be created 22 | * automatically. It defaults to functions that accept no arguments and a 23 | * void return type. If previously defined using `define`, it will return 24 | * the previously defined function. 25 | * 26 | * Example accessing functions: 27 | * 28 | * - Array syntax: `myLibrary.s["myFunction"].call()` 29 | * - Field syntax: `myLibrary.s.myFunction.call()` 30 | * 31 | * Example getting a pointer: 32 | * 33 | * - Array syntax: `myLibrary.s["myFunction"].pointer()` 34 | * - Field syntax: `myLibrary.s.myFunction.pointer()` 35 | */ 36 | public final s:LibrarySymbolAccess; 37 | 38 | public function new(context:Context, libraryHandle:LibraryHandle) { 39 | this.context = context; 40 | this.libraryHandle = libraryHandle; 41 | functions = []; 42 | s = this; 43 | } 44 | 45 | /** 46 | * Returns whether the given symbol name is in the library's symbol table. 47 | */ 48 | public function hasSymbol(name:String):Bool { 49 | return libraryHandle.hasSymbol(name); 50 | } 51 | 52 | /** 53 | * Defines a C function's signature. 54 | * 55 | * @param name Function's symbol name. 56 | * @param params Data types corresponding to the function parameters. 57 | * If the function does not accept arguments, specify `null` or empty 58 | * array. 59 | * @param returnType Data type of the return value. If the function does 60 | * not return a value. Specify `null` or `DataType.Void`. 61 | * @param alias Name used to access this function. If not provided, it 62 | * defaults to the function's name. 63 | * @param abi If supported by the platform and target, an ABI calling 64 | * method matching `enum ffi_abi` defined in `ffitarget.h`. 65 | * 66 | * @see `Library.defineVariadic` for C variadic functions. 67 | */ 68 | public function define(name:String, ?params:Array, 69 | ?returnType:DataType, ?alias:String, ?abi:Int):Function { 70 | return addNewFunction(name, params, returnType, alias, abi); 71 | } 72 | 73 | /** 74 | * Defines a variadic C function. 75 | * 76 | * When calling variadic functions, the number of arguments must match 77 | * the number of parameters in the definition. As such, a separate 78 | * definition must be made for the same function if the number of arguments 79 | * is different. Use `alias` to give each definition a different name. 80 | * 81 | * @param name Function's symbol name. 82 | * @param params Data types corresponding to the function parameters. 83 | * @param fixedParamCount Number of parameters that are fixed (not variadic) 84 | * at the start of the parameters list. 85 | * @param returnType Data type of the return value. 86 | * @param alias Name used to access this function. If not provided, it 87 | * defaults to the function's name. 88 | * @param abi ABI calling method. 89 | * 90 | * @see `Library.define` for full parameter documentation. 91 | */ 92 | public function defineVariadic(name:String, params:Array, 93 | fixedParamCount:Int, ?returnType:DataType, 94 | ?alias:String, ?abi:Int):Function { 95 | return addNewFunction(name, params, fixedParamCount, returnType, alias, abi); 96 | } 97 | 98 | function addNewFunction(name:String, ?params:Array, 99 | ?fixedParamCount:Int, ?returnType:DataType, 100 | ?alias:String, ?abi:Int):Function { 101 | final key = alias != null ? alias : name; 102 | 103 | if (functions.exists(key)) { 104 | throw 'Function is already defined: $key'; 105 | } 106 | 107 | if (!hasSymbol(name)) { 108 | throw 'Symbol not in library: $name'; 109 | } 110 | 111 | final func = new Function(name, context, this, libraryHandle); 112 | 113 | if (params != null) { 114 | func.params = params; 115 | } 116 | 117 | if (returnType != null) { 118 | func.returnType = returnType; 119 | } 120 | 121 | if (fixedParamCount != null) { 122 | func.fixedParamCount = Some(fixedParamCount); 123 | } 124 | 125 | if (abi != null) { 126 | func.abi = Some(abi); 127 | } 128 | 129 | functions.set(key, func); 130 | 131 | return func; 132 | } 133 | 134 | /** 135 | * Returns a function from the given name. 136 | * 137 | * @param name Function symbol name or alias name. 138 | * @return If the function has been previously defined with `Library.define`, 139 | * it will return the previously function. Otherwise, a new `Function` 140 | * will be created. 141 | */ 142 | public function get(name:String):Function { 143 | final func = functions.get(name); 144 | 145 | if (func != null) { 146 | return func; 147 | } else { 148 | return addNewFunction(name); 149 | } 150 | } 151 | 152 | /** 153 | * Removes a previously defined function, disposing it if necessary. 154 | * 155 | * @param name Function symbol name or alias name. 156 | */ 157 | public function undefine(name:String) { 158 | final func = functions.get(name); 159 | 160 | if (func != null) { 161 | func.dispose(); 162 | functions.remove(name); 163 | } 164 | } 165 | 166 | /** 167 | * Dispose all functions. 168 | */ 169 | public function disposeFunctions() { 170 | for (func in functions) { 171 | func.dispose(); 172 | } 173 | 174 | functions.clear(); 175 | } 176 | 177 | /** 178 | * Dispose all functions and the library itself. 179 | */ 180 | public function dispose() { 181 | disposeFunctions(); 182 | libraryHandle.dispose(); 183 | } 184 | } 185 | 186 | 187 | abstract LibrarySymbolAccess(Library) from Library { 188 | @:arrayAccess @:op(a.b) 189 | inline function get(name:String) { 190 | return this.get(name); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/haxe/callfunc/Pointer.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.core.BasicPointer; 4 | import callfunc.core.Context; 5 | import callfunc.string.StringUtil; 6 | import callfunc.string.Encoding; 7 | import haxe.Int64; 8 | import haxe.io.Bytes; 9 | 10 | /** 11 | * Read and write values of a C pointer. 12 | */ 13 | class Pointer { 14 | final context:Context; 15 | @:allow(callfunc.Pointer) 16 | final basicPointer:BasicPointer; 17 | 18 | /** 19 | * The value of the pointer that represents an address in memory 20 | * where the targeted data is stored. 21 | */ 22 | public var address(get, never):Int64; 23 | 24 | /** 25 | * Default data type if not specified in get or set methods. 26 | */ 27 | public var dataType:DataType; 28 | 29 | public function new(context:Context, basicPointer:BasicPointer) { 30 | this.context = context; 31 | this.basicPointer = basicPointer; 32 | dataType = DataType.SInt; 33 | } 34 | 35 | function get_address() { 36 | return basicPointer.address; 37 | } 38 | 39 | /** 40 | * Returns whether the pointer is a null pointer. 41 | * 42 | * If the pointer is a null pointer, the address is not valid and typically 43 | * represented with a value of 0. 44 | */ 45 | public function isNull():Bool { 46 | return address == 0; 47 | } 48 | 49 | inline function getDataType(dataType:Null):DataType { 50 | return dataType != null ? dataType : this.dataType; 51 | } 52 | 53 | /** 54 | * Returns the value at the addressed memory location. 55 | * 56 | * @param dataType Data type of the value expected at the addressed memory. 57 | * @param offset Value in bytes used to offset the address. This is used to 58 | * access fields in a struct. 59 | * @return A value converted to the type `Int`, `haxe.io.Int64`, 60 | * `Float`, or `Pointer`. 61 | * 62 | * Integer data types that fit within 32 bits will be 63 | * promoted to `Int` while wider integers will be promoted 64 | * to `haxe.io.Int64`. 65 | */ 66 | public function get(?dataType:DataType, offset:Int = 0):Any { 67 | return wrap(basicPointer.get(getDataType(dataType), offset), context); 68 | } 69 | 70 | /** 71 | * Sets the value at the addressed memory location. 72 | * 73 | * @param value A value of type `Int`, `haxe.io.Int64`, 74 | * `Float`, or `Pointer`. Numeric types will be promoted and casted 75 | * appropriately. 76 | * @param dataType Data type of the value expected at the addressed memory. 77 | * @param offset Value in bytes used to offset the address. This is used to 78 | * access fields in a struct. 79 | */ 80 | public function set(value:Any, ?dataType:DataType, offset:Int = 0) { 81 | basicPointer.set(unwrap(value), getDataType(dataType), offset); 82 | } 83 | 84 | /** 85 | * Returns the element value at the addressed C array location. 86 | * 87 | * @param index Element index of the array. 88 | * @param dataType Data type of the array. 89 | * @see `Pointer.get` for return types. 90 | */ 91 | public function arrayGet(index:Int, ?dataType:DataType):Any { 92 | return wrap(basicPointer.arrayGet(index, getDataType(dataType)), context); 93 | } 94 | 95 | /** 96 | * Sets the element value at the addressed C array location. 97 | * @param index Element index. 98 | * @param value Element value. 99 | * @param dataType Data type of the array. 100 | * @see `Pointer.set` for parameter types. 101 | */ 102 | public function arraySet(index:Int, value:Any, ?dataType:DataType) { 103 | basicPointer.arraySet(index, unwrap(value), getDataType(dataType)); 104 | } 105 | 106 | /** 107 | * Removes the memory allocated at the addressed location. 108 | * 109 | * This should only be called for pointers for which the caller has 110 | * ownership for previously allocated memory. 111 | * 112 | * Calling this more than once is undefined behavior. 113 | */ 114 | public function free() { 115 | context.free(basicPointer); 116 | } 117 | 118 | /** 119 | * Decodes the addressed value as a char array and returns new Haxe string. 120 | * 121 | * @param length Number in bytes (not code units and not code points) of the 122 | * string. If not given, the length of the string is determined by 123 | * searching for a null terminator. 124 | * @param encoding 125 | */ 126 | public function getString(?length:Int, 127 | encoding:Encoding = Encoding.UTF8):String { 128 | 129 | final length = StringUtil.getPointerNullTerminator(this, encoding); 130 | final view = getDataView(length); 131 | 132 | return view.getStringFull(0, length, encoding); 133 | } 134 | 135 | /** 136 | * Encodes the given string and writes it as a char array. 137 | * 138 | * @param text String to be encoded, 139 | * @param encoding Encoding such as UTF-8. 140 | * @param terminator Whether to include a null-terminator. 141 | * @return Number of bytes written 142 | */ 143 | public function setString(text:String, encoding:Encoding = Encoding.UTF8, 144 | terminator:Bool = false, ?lengthCallback:Int->Void):Int { 145 | final length = StringUtil.getEncodedLength(text, encoding, terminator); 146 | final view = getDataView(length); 147 | 148 | return view.setStringFull(0, text, encoding, terminator); 149 | } 150 | 151 | #if sys 152 | /** 153 | * Returns a Haxe Bytes using the pointer's value as the underlying data. 154 | * 155 | * @see `getDataView` for the purpose of this method. 156 | * 157 | * Compared to `getDataView` this method may be slightly faster but is not 158 | * portable between targets. 159 | * 160 | */ 161 | public function getBytes(count:Int):Bytes { 162 | return context.pointerToBytes(basicPointer, count); 163 | } 164 | #end 165 | 166 | /** 167 | * Return a data view using the pointer's value as the underlying data. 168 | * 169 | * The pointer is interpreted as the target's native array and used as 170 | * the data view's underlying bytes data. 171 | * 172 | * This method is useful for easier and faster access than the 173 | * `arrayGet` and `arraySet` methods. 174 | * 175 | * Care must be ensured that the pointer is not freed or else the data 176 | * view will access invalid memory locations. 177 | * 178 | * The pointer is not automatically freed when the returned data view is 179 | * garbage collected. 180 | * 181 | * @param count Array length in bytes. 182 | */ 183 | public function getDataView(count:Int):DataView { 184 | return context.pointerToDataView(basicPointer, count); 185 | } 186 | 187 | @:allow(callfunc.Function) 188 | static function wrap(value:Any, context:Context):Any { 189 | if (Std.is(value, BasicPointer)) { 190 | return new Pointer(context, value); 191 | } else { 192 | return value; 193 | } 194 | } 195 | 196 | @:allow(callfunc) 197 | static function unwrap(value:Any):Any { 198 | if (Std.is(value, Pointer)) { 199 | return (value:Pointer).basicPointer; 200 | } else { 201 | return value; 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /src/haxe/callfunc/StructAccess.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.core.StructTypeHandle; 4 | 5 | class StructAccessImpl { 6 | final pointer:Pointer; 7 | final structType:StructTypeHandle; 8 | final nameMap:Map; 9 | 10 | public function new(pointer:Pointer, structType:StructTypeHandle, names:Iterable) { 11 | this.pointer = pointer; 12 | this.structType = structType; 13 | this.nameMap = new Map(); 14 | 15 | var index = 0; 16 | for (name in names) { 17 | nameMap.set(name, index); 18 | index += 1; 19 | } 20 | } 21 | 22 | function getIndex(name:String):Int { 23 | var index = nameMap.get(name); 24 | 25 | if (index == null) { 26 | throw 'Field $name not defined'; 27 | } 28 | 29 | return index; 30 | } 31 | 32 | public function get(name:String):Any { 33 | var index = getIndex(name); 34 | 35 | return pointer.get(structType.dataTypes[index], 36 | structType.offsets[index]); 37 | } 38 | 39 | public function set(name:String, value:T):T { 40 | var index = getIndex(name); 41 | 42 | pointer.set(value, structType.dataTypes[index], 43 | structType.offsets[index]); 44 | 45 | return value; 46 | } 47 | } 48 | 49 | /** 50 | * Provides array and field access to a struct pointer by names. 51 | * 52 | * The struct can be accessed using array syntax `myStruct["fieldName"]` 53 | * or by field access syntax `myStruct.fieldName`. 54 | */ 55 | @:forward 56 | abstract StructAccess(StructAccessImpl) { 57 | /** 58 | * @param pointer Pointer to an existing struct. 59 | * @param structType Structure definition. 60 | * @param names Names of the fields in the struct. 61 | */ 62 | public inline function new(pointer:Pointer, structType:StructTypeHandle, 63 | names:Iterable) { 64 | this = new StructAccessImpl(pointer, structType, names); 65 | } 66 | 67 | @:op([]) @:op(a.b) 68 | inline function _get(name:String):Any { 69 | return this.get(name); 70 | } 71 | 72 | @:op([]) @:op(a.b) 73 | inline function _set(name:String, value:T):T { 74 | return this.set(name, value); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/haxe/callfunc/StructDef.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.core.StructTypeHandle; 4 | 5 | /** 6 | * Definition of C structure (composite data type) packing information 7 | * and field names. 8 | */ 9 | class StructDef implements Disposable { 10 | final structTypeHandle:StructTypeHandle; 11 | 12 | /** 13 | * Names of each field in order. 14 | */ 15 | public final names:Array; 16 | 17 | /** 18 | * Size in bytes of the struct. 19 | */ 20 | public var size(get, never):Int; 21 | 22 | /** 23 | * Data types of each field. 24 | */ 25 | public var dataTypes(get, never):Array; 26 | 27 | /** 28 | * Location of each field in the struct in bytes. 29 | */ 30 | public var offsets(get, never):Array; 31 | 32 | public function new(structType:StructTypeHandle, names:Array) { 33 | this.structTypeHandle = structType; 34 | this.names = names; 35 | } 36 | 37 | function get_size() { 38 | return structTypeHandle.size; 39 | } 40 | 41 | function get_dataTypes() { 42 | return structTypeHandle.dataTypes; 43 | } 44 | 45 | function get_offsets() { 46 | return structTypeHandle.offsets; 47 | } 48 | 49 | /** 50 | * Return a StructAccess object from a given pointer. 51 | * 52 | * @param pointer Pointer to a C struct with a matching definition. 53 | */ 54 | public function access(pointer:Pointer):StructAccess { 55 | return new StructAccess(pointer, structTypeHandle, names); 56 | } 57 | 58 | public function dispose() { 59 | structTypeHandle.dispose(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/BasicPointer.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core; 2 | 3 | import haxe.Int64; 4 | 5 | /** 6 | * C pointer methods 7 | */ 8 | interface BasicPointer { 9 | public var address(get, never):Int64; 10 | 11 | /** 12 | * Returns the value at the addressed memory location. 13 | * 14 | * @see `Pointer.get` 15 | */ 16 | public function get(dataType:DataType, offset:Int = 0):Any; 17 | 18 | /** 19 | * Sets the value at the addressed memory location. 20 | * 21 | * @see `Pointer.set` 22 | */ 23 | public function set(value:Any, dataType:DataType, offset:Int = 0):Void; 24 | 25 | /** 26 | * Returns the element value at the addressed C array location. 27 | * 28 | * @see `Pointer.arrayGet` 29 | */ 30 | public function arrayGet(index:Int, dataType:DataType):Any; 31 | 32 | /** 33 | * Sets the element value at the address C array location. 34 | * 35 | * @see `Pointer.arraySet` 36 | */ 37 | public function arraySet(index:Int, value:Any, dataType:DataType):Void; 38 | } 39 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/CallbackHandle.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core; 2 | 3 | /** 4 | * Handle to a libffi callback definition 5 | */ 6 | interface CallbackHandle extends Disposable { 7 | /** 8 | * Returns a function pointer which can be called by C code. 9 | */ 10 | public function getPointer():BasicPointer; 11 | } 12 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/Context.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core; 2 | 3 | import haxe.Int64; 4 | import haxe.io.Bytes; 5 | 6 | /** 7 | * Handle to access foreign functions and data. 8 | */ 9 | interface Context { 10 | /** 11 | * Allocates memory on the heap. 12 | * 13 | * @see `Callfunc.alloc` 14 | */ 15 | public function alloc(size:Int, initZero:Bool = false):BasicPointer; 16 | 17 | /** 18 | * Releases previously allocated memory. 19 | */ 20 | public function free(pointer:BasicPointer):Void; 21 | 22 | /** 23 | * Returns the width of the data type. 24 | * @return Number of bytes. 25 | */ 26 | public function sizeOf(type:DataType):Int; 27 | 28 | /** 29 | * Returns a pointer from the given address. 30 | * 31 | * @param address An address in memory. 32 | */ 33 | public function getPointer(address:Int64):BasicPointer; 34 | 35 | #if sys 36 | /** 37 | * Exposes a pointer to the underlying C array of a Haxe `Bytes`. 38 | * 39 | * @see `Callfunc.bytesToPointer` 40 | */ 41 | public function bytesToPointer(bytes:Bytes):BasicPointer; 42 | 43 | /** 44 | * Wraps a pointer of a C array to Haxe `Bytes`. 45 | * 46 | * @see `Pointer.getBytes` 47 | */ 48 | public function pointerToBytes(pointer:BasicPointer, count:Int):Bytes; 49 | #end 50 | 51 | /** 52 | * Wraps a pointer of C array to a data view. 53 | * 54 | * @see `Pointer.getDataView` 55 | */ 56 | public function pointerToDataView(pointer:BasicPointer, count:Int):DataView; 57 | 58 | /** 59 | * Returns a library handle to a dynamic library. 60 | * 61 | * @see `Callfunc.openLibrary` 62 | */ 63 | public function newLibrary(name:String):LibraryHandle; 64 | 65 | /** 66 | * Returns a C struct type information. 67 | * @param dataTypes Data types for each field of the struct. 68 | * @throws String An error message if the data type is invalid. 69 | */ 70 | public function newStructType(dataTypes:Array):StructTypeHandle; 71 | 72 | /** 73 | * Returns a callback handle for passing Haxe functions to C code. 74 | * 75 | * @see `Callfunc.wrapCallback` 76 | */ 77 | public function newCallback(haxeFunction:Array->Any, 78 | ?params:Array, 79 | ?returnType:DataType):CallbackHandle; 80 | } 81 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/CoreDataTypeTable.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core; 2 | 3 | import haxe.ds.Vector; 4 | 5 | /** 6 | * Table of mappings from DataType to CoreDataType. 7 | */ 8 | class CoreDataTypeTable { 9 | final context:Context; 10 | final table:Vector; 11 | final tableValid:Vector; 12 | final fixedWidthTable:Vector; 13 | final fixedWidthTableValid:Vector; 14 | 15 | public function new(context:Context) { 16 | this.context = context; 17 | final size = 28; 18 | table = new Vector(size); 19 | tableValid = new Vector(size); 20 | fixedWidthTable = new Vector(size); 21 | fixedWidthTableValid = new Vector(size); 22 | 23 | populateTables(); 24 | } 25 | 26 | function populateTables() { 27 | final enumValues:Array = [ 28 | Void, 29 | UInt8, 30 | SInt8, 31 | UInt16, 32 | SInt16, 33 | UInt32, 34 | SInt32, 35 | UInt64, 36 | SInt64, 37 | Float, 38 | Double, 39 | UChar, 40 | SChar, 41 | UShort, 42 | SShort, 43 | SInt, 44 | UInt, 45 | SLong, 46 | ULong, 47 | Pointer, 48 | LongDouble, 49 | ComplexFloat, 50 | ComplexDouble, 51 | ComplexLongDouble, 52 | Size, 53 | PtrDiff, 54 | WChar, 55 | Struct([]), 56 | ]; 57 | 58 | for (index in 0...enumValues.length) { 59 | try { 60 | table[index] = DataTypeTools.toCoreDataType(context, enumValues[index], false); 61 | tableValid[index] = true; 62 | } catch (exception:String) { 63 | tableValid[index] = false; 64 | } 65 | 66 | try { 67 | fixedWidthTable[index] = DataTypeTools.toCoreDataType(context, enumValues[index], true); 68 | fixedWidthTableValid[index] = true; 69 | } catch (exception:String) { 70 | fixedWidthTableValid[index] = false; 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * Converts data type to a appropriate core data type. 77 | * 78 | * @param dataType 79 | * @param fixedWidth Whether integer data types are substituted to their 80 | * fixed-width data types. If the data type is not an integer, it is left unchanged. 81 | * @throws String If the data type is not a supported core data type. 82 | */ 83 | public function toCoreDataType(dataType:DataType, 84 | fixedWidth:Bool = false):CoreDataType { 85 | final table:Vector = fixedWidth ? fixedWidthTable : this.table; 86 | final valid:Vector = fixedWidth ? fixedWidthTableValid : tableValid; 87 | var index:Int; 88 | 89 | switch dataType { 90 | case Void: index = 0; 91 | case UInt8: index = 1; 92 | case SInt8: index = 2; 93 | case UInt16: index = 3; 94 | case SInt16: index = 4; 95 | case UInt32: index = 5; 96 | case SInt32: index = 6; 97 | case UInt64: index = 7; 98 | case SInt64: index = 8; 99 | case Float: index = 9; 100 | case Double: index = 10; 101 | case UChar: index = 11; 102 | case SChar: index = 12; 103 | case UShort: index = 13; 104 | case SShort: index = 14; 105 | case SInt: index = 15; 106 | case UInt: index = 16; 107 | case SLong: index = 17; 108 | case ULong: index = 18; 109 | case Pointer: index = 19; 110 | case LongDouble: index = 20; 111 | case ComplexFloat: index = 21; 112 | case ComplexDouble: index = 22; 113 | case ComplexLongDouble: index = 23; 114 | case Size: index = 24; 115 | case PtrDiff: index = 25; 116 | case WChar: index = 26; 117 | case Struct(fields): index = 27; 118 | } 119 | 120 | if (valid[index]) { 121 | return table[index]; 122 | } else { 123 | throw 'Unable to convert DataType $dataType to CoreDataType'; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/DataTypeTools.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core; 2 | 3 | class DataTypeTools { 4 | /** 5 | * Converts data type to a appropriate core data type. 6 | * 7 | * @param dataType 8 | * @param fixedWidth Whether integer data types are substituted to their 9 | * fixed-width data types. If the data type is not an integer, it is left unchanged. 10 | * @throws String If the data type is not a supported core data type. 11 | * 12 | * @see `CoreDataTypeTable` for cached version. 13 | */ 14 | public static function toCoreDataType(context:Context, dataType:DataType, 15 | fixedWidth:Bool = false):CoreDataType { 16 | if (fixedWidth) { 17 | dataType = toFixedWidth(context, dataType); 18 | } 19 | 20 | try { 21 | return CoreDataType.fromDataType(dataType); 22 | } catch (exception:String) { 23 | // continue 24 | } 25 | 26 | dataType = toFixedWidth(context, dataType); 27 | 28 | return CoreDataType.fromDataType(dataType); 29 | } 30 | 31 | static function toFixedWidth(context:Context, dataType:DataType):DataType { 32 | var size:Int; 33 | var signed:Bool; 34 | 35 | switch dataType { 36 | case UChar | UShort | UInt | ULong | Size | WChar: 37 | size = context.sizeOf(dataType); 38 | signed = false; 39 | case SChar | SShort | SInt | SLong | PtrDiff: 40 | size = context.sizeOf(dataType); 41 | signed = true; 42 | default: 43 | return dataType; 44 | } 45 | 46 | switch size { 47 | case 1: return signed ? SInt8 : UInt8; 48 | case 2: return signed ? SInt16 : UInt16; 49 | case 4: return signed ? SInt32 : UInt32; 50 | case 8: return signed ? SInt64 : UInt64; 51 | default: throw 'Unsupported size $size for type $dataType'; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/FunctionHandle.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core; 2 | 3 | /** 4 | * A handle to a function in a dynamic library. 5 | */ 6 | interface FunctionHandle extends Disposable { 7 | /** 8 | * Execute the function. 9 | * 10 | * @see `Function.arrayCall` 11 | */ 12 | public function call(?args:Array):Any; 13 | } 14 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/LibraryHandle.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core; 2 | 3 | /** 4 | * Handle to a loaded dynamic library. 5 | */ 6 | interface LibraryHandle extends Disposable { 7 | public function hasSymbol(name:String):Bool; 8 | 9 | /** 10 | * Returns a pointer to a symbol. 11 | * 12 | * @throws String An error message if the symbol was not found or any 13 | * other error. 14 | */ 15 | public function getPointer(name:String):BasicPointer; 16 | 17 | /** 18 | * Create a handle to a function. 19 | * 20 | * @see `Library` 21 | */ 22 | public function newFunction(name:String, ?params:Array, 23 | ?returnType:DataType, ?abi:Int):FunctionHandle; 24 | 25 | /** 26 | * Create a handle to a variadic function. 27 | * 28 | * @see `Library` 29 | */ 30 | public function newVariadicFunction(name:String, params:Array, 31 | fixedParamCount:Int, ?returnType:DataType, ?abi:Int):FunctionHandle; 32 | } 33 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/StructTypeHandle.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core; 2 | 3 | /** 4 | * Handle to C structure (composite data type) packing information. 5 | */ 6 | interface StructTypeHandle extends Disposable { 7 | /** 8 | * Size in bytes of the struct. 9 | */ 10 | public var size(get, never):Int; 11 | 12 | /** 13 | * Data types of each field. 14 | */ 15 | public var dataTypes(get, never):Array; 16 | 17 | /** 18 | * Location of each field in the struct. 19 | */ 20 | public var offsets(get, never):Array; 21 | } 22 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/dummy/DummyContext.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.dummy; 2 | 3 | import haxe.Int64; 4 | import haxe.io.Bytes; 5 | 6 | class DummyContext implements Context { 7 | public function new() { 8 | } 9 | 10 | public function alloc(size:Int, initZero:Bool = false):BasicPointer { 11 | throw "Not implemented"; 12 | } 13 | 14 | public function free(pointer:BasicPointer):Void { 15 | throw "Not implemented"; 16 | } 17 | 18 | public function sizeOf(type:DataType):Int { 19 | throw "Not implemented"; 20 | } 21 | 22 | public function getPointer(address:Int64):BasicPointer { 23 | throw "Not implemented"; 24 | } 25 | 26 | #if sys 27 | public function bytesToPointer(bytes:Bytes):BasicPointer { 28 | throw "Not implemented"; 29 | } 30 | 31 | public function pointerToBytes(pointer:BasicPointer, count:Int):Bytes { 32 | throw "Not implemented"; 33 | } 34 | #end 35 | 36 | public function pointerToDataView(pointer:BasicPointer, count:Int):DataView { 37 | throw "Not implemented"; 38 | } 39 | 40 | public function newLibrary(name:String):LibraryHandle { 41 | throw "Not implemented"; 42 | } 43 | 44 | public function newStructType(dataTypes:Array):StructTypeHandle { 45 | throw "Not implemented"; 46 | } 47 | 48 | public function newCallback(haxeFunction:Array->Any, 49 | ?params:Array, 50 | ?returnType:DataType):CallbackHandle { 51 | throw "Not implemented"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/emscripten/EmCallback.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.emscripten; 2 | 3 | class EmCallback implements CallbackHandle { 4 | final context:EmContext; 5 | final pointer:BasicPointer; 6 | 7 | public function new(context:EmContext, 8 | haxeFunction:Array->Any, 9 | ?params:Array, ?returnType:DataType) { 10 | params = params != null ? params : []; 11 | returnType = returnType != null ? returnType : DataType.Void; 12 | 13 | this.context = context; 14 | 15 | var signatureBuffer = new StringBuf(); 16 | 17 | signatureBuffer.add(EmDataType.toWasmSignature(returnType)); 18 | 19 | for (param in params) { 20 | signatureBuffer.add(EmDataType.toWasmSignature(param)); 21 | } 22 | 23 | var nativePointer = context.module.addFunction( 24 | Reflect.makeVarArgs(cast haxeFunction), signatureBuffer.toString()); 25 | pointer = new EmPointer(context, nativePointer); 26 | } 27 | 28 | public function getPointer():BasicPointer { 29 | return pointer; 30 | } 31 | 32 | public function dispose() { 33 | // nothing 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/emscripten/EmContext.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.emscripten; 2 | 3 | import haxe.Int64; 4 | import haxe.io.Bytes; 5 | 6 | using callfunc.core.emscripten.ModuleTools; 7 | 8 | class EmContext implements Context { 9 | @:allow(callfunc.core.emscripten) 10 | final module:EmscriptenModule; 11 | 12 | @:allow(callfunc.core.emscripten) 13 | final coreDataTypeTable:CoreDataTypeTable; 14 | 15 | public function new(module:EmscriptenModule) { 16 | this.module = module; 17 | coreDataTypeTable = new CoreDataTypeTable(this); 18 | } 19 | 20 | public function alloc(size:Int, initZero:Bool = false):BasicPointer { 21 | var rawPointer; 22 | 23 | if (initZero) { 24 | rawPointer = module.getSymbol("calloc")(size, 1); 25 | 26 | } else { 27 | rawPointer = module.getSymbol("malloc")(size); 28 | } 29 | 30 | if (rawPointer == 0) { 31 | throw "Alloc failed"; 32 | } 33 | 34 | return new EmPointer(this, rawPointer); 35 | } 36 | 37 | public function free(pointer:BasicPointer) { 38 | module.getSymbol("free")(cast(pointer, EmPointer).nativePointer); 39 | } 40 | 41 | public function sizeOf(type:DataType):Int { 42 | return EmDataType.getSize(type); 43 | } 44 | 45 | public function getPointer(address:Int64):BasicPointer { 46 | return new EmPointer(this, address.low); 47 | } 48 | 49 | public function pointerToDataView(pointer:BasicPointer, count:Int):DataView { 50 | var nativePointer = cast(pointer, EmPointer).nativePointer; 51 | var bytes = Bytes.ofData(module.HEAPU8.buffer); 52 | return new BytesDataView(bytes, nativePointer, count); 53 | } 54 | 55 | public function newLibrary(name:String):LibraryHandle { 56 | if (name != "") { 57 | throw "Library name cannot be specified. Only empty string \"\" is supported."; 58 | } 59 | 60 | return new EmLibrary(this); 61 | } 62 | 63 | public function newStructType(dataTypes:Array):StructTypeHandle { 64 | return new EmStructType(dataTypes); 65 | } 66 | 67 | public function newCallback(haxeFunction:Array->Any, 68 | ?params:Array, 69 | ?returnType:DataType):CallbackHandle { 70 | return new EmCallback(this, haxeFunction, params, returnType); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/emscripten/EmDataType.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.emscripten; 2 | 3 | class EmDataType { 4 | public static function toLLVMType(dataType:DataType):String { 5 | switch dataType { 6 | case Pointer: 7 | return "*"; 8 | case UInt8 | SInt8 | UChar | SChar | WChar: 9 | return "i8"; 10 | case UInt16 | SInt16 | UShort | SShort: 11 | return "i16"; 12 | case UInt32 | SInt32 | SInt | UInt | Size | PtrDiff: 13 | return "i32"; 14 | case UInt64 | SInt64 | SLong | ULong: 15 | return "i64"; 16 | case Float: 17 | return "float"; 18 | case Double: 19 | return "double"; 20 | case Void: 21 | throw "Void is not a real type"; 22 | case LongDouble | ComplexFloat | ComplexDouble | ComplexLongDouble | Struct(_): 23 | throw 'Not supported data type $dataType'; 24 | } 25 | } 26 | 27 | public static function toLLVMReturnType(dataType:DataType):Null { 28 | if (dataType == DataType.Void) { 29 | return null; 30 | } else { 31 | return toLLVMType(dataType); 32 | } 33 | } 34 | 35 | public static function getSize(dataType:DataType):Int { 36 | switch dataType { 37 | case Pointer: 38 | return 4; 39 | case UInt8 | SInt8 | UChar | SChar | WChar: 40 | return 1; 41 | case UInt16 | SInt16 | UShort | SShort: 42 | return 2; 43 | case UInt32 | SInt32 | SInt | UInt | Size | PtrDiff: 44 | return 4; 45 | case UInt64 | SInt64 | SLong | ULong: 46 | return 8; 47 | case Float: 48 | return 4; 49 | case Double: 50 | return 8; 51 | case Void: 52 | throw "Void is not a real type"; 53 | case LongDouble | ComplexFloat | ComplexDouble | ComplexLongDouble | Struct(_): 54 | return 0; 55 | } 56 | } 57 | 58 | public static function toCCallType(dataType:DataType):String { 59 | return "number"; 60 | } 61 | 62 | public static function toCCallReturnType(dataType:DataType):Null { 63 | return dataType != DataType.Void ? toCCallType(dataType) : null; 64 | } 65 | 66 | public static function toWasmSignature(dataType:DataType):String { 67 | switch dataType { 68 | case Pointer: 69 | return "i"; 70 | case UInt8 | SInt8 | UChar | SChar | 71 | UInt16 | SInt16 | UShort | SShort | 72 | UInt32 | SInt32 | SInt | UInt | 73 | WChar | Size | PtrDiff: 74 | return "i"; 75 | case UInt64 | SInt64 | SLong | ULong: 76 | return "j"; 77 | case Float: 78 | return "f"; 79 | case Double: 80 | return "d"; 81 | case Void: 82 | return "v"; 83 | case LongDouble | ComplexFloat | ComplexDouble | ComplexLongDouble | Struct(_): 84 | throw 'Not supported data type $dataType'; 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/emscripten/EmFunction.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.emscripten; 2 | 3 | using callfunc.core.emscripten.ModuleTools; 4 | 5 | class EmFunction implements FunctionHandle { 6 | final context:EmContext; 7 | final params:Array; 8 | final returnType:DataType; 9 | final jsFunc:haxe.Constraints.Function; 10 | final fixedParamCount:Int; 11 | var varargBuffer:Float = 0; 12 | 13 | public function new(context:EmContext, name:String, 14 | ?params:Array, ?returnType:DataType, 15 | ?fixedParamCount:Int) { 16 | this.context = context; 17 | this.params = params != null ? params : []; 18 | this.returnType = returnType != null ? returnType : DataType.Void; 19 | this.fixedParamCount = fixedParamCount != null ? fixedParamCount : -1; 20 | 21 | jsFunc = context.module.getSymbol(name); 22 | } 23 | 24 | public function dispose() { 25 | if (varargBuffer != 0) { 26 | context.module.getSymbol("free")(varargBuffer); 27 | varargBuffer = 0; 28 | } 29 | } 30 | 31 | public function call(?args:Array):Any { 32 | args = args != null ? args : []; 33 | args = unwrapArgs(args); 34 | 35 | if (fixedParamCount < 0) { 36 | final result = @:nullSafety(Off) 37 | Reflect.callMethod(null, jsFunc, args); 38 | 39 | return wrapReturnValue(result); 40 | } else { 41 | // WASM ABI convention is fixed arguments are passed as normal 42 | // but variadic arguments are stored in the stack and a pointer 43 | // to them is passed to the function. 44 | if (varargBuffer == 0) { 45 | allocateVarargBuffer(); 46 | } 47 | 48 | populateVarargBuffer(args); 49 | 50 | final wasmABIArgs = args.slice(0, fixedParamCount); 51 | wasmABIArgs.push(varargBuffer); 52 | 53 | final result = @:nullSafety(Off) 54 | Reflect.callMethod(null, jsFunc, wasmABIArgs); 55 | 56 | return wrapReturnValue(result); 57 | } 58 | } 59 | 60 | function unwrapArgs(args:Array):Array { 61 | for (index in 0...args.length) { 62 | if (params[index] == DataType.Pointer) { 63 | args[index] = cast(args[index], EmPointer).nativePointer; 64 | } 65 | } 66 | 67 | return args; 68 | } 69 | 70 | function wrapReturnValue(value:Any):Any { 71 | switch returnType { 72 | case Pointer: 73 | return new EmPointer(context, value); 74 | default: 75 | return value; 76 | } 77 | } 78 | 79 | function getVarargBufferSize():Int { 80 | var size = 0; 81 | 82 | if (fixedParamCount > 0) { 83 | for (dataType in params.slice(fixedParamCount)) { 84 | size += EmDataType.getSize(dataType); 85 | } 86 | } 87 | 88 | return size; 89 | } 90 | 91 | function allocateVarargBuffer() { 92 | varargBuffer = context.module.getSymbol("malloc")(getVarargBufferSize()); 93 | 94 | if (varargBuffer == 0) { 95 | throw "Memory allocation failed"; 96 | } 97 | } 98 | 99 | function populateVarargBuffer(args:Array) { 100 | Debug.assert(fixedParamCount >= 0); 101 | var offset = 0; 102 | 103 | for (index in fixedParamCount...params.length) { 104 | final dataType = params[index]; 105 | final arg = args[index]; 106 | 107 | context.module.setValue( 108 | varargBuffer + offset, 109 | arg, 110 | EmDataType.toLLVMType(dataType) 111 | ); 112 | 113 | offset += EmDataType.getSize(dataType); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/emscripten/EmLibrary.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.emscripten; 2 | 3 | using callfunc.core.emscripten.ModuleTools; 4 | 5 | class EmLibrary implements LibraryHandle { 6 | final context:EmContext; 7 | 8 | public function new(context:EmContext) { 9 | this.context = context; 10 | } 11 | 12 | public function dispose() { 13 | // nothing 14 | } 15 | 16 | public function hasSymbol(name:String):Bool { 17 | try { 18 | context.module.getSymbol(name); 19 | } catch (error:String) { 20 | return false; 21 | } 22 | 23 | return true; 24 | } 25 | 26 | public function getPointer(name:String):BasicPointer { 27 | throw "Not supported"; 28 | } 29 | 30 | public function newFunction(name:String, ?params:Array, 31 | ?returnType:DataType, ?abi:Int):FunctionHandle { 32 | return new EmFunction(context, name, params, returnType); 33 | } 34 | 35 | public function newVariadicFunction(name:String, params:Array, 36 | fixedParamCount:Int, 37 | ?returnType:DataType, ?abi:Int):FunctionHandle { 38 | return new EmFunction(context, name, params, returnType, fixedParamCount); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/emscripten/EmPointer.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.emscripten; 2 | 3 | import callfunc.core.serialization.NumberUtil; 4 | import haxe.Int64; 5 | 6 | using callfunc.core.emscripten.ModuleTools; 7 | using callfunc.core.DataTypeTools; 8 | 9 | class EmPointer implements BasicPointer { 10 | public var address(get, never):Int64; 11 | 12 | final _address:Int64; 13 | public final nativePointer:Int; 14 | final context:EmContext; 15 | 16 | public function new(context:EmContext, nativePointer:Int) { 17 | this.context = context; 18 | this.nativePointer = nativePointer; 19 | _address = Int64.fromFloat(nativePointer); 20 | } 21 | 22 | function get_address() { 23 | return _address; 24 | } 25 | 26 | public function get(dataType:DataType, offset:Int = 0):Any { 27 | final coreDataType = context.coreDataTypeTable.toCoreDataType(dataType, true); 28 | 29 | switch coreDataType { 30 | case SInt64 | UInt64: return getInt64(offset); 31 | case Void: throw "Void is only for function definition"; 32 | default: // pass 33 | } 34 | 35 | var value = context.module.getValue( 36 | nativePointer + offset, 37 | EmDataType.toLLVMType(dataType) 38 | ); 39 | 40 | switch coreDataType { 41 | case UInt8: value = NumberUtil.intToUInt8(value); 42 | case UInt16: value = NumberUtil.intToUInt16(value); 43 | case UInt32: value = NumberUtil.intToUInt(value); 44 | default: // pass 45 | } 46 | 47 | return value; 48 | } 49 | 50 | function getInt64(offset:Int) { 51 | return Int64.make( 52 | context.module.getValue( 53 | nativePointer + offset + 4, 54 | EmDataType.toLLVMType(DataType.SInt32) 55 | ), 56 | context.module.getValue( 57 | nativePointer + offset, 58 | EmDataType.toLLVMType(DataType.SInt32) 59 | ) 60 | ); 61 | } 62 | 63 | public function set(value:Any, dataType:DataType, offset:Int = 0) { 64 | switch context.coreDataTypeTable.toCoreDataType(dataType, true) { 65 | case SInt64 | UInt64: 66 | setInt64(NumberUtil.toInt64(value), offset); 67 | default: 68 | context.module.setValue( 69 | nativePointer + offset, 70 | value, 71 | EmDataType.toLLVMType(dataType) 72 | ); 73 | } 74 | } 75 | 76 | function setInt64(value:Int64, offset:Int) { 77 | context.module.setValue( 78 | nativePointer + offset, 79 | value.low, 80 | EmDataType.toLLVMType(DataType.SInt32) 81 | ); 82 | context.module.setValue( 83 | nativePointer + offset + 4, 84 | value.high, 85 | EmDataType.toLLVMType(DataType.SInt32) 86 | ); 87 | } 88 | 89 | public function arrayGet(index:Int, dataType:DataType):Any { 90 | return get(dataType, index * EmDataType.getSize(dataType)); 91 | } 92 | 93 | public function arraySet(index:Int, value:Any, dataType:DataType) { 94 | set(value, dataType, index * EmDataType.getSize(dataType)); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/emscripten/EmStructType.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.emscripten; 2 | 3 | class EmStructType implements StructTypeHandle { 4 | public var size(get, never):Int; 5 | public var dataTypes(get, never):Array; 6 | public var offsets(get, never):Array; 7 | 8 | final _size:Int; 9 | final _dataTypes:Array; 10 | final _offsets:Array; 11 | 12 | public function new(dataTypes:Array) { 13 | this._dataTypes = dataTypes; 14 | _offsets = []; 15 | 16 | var offset = 0; 17 | 18 | for (dataType in dataTypes) { 19 | var size = EmDataType.getSize(dataType); 20 | 21 | while (offset % size != 0) { 22 | offset += 1; 23 | } 24 | 25 | _offsets.push(offset); 26 | offset += size; 27 | } 28 | 29 | _size = offset; 30 | } 31 | 32 | function get_size() { 33 | return _size; 34 | } 35 | 36 | function get_dataTypes() { 37 | return _dataTypes; 38 | } 39 | 40 | function get_offsets() { 41 | return _offsets; 42 | } 43 | 44 | public function dispose() { 45 | // nothing; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/emscripten/EmscriptenModule.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.emscripten; 2 | 3 | import haxe.DynamicAccess; 4 | import haxe.io.Float64Array; 5 | import js.lib.Float32Array; 6 | import js.lib.Int16Array; 7 | import js.lib.Int32Array; 8 | import js.lib.Int8Array; 9 | import js.lib.Uint16Array; 10 | import js.lib.Uint32Array; 11 | import js.lib.Uint8Array; 12 | 13 | extern class EmscriptenModule { 14 | public var HEAP8:Int8Array; 15 | public var HEAP16:Int16Array; 16 | public var HEAP32:Int32Array; 17 | public var HEAPU8:Uint8Array; 18 | public var HEAPU16:Uint16Array; 19 | public var HEAPU32:Uint32Array; 20 | public var HEAPF32:Float32Array; 21 | public var HEAPF64:Float64Array; 22 | 23 | public function ccall(ident:String, ?returnType:Null, 24 | ?argTypes:Array, ?args:Array, ?opts:Any):Any; 25 | 26 | public function cwrap(ident:String, ?returnType:Null, 27 | ?argTypes:Array, ?opts:Any):haxe.Constraints.Function; 28 | 29 | public function setValue(ptr:Float, value:Any, type:String):Void; 30 | 31 | public function getValue(ptr:Float, type:String):Any; 32 | 33 | public function addFunction(func:haxe.Constraints.Function, 34 | signature:String):Int; 35 | } 36 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/emscripten/ModuleTools.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.emscripten; 2 | 3 | import haxe.Int64; 4 | 5 | class ModuleTools { 6 | public static function getSymbol(module:EmscriptenModule, name:String):Dynamic { 7 | var result = Reflect.field(module, '_$name'); 8 | 9 | if (result == null) { 10 | throw 'Cannot get symbol _$name. Is it in EXPORTED_FUNCTIONS?'; 11 | } 12 | 13 | return result; 14 | } 15 | 16 | public static function toFloat(int64:Int64):Float { 17 | return (int64.low:Float) + int64.high * 4294967296; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/impl/CallbackImpl.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.impl; 2 | 3 | import callfunc.core.impl.ExternDef.ExternCallback; 4 | import callfunc.core.serialization.ArgSerializer; 5 | import haxe.io.Bytes; 6 | 7 | class CallbackImpl implements CallbackHandle { 8 | final nativePointer:ExternCallback; 9 | final context:ContextImpl; 10 | final argSerializer:ArgSerializer; 11 | final argBuffer:Bytes; 12 | final haxeFunction:Array->Any; 13 | final params:Array; 14 | final returnType:DataType; 15 | final handlerRef:Void->Void; 16 | 17 | public function new(context:ContextImpl, haxeFunction:Array->Any, 18 | ?params:Array, ?returnType:DataType) { 19 | this.context = context; 20 | this.haxeFunction = haxeFunction; 21 | 22 | params = params != null ? params : []; 23 | returnType = returnType != null ? returnType : DataType.Void; 24 | 25 | this.params = params; 26 | this.returnType = returnType; 27 | 28 | argSerializer = new ArgSerializer(context); 29 | argBuffer = Bytes.alloc(argSerializer.getArgBufferLength(params)); 30 | argBuffer.setInt32(0, argBuffer.length); 31 | 32 | nativePointer = ExternDef.newCallback(); 33 | 34 | if (nativePointer == null) { 35 | throw "Failed to allocate callback struct."; 36 | } 37 | 38 | var paramBuffer = argSerializer.serializeParams(params, returnType); 39 | var error = ExternDef.callbackDefine(nativePointer, 40 | ContextImpl.bytesToBytesData(paramBuffer)); 41 | 42 | if (error != 0) { 43 | throw NativeUtil.fromNativeString(ExternDef.getErrorMessage()); 44 | } 45 | 46 | // Create a dynamic and keep it from being garbage collected: 47 | handlerRef = handler; 48 | 49 | error = ExternDef.callbackBind(nativePointer, 50 | ContextImpl.bytesToBytesData(argBuffer), handlerRef); 51 | 52 | if (error != 0) { 53 | throw NativeUtil.fromNativeString(ExternDef.getErrorMessage()); 54 | } 55 | } 56 | 57 | public function getPointer():BasicPointer { 58 | var nativeCallbackPointer = ExternDef.callbackGetPointer(nativePointer); 59 | 60 | return new PointerImpl( 61 | #if cpp 62 | cpp.Pointer.fromRaw(nativeCallbackPointer) 63 | #else 64 | nativeCallbackPointer 65 | #end 66 | , context); 67 | } 68 | 69 | public function dispose() { 70 | ExternDef.delCallback(nativePointer); 71 | } 72 | 73 | function handler() { 74 | var args = argSerializer.deserializeArgs(params, argBuffer); 75 | var returnValue = haxeFunction(args); 76 | 77 | if (returnType != DataType.Void) { 78 | argSerializer.setReturnValue(argBuffer, returnType, returnValue); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/impl/ContextImpl.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.impl; 2 | 3 | import callfunc.core.impl.ExternDef.ExternBytesData; 4 | import haxe.ds.Vector; 5 | import haxe.Int64; 6 | import haxe.io.Bytes; 7 | 8 | #if cpp 9 | import haxe.io.BytesData; 10 | #end 11 | 12 | class ContextImpl implements Context { 13 | static final API_VERSION = 3; 14 | 15 | final sizeOfTable:Vector; 16 | @:allow(callfunc.core.impl) final coreDataTypeTable:CoreDataTypeTable; 17 | 18 | public function new() { 19 | var version = ExternDef.apiVersion(); 20 | 21 | if (version != API_VERSION) { 22 | throw 'API version mismatch. Haxe: $API_VERSION, Native: $version'; 23 | } 24 | 25 | sizeOfTable = inline getSizeOfTable(); 26 | coreDataTypeTable = new CoreDataTypeTable(this); 27 | } 28 | 29 | public function alloc(size:Int, initZero:Bool = false):BasicPointer { 30 | var nativePointer = ExternDef.alloc(size, initZero); 31 | 32 | if (nativePointer == null) { 33 | throw "Alloc failed"; 34 | } 35 | 36 | return new PointerImpl( 37 | #if cpp 38 | cpp.Pointer.fromRaw(nativePointer) 39 | #else 40 | nativePointer 41 | #end 42 | , this); 43 | } 44 | 45 | public function free(pointer:BasicPointer) { 46 | var pointerImpl = cast(pointer, PointerImpl); 47 | ExternDef.free(pointerImpl.nativePointer); 48 | } 49 | 50 | public function sizeOf(type:DataType):Int { 51 | switch type { 52 | case DataType.UInt8: return sizeOfTable.get(0); 53 | case DataType.SInt8: return sizeOfTable.get(1); 54 | case DataType.UInt16: return sizeOfTable.get(2); 55 | case DataType.SInt16: return sizeOfTable.get(3); 56 | case DataType.UInt32: return sizeOfTable.get(4); 57 | case DataType.SInt32: return sizeOfTable.get(5); 58 | case DataType.UInt64: return sizeOfTable.get(6); 59 | case DataType.SInt64: return sizeOfTable.get(7); 60 | case DataType.Float: return sizeOfTable.get(8); 61 | case DataType.Double: return sizeOfTable.get(9); 62 | case DataType.UChar: return sizeOfTable.get(10); 63 | case DataType.SChar: return sizeOfTable.get(11); 64 | case DataType.UShort: return sizeOfTable.get(12); 65 | case DataType.SShort: return sizeOfTable.get(13); 66 | case DataType.SInt: return sizeOfTable.get(14); 67 | case DataType.UInt: return sizeOfTable.get(15); 68 | case DataType.SLong: return sizeOfTable.get(16); 69 | case DataType.ULong: return sizeOfTable.get(17); 70 | case DataType.Pointer: return sizeOfTable.get(18); 71 | case DataType.LongDouble: return sizeOfTable.get(19); 72 | case DataType.ComplexFloat: return sizeOfTable.get(20); 73 | case DataType.ComplexDouble: return sizeOfTable.get(21); 74 | case DataType.ComplexLongDouble: return sizeOfTable.get(22); 75 | case DataType.Size: return sizeOfTable.get(23); 76 | case DataType.PtrDiff: return sizeOfTable.get(24); 77 | case DataType.WChar: return sizeOfTable.get(25); 78 | default: throw "not supported"; 79 | } 80 | } 81 | 82 | function getSizeOfTable() { 83 | var buffer = Bytes.alloc(27); 84 | 85 | ExternDef.getSizeOfTable(bytesToBytesData(buffer)); 86 | 87 | var table = new Vector(buffer.length); 88 | 89 | for (index in 0...buffer.length) { 90 | table.set(index, buffer.get(index)); 91 | } 92 | return table; 93 | } 94 | 95 | public static function bytesToBytesData(bytes:Bytes):ExternBytesData { 96 | #if cpp 97 | var array = bytes.getData(); 98 | return cpp.Pointer.ofArray(array); 99 | 100 | #elseif hl 101 | return hl.Bytes.fromBytes(bytes); 102 | 103 | #else 104 | #error 105 | #end 106 | } 107 | 108 | public function getPointer(address:Int64):BasicPointer { 109 | var nativePointer = ExternDef.int64ToPointer(address); 110 | 111 | return new PointerImpl( 112 | #if cpp 113 | cpp.Pointer.fromRaw(nativePointer) 114 | #else 115 | nativePointer 116 | #end 117 | , this); 118 | } 119 | 120 | #if sys 121 | public function bytesToPointer(bytes:Bytes):BasicPointer { 122 | #if cpp 123 | var managedPointer = bytesToBytesData(bytes); 124 | return new PointerImpl(cast managedPointer.raw, this); 125 | 126 | #elseif hl 127 | return getPointer(bytesToBytesData(bytes).address()); 128 | 129 | #else 130 | #error 131 | 132 | #end 133 | } 134 | 135 | public function pointerToBytes(pointer:BasicPointer, count:Int):Bytes { 136 | #if cpp 137 | var bytesData = new BytesData(); 138 | var pointerImpl = cast(pointer, PointerImpl); 139 | cpp.NativeArray.setUnmanagedData( 140 | bytesData, 141 | cast cpp.Pointer.fromRaw(pointerImpl.nativePointer), 142 | count); 143 | return Bytes.ofData(bytesData); 144 | 145 | #elseif hl 146 | 147 | var hlBytes = hl.Bytes.fromAddress(pointer.address); 148 | return hlBytes.toBytes(count); 149 | 150 | #else 151 | #error 152 | 153 | #end 154 | } 155 | #end 156 | 157 | public function pointerToDataView(pointer:BasicPointer, count:Int):DataView { 158 | return new BytesDataView(pointerToBytes(pointer, count)); 159 | } 160 | 161 | public function newLibrary(name:String):LibraryHandle { 162 | #if (cpp || hl) 163 | return new callfunc.core.impl.LibraryImpl(name, this); 164 | #else 165 | #error 166 | #end 167 | } 168 | 169 | public function newStructType(dataTypes:Array):StructTypeHandle { 170 | #if (cpp || hl) 171 | return new callfunc.core.impl.StructTypeImpl(dataTypes, this); 172 | #else 173 | #error 174 | #end 175 | } 176 | 177 | public function newCallback(haxeFunction:Array->Any, 178 | ?params:Array, 179 | ?returnType:DataType):CallbackHandle { 180 | #if (cpp || hl) 181 | return new callfunc.core.impl.CallbackImpl(this, haxeFunction, params, 182 | returnType); 183 | #else 184 | #error 185 | #end 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/impl/FunctionImpl.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.impl; 2 | 3 | import haxe.io.Bytes; 4 | import callfunc.core.impl.ExternDef; 5 | 6 | class FunctionImpl implements FunctionHandle { 7 | final MAX_RETURN_SIZE = 8; 8 | final DEFAULT_ABI = -999; 9 | 10 | final name:String; 11 | final params:Array; 12 | final returnType:DataType; 13 | final abi:Int; 14 | final nativePointer:ExternFunction; 15 | final library:LibraryImpl; 16 | var buffer:Null; 17 | 18 | public function new(library:LibraryImpl, name:String, 19 | ?params:Array, fixedParamCount:Int = -1, 20 | ?returnType:DataType, ?abi:Int) { 21 | this.library = library; 22 | this.name = name; 23 | this.params = params = params != null ? params : []; 24 | this.returnType = returnType = returnType != null ? returnType : DataType.Void; 25 | this.abi = abi = abi != null ? abi : DEFAULT_ABI; 26 | 27 | nativePointer = ExternDef.newFunction(library.nativePointer); 28 | 29 | if (nativePointer == null) { 30 | throw "Failed to allocate function struct."; 31 | } 32 | 33 | var buffer = library.argSerializer.serializeParams( 34 | params, fixedParamCount, returnType); 35 | var pointer = cast(library.getPointer(name), PointerImpl); 36 | 37 | var error = ExternDef.functionDefine(nativePointer, 38 | pointer.nativePointer, abi, ContextImpl.bytesToBytesData(buffer)); 39 | 40 | if (error != 0) { 41 | dispose(); 42 | throw NativeUtil.fromNativeString(ExternDef.getErrorMessage()); 43 | } 44 | } 45 | 46 | public function dispose() { 47 | ExternDef.delFunction(nativePointer); 48 | } 49 | 50 | public function call(?args:Array):Null { 51 | args = args != null ? args : []; 52 | 53 | if (args.length != params.length) { 54 | throw "Function argument count mismatch"; 55 | } 56 | 57 | buffer = library.argSerializer.serializeArgs(params, args, buffer); 58 | 59 | ExternDef.functionCall(nativePointer, ContextImpl.bytesToBytesData(buffer)); 60 | 61 | if (returnType != DataType.Void) { 62 | return library.argSerializer.getReturnValue(buffer, returnType); 63 | } else { 64 | return null; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/impl/LibraryImpl.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.impl; 2 | 3 | import haxe.io.Bytes; 4 | import callfunc.core.impl.ExternDef; 5 | import callfunc.core.serialization.ArgSerializer; 6 | 7 | class LibraryImpl implements LibraryHandle { 8 | public final nativePointer:ExternLibrary; 9 | public final context:ContextImpl; 10 | public final argSerializer:ArgSerializer; 11 | 12 | public function new(name:String, context:ContextImpl) { 13 | nativePointer = ExternDef.newLibrary(); 14 | this.context = context; 15 | 16 | argSerializer = new ArgSerializer(context); 17 | 18 | if (nativePointer == null) { 19 | throw "Failed to allocate library struct."; 20 | } 21 | 22 | var error = ExternDef.libraryOpen( 23 | nativePointer, 24 | NativeUtil.toNativeString(name) 25 | ); 26 | 27 | if (error != 0) { 28 | dispose(); 29 | throw NativeUtil.fromNativeString(ExternDef.getErrorMessage()); 30 | } 31 | } 32 | 33 | public function hasSymbol(name:String):Bool { 34 | @:nullSafety(Off) var targetPointer:ExternVoidStar = null; 35 | 36 | #if cpp 37 | var targetRef = cpp.RawPointer.addressOf(targetPointer); 38 | #elseif hl 39 | var targetRef = hl.Ref.make(targetPointer); 40 | #else 41 | #error 42 | #end 43 | 44 | var error = ExternDef.libraryGetAddress( 45 | nativePointer, 46 | NativeUtil.toNativeString(name), 47 | targetRef); 48 | 49 | return error == 0; 50 | } 51 | 52 | public function getPointer(name:String):BasicPointer { 53 | @:nullSafety(Off) var targetPointer:ExternVoidStar = null; 54 | 55 | #if cpp 56 | var targetRef = cpp.RawPointer.addressOf(targetPointer); 57 | #elseif hl 58 | var targetRef = hl.Ref.make(targetPointer); 59 | #else 60 | #error 61 | #end 62 | 63 | var error = ExternDef.libraryGetAddress( 64 | nativePointer, 65 | NativeUtil.toNativeString(name), 66 | targetRef); 67 | 68 | if (error != 0) { 69 | throw NativeUtil.fromNativeString(ExternDef.getErrorMessage()); 70 | } 71 | 72 | #if cpp 73 | return new PointerImpl(cpp.Pointer.fromRaw(targetPointer), context); 74 | #else 75 | return new PointerImpl(targetPointer, context); 76 | #end 77 | } 78 | 79 | 80 | public function newFunction(name:String, ?params:Array, 81 | ?returnType:DataType, ?abi:Int):FunctionHandle { 82 | return new FunctionImpl(this, name, params, -1, returnType, abi); 83 | } 84 | 85 | public function newVariadicFunction(name:String, params:Array, 86 | fixedParamCount:Int, ?returnType:DataType, ?abi:Int):FunctionHandle { 87 | return new FunctionImpl(this, name, params, fixedParamCount, returnType, abi); 88 | } 89 | 90 | public function dispose() { 91 | ExternDef.libraryClose(nativePointer); 92 | ExternDef.delLibrary(nativePointer); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/impl/NativeUtil.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.impl; 2 | 3 | import haxe.io.Bytes; 4 | 5 | import callfunc.core.impl.ExternDef.ExternString; 6 | 7 | class NativeUtil { 8 | public static function toNativeString(text:String):ExternString { 9 | // Haxe to UTF-8 null-terminated C string 10 | #if hl 11 | final encoded = Bytes.ofString(text, haxe.io.Encoding.UTF8); 12 | final array = Bytes.alloc(encoded.length + 1); 13 | array.blit(0, encoded, 0, encoded.length); 14 | return array; 15 | #else 16 | return text; 17 | #end 18 | } 19 | 20 | public static function fromNativeString(native:ExternString):String { 21 | // UTF-8 null-terminated C string to Haxe 22 | #if hl 23 | var length = 0; 24 | 25 | while (true) { 26 | if (native[length] == 0) { 27 | break; 28 | } else { 29 | length += 1; 30 | } 31 | } 32 | 33 | return native.toBytes(length + 1).toString(); 34 | 35 | #else 36 | return native.toString(); 37 | #end 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/impl/PointerImpl.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.impl; 2 | 3 | import callfunc.core.impl.ExternDef; 4 | import callfunc.core.serialization.DataValueSerializer; 5 | import haxe.io.Bytes; 6 | import haxe.Int64; 7 | 8 | using callfunc.core.DataTypeTools; 9 | 10 | class PointerImpl implements BasicPointer { 11 | public var address(get, never):Int64; 12 | 13 | final _address:Int64; 14 | public final nativePointer:ExternVoidStar; 15 | final buffer:Bytes; 16 | final context:ContextImpl; 17 | final serializer:DataValueSerializer; 18 | 19 | var _dataType:DataType; 20 | 21 | public function new( 22 | #if cpp 23 | haxePointer:cpp.Pointer 24 | #else 25 | nativePointer:ExternVoidStar 26 | #end, 27 | context:ContextImpl) { 28 | 29 | #if cpp 30 | nativePointer = haxePointer.raw; 31 | #else 32 | this.nativePointer = nativePointer; 33 | #end 34 | 35 | _address = ExternDef.pointerToInt64(nativePointer); 36 | buffer = Bytes.alloc(8); 37 | this.context = context; 38 | _dataType = DataType.SInt; 39 | serializer = new DataValueSerializer(context); 40 | } 41 | 42 | function get_address():Int64 { 43 | return _address; 44 | } 45 | 46 | public function get(dataType:DataType, offset:Int = 0):Any { 47 | ExternDef.pointerGet(nativePointer, 48 | context.coreDataTypeTable.toCoreDataType(dataType).toInt(), 49 | ContextImpl.bytesToBytesData(buffer), offset); 50 | 51 | return serializer.deserializeValue(buffer, 0, dataType); 52 | } 53 | 54 | public function set(value:Any, dataType:DataType, offset:Int = 0) { 55 | serializer.serializeValue(buffer, 0, dataType, value); 56 | 57 | ExternDef.pointerSet(nativePointer, 58 | context.coreDataTypeTable.toCoreDataType(dataType).toInt(), 59 | ContextImpl.bytesToBytesData(buffer), offset); 60 | } 61 | 62 | public function arrayGet(index:Int, dataType:DataType):Any { 63 | ExternDef.pointerArrayGet(nativePointer, 64 | context.coreDataTypeTable.toCoreDataType(dataType).toInt(), 65 | ContextImpl.bytesToBytesData(buffer), index); 66 | 67 | return serializer.deserializeValue(buffer, 0, dataType); 68 | } 69 | 70 | public function arraySet(index:Int, value:Any, dataType:DataType) { 71 | serializer.serializeValue(buffer, 0, dataType, value); 72 | 73 | ExternDef.pointerArraySet(nativePointer, 74 | context.coreDataTypeTable.toCoreDataType(dataType).toInt(), 75 | ContextImpl.bytesToBytesData(buffer), index); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/impl/StructTypeImpl.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.impl; 2 | 3 | import haxe.io.Bytes; 4 | import callfunc.core.impl.ExternDef; 5 | import callfunc.core.serialization.StructTypeSerializer; 6 | 7 | class StructTypeImpl implements StructTypeHandle { 8 | public var size(get, never):Int; 9 | public var dataTypes(get, never):Array; 10 | public var offsets(get, never):Array; 11 | 12 | final nativePointer:ExternStructType; 13 | final _dataTypes:Array; 14 | final _size:Int; 15 | final _offsets:Array; 16 | final serializer:StructTypeSerializer; 17 | 18 | public function new(dataTypes:Array, context:ContextImpl) { 19 | _dataTypes = dataTypes; 20 | serializer = new StructTypeSerializer(context); 21 | 22 | nativePointer = ExternDef.newStructType(); 23 | 24 | if (nativePointer == null) { 25 | throw "Failed to allocate struct type"; 26 | } 27 | 28 | var definitionBuffer = serializer.serializeFields(dataTypes); 29 | var infoBuffer = Bytes.alloc(4 * (1 + dataTypes.length)); 30 | 31 | var error = ExternDef.structTypeDefine(nativePointer, 32 | ContextImpl.bytesToBytesData(definitionBuffer), 33 | ContextImpl.bytesToBytesData(infoBuffer) 34 | ); 35 | 36 | if (error != 0) { 37 | throw NativeUtil.fromNativeString(ExternDef.getErrorMessage()); 38 | } 39 | 40 | var info = serializer.deserializeInfo(dataTypes, infoBuffer); 41 | _size = info.size; 42 | _offsets = info.offsets; 43 | } 44 | 45 | function get_size():Int { 46 | return _size; 47 | } 48 | 49 | function get_dataTypes():Array { 50 | return _dataTypes; 51 | } 52 | 53 | function get_offsets():Array { 54 | return _offsets; 55 | } 56 | 57 | public function dispose() { 58 | ExternDef.delStructType(nativePointer); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/serialization/ArgSerializer.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.serialization; 2 | 3 | import haxe.Int64; 4 | import haxe.io.Bytes; 5 | 6 | class ArgSerializer extends DataValueSerializer { 7 | static final BUFFER_VALUE_SIZE = 4; 8 | static final NUM_PARAM_VALUE_SIZE = 4; 9 | static final RETURN_VALUE_SIZE = 8; 10 | 11 | public function serializeParams(params:Array, 12 | fixedParamCount:Int = -1, ?returnType:DataType, 13 | ?buffer:Bytes):Bytes { 14 | var bufferSize = getParamBufferSize(params, returnType); 15 | 16 | if (buffer == null) { 17 | buffer = Bytes.alloc(bufferSize); 18 | } 19 | 20 | if (buffer.length < bufferSize) { 21 | throw "Buffer too small"; 22 | } 23 | 24 | if (fixedParamCount == 0) { 25 | throw "fixedParamCount can't be 0"; 26 | } 27 | 28 | var bufferIndex = 0; 29 | 30 | buffer.setInt32(bufferIndex, bufferSize); 31 | bufferIndex += BUFFER_VALUE_SIZE; 32 | 33 | buffer.setInt32(bufferIndex, params.length); 34 | bufferIndex += NUM_PARAM_VALUE_SIZE; 35 | 36 | buffer.setInt32(bufferIndex, fixedParamCount); 37 | bufferIndex += NUM_PARAM_VALUE_SIZE; 38 | 39 | if (returnType == null) { 40 | returnType = DataType.Void; 41 | } 42 | 43 | bufferIndex += serializeDataType(buffer, bufferIndex, returnType); 44 | 45 | for (param in params) { 46 | if (param == DataType.Void) { 47 | throw "Void can only be used to indicate no return type"; 48 | } 49 | 50 | bufferIndex += serializeDataType(buffer, bufferIndex, param); 51 | } 52 | 53 | return buffer; 54 | } 55 | 56 | function getParamBufferSize(params:Array, returnType:Null):Int { 57 | var bufferSize = BUFFER_VALUE_SIZE + NUM_PARAM_VALUE_SIZE * 2; 58 | 59 | if (returnType == null) { 60 | returnType = DataType.Void; 61 | } 62 | 63 | bufferSize += getSerializedDataTypeSize(returnType); 64 | 65 | for (dataType in params) { 66 | bufferSize += getSerializedDataTypeSize(dataType); 67 | } 68 | 69 | return bufferSize; 70 | } 71 | 72 | public function getArgBufferLength(params:Array):Int { 73 | var bufferSize = BUFFER_VALUE_SIZE + RETURN_VALUE_SIZE; 74 | 75 | for (dataType in params) { 76 | bufferSize += getSerializedValueSize(dataType); 77 | } 78 | 79 | return bufferSize; 80 | } 81 | 82 | public function serializeArgs(params:Array, args:Array, ?buffer:Bytes):Bytes { 83 | var bufferSize; 84 | 85 | if (buffer == null) { 86 | bufferSize = getArgBufferLength(params); 87 | buffer = Bytes.alloc(bufferSize); 88 | } 89 | 90 | bufferSize = buffer.length; 91 | Debug.assert(buffer.length >= getArgBufferLength(params)); 92 | 93 | var bufferIndex = 0; 94 | 95 | buffer.setInt32(bufferIndex, bufferSize); 96 | bufferIndex += BUFFER_VALUE_SIZE; 97 | 98 | // To be filled in after ffi call: 99 | bufferIndex += RETURN_VALUE_SIZE; 100 | 101 | for (argIndex in 0...args.length) { 102 | var dataType = params[argIndex]; 103 | var arg = args[argIndex]; 104 | 105 | bufferIndex += serializeValue(buffer, bufferIndex, dataType, arg); 106 | } 107 | 108 | return buffer; 109 | } 110 | 111 | public function deserializeArgs(params:Array, buffer:Bytes):Array { 112 | var bufferIndex = 0; 113 | 114 | var expected_buffer_length = buffer.getInt32(bufferIndex); 115 | bufferIndex += BUFFER_VALUE_SIZE; 116 | 117 | Debug.assert(expected_buffer_length <= buffer.length); 118 | 119 | bufferIndex += RETURN_VALUE_SIZE; 120 | 121 | var args = []; 122 | 123 | for (argIndex in 0...params.length) { 124 | var dataType = params[argIndex]; 125 | var dataSize = context.sizeOf(dataType); 126 | 127 | var arg = deserializeValue(buffer, bufferIndex, dataType); 128 | args.push(arg); 129 | 130 | bufferIndex += dataSize; 131 | } 132 | 133 | return args; 134 | } 135 | 136 | public function getReturnValue(buffer:Bytes, returnType:DataType):Any { 137 | return deserializeValue(buffer, 4, returnType); 138 | } 139 | 140 | public function setReturnValue(buffer:Bytes, returnType:DataType, value:Any) { 141 | serializeValue(buffer, 4, returnType, value); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/serialization/DataValueSerializer.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.serialization; 2 | 3 | import haxe.io.Bytes; 4 | import haxe.Int64; 5 | 6 | using callfunc.BytesTools; 7 | using callfunc.core.DataTypeTools; 8 | 9 | class DataValueSerializer { 10 | final context:Context; 11 | final coreDataTypeTable:CoreDataTypeTable; 12 | 13 | public function new(context:Context) { 14 | this.context = context; 15 | this.coreDataTypeTable = new CoreDataTypeTable(context); 16 | } 17 | 18 | public function serializeDataType(buffer:Bytes, bufferIndex:Int, dataType:DataType):Int { 19 | buffer.set(bufferIndex, coreDataTypeTable.toCoreDataType(dataType).toInt()); 20 | 21 | switch dataType { 22 | case Struct(fields): 23 | var size = 1; 24 | 25 | buffer.setInt32(bufferIndex + size, fields.length); 26 | size += 4; 27 | 28 | for (field in fields) { 29 | size += serializeDataType(buffer, bufferIndex + size, field); 30 | } 31 | 32 | return size; 33 | default: 34 | return 1; 35 | } 36 | } 37 | 38 | public function getSerializedDataTypeSize(dataType:DataType):Int { 39 | switch dataType { 40 | case Struct(fields): 41 | var size = 1 + 4; 42 | 43 | for (field in fields) { 44 | size += getSerializedDataTypeSize(field); 45 | } 46 | 47 | return size; 48 | default: 49 | return 1; 50 | } 51 | } 52 | 53 | public function getSerializedValueSize(dataType:DataType):Int { 54 | if (dataType.match(DataType.Struct(_))) { 55 | dataType = DataType.Pointer; 56 | } 57 | 58 | return context.sizeOf(dataType); 59 | } 60 | 61 | public function serializeValue(buffer:Bytes, bufferIndex:Int, dataType:DataType, value:Any):Int { 62 | switch coreDataTypeTable.toCoreDataType(dataType, true) { 63 | case SInt8 | UInt8: 64 | buffer.set(bufferIndex, NumberUtil.toInt(value)); 65 | case SInt16 | UInt16: 66 | buffer.setUInt16(bufferIndex, NumberUtil.toInt(value)); 67 | case SInt32 | UInt32: 68 | buffer.setInt32(bufferIndex, NumberUtil.toInt(value)); 69 | case SInt64 | UInt64: 70 | buffer.setInt64(bufferIndex, NumberUtil.toInt64(value)); 71 | case Float: 72 | buffer.setFloat(bufferIndex, value); 73 | case Double: 74 | buffer.setDouble(bufferIndex, value); 75 | case Pointer | Struct: 76 | serializePointer(buffer, bufferIndex, value); 77 | dataType = DataType.Pointer; 78 | default: 79 | throw "Shouldn't reach here"; 80 | } 81 | 82 | var valueSize = context.sizeOf(dataType); 83 | return valueSize; 84 | } 85 | 86 | function serializePointer(buffer:Bytes, bufferIndex:Int, pointer:BasicPointer) { 87 | var valueSize = context.sizeOf(DataType.Pointer); 88 | 89 | switch valueSize { 90 | case 8: 91 | buffer.setInt64(bufferIndex, pointer.address); 92 | case 4: 93 | buffer.setInt32(bufferIndex, pointer.address.low); 94 | default: 95 | throw 'Unsupported pointer width $valueSize'; 96 | } 97 | } 98 | 99 | public function deserializeValue(buffer:Bytes, bufferIndex:Int, dataType:DataType):Any { 100 | switch coreDataTypeTable.toCoreDataType(dataType, true) { 101 | case UInt8: 102 | return buffer.get(bufferIndex); 103 | case SInt8: 104 | return buffer.getSInt8(bufferIndex); 105 | case UInt16: 106 | return buffer.getUInt16(bufferIndex); 107 | case SInt16: 108 | return buffer.getSInt16(bufferIndex); 109 | case SInt32: 110 | return buffer.getInt32(bufferIndex); 111 | case UInt32: 112 | return (buffer.getInt32(bufferIndex):UInt); 113 | case SInt64 | UInt64: 114 | return buffer.getInt64(bufferIndex); 115 | case Double: 116 | return buffer.getDouble(bufferIndex); 117 | case Float: 118 | return buffer.getFloat(bufferIndex); 119 | case Pointer | Struct: 120 | return deserializePointer(buffer, bufferIndex); 121 | case Void: 122 | throw "Void type"; 123 | default: 124 | throw "Shouldn't reach here"; 125 | } 126 | } 127 | 128 | function deserializePointer(buffer:Bytes, bufferIndex:Int):BasicPointer { 129 | var valueSize = context.sizeOf(DataType.Pointer); 130 | 131 | switch valueSize { 132 | case 8: 133 | return context.getPointer(buffer.getInt64(bufferIndex)); 134 | case 4: 135 | return context.getPointer( 136 | Int64.make(0, buffer.getInt32(bufferIndex))); 137 | default: 138 | throw 'Unsupported pointer width $valueSize'; 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/serialization/NumberUtil.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.serialization; 2 | 3 | import haxe.Int64; 4 | 5 | class NumberUtil { 6 | public static function toInt(value:Any):Int { 7 | if (Std.is(value, Int)) { 8 | return value; 9 | } else if (Int64.is(value)) { 10 | return (value:Int64).low; 11 | } else if (Std.is(value, Float)) { 12 | return Std.int(value); 13 | } else { 14 | throw "Cannot convert value to Int"; 15 | } 16 | } 17 | 18 | public static function toInt64(value:Any):Int64 { 19 | if (Int64.is(value)) { 20 | return value; 21 | } else if (Std.is(value, Int)) { 22 | return Int64.make(0, value); 23 | } else if (Std.is(value, Float)) { 24 | return Int64.fromFloat(value); 25 | } else { 26 | throw "Cannot convert value to Int64"; 27 | } 28 | } 29 | 30 | public static function intToUInt8(value:Int):Int { 31 | if (value >= 0) { 32 | return value; 33 | } else { 34 | return (~(-value) & 0xff) + 1; 35 | } 36 | } 37 | 38 | public static function intToUInt16(value:Int):Int { 39 | if (value >= 0) { 40 | return value; 41 | } else { 42 | return (~(-value) & 0xffff) + 1; 43 | } 44 | } 45 | 46 | public static function intToUInt(value:Int):UInt { 47 | if (value >= 0) { 48 | return value; 49 | } else { 50 | return (~(-value) & 0xffffffff) + 1; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/haxe/callfunc/core/serialization/StructTypeSerializer.hx: -------------------------------------------------------------------------------- 1 | package callfunc.core.serialization; 2 | 3 | import haxe.io.Bytes; 4 | 5 | typedef StructTypeInfo = { 6 | size:Int, 7 | offsets:Array 8 | }; 9 | 10 | class StructTypeSerializer extends DataValueSerializer { 11 | static final BUFFER_VALUE_SIZE = 4; 12 | static final NUM_PARAM_VALUE_SIZE = 4; 13 | 14 | public function serializeFields(fields:Array, ?buffer:Bytes):Bytes { 15 | var bufferSize = BUFFER_VALUE_SIZE + NUM_PARAM_VALUE_SIZE; 16 | var offset = 0; 17 | 18 | for (field in fields) { 19 | bufferSize += getSerializedDataTypeSize(field); 20 | } 21 | 22 | if (buffer == null) { 23 | buffer = Bytes.alloc(bufferSize); 24 | } 25 | 26 | if (buffer.length < bufferSize) { 27 | throw "Buffer too small"; 28 | } 29 | 30 | buffer.setInt32(offset, bufferSize); 31 | offset += BUFFER_VALUE_SIZE; 32 | 33 | buffer.setInt32(offset, fields.length); 34 | offset += NUM_PARAM_VALUE_SIZE; 35 | 36 | for (field in fields) { 37 | if (field == DataType.Void) { 38 | throw "Invalid type Void"; 39 | } 40 | 41 | offset += serializeDataType(buffer, offset, field); 42 | } 43 | 44 | return buffer; 45 | } 46 | 47 | public function deserializeInfo(fields:Array, buffer:Bytes):StructTypeInfo { 48 | var size = buffer.getInt32(0); 49 | var offsets = []; 50 | 51 | for (fieldIndex in 0...fields.length) { 52 | offsets.push(buffer.getInt32(4 + 4 * fieldIndex)); 53 | } 54 | 55 | return { 56 | size: size, 57 | offsets: offsets 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/haxe/callfunc/string/Encoder.hx: -------------------------------------------------------------------------------- 1 | package callfunc.string; 2 | 3 | import haxe.io.Bytes; 4 | import haxe.io.BytesBuffer; 5 | import unifill.Utf8; 6 | import unifill.Utf16; 7 | 8 | class Encoder { 9 | 10 | public static function decodeFromBytes(bytes:Bytes, offset:Int, 11 | ?length:Int, encoding:Encoding = UTF8):String { 12 | if (length == null) { 13 | length = StringUtil.getNullTerminator(bytes, offset, encoding); 14 | } 15 | 16 | switch encoding { 17 | case UTF8: 18 | return bytes.getString(offset, length, haxe.io.Encoding.UTF8); 19 | default: // pass 20 | } 21 | 22 | final codeUnits = []; 23 | var position = offset; 24 | 25 | var codeUnitWidth; 26 | 27 | switch encoding { 28 | case UTF8: 29 | codeUnitWidth = 1; 30 | case UTF16LE: 31 | codeUnitWidth = 2; 32 | } 33 | 34 | while (position < offset + length) { 35 | var codeUnit; 36 | 37 | switch encoding { 38 | case UTF8: 39 | codeUnit = bytes.get(position); 40 | case UTF16LE: 41 | codeUnit = bytes.getUInt16(position); 42 | } 43 | 44 | codeUnits.push(codeUnit); 45 | position += codeUnitWidth; 46 | } 47 | 48 | switch encoding { 49 | case UTF8: 50 | throw "not implemented"; 51 | case UTF16LE: 52 | return Utf16.fromArray(codeUnits).toString(); 53 | } 54 | } 55 | 56 | public static function encodeToBytes(bytes:Bytes, offset:Int, text:String, 57 | encoding:Encoding = UTF8, terminator:Bool = false):Int { 58 | var position = offset; 59 | 60 | final uString = new UnicodeString(text); 61 | 62 | function utf8CodeUnitCallback(codeUnit:Int) { 63 | bytes.set(position, codeUnit); 64 | position += 1; 65 | } 66 | 67 | function utf16CodeUnitCallback(codeUnit:Int) { 68 | bytes.setUInt16(position, codeUnit); 69 | position += 2; 70 | } 71 | 72 | var encodeCodePoint:Int->Void; 73 | 74 | switch encoding { 75 | case UTF8: 76 | encodeCodePoint = (codePoint:Int) -> { 77 | Utf8.encodeWith(utf8CodeUnitCallback, codePoint); 78 | } 79 | case UTF16LE: 80 | encodeCodePoint = (codePoint:Int) -> { 81 | Utf16.encodeWith(utf16CodeUnitCallback, codePoint); 82 | } 83 | } 84 | 85 | for (codePoint in uString) { 86 | encodeCodePoint(codePoint); 87 | } 88 | 89 | if (terminator) { 90 | switch encoding { 91 | case UTF8: 92 | bytes.set(position, 0); 93 | position += 1; 94 | case UTF16LE: 95 | bytes.setUInt16(position, 0); 96 | position += 2; 97 | } 98 | } 99 | 100 | return position; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/haxe/callfunc/string/Encoding.hx: -------------------------------------------------------------------------------- 1 | package callfunc.string; 2 | 3 | enum Encoding { 4 | UTF8; 5 | UTF16LE; 6 | } 7 | -------------------------------------------------------------------------------- /src/haxe/callfunc/string/StringUtil.hx: -------------------------------------------------------------------------------- 1 | package callfunc.string; 2 | 3 | import haxe.io.Bytes; 4 | import unifill.Utf16; 5 | import unifill.Utf8; 6 | 7 | class StringUtil { 8 | public static function getEncodedLength(text:String, encoding:Encoding, 9 | terminator:Bool):Int { 10 | final uString = new UnicodeString(text); 11 | var position = 0; 12 | 13 | function utf8CodeUnitCallback(codeUnit:Int) { 14 | position += 1; 15 | } 16 | 17 | function utf16CodeUnitCallback(codeUnit:Int) { 18 | position += 2; 19 | } 20 | 21 | var encodeCodePoint:Int->Void; 22 | 23 | switch encoding { 24 | case UTF8: 25 | encodeCodePoint = (codePoint:Int) -> { 26 | Utf8.encodeWith(utf8CodeUnitCallback, codePoint); 27 | } 28 | case UTF16LE: 29 | encodeCodePoint = (codePoint:Int) -> { 30 | Utf16.encodeWith(utf16CodeUnitCallback, codePoint); 31 | } 32 | } 33 | 34 | for (codePoint in uString) { 35 | encodeCodePoint(codePoint); 36 | } 37 | 38 | if (terminator) { 39 | switch encoding { 40 | case UTF8: 41 | position += 1; 42 | case UTF16LE: 43 | position += 2; 44 | } 45 | } 46 | 47 | return position; 48 | } 49 | 50 | public static function getPointerNullTerminator(pointer:Pointer, encoding:Encoding):Int { 51 | switch encoding { 52 | case UTF8: 53 | return findPointerNullTerminatorByCodeUnit(pointer, DataType.UInt8); 54 | case UTF16LE: 55 | return findPointerNullTerminatorByCodeUnit(pointer, DataType.UInt16) * 2; 56 | } 57 | } 58 | 59 | static function findPointerNullTerminatorByCodeUnit(pointer:Pointer, dataType:DataType):Int { 60 | var length = 0; 61 | 62 | while (true) { 63 | if (pointer.arrayGet(length, dataType) == 0) { 64 | break; 65 | } 66 | 67 | length += 1; 68 | if (length < 0) { 69 | throw "string length overflow"; 70 | } 71 | } 72 | 73 | return length; 74 | } 75 | 76 | public static function getNullTerminator(bytes:Bytes, offset:Int, encoding:Encoding):Int { 77 | switch encoding { 78 | case UTF8: 79 | return findNullTerminatorByCodeUnit(bytes, offset, 1); 80 | case UTF16LE: 81 | return findNullTerminatorByCodeUnit(bytes, offset, 2) * 2; 82 | } 83 | } 84 | 85 | static function findNullTerminatorByCodeUnit(bytes:Bytes, offset:Int, codeUnitWidth:Int):Int { 86 | var position = offset; 87 | 88 | while (true) { 89 | switch codeUnitWidth { 90 | case 1: 91 | if (bytes.get(position) == 0) break; 92 | case 2: 93 | if (bytes.getUInt16(position) == 0) break; 94 | default: 95 | throw "Code unit width not implemented"; 96 | } 97 | 98 | position += 1; 99 | if (position < 0) { 100 | throw "string length overflow"; 101 | } 102 | } 103 | 104 | return position; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /test.hxml: -------------------------------------------------------------------------------- 1 | libs.hxml 2 | -lib utest 3 | -cp src/haxe/ 4 | -cp test/haxe/ 5 | -main callfunc.TestAll 6 | -debug 7 | --macro nullSafety("callfunc", Loose) 8 | -------------------------------------------------------------------------------- /test/c/examplelib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | project(callfunc) 3 | 4 | include (GenerateExportHeader) 5 | add_library(examplelib SHARED examplelib.c) 6 | 7 | GENERATE_EXPORT_HEADER( examplelib 8 | BASE_NAME examplelib 9 | EXPORT_MACRO_NAME examplelib_EXPORT 10 | EXPORT_FILE_NAME examplelib_Export.h 11 | STATIC_DEFINE examplelib_BUILT_AS_STATIC 12 | ) 13 | 14 | if(MSVC) 15 | target_compile_options(examplelib PRIVATE /W4 /WX) 16 | else() 17 | target_compile_options(examplelib PRIVATE -Wall -Wextra -pedantic -Werror) 18 | endif() 19 | 20 | set_target_properties(examplelib PROPERTIES 21 | PREFIX "" 22 | C_STANDARD 99 23 | ) 24 | -------------------------------------------------------------------------------- /test/c/examplelib/examplelib.c: -------------------------------------------------------------------------------- 1 | #include "examplelib.h" 2 | #include 3 | #include 4 | #include 5 | 6 | int32_t examplelib_ints(int32_t a, int32_t b, int32_t * c) { 7 | *c = a + b; 8 | return 0xcafe; 9 | } 10 | 11 | const char * examplelib_string(const char * text) { 12 | char * new_string = (char *) malloc(strlen(text) + 1); 13 | 14 | for (size_t index = 0; index < strlen(text) + 1; index++) { 15 | if ('a' <= text[index] && text[index] <= 'z') { 16 | new_string[index] = text[index] ^ 0x20; 17 | } else { 18 | new_string[index] = text[index]; 19 | } 20 | } 21 | 22 | return new_string; 23 | } 24 | 25 | int32_t examplelib_variadic(unsigned int count, ...) { 26 | int32_t sum = 0; 27 | 28 | va_list p; 29 | va_start(p, count); 30 | 31 | for (unsigned int index = 0; index < count; index++) { 32 | sum += va_arg(p, int32_t); 33 | } 34 | 35 | va_end(p); 36 | 37 | return sum; 38 | } 39 | 40 | int32_t examplelib_callback(int32_t (*callback)(int32_t a, int32_t b)) { 41 | return callback(123, 456); 42 | } 43 | 44 | struct examplelib_struct1 examplelib_struct_value(struct examplelib_struct1 value) { 45 | struct examplelib_struct1 return_value; 46 | 47 | return_value.a = value.a ^ 0x20; 48 | return_value.b = value.b ^ 0x20; 49 | return_value.c = value.c * 2; 50 | 51 | return return_value; 52 | } 53 | -------------------------------------------------------------------------------- /test/c/examplelib/examplelib.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "../../../out/examplelib/examplelib_Export.h" 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | struct examplelib_struct1 { 12 | char a; 13 | int32_t b; 14 | double c; 15 | }; 16 | 17 | examplelib_EXPORT int32_t examplelib_ints(int32_t a, int32_t b, int32_t * c); 18 | examplelib_EXPORT const char * examplelib_string(const char * text); 19 | examplelib_EXPORT int32_t examplelib_variadic(unsigned int count, ...); 20 | examplelib_EXPORT int32_t examplelib_callback(int32_t (*callback)(int32_t a, int32_t b)); 21 | examplelib_EXPORT struct examplelib_struct1 examplelib_struct_value(struct examplelib_struct1 value); 22 | 23 | #ifdef __cplusplus 24 | } 25 | #endif 26 | -------------------------------------------------------------------------------- /test/haxe/callfunc/ListDataTypes.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.core.CoreDataTypeTable; 4 | using haxe.EnumTools; 5 | 6 | class ListDataTypes { 7 | public static function main() { 8 | final ffi = Callfunc.instance(); 9 | 10 | final dataTypes:Array = [ 11 | Void, 12 | UInt8, 13 | SInt8, 14 | UInt16, 15 | SInt16, 16 | UInt32, 17 | SInt32, 18 | UInt64, 19 | SInt64, 20 | Float, 21 | Double, 22 | UChar, 23 | SChar, 24 | UShort, 25 | SShort, 26 | SInt, 27 | UInt, 28 | SLong, 29 | ULong, 30 | Pointer, 31 | LongDouble, 32 | ComplexFloat, 33 | ComplexDouble, 34 | ComplexLongDouble, 35 | Size, 36 | PtrDiff, 37 | WChar, 38 | Struct([]), 39 | ]; 40 | 41 | final table = new CoreDataTypeTable(ffi.context); 42 | final stdout = Sys.stdout(); 43 | 44 | for (dataType in dataTypes) { 45 | stdout.writeString('data type ${dataType.getName()}\n'); 46 | try { 47 | stdout.writeString(' bytes wide ${ffi.sizeOf(dataType)} \n'); 48 | } catch (exception:Any) { 49 | stdout.writeString(' no size\n'); 50 | } 51 | 52 | try { 53 | stdout.writeString(' core type ${CoreDataType.getName(table.toCoreDataType(dataType, false))}\n'); 54 | stdout.writeString(' ${CoreDataType.getName(table.toCoreDataType(dataType, true))}\n'); 55 | } catch (exception:Any) { 56 | stdout.writeString(' no core type\n'); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /test/haxe/callfunc/PerformanceTest.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import callfunc.test.TestExamplelib; 4 | 5 | class PerformanceTest { 6 | public static function main() { 7 | var ffi = Callfunc.instance(); 8 | var library = ffi.openLibrary(TestExamplelib.getLibName()); 9 | 10 | library.define( 11 | "examplelib_ints", 12 | [DataType.SInt32, DataType.SInt32, DataType.Pointer], 13 | DataType.SInt32 14 | ); 15 | library.define( 16 | "examplelib_callback", 17 | [DataType.Pointer], 18 | DataType.SInt32 19 | ); 20 | 21 | var outputPointer = ffi.alloc(4); 22 | outputPointer.dataType = DataType.SInt32; 23 | 24 | for (x in 0...1000) { 25 | for (y in 0...100) { 26 | library.s.examplelib_ints.call(x, y, outputPointer); 27 | outputPointer.get(); 28 | outputPointer.arrayGet(0); 29 | } 30 | } 31 | 32 | function callback(a:Int, b:Int):Int { 33 | return a + b; 34 | } 35 | 36 | var callbackHandle = ffi.wrapCallback( 37 | callback, 38 | [DataType.SInt32, DataType.SInt32], 39 | DataType.SInt32); 40 | 41 | for (trial in 0...10000) { 42 | library.s.examplelib_callback.call(callbackHandle.pointer); 43 | } 44 | 45 | outputPointer.free(); 46 | library.dispose(); 47 | callbackHandle.dispose(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/haxe/callfunc/TestAll.hx: -------------------------------------------------------------------------------- 1 | package callfunc; 2 | 3 | import utest.Runner; 4 | import utest.ui.Report; 5 | 6 | #if js 7 | import callfunc.core.emscripten.EmContext; 8 | #end 9 | 10 | 11 | class TestAll { 12 | public static function main() { 13 | #if sys 14 | if (Sys.args().indexOf("--test-performance") >= 0) { 15 | trace("PerformanceTest"); 16 | PerformanceTest.main(); 17 | } 18 | if (Sys.args().indexOf("--list-data-types") >= 0) { 19 | ListDataTypes.main(); 20 | } 21 | #end 22 | 23 | #if js 24 | var context = new EmContext(Reflect.field(js.Browser.window, "Module")); 25 | Callfunc.setInstance(new Callfunc(context)); 26 | 27 | js.Syntax.code("waitForLoad({0})", runTests); 28 | #else 29 | runTests(); 30 | #end 31 | } 32 | 33 | static function runTests() { 34 | var runner = new Runner(); 35 | 36 | #if sys 37 | runner.addCase(new callfunc.test.TestCairoMatrix()); 38 | runner.addCase(new callfunc.test.TestCairoSurface()); 39 | #end 40 | 41 | runner.addCase(new callfunc.test.TestAnyInt()); 42 | runner.addCase(new callfunc.test.TestDataView()); 43 | runner.addCase(new callfunc.test.TestExamplelib()); 44 | runner.addCase(new callfunc.test.TestCallfunc()); 45 | runner.addCase(new callfunc.test.TestPointer()); 46 | runner.addCase(new callfunc.test.TestStructAccess()); 47 | Report.create(runner); 48 | runner.run(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/haxe/callfunc/test/TestAnyInt.hx: -------------------------------------------------------------------------------- 1 | package callfunc.test; 2 | 3 | import utest.Assert; 4 | import haxe.Int64; 5 | import utest.Test; 6 | 7 | class TestAnyInt extends Test { 8 | public function testToInt() { 9 | var a:AnyInt = 123; 10 | var b:AnyInt = Int64.make(123, 456); 11 | var b2:AnyInt = Int64.make(0, 456); 12 | var c:AnyInt = "abc"; 13 | 14 | Assert.equals(123, a.toInt()); 15 | Assert.equals(456, b.toInt()); 16 | 17 | // FIXME: bind on abstract on Hashlink broken? 18 | // Assert.raises(b.toInt.bind(true), String); 19 | 20 | try { 21 | b.toInt(true); 22 | Assert.fail(); 23 | } catch (error:String) { 24 | Assert.pass(); 25 | } 26 | 27 | Assert.equals(456, b2.toInt(true)); 28 | 29 | Assert.raises(() -> c.toInt(), String); 30 | } 31 | 32 | public function testToInt64() { 33 | var a:AnyInt = 123; 34 | var b:AnyInt = Int64.make(123, 456); 35 | var c:AnyInt = "abc"; 36 | 37 | Assert.isTrue(Int64.eq(Int64.make(0, 123), a.toInt64())); 38 | Assert.isTrue(Int64.eq(Int64.make(123, 456), b.toInt64())); 39 | Assert.raises(() -> c.toInt64(), String); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/haxe/callfunc/test/TestCairoMatrix.hx: -------------------------------------------------------------------------------- 1 | package callfunc.test; 2 | 3 | import utest.Assert; 4 | 5 | class TestCairoMatrix extends utest.Test { 6 | public static function getLibName() { 7 | #if js 8 | return ""; 9 | #else 10 | switch Sys.systemName() { 11 | case "Windows": 12 | return "cairo.dll"; 13 | case "Mac": 14 | return "libcairo.dylib"; 15 | default: 16 | return "libcairo.so"; 17 | } 18 | #end 19 | } 20 | 21 | public function testMatrixScale() { 22 | var ffi = Callfunc.instance(); 23 | var library = ffi.openLibrary(getLibName()); 24 | 25 | library.define( 26 | "cairo_matrix_init_identity", 27 | [DataType.Pointer] 28 | ); 29 | 30 | library.define( 31 | "cairo_matrix_scale", 32 | [DataType.Pointer, DataType.Double, DataType.Double] 33 | ); 34 | 35 | library.define( 36 | "cairo_matrix_transform_point", 37 | [DataType.Pointer, DataType.Pointer, DataType.Pointer] 38 | ); 39 | 40 | var matrixStructDef = ffi.defineStruct( 41 | [DataType.Double, DataType.Double, DataType.Double, 42 | DataType.Double, DataType.Double, DataType.Double], 43 | ["xx", "yx", "xy", "yy", "x0", "y0"] 44 | ); 45 | 46 | Assert.isTrue(matrixStructDef.size >= 8 * 6); 47 | 48 | var matrixPointer = ffi.alloc(matrixStructDef.size); 49 | var matrix = matrixStructDef.access(matrixPointer); 50 | 51 | var i = matrixPointer.address; 52 | 53 | library.s.cairo_matrix_init_identity.call(matrixPointer); 54 | 55 | Assert.equals(1.0, matrix.xx); 56 | Assert.equals(0.0, matrix.yx); 57 | Assert.equals(0.0, matrix.xy); 58 | Assert.equals(1.0, matrix.yy); 59 | Assert.equals(0.0, matrix.x0); 60 | Assert.equals(0.0, matrix.y0); 61 | 62 | library.s.cairo_matrix_scale.call(matrixPointer, 2.0, 1.0); 63 | 64 | var xPointer = ffi.alloc(ffi.sizeOf(DataType.Double)); 65 | var yPointer = ffi.alloc(ffi.sizeOf(DataType.Double)); 66 | xPointer.dataType = yPointer.dataType = DataType.Double; 67 | 68 | xPointer.set(10.0); 69 | yPointer.set(10.0); 70 | 71 | library.s.cairo_matrix_transform_point.call(matrixPointer, xPointer, yPointer); 72 | 73 | Assert.equals(20.0, xPointer.get()); 74 | Assert.equals(10.0, yPointer.get()); 75 | 76 | matrixPointer.free(); 77 | matrixStructDef.dispose(); 78 | library.dispose(); 79 | xPointer.free(); 80 | yPointer.free(); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/haxe/callfunc/test/TestCairoSurface.hx: -------------------------------------------------------------------------------- 1 | package callfunc.test; 2 | 3 | import utest.Assert; 4 | import utest.Test; 5 | 6 | class TestCairoSurface extends Test { 7 | public function testSimpleDraw() { 8 | var ffi = Callfunc.instance(); 9 | var library = ffi.openLibrary(TestCairoMatrix.getLibName()); 10 | 11 | library.define( 12 | "cairo_image_surface_create", 13 | [DataType.SInt32, DataType.SInt, DataType.SInt], 14 | DataType.Pointer 15 | ); 16 | 17 | library.define( 18 | "cairo_surface_status", 19 | [DataType.Pointer], 20 | DataType.SInt 21 | ); 22 | 23 | library.define( 24 | "cairo_image_surface_get_data", 25 | [DataType.Pointer], 26 | DataType.Pointer 27 | ); 28 | 29 | library.define( 30 | "cairo_surface_flush", 31 | [DataType.Pointer] 32 | ); 33 | 34 | library.define( 35 | "cairo_surface_destroy", 36 | [DataType.Pointer] 37 | ); 38 | 39 | library.define( 40 | "cairo_create", 41 | [DataType.Pointer], 42 | DataType.Pointer 43 | ); 44 | 45 | library.define( 46 | "cairo_destroy", 47 | [DataType.Pointer] 48 | ); 49 | 50 | library.define( 51 | "cairo_rectangle", 52 | [DataType.Pointer, DataType.Double, DataType.Double, 53 | DataType.Double, DataType.Double] 54 | ); 55 | 56 | library.define( 57 | "cairo_fill", 58 | [DataType.Pointer] 59 | ); 60 | 61 | var surface = library.s.cairo_image_surface_create.call(0, 100, 100); 62 | var status = library.s.cairo_surface_status.call(surface); 63 | 64 | Assert.equals(0, status); 65 | 66 | var context = library.s.cairo_create.call(surface); 67 | 68 | library.s.cairo_rectangle.call(context, 1, 0, 20, 20); 69 | library.s.cairo_fill.call(context); 70 | library.s.cairo_surface_flush.call(surface); 71 | 72 | var data:Pointer = library.s.cairo_image_surface_get_data.call(surface); 73 | 74 | Assert.equals(0, data.get(DataType.UInt32)); 75 | Assert.equals(0xff000000, 76 | data.get(DataType.UInt32, 77 | 1 * ffi.sizeOf(DataType.UInt32))); 78 | 79 | library.s.cairo_destroy.call(context); 80 | library.s.cairo_surface_destroy.call(surface); 81 | library.dispose(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/haxe/callfunc/test/TestCallfunc.hx: -------------------------------------------------------------------------------- 1 | package callfunc.test; 2 | 3 | import callfunc.core.CoreDataTypeTable; 4 | import haxe.io.Bytes; 5 | import haxe.Int64; 6 | import utest.Assert; 7 | 8 | using callfunc.core.DataTypeTools; 9 | 10 | class TestCallfunc extends utest.Test { 11 | public function testAllocFree() { 12 | var ffi = Callfunc.instance(); 13 | 14 | var pointer = ffi.alloc(100); 15 | 16 | Assert.notNull(pointer); 17 | Assert.isFalse(pointer.isNull()); 18 | Assert.notEquals(0, pointer.address); 19 | 20 | pointer.free(); 21 | } 22 | 23 | public function testSizeOf() { 24 | var ffi = Callfunc.instance(); 25 | 26 | Assert.equals(1, ffi.sizeOf(DataType.UInt8)); 27 | Assert.equals(1, ffi.sizeOf(DataType.SInt8)); 28 | Assert.equals(2, ffi.sizeOf(DataType.UInt16)); 29 | Assert.equals(2, ffi.sizeOf(DataType.SInt16)); 30 | Assert.equals(4, ffi.sizeOf(DataType.UInt32)); 31 | Assert.equals(4, ffi.sizeOf(DataType.SInt32)); 32 | Assert.equals(8, ffi.sizeOf(DataType.UInt64)); 33 | Assert.equals(8, ffi.sizeOf(DataType.SInt64)); 34 | Assert.equals(4, ffi.sizeOf(DataType.Float)); 35 | Assert.equals(8, ffi.sizeOf(DataType.Double)); 36 | Assert.isTrue(ffi.sizeOf(DataType.UChar) >= 1); 37 | Assert.isTrue(ffi.sizeOf(DataType.SChar) >= 1); 38 | Assert.isTrue(ffi.sizeOf(DataType.UShort) >= 2); 39 | Assert.isTrue(ffi.sizeOf(DataType.SShort) >= 2); 40 | Assert.isTrue(ffi.sizeOf(DataType.SInt) >= 2); 41 | Assert.isTrue(ffi.sizeOf(DataType.UInt) >= 2); 42 | Assert.isTrue(ffi.sizeOf(DataType.SLong) >= 4); 43 | Assert.isTrue(ffi.sizeOf(DataType.ULong) >= 4); 44 | Assert.isTrue(ffi.sizeOf(DataType.Pointer) >= 2); 45 | Assert.isTrue(ffi.sizeOf(DataType.LongDouble) >= 0); 46 | Assert.isTrue(ffi.sizeOf(DataType.ComplexFloat) >= 0); 47 | Assert.isTrue(ffi.sizeOf(DataType.ComplexDouble) >= 0); 48 | Assert.isTrue(ffi.sizeOf(DataType.ComplexLongDouble) >= 0); 49 | Assert.isTrue(ffi.sizeOf(DataType.Size) >= 0); 50 | Assert.isTrue(ffi.sizeOf(DataType.PtrDiff) >= 0); 51 | Assert.isTrue(ffi.sizeOf(DataType.WChar) >= 0); 52 | } 53 | 54 | public function testGetPointer() { 55 | var ffi = Callfunc.instance(); 56 | var address = Int64.make(0, 0xcafe); 57 | 58 | var pointer = ffi.getPointer(address); 59 | 60 | Assert.notNull(pointer); 61 | Assert.isFalse(pointer.isNull()); 62 | Assert.notEquals(0, pointer.address); 63 | Assert.isTrue(Int64.eq(address, pointer.address)); 64 | } 65 | 66 | #if sys 67 | public function testBytesToPointer() { 68 | var ffi = Callfunc.instance(); 69 | var bytes = Bytes.alloc(8); 70 | 71 | bytes.setInt32(0, 12345678); 72 | bytes.setInt32(4, 87654321); 73 | 74 | var pointer = ffi.bytesToPointer(bytes); 75 | 76 | Assert.notNull(pointer); 77 | Assert.isFalse(pointer.isNull()); 78 | Assert.notEquals(0, pointer.address); 79 | Assert.equals(12345678, pointer.get(DataType.SInt32, 0)); 80 | Assert.equals(87654321, pointer.get(DataType.SInt32, 4)); 81 | 82 | bytes.setInt32(0, 1111); 83 | bytes.setInt32(4, 2222); 84 | 85 | Assert.equals(1111, pointer.get(DataType.SInt32, 0)); 86 | Assert.equals(2222, pointer.get(DataType.SInt32, 4)); 87 | } 88 | #end 89 | 90 | public function testToCoreDataType() { 91 | var ffi = Callfunc.instance(); 92 | 93 | Assert.same(CoreDataType.SInt8, ffi.context.toCoreDataType(DataType.SInt8)); 94 | Assert.same(CoreDataType.Double, ffi.context.toCoreDataType(DataType.Double)); 95 | Assert.same(CoreDataType.Void, ffi.context.toCoreDataType(DataType.Void)); 96 | 97 | Assert.notEquals(CoreDataType.UChar, ffi.context.toCoreDataType(DataType.UChar, true)); 98 | Assert.notEquals(CoreDataType.SChar, ffi.context.toCoreDataType(DataType.SChar, true)); 99 | Assert.notEquals(CoreDataType.SInt, ffi.context.toCoreDataType(DataType.SInt, true)); 100 | Assert.notEquals(CoreDataType.UInt, ffi.context.toCoreDataType(DataType.UInt, true)); 101 | Assert.notEquals(CoreDataType.SLong, ffi.context.toCoreDataType(DataType.SLong, true)); 102 | Assert.notEquals(CoreDataType.ULong, ffi.context.toCoreDataType(DataType.ULong, true)); 103 | 104 | for (dataType in [DataType.Size, DataType.PtrDiff, DataType.WChar]) { 105 | if (ffi.sizeOf(dataType) > 0) { 106 | ffi.context.toCoreDataType(dataType); 107 | } 108 | } 109 | } 110 | 111 | public function testCoreDataTypeTable() { 112 | var ffi = Callfunc.instance(); 113 | final table = new CoreDataTypeTable(ffi.context); 114 | 115 | Assert.same(CoreDataType.SInt8, table.toCoreDataType(DataType.SInt8)); 116 | Assert.same(CoreDataType.Double, table.toCoreDataType(DataType.Double)); 117 | Assert.same(CoreDataType.Void, table.toCoreDataType(DataType.Void)); 118 | 119 | Assert.notEquals(CoreDataType.UChar, table.toCoreDataType(DataType.UChar, true)); 120 | Assert.notEquals(CoreDataType.SChar, table.toCoreDataType(DataType.SChar, true)); 121 | Assert.notEquals(CoreDataType.SInt, table.toCoreDataType(DataType.SInt, true)); 122 | Assert.notEquals(CoreDataType.UInt, table.toCoreDataType(DataType.UInt, true)); 123 | Assert.notEquals(CoreDataType.SLong, table.toCoreDataType(DataType.SLong, true)); 124 | Assert.notEquals(CoreDataType.ULong, table.toCoreDataType(DataType.ULong, true)); 125 | 126 | for (dataType in [DataType.Size, DataType.PtrDiff, DataType.WChar]) { 127 | if (ffi.sizeOf(dataType) > 0) { 128 | table.toCoreDataType(dataType); 129 | } 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /test/haxe/callfunc/test/TestDataView.hx: -------------------------------------------------------------------------------- 1 | package callfunc.test; 2 | 3 | import haxe.Int64; 4 | import utest.Assert; 5 | import haxe.io.Bytes; 6 | import callfunc.BytesDataView; 7 | import utest.Test; 8 | 9 | class TestDataView extends Test { 10 | public function testGetSet() { 11 | var view:DataView = new BytesDataView(Bytes.alloc(24), 8, 8); 12 | 13 | view.set(0, 123); 14 | Assert.equals(123, view.get(0)); 15 | 16 | view.setUInt8(0, 123); 17 | Assert.equals(123, view.getUInt8(0)); 18 | view.setInt8(0, -123); 19 | Assert.equals(-123, view.getInt8(0)); 20 | 21 | view.setUInt16(0, 12345); 22 | Assert.equals(12345, view.getUInt16(0)); 23 | view.setInt16(0, -32100); 24 | Assert.equals(-32100, view.getInt16(0)); 25 | 26 | view.setUInt32(0, 12345678); 27 | Assert.equals(12345678, view.getUInt32(0)); 28 | view.setInt32(0, -12345678); 29 | Assert.equals(-12345678, view.getInt32(0)); 30 | 31 | view.setInt64(0, Int64.fromFloat(-12345678901234)); 32 | Assert.isTrue(Int64.fromFloat(-12345678901234) == view.getInt64(0)); 33 | 34 | view.setFloat(0, 123.456); 35 | Assert.floatEquals(123.456, view.getFloat(0)); 36 | 37 | view.setDouble(0, 123.456); 38 | Assert.floatEquals(123.456, view.getDouble(0)); 39 | } 40 | 41 | public function testFill() { 42 | var view:DataView = new BytesDataView(Bytes.alloc(24), 8, 8); 43 | 44 | view.fill(0, 8, 123); 45 | 46 | for (index in 0...8) { 47 | Assert.equals(123, view.get(index)); 48 | } 49 | } 50 | 51 | public function testBlit() { 52 | var view:DataView = new BytesDataView(Bytes.alloc(24), 8, 8); 53 | var view2:DataView = new BytesDataView(Bytes.alloc(24), 8, 8); 54 | 55 | view.set(0, 1); 56 | view.set(1, 2); 57 | view.set(2, 3); 58 | view.set(3, 4); 59 | 60 | view2.blit(2, view, 2, 1); 61 | 62 | Assert.equals(0, view2.get(0)); 63 | Assert.equals(0, view2.get(1)); 64 | Assert.equals(3, view2.get(2)); 65 | Assert.equals(0, view2.get(3)); 66 | } 67 | 68 | public function testSub() { 69 | var view:DataView = new BytesDataView(Bytes.alloc(24), 8, 8); 70 | var view2 = view.sub(2, 2); 71 | 72 | view.set(2, 123); 73 | 74 | Assert.equals(10, view2.byteOffset); 75 | Assert.equals(2, view2.byteLength); 76 | Assert.equals(123, view2.get(0)); 77 | Assert.equals(0, view2.get(1)); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/haxe/callfunc/test/TestExamplelib.hx: -------------------------------------------------------------------------------- 1 | package callfunc.test; 2 | 3 | import utest.Assert; 4 | import utest.Test; 5 | 6 | class TestExamplelib extends Test { 7 | public static function getLibName() { 8 | #if js 9 | return ""; 10 | #else 11 | switch Sys.systemName() { 12 | case "Windows": 13 | return "examplelib.dll"; 14 | case "Mac": 15 | return "examplelib.dylib"; 16 | default: 17 | return "examplelib.so"; 18 | } 19 | #end 20 | } 21 | 22 | public function testNonexistentLibrary() { 23 | var ffi = Callfunc.instance(); 24 | Assert.raises(ffi.openLibrary.bind("nonexistent-library-1234")); 25 | } 26 | 27 | public function testNonexistentFunction() { 28 | var ffi = Callfunc.instance(); 29 | var library = ffi.openLibrary(getLibName()); 30 | 31 | Assert.raises(library.define.bind("nonexistent_function")); 32 | Assert.raises(library.defineVariadic.bind("nonexistent_function", [DataType.SInt], 1)); 33 | 34 | library.dispose(); 35 | } 36 | 37 | public function testInts() { 38 | var ffi = Callfunc.instance(); 39 | var library = ffi.openLibrary(getLibName()); 40 | 41 | library.define( 42 | "examplelib_ints", 43 | [DataType.SInt32, DataType.SInt32, DataType.Pointer], 44 | DataType.SInt32 45 | ); 46 | 47 | var outputPointer = ffi.alloc(4); 48 | outputPointer.dataType = DataType.SInt32; 49 | 50 | var result = library.s.examplelib_ints.call(123, 456, outputPointer); 51 | 52 | Assert.equals(0xcafe, result); 53 | Assert.equals(579, outputPointer.get()); 54 | 55 | result = library.s["examplelib_ints"].call(10, 20, outputPointer); 56 | Assert.equals(30, outputPointer.get()); 57 | 58 | outputPointer.free(); 59 | library.dispose(); 60 | } 61 | 62 | public function testString() { 63 | var ffi = Callfunc.instance(); 64 | var library = ffi.openLibrary(getLibName()); 65 | 66 | library.define( 67 | "examplelib_string", 68 | [DataType.Pointer], 69 | DataType.Pointer 70 | ); 71 | 72 | var inputStringPointer = ffi.allocString("Hello world!"); 73 | var result:Pointer = library.s.examplelib_string.call(inputStringPointer); 74 | var resultString = result.getString(); 75 | 76 | Assert.equals("HELLO WORLD!", resultString); 77 | 78 | result.free(); 79 | inputStringPointer.free(); 80 | library.dispose(); 81 | } 82 | 83 | public function testVariadic() { 84 | var ffi = Callfunc.instance(); 85 | var library = ffi.openLibrary(getLibName()); 86 | 87 | library.defineVariadic( 88 | "examplelib_variadic", 89 | [DataType.UInt, DataType.SInt32, DataType.SInt32], 90 | 1, 91 | DataType.SInt32 92 | ); 93 | library.defineVariadic( 94 | "examplelib_variadic", 95 | [DataType.UInt, DataType.SInt32, DataType.SInt32, DataType.SInt32], 96 | 1, 97 | DataType.SInt32, 98 | "examplelib_variadic__2" 99 | ); 100 | 101 | var result = library.s.examplelib_variadic.call(2, 123, 456); 102 | 103 | Assert.equals(579, result); 104 | 105 | result = library.s.examplelib_variadic__2.call(3, 123, 456, 789); 106 | 107 | Assert.equals(1368, result); 108 | 109 | library.dispose(); 110 | } 111 | 112 | public function testCallback() { 113 | var ffi = Callfunc.instance(); 114 | var library = ffi.openLibrary(getLibName()); 115 | 116 | library.define( 117 | "examplelib_callback", 118 | [DataType.Pointer], 119 | DataType.SInt32 120 | ); 121 | 122 | function callback(a:Int, b:Int):Int { 123 | return a + b; 124 | } 125 | 126 | var callbackHandle = ffi.wrapCallback( 127 | callback, 128 | [DataType.SInt32, DataType.SInt32], 129 | DataType.SInt32); 130 | 131 | var result = library.s.examplelib_callback.call(callbackHandle.pointer); 132 | 133 | Assert.equals(123 + 456, result); 134 | 135 | for (dummy in 0...10000) { 136 | library.s.examplelib_callback.call(callbackHandle.pointer); 137 | } 138 | 139 | library.dispose(); 140 | callbackHandle.dispose(); 141 | } 142 | 143 | public function testStructType() { 144 | var ffi = Callfunc.instance(); 145 | 146 | if (ffi.sizeOf(DataType.SInt) != 4) { 147 | Assert.warn("Skipping test because it doesn't seem to be x86"); 148 | return; 149 | } 150 | 151 | var structDef = ffi.defineStruct( 152 | [DataType.SChar, DataType.SInt], 153 | ["a", "b"] 154 | ); 155 | 156 | Assert.equals(8, structDef.size); 157 | Assert.same([0, 4], structDef.offsets); 158 | 159 | structDef.dispose(); 160 | } 161 | 162 | #if js 163 | @Ignored("not supported") 164 | #end 165 | public function testStructPassByValue() { 166 | var ffi = Callfunc.instance(); 167 | var library = ffi.openLibrary(getLibName()); 168 | var structDataTypes = [DataType.UChar, DataType.SInt32, DataType.Double]; 169 | var structDef = ffi.defineStruct(structDataTypes, ["a", "b", "c"]); 170 | 171 | library.define( 172 | "examplelib_struct_value", 173 | [DataType.Struct(structDataTypes)], 174 | DataType.Struct(structDataTypes) 175 | ); 176 | 177 | var inputStructPointer = ffi.alloc(structDef.size); 178 | var inputStruct = structDef.access(inputStructPointer); 179 | inputStruct.a = 0x65; 180 | inputStruct.b = 0x65; 181 | inputStruct.c = 123.456; 182 | 183 | var result:Pointer = library.s.examplelib_struct_value.call(inputStructPointer); 184 | var resultStruct = structDef.access(result); 185 | 186 | Assert.equals(0x45, resultStruct.a); 187 | Assert.equals(0x45, resultStruct.b); 188 | Assert.equals(246.912, resultStruct.c); 189 | 190 | structDef.dispose(); 191 | library.dispose(); 192 | inputStructPointer.free(); 193 | result.free(); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /test/haxe/callfunc/test/TestPointer.hx: -------------------------------------------------------------------------------- 1 | package callfunc.test; 2 | 3 | import callfunc.string.Encoding; 4 | import haxe.Int64; 5 | import utest.Assert; 6 | 7 | class TestPointer extends utest.Test { 8 | public function testGetSet() { 9 | var ffi = Callfunc.instance(); 10 | 11 | var pointer = ffi.alloc(8); 12 | 13 | pointer.set(-1, DataType.SInt8); 14 | Assert.equals(-1, pointer.get(DataType.SInt8)); 15 | 16 | pointer.set(200, DataType.UInt8); 17 | Assert.equals(200, pointer.get(DataType.UInt8)); 18 | 19 | pointer.set(11111, DataType.UInt16); 20 | Assert.equals(11111, pointer.get(DataType.UInt16, 0)); 21 | 22 | pointer.set(50000, DataType.UInt16); 23 | Assert.equals(50000, pointer.get(DataType.UInt16, 0)); 24 | 25 | pointer.set(-32111, DataType.SInt16); 26 | Assert.equals(-32111, pointer.get(DataType.SInt16)); 27 | 28 | pointer.set(-11111111, DataType.SInt32); 29 | Assert.equals(-11111111, pointer.get(DataType.SInt32)); 30 | 31 | pointer.set(0xffaabbcc, DataType.UInt32); 32 | Assert.equals(0xffaabbcc, pointer.get(DataType.UInt32)); 33 | 34 | pointer.set(Int64.make(0x12345678, 0xc001cafe), DataType.SInt64); 35 | // Assert.equals(Int64.make(0x12345678, 0xc001cafe), pointer.get(DataType.SInt64)); 36 | Assert.isTrue(Int64.make(0x12345678, 0xc001cafe) == pointer.get(DataType.SInt64)); 37 | 38 | pointer.set(123.456, DataType.Float); 39 | Assert.floatEquals(123.456, pointer.get(DataType.Float, 0)); 40 | 41 | pointer.set(123.456, DataType.Double); 42 | Assert.equals(123.456, pointer.get(DataType.Double, 0)); 43 | 44 | pointer.free(); 45 | } 46 | 47 | public function testPointerArray() { 48 | var ffi = Callfunc.instance(); 49 | 50 | var pointer = ffi.alloc(16); 51 | 52 | pointer.arraySet(0, 1, DataType.SInt32); 53 | pointer.arraySet(1, 2, DataType.SInt32); 54 | pointer.arraySet(2, 3, DataType.SInt32); 55 | pointer.arraySet(3, 4, DataType.SInt32); 56 | 57 | Assert.equals(1, pointer.arrayGet(0, DataType.SInt32)); 58 | Assert.equals(2, pointer.arrayGet(1, DataType.SInt32)); 59 | Assert.equals(3, pointer.arrayGet(2, DataType.SInt32)); 60 | Assert.equals(4, pointer.arrayGet(3, DataType.SInt32)); 61 | 62 | pointer.free(); 63 | } 64 | 65 | public function testDefaultDataType() { 66 | var ffi = Callfunc.instance(); 67 | 68 | var pointer = ffi.alloc(8); 69 | pointer.dataType = DataType.SInt16; 70 | 71 | function clear () { 72 | for (index in 0...8) { 73 | pointer.set(0, DataType.SInt8, index); 74 | } 75 | } 76 | 77 | // These tests assume little endian 78 | clear(); 79 | pointer.set(-1); 80 | Assert.equals(-1, pointer.get()); 81 | Assert.equals(0xffff, pointer.get(DataType.SInt32)); 82 | 83 | clear(); 84 | pointer.set(0xc001cafe, DataType.SInt32); 85 | Assert.equals(-13570, pointer.get()); 86 | 87 | clear(); 88 | pointer.arraySet(0, -1); 89 | Assert.equals(-1, pointer.arrayGet(0)); 90 | Assert.equals(0xffff, pointer.arrayGet(0, DataType.SInt32)); 91 | 92 | clear(); 93 | pointer.arraySet(0, 0xc001cafe, DataType.SInt32); 94 | Assert.equals(-13570, pointer.arrayGet(0)); 95 | 96 | pointer.free(); 97 | } 98 | 99 | public function testToDataView() { 100 | var ffi = Callfunc.instance(); 101 | var pointer = ffi.alloc(8, true); 102 | 103 | pointer.set(12345678, DataType.SInt32, 0); 104 | pointer.set(87654321, DataType.SInt32, 4); 105 | 106 | var view = pointer.getDataView(8); 107 | 108 | Assert.equals(12345678, view.getInt32(0)); 109 | Assert.equals(87654321, view.getInt32(4)); 110 | 111 | pointer.set(1111, DataType.SInt32, 0); 112 | pointer.set(2222, DataType.SInt32, 4); 113 | 114 | Assert.equals(1111, view.getInt32(0)); 115 | Assert.equals(2222, view.getInt32(4)); 116 | 117 | pointer.free(); 118 | } 119 | 120 | #if sys 121 | public function testToBytes() { 122 | var ffi = Callfunc.instance(); 123 | var pointer = ffi.alloc(8, true); 124 | 125 | pointer.set(12345678, DataType.SInt32, 0); 126 | pointer.set(87654321, DataType.SInt32, 4); 127 | 128 | var bytes = pointer.getBytes(8); 129 | 130 | Assert.equals(12345678, bytes.getInt32(0)); 131 | Assert.equals(87654321, bytes.getInt32(4)); 132 | 133 | pointer.set(1111, DataType.SInt32, 0); 134 | pointer.set(2222, DataType.SInt32, 4); 135 | 136 | Assert.equals(1111, bytes.getInt32(0)); 137 | Assert.equals(2222, bytes.getInt32(4)); 138 | 139 | pointer.free(); 140 | } 141 | #end 142 | 143 | public function testString() { 144 | var ffi = Callfunc.instance(); 145 | var pointer = ffi.alloc(100, true); 146 | 147 | pointer.setString("abcdé", true); 148 | Assert.equals("abcdé", pointer.getString()); 149 | 150 | pointer.setString("abcdé", Encoding.UTF16LE, true); 151 | Assert.equals("abcdé", pointer.getString(Encoding.UTF16LE)); 152 | 153 | pointer.free(); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /test/haxe/callfunc/test/TestStructAccess.hx: -------------------------------------------------------------------------------- 1 | package callfunc.test; 2 | 3 | import utest.Assert; 4 | import utest.Test; 5 | 6 | class TestStructAccess extends Test { 7 | public function test() { 8 | var ffi = Callfunc.instance(); 9 | var pointer = ffi.alloc(100); 10 | var structDef = ffi.defineStruct( 11 | [DataType.SInt32, DataType.Double, DataType.UInt8], 12 | ["a", "b", "c"] 13 | ); 14 | 15 | var struct = structDef.access(pointer); 16 | 17 | struct.a = 123; 18 | struct.b = 123.456; 19 | struct["c"] = 200; 20 | 21 | Assert.equals(123, struct["a"]); 22 | Assert.equals(123.456, struct["b"]); 23 | Assert.equals(200, struct.c); 24 | 25 | Assert.raises(struct.get.bind("nonexist")); 26 | Assert.raises(struct.set.bind("nonexist", 123)); 27 | 28 | structDef.dispose(); 29 | pointer.free(); 30 | } 31 | } 32 | --------------------------------------------------------------------------------