├── .github └── workflows │ └── maven.yml ├── LICENSE ├── README.md ├── build.xml ├── fl_dll ├── CMakeLists.txt ├── library.c └── library.h ├── libpassport_test.dll ├── libpassport_test.so ├── passing_double_arr.png ├── passing_doubles.png ├── pom.xml └── src ├── main ├── java │ ├── jpassport │ │ ├── FunctionPtr.java │ │ ├── GenericPointer.java │ │ ├── MemoryBlock.java │ │ ├── NamedLookup.java │ │ ├── Passport.java │ │ ├── PassportBuilder.java │ │ ├── PassportException.java │ │ ├── PassportFactory.java │ │ ├── PassportInvocationHandler.java │ │ ├── PassportWriter.java │ │ ├── Pointer.java │ │ ├── Utils.java │ │ ├── Version.java │ │ ├── annotations │ │ │ ├── Array.java │ │ │ ├── Critical.java │ │ │ ├── NotRequired.java │ │ │ ├── Ptr.java │ │ │ ├── PtrPtrArg.java │ │ │ ├── RefArg.java │ │ │ └── StructPadding.java │ │ └── version.properties │ └── module-info.java └── resources │ └── jpassport │ └── version.properties └── test └── java ├── jpassport └── test │ ├── PureJava.java │ ├── TestJPassport.java │ ├── TestLink.java │ ├── TestLinkHelp.java │ ├── TestLinkJNADirect.java │ ├── callback │ ├── CallbackNative.java │ ├── CallbackObj.java │ └── TestCallback.java │ ├── performance │ ├── JPassportMicroBenchmark.java │ ├── PerfTest.java │ ├── PerformanceTest.java │ └── PureJavaPerf.java │ ├── structs │ ├── ComplexStruct.java │ ├── PassingArrays.java │ ├── StructWithPrt.java │ ├── TestStruct.java │ ├── TestStructCalls.java │ └── TestUsingStructs.java │ └── util │ └── CSVOutput.java └── module-info.java /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Java CI with Maven 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: windows-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Set up JDK 17 20 | uses: actions/setup-java@v2 21 | with: 22 | java-version: '17' 23 | distribution: 'temurin' 24 | cache: maven 25 | - name: Build with Maven 26 | run: mvn -B package --file pom.xml 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JPassport - Java 22 2 | 3 | JPassport works like [Java Native Access (JNA)](https://github.com/java-native-access/jna) but uses the 4 | [Foreign Linker API](https://openjdk.java.net/jeps/393) instead of JNI. 5 | Similar to JNA, you declare a Java interface that is bound to the external C library using method names. 6 | The goal of this project is to a) start working with the Foreign Linker, b) provide a drop in replacement 7 | for JNA in simple applications. 8 | 9 | As part of the Foreign Linker API a tool called [JExtract](https://github.com/openjdk/panama-foreign/blob/foreign-jextract/doc/panama_jextract.md) 10 | is available. Given a header file JExtract will build the classes needed to access a C library. If you have 11 | a large header file then JExtract is likely an easier tool for you to use if you don't already have interfaces 12 | defined for JNA. 13 | 14 | **Java 22 and later** are required to use this library. There are separate branches for Java 17 to 22. 15 | 16 | The Foreign Linker API is final in Java 22. 17 | 18 | # Getting Started 19 | 20 | ### Source 21 | Download the source and run the maven build. 22 | 23 | ### Maven 24 | 25 | io.github.boulder-on 26 | JPassport 27 | 1.0.1-22 28 | 29 | 30 | 31 | # Calling a native library example 32 | 33 | The native api refers to these a "down calls". 34 | 35 | C, compiled into libforeign.dll or libforeign.so: 36 | ``` 37 | int string_length(const char* string) 38 | { 39 | return strlen(string); 40 | } 41 | 42 | double sumArrD(const double *arr, const int count) 43 | { 44 | double r = 0; 45 | for (int n = 0; n < count; ++n) 46 | r += arr[n]; 47 | return r; 48 | } 49 | ``` 50 | 51 | Java Interface: 52 | ```Java 53 | public interface Linked extends Passport { 54 | int string_length(String s); 55 | double sumArrD(double[] arr, int count); 56 | } 57 | ``` 58 | Standard usage - writes a .java file to disk, compiles and loads: 59 | ```Java 60 | Linked L = PassportFactory.link("libforeign", Linked.class); 61 | int n = L.string_length("hello"); 62 | double sum = L.sumArrD(new double[] {1, 2, 3}, 3); 63 | ``` 64 | 65 | Static usage - writes a .java file to disk that you can include in your codebase: 66 | ```java 67 | PassportWriter pw = new PassportWriter(Linked.class); 68 | pw.writeModule(Path.of('output_location')); 69 | ``` 70 | Once the class is compiled, to use it: 71 | ```java 72 | Linked l = new Linked_Impl(PassportFactory.loadMethodHandles("libforeign", Linked.class)); 73 | ``` 74 | 75 | In order to use this library you will need to provide the VM these arguments: 76 | 77 | __-Djava.library.path=[path to lib] --enable-native-access jpassport__ 78 | 79 | JPassport works by writing a class that implements your interface, compiling it and passing it back to you. 80 | By default, the classes are written to the folder specified by System.getProperty("java.io.tmpdir"). 81 | If you provide the system property __"jpassport.build.home"__ then the classes will be written and 82 | compiled there. 83 | 84 | # Callback example 85 | 86 | The native API refers to these as "up calls". It's common in native programming to pass a function 87 | as a pointer into another function. This technique is used to create call-backs. 88 | 89 | ```java 90 | import jpassport.FunctionPtr; 91 | 92 | public interface CallbackNative extends Passport { 93 | void passMethod(FunctionPtr functionPtr); 94 | } 95 | 96 | public class MyCallback { 97 | public void callbackMethod(int value, String name) { 98 | System.out.println(value + ". " + name); 99 | } 100 | } 101 | 102 | MyCallback cb = new MyCallback(); 103 | FunctionPtr functionPtr = PassportFactory.createCallback(cb, "callbackMethod"); 104 | 105 | CallbackNative cbn = PassportFactory.link("libforeign", CallbackNative.class); 106 | cbn.passMethod(functionPtr); 107 | ``` 108 | 109 | At the moment this does not work for static methods. 110 | 111 | __NOTE:__ If your callback method uses Java synchronization, or interacts with object member variables 112 | then the thread must be a Java thread. In testing I've done, if a callback is called from a 113 | normal Linux Thread then synchronized blocks do not work. 114 | 115 | # Performance 116 | Performance was tested vs JNA, JNA Direct, and pure Java. 117 | 118 | Performance of a method that passes 2 doubles. JPassport is about 5x faster than 119 | JNA. JNA Direct is impressively fast. JPassport that uses a proxy class performs 120 | quite poorly because of its heavy use of reflection. 121 | 122 | ![primative performance](passing_doubles.png) 123 | 124 | Performance of a method that passes an array of doubles. The gap here 125 | is much smaller between JNA and JPassport. 126 | 127 | ![array performance](passing_double_arr.png) 128 | 129 | (Tests were run on Windows 11 with an i7-10850H.) 130 | 131 | # C Data Types Handled Automatically 132 | 133 | | C Data Type | Java Data Type | 134 | |-------------------|-----------------------| 135 | | double | double | 136 | | double*, double[] | double[] | 137 | | double** | @PtrPtrArg double[][] | 138 | | double[][] | double[][] | 139 | | float | float | 140 | | float*, float[] | float[] | 141 | | float** | @PtrPtrArg float[][] | 142 | | float[][] | float[][] | 143 | | long | long | 144 | | long*, long[] | long[] | 145 | | long** | @PtrPtrArg long[][] | 146 | | long[][] | long[][] | 147 | | int | int | 148 | | int*, int[] | int[] | 149 | | int** | @PtrPtrArg int[][] | 150 | | int[][] | int[][] | 151 | | short | short | 152 | | short*, short[] | short[] | 153 | | short** | @PtrPtrArg short[][] | 154 | | short[][] | short[][] | 155 | | char | byte | 156 | | char* | byte[] or String | 157 | | char[] | byte[] or String | 158 | | char** | @PtrPtrArg byte[][] | 159 | | char[][] | byte[][] | 160 | | structs | Records | 161 | | char*, void * | MemoryBlock | 162 | | n/a | Arena (see below) | 163 | 164 | Any C argument that is defined with ** must be annotated with @PTrPtrArg in your Java interface. 165 | 166 | An **Arena** object can be added to any interface method signature. JPassport will use 167 | that Arena to allocate memory instead of creating its own Arena. This can help with 168 | efficiency by allowing you to hold a large block of memory open longer, rather 169 | than regularly re-allocating it. Only one Arena can be passed. 170 | 171 | Return types can be: 172 | 1. double 173 | 2. float 174 | 3. long 175 | 4. int 176 | 5. short 177 | 6. char 178 | 7. void 179 | 8. char* (maps to a Java String) 180 | 9. any pointer (see limitations) 181 | 182 | If an argument is changed by the C library call then the @RefArg annotation is required for that argument. 183 | The argument also needs to be passed as an array of length one. Ex. 184 | 185 | C: 186 | ``` 187 | void setInt(int *val, int set) 188 | { 189 | *val = set; 190 | } 191 | ``` 192 | 193 | Java: 194 | ```Java 195 | public interface Test extends Passport { 196 | void setInt(@RefArg int[] d, int set); 197 | } 198 | 199 | Linked lib = PassportFactory.link("foreign_link", Test.class); 200 | int[] ref = new int[1]; 201 | lib.setInt(ref, 10); 202 | ``` 203 | 204 | Without the @RefArg, when ref[] is returned it will not have been updated. 205 | ## Structs and Records 206 | In order to handle C Structs you must make an equivalent Java Record. For example 207 | ``` 208 | struct PassingData 209 | { 210 | int s_int; 211 | long long s_long; 212 | float s_float; 213 | double s_double; 214 | }; 215 | 216 | struct ComplexPassing 217 | { 218 | int s_ID; 219 | struct PassingData s_passingData; 220 | struct PassingData* s_ptrPassingData; 221 | char* s_string; 222 | }; 223 | 224 | double passSimple(struct PassingData* complex) 225 | { 226 | ... 227 | } 228 | 229 | double passComplex(struct ComplexPassing* complex) 230 | { 231 | ... 232 | } 233 | ``` 234 | 235 | ```java 236 | import jpassport.annotations.RefArg; 237 | 238 | public record PassingData( 239 | @StructPadding(bytes = 4) int s_int, 240 | long s_long, 241 | @StructPadding(bytes = 4) float s_float, 242 | double s_double) { 243 | } 244 | 245 | public record ComplexPassing( 246 | @StructPadding(bytes = 4) int ID, 247 | PassingData ts, 248 | @Ptr TestStruct tsPtr, 249 | String string) { 250 | } 251 | 252 | public interface PerfTest extends Passport { 253 | double passStruct(PassingData structData); 254 | double passComplex(@RefArg ComplexPassing[] complexStruct); 255 | } 256 | ``` 257 | The @StructPadding annotation here is optional and maintained for legacy reasons (and in case my 258 | calculations for padding a wrong on some platforms). Also, I guess it's possible that you have a 259 | very strange struct where you need bespoke padding. In general, the library will automatically 260 | add the padding that it thinks is required. If you use @StructPadding that tells JPassport 261 | how much padding to put before or after a struct member (negative numbers indicate pre-member 262 | padding). There are also separate annotation values for different platforms (windowsBytes, macBytes, linuxBytes). 263 | 264 | The other important annotation is @Ptr, this lets JPassport know to treat the member of the struct as 265 | a pointer to another struct. 266 | 267 | Arrays of Records can only be 1 element long. Longer arrays of Records are not supported. 268 | 269 | Records can contain primitives, arrays of primitives, pointers to arrays of primitives, Strings, or pointers 270 | to other Records. 271 | 272 | # Annotations 273 | JPassport uses annotations as code generation hints. The available annotations are: 274 | 275 | | Annotation | Usage | Meaning | 276 | |------------------------------|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 277 | | Array | Record members | If a C Struct takes a pointer to a primative array, this allows you to say what the size of the primative array is for RefArgs. | 278 | | NotRequired | Methods | If a function could not be found in the native library then no exception will be thrown. Use hasMethod("") to determine if the function was found. | | 279 | | Ptr | Record members | If a C Struct takes a pointer to a primative or another struct then use this annotation. | 280 | | PtrPtrArg | Function argument| Any C function that takes a ** must be annotated with this. | 281 | | RefArg | Function argument | Any C function that changes the contents of a pointer must be annotated with this to force the read back of the parameter | 282 | | RefArg (read_back_only=true) | Function argument | If you only need to pass a blank memory space for a method to fill, use this optimization, otherwise the values in the array are copied to memory that is passed to C. | 283 | | StructPadding | Record members | See the Javadoc or the above section on structs and records. | 284 | | Critical | Methods | Removes some overhead for calling a native method. Cannot be used when callbacks are used. See the JDK's Linker.Option.critical for more details. | 285 | # Limitations 286 | 287 | * Only arrays of Records of length 1 work. 288 | * Only 1D and 2D arrays of primitives are supported, deeper nestings do not work. 289 | * The interface file passed to PassportFactory and all required Records must be exported by your module. 290 | 291 | Pointers as function returns only work in a limited fashion. Based on a C 292 | function declaration there isn't a way to tell exactly what a method is returning. 293 | For example, returning int* could return any number of ints. There is 294 | little a library like JPassport can do to handle returned pointers automatically. 295 | The work-around is for your interface function to return MemorySegment. From there 296 | it would be up to you to decipher the return. 297 | 298 | Declaring your interface method to take MemorySegment objects allow you to 299 | manage all the data yourself (like JExtract). 300 | 301 | ``` 302 | double* mallocDoubles(const int count) 303 | { 304 | double* ret = malloc(count * sizeof(double )); 305 | 306 | for (int n = 0; n < count; ++n) 307 | ret[n] = (double)n; 308 | 309 | return ret; 310 | } 311 | 312 | void freeMemory(void *memory) 313 | { 314 | free(memory); 315 | } 316 | ``` 317 | 318 | ```Java 319 | public interface TestLink extends Passport { 320 | MemorySegment mallocDoubles(int count); 321 | void freeMemory(MemorySegment addr); 322 | } 323 | 324 | double[] testReturnPointer(int count) { 325 | MemorySegment address = linked_lib.mallocDoubles(count); 326 | double[] values = Utils.toArrDouble(address, count); 327 | linked_lib.freeMemory(address); 328 | return values; 329 | } 330 | ``` 331 | # Dependencies 332 | 333 | JPassport itself only requires **Java 22 or later** to build and run. There are separate Java 17-20 branches. 334 | 335 | 336 | # Work To-Do 337 | Roughly in order of importance 338 | 339 | 1. Support arrays of Records 340 | 2. Support returning a Record 341 | 3. Use the Java Micro-benchmarking harness. 342 | 4. Use the new Classfile API to build the class in memory 343 | 344 | # Release Notes 345 | - 1.0.1-22 346 | - Fixed an issue where System libraries could not be loaded (ex. malloc). 347 | - 1.0.0-22 348 | - Full 1.0 since Java 22 has gone GA and the foreign function API is now official 349 | - Added MemoryBlock as a method argument to pass allocated memory to a foreign function. 350 | - An Arena can now be an argument to a method. The Arena will be used for allocations during the call. In some cases this may be an optimization. 351 | - 0.7.0-22 352 | - Support Java 22 353 | - Added support for arrays of GenericPointer 354 | - Added Pointer as a sub-class of GenericPointer for better JNA compatability 355 | - Added the ability to use a Proxy object rather than writing a full new class 356 | - Using a Proxy is faster to create, but slower to invoke. Proxies are much slower than invoking a normal method, but the code to handle the native call is much less optimized as well. 357 | - The RefArg annotation can be added to an interface to indicate that all arrays should be read back after a call. 358 | - 0.6.0-21 359 | - Support Java 21 360 | - Make specifying byte padding in records/structs optional. 361 | - 0.6 362 | - Added the version of Java the library uses to the version (0.6.0-[java version]) 363 | - Added GenericPointer returns and method arguments. 364 | - Added @NotRequired annotation for methods that may not exist. 365 | - Default functions in the interface are now ignored. 366 | - 0.5 367 | - Added the GenericPointer class to help with returning things like win32 HANDLEs 368 | - Added RefArg(read_back_only = true) to optimize the returning of reference arguments. 369 | - 0.5 370 | - Fixed and issue where zero argument methods would not compile 371 | - Fixed issues where passing and receiving null values caused their own NullPointerExceptions 372 | - 0.4 373 | - Original release 374 | -------------------------------------------------------------------------------- /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 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /fl_dll/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | project(foreign_link C) 3 | 4 | set(CMAKE_C_STANDARD 99) 5 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "../..") 6 | add_library(passport_test SHARED library.c library.h) -------------------------------------------------------------------------------- /fl_dll/library.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | #include "library.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | double sumD(const double d1, const double d2) 20 | { 21 | return (d1 + d2); 22 | } 23 | 24 | double sumArrD(const double *arr, const int count) 25 | { 26 | if (arr == NULL) 27 | return 0; 28 | 29 | double r = 0; 30 | 31 | for (int n = 0; n < count; ++n) 32 | r += arr[n]; 33 | 34 | return r; 35 | } 36 | 37 | double sumArrDD(const double *arr,const double *arr2, const int count) 38 | { 39 | if (arr == NULL || arr2 == NULL) 40 | return 0; 41 | 42 | double r = 0; 43 | 44 | for (int n = 0; n < count; ++n) 45 | r += arr[n] + arr2[n]; 46 | 47 | return r; 48 | } 49 | 50 | void readD(double *val, int set) 51 | { 52 | *val = (double)set; 53 | } 54 | 55 | float sumArrF(const float *arr, const int count) 56 | { 57 | if (arr == NULL) 58 | return 0; 59 | 60 | float r = 0; 61 | 62 | for (int n = 0; n < count; ++n) 63 | r += arr[n]; 64 | 65 | return r; 66 | } 67 | 68 | void readF(float *val, float set) 69 | { 70 | *val = set; 71 | } 72 | 73 | long long sumArrL(const long long *arr, const long long count) 74 | { 75 | if (arr == NULL) 76 | return 0; 77 | 78 | long long r = 0; 79 | 80 | for (int n = 0; n < count; ++n) 81 | r += arr[n]; 82 | 83 | return r; 84 | } 85 | 86 | void readL(long long *val, long long set) 87 | { 88 | *val = set; 89 | } 90 | 91 | void readPointer(long long *val, long long set) 92 | { 93 | val[0] = set; 94 | } 95 | 96 | long long getPointer(long long *val, long long set) 97 | { 98 | val[0] = set; 99 | return val[0]; 100 | } 101 | 102 | int swapStrings(char** strings, int i, int j) 103 | { 104 | char* tmp = strings[i]; 105 | strings[i] = strings[j]; 106 | strings[j] = tmp; 107 | return strlen(strings[i]) + strlen(strings[j]); 108 | } 109 | 110 | int sumArrI(const int *arr, const int count) 111 | { 112 | if (arr == NULL) 113 | return 0; 114 | 115 | int r = 0; 116 | 117 | for (int n = 0; n < count; ++n) 118 | r += arr[n]; 119 | 120 | return r; 121 | } 122 | 123 | void readI(int *val, int set) 124 | { 125 | *val = set; 126 | } 127 | 128 | short sumArrS(const short *arr, const short count) 129 | { 130 | short r = 0; 131 | 132 | for (int n = 0; n < count; ++n) 133 | r += arr[n]; 134 | 135 | return r; 136 | } 137 | 138 | void readS(short *val, short set) 139 | { 140 | *val = set; 141 | } 142 | 143 | char sumArrB(const char *arr, const char count) 144 | { 145 | char r = 0; 146 | 147 | for (int n = 0; n < count; ++n) 148 | r += arr[n]; 149 | 150 | return r; 151 | } 152 | 153 | void readB(char *val, char set) 154 | { 155 | *val = set; 156 | } 157 | 158 | 159 | double sumMatD(int rows, int cols, double mat[rows][cols]) 160 | { 161 | int total = 0; 162 | 163 | for (int yy = 0; yy < rows; ++yy) 164 | { 165 | for (int xx = 0; xx < cols; ++xx) 166 | total += mat[yy][xx]; 167 | } 168 | 169 | return total; 170 | } 171 | 172 | double sumMatDPtrPtr(const int rows, const int cols, const double** mat) 173 | { 174 | double total = 0; 175 | 176 | for (int yy = 0; yy < rows; ++yy) 177 | { 178 | for (int xx = 0; xx < cols; ++xx) 179 | total += mat[yy][xx]; 180 | } 181 | return total; 182 | } 183 | 184 | 185 | float sumMatF(int rows, int cols, float mat[rows][cols]) 186 | { 187 | float total = 0; 188 | 189 | for (int yy = 0; yy < rows; ++yy) 190 | { 191 | for (int xx = 0; xx < cols; ++xx) 192 | total += mat[yy][xx]; 193 | } 194 | 195 | return total; 196 | } 197 | 198 | float sumMatFPtrPtr(const int rows, const int cols, const float** mat) 199 | { 200 | float total = 0; 201 | 202 | for (int yy = 0; yy < rows; ++yy) 203 | { 204 | for (int xx = 0; xx < cols; ++xx) 205 | total += mat[yy][xx]; 206 | } 207 | return total; 208 | } 209 | 210 | long long sumMatL(int rows, int cols, long long mat[rows][cols]) 211 | { 212 | long long total = 0; 213 | 214 | for (int yy = 0; yy < rows; ++yy) 215 | { 216 | for (int xx = 0; xx < cols; ++xx) 217 | total += mat[yy][xx]; 218 | } 219 | 220 | return total; 221 | } 222 | 223 | long long sumMatLPtrPtr(const int rows, const int cols, const long long** mat) 224 | { 225 | long long total = 0; 226 | 227 | for (int yy = 0; yy < rows; ++yy) 228 | { 229 | for (int xx = 0; xx < cols; ++xx) 230 | total += mat[yy][xx]; 231 | } 232 | return total; 233 | } 234 | 235 | int sumMatI(int rows, int cols, int mat[rows][cols]) 236 | { 237 | int total = 0; 238 | 239 | for (int yy = 0; yy < rows; ++yy) 240 | { 241 | for (int xx = 0; xx < cols; ++xx) 242 | total += mat[yy][xx]; 243 | } 244 | 245 | return total; 246 | } 247 | 248 | int sumMatIPtrPtr(const int rows, const int cols, const int** mat) 249 | { 250 | int total = 0; 251 | 252 | for (int yy = 0; yy < rows; ++yy) 253 | { 254 | for (int xx = 0; xx < cols; ++xx) 255 | total += mat[yy][xx]; 256 | } 257 | return total; 258 | } 259 | 260 | int sumMatS(int rows, int cols, short mat[rows][cols]) 261 | { 262 | int total = 0; 263 | 264 | for (int yy = 0; yy < rows; ++yy) 265 | { 266 | for (int xx = 0; xx < cols; ++xx) 267 | total += mat[yy][xx]; 268 | } 269 | 270 | return total; 271 | } 272 | 273 | int sumMatSPtrPtr(const int rows, const int cols, const short ** mat) 274 | { 275 | int total = 0; 276 | 277 | for (int yy = 0; yy < rows; ++yy) 278 | { 279 | for (int xx = 0; xx < cols; ++xx) 280 | total += mat[yy][xx]; 281 | } 282 | return total; 283 | } 284 | 285 | 286 | int sumMatB(int rows, int cols, char mat[rows][cols]) 287 | { 288 | int total = 0; 289 | 290 | for (int yy = 0; yy < rows; ++yy) 291 | { 292 | for (int xx = 0; xx < cols; ++xx) 293 | total += mat[yy][xx]; 294 | } 295 | 296 | return total; 297 | } 298 | 299 | int sumMatBPtrPtr(const int rows, const int cols, const char ** mat) 300 | { 301 | int total = 0; 302 | 303 | for (int yy = 0; yy < rows; ++yy) 304 | { 305 | for (int xx = 0; xx < cols; ++xx) 306 | total += mat[yy][xx]; 307 | } 308 | return total; 309 | } 310 | 311 | int cstringLength(const char* string) 312 | { 313 | return strlen(string); 314 | } 315 | 316 | char* mallocString(const char* origString) 317 | { 318 | if (origString == NULL) 319 | return NULL; 320 | 321 | char* ret = malloc(strlen(origString) * sizeof(char)); 322 | strcpy(ret, origString); 323 | return ret; 324 | } 325 | 326 | double* mallocDoubles(const int count) 327 | { 328 | if (count <= 0) 329 | return NULL; 330 | 331 | double* ret = malloc(count *sizeof(double )); 332 | 333 | for (int n = 0; n < count; ++n) 334 | ret[n] = (double)n; 335 | 336 | return ret; 337 | } 338 | 339 | void freeMemory(void *memory) 340 | { 341 | free(memory); 342 | } 343 | 344 | double passStruct(struct PassingData* data) 345 | { 346 | double ret = 0; 347 | ret += (double)data->s_long; 348 | ret += data->s_float; 349 | ret += data->s_int; 350 | ret += data->s_double; 351 | 352 | return ret; 353 | } 354 | 355 | double passComplex(struct ComplexPassing* complex) 356 | { 357 | double ret = passStruct(&complex->s_passingData); 358 | ret += passStruct(complex->s_ptrPassingData); 359 | 360 | int len = strlen(complex->s_string); 361 | for (int n = 0; n < len; ++n) 362 | complex->s_string[n] -= 32; 363 | 364 | complex->s_ID += 10; 365 | complex->s_passingData.s_int += 10; 366 | complex->s_ptrPassingData->s_int +=20; 367 | return ret; 368 | } 369 | 370 | double passStructWithArrays(struct PassingArrays* structWithArrays) 371 | { 372 | double ret = 0; 373 | // printf("Size = %lld\n", sizeof(struct PassingArrays)); 374 | int count = sizeof(structWithArrays->s_double)/sizeof(double); 375 | for (int n = 0; n < count; ++n) 376 | { 377 | ret += structWithArrays->s_double[n]; 378 | // printf("double [%d] = %f (%f)\n", n, structWithArrays->s_double[n], ret); 379 | } 380 | 381 | count = sizeof(structWithArrays->s_long)/sizeof(long long); 382 | for (int n = 0; n < count; ++n) 383 | { 384 | ret += structWithArrays->s_long[n]; 385 | // printf("long [%d] = %lld (%f)\n", n, structWithArrays->s_long[n], ret); 386 | } 387 | 388 | for (int n = 0; n < structWithArrays->s_doublePtrCount; ++n) 389 | { 390 | ret += structWithArrays->s_doublePtr[n]; 391 | // printf("double Ptr[%d] = %f (%f)\n", n, structWithArrays->s_doublePtr[n], ret); 392 | 393 | if (n < count) 394 | structWithArrays->s_double[n] = structWithArrays->s_doublePtr[n]; 395 | } 396 | 397 | 398 | for (int n = 0; n < structWithArrays->s_longPtrCount; ++n) 399 | { 400 | ret += structWithArrays->s_longPtr[n]; 401 | // printf("long Ptr[%d] = %lld (%f)\n", n, structWithArrays->s_longPtr[n], ret); 402 | 403 | if (n < count) 404 | structWithArrays->s_longPtr[n] = structWithArrays->s_long[n]; 405 | } 406 | 407 | return ret; 408 | } 409 | 410 | int call_CB(callbackFN fn, int v, double v2) 411 | { 412 | int sum = 0; 413 | for (int n = 0; n < v; n++) 414 | sum += fn(v, v2); 415 | return sum; 416 | } 417 | 418 | 419 | void call_CBArr(callbackFNArr fn, int* vals, int count) 420 | { 421 | fn(vals, count); 422 | } 423 | 424 | extern int fillChars(char* fillThis, int sizemax) 425 | { 426 | strncpy(fillThis, "hello world", sizemax); 427 | return strlen(fillThis); 428 | } 429 | 430 | extern int passChars(char* fillThis, int sizemax) 431 | { 432 | int s = 0; 433 | for (int n = 0; n < sizemax; ++n) 434 | s += fillThis[n]; 435 | 436 | return s; 437 | } -------------------------------------------------------------------------------- /fl_dll/library.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | #ifndef FL_DLL_LIBRARY_H 13 | #define FL_DLL_LIBRARY_H 14 | 15 | extern double sumD(double d1, double d2); 16 | extern double sumArrD(const double *arr, int count); 17 | extern double sumArrDD(const double *arr, const double *arr2, int count); 18 | extern void readD(double *v, int set); 19 | extern double sumMatD(int rows, int cols, double mat[rows][cols]); 20 | extern double sumMatDPtrPtr(int rows, int cols, const double** mat); 21 | 22 | extern float sumArrF(const float *arr, int count); 23 | extern void readF(float *val, float set); 24 | extern float sumMatF(int rows, int cols, float mat[rows][cols]); 25 | extern float sumMatFPtrPtr(int rows, int cols, const float** mat); 26 | 27 | extern long long sumArrL(const long long *arr, long long count); 28 | extern void readL(long long *val, long long set); 29 | extern long long sumMatL(int rows, int cols, long long mat[rows][cols]); 30 | extern long long sumMatLPtrPtr(int rows, int cols, const long long** mat); 31 | 32 | extern int sumArrI(const int *arr, int count); 33 | extern void readI(int *val, int set); 34 | extern int sumMatI(int rows, int cols, int mat[rows][cols]); 35 | extern int sumMatIPtrPtr(int rows, int cols, const int** mat); 36 | 37 | extern short sumArrS(const short *arr, short count); 38 | extern void readS(short *val, short set); 39 | extern int sumMatS(int rows, int cols, short mat[rows][cols]); 40 | extern int sumMatSPtrPtr(int rows, int cols, const short ** mat); 41 | 42 | extern char sumArrB(const char *arr, char count); 43 | extern void readB(char *val, char set); 44 | extern int sumMatB(int rows, int cols, char mat[rows][cols]); 45 | extern int sumMatBPtrPtr(int rows, int cols, const char ** mat); 46 | 47 | extern int cstringLength(const char* string); 48 | extern char* mallocString(const char* origString); 49 | extern double* mallocDoubles(int count); 50 | extern void freeMemory(void *memory); 51 | 52 | extern void readPointer(long long *val, long long set); 53 | extern long long getPointer(long long *val, long long set); 54 | 55 | extern int fillChars(char* fillThis, int sizemax); 56 | extern int passChars(char* fillThis, int sizemax); 57 | 58 | struct PassingData 59 | { 60 | int s_int; 61 | long long s_long; 62 | float s_float; 63 | double s_double; 64 | }; 65 | 66 | struct ComplexPassing 67 | { 68 | int s_ID; 69 | struct PassingData s_passingData; 70 | struct PassingData* s_ptrPassingData; 71 | char* s_string; 72 | }; 73 | 74 | struct PassingArrays 75 | { 76 | double s_double[5]; 77 | long long s_long[8]; 78 | long long s_doublePtrCount; 79 | double* s_doublePtr; 80 | long long s_longPtrCount; 81 | long long* s_longPtr; 82 | }; 83 | 84 | extern double passStruct(struct PassingData* data); 85 | extern double passComplex(struct ComplexPassing* complex); 86 | extern double passStructWithArrays(struct PassingArrays* structWithArrays); 87 | 88 | typedef int (*callbackFN) (int, double); 89 | extern int call_CB(callbackFN fn, int, double); 90 | 91 | typedef int (*callbackFNArr) (int*, int); 92 | extern void call_CBArr(callbackFNArr fn, int*, int); 93 | 94 | #endif //FL_DLL_LIBRARY_H 95 | -------------------------------------------------------------------------------- /libpassport_test.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boulder-on/JPassport/d6ea587efbc37ba011ad29ca513cc8d887a0f601/libpassport_test.dll -------------------------------------------------------------------------------- /libpassport_test.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boulder-on/JPassport/d6ea587efbc37ba011ad29ca513cc8d887a0f601/libpassport_test.so -------------------------------------------------------------------------------- /passing_double_arr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boulder-on/JPassport/d6ea587efbc37ba011ad29ca513cc8d887a0f601/passing_double_arr.png -------------------------------------------------------------------------------- /passing_doubles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boulder-on/JPassport/d6ea587efbc37ba011ad29ca513cc8d887a0f601/passing_doubles.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.github.boulder-on 8 | JPassport 9 | 1.0.1-22 10 | 11 | jar 12 | A Java library for interfacing with the foreign linker API. 13 | https://github.com/boulder-on/JPassport#readme 14 | 15 | ${project.groupId}:${project.artifactId} 16 | 17 | 18 | The Apache License, Version 2.0 19 | http://www.apache.org/licenses/LICENSE-2.0.txt 20 | 21 | 22 | 23 | 24 | 25 | Duncan McLean 26 | duncan.mclean@gmail.com 27 | Duncan McLean 28 | https://github.com/boulder-on 29 | 30 | 31 | 32 | scm:git:git://github.com/boulder-on/JPassport 33 | scm:git:ssh://github.com/boulder-on/JPassport 34 | http://github.com/boulder-on/JPassport 35 | 36 | 37 | 23 38 | 23 39 | 40 | 41 | 42 | 43 | 44 | org.junit.jupiter 45 | junit-jupiter-api 46 | 5.4.2 47 | 48 | 49 | org.junit.jupiter 50 | junit-jupiter-engine 51 | 5.4.2 52 | 53 | 54 | net.java.dev.jna 55 | jna-platform 56 | 5.8.0 57 | 58 | 59 | net.java.dev.jna 60 | jna 61 | 5.8.0 62 | 63 | 64 | org.openjdk.jmh 65 | jmh-core 66 | 1.32 67 | 68 | 69 | org.openjdk.jmh 70 | jmh-generator-annprocess 71 | 1.32 72 | 73 | 74 | org.apache.commons 75 | commons-csv 76 | 1.8 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | org.apache.maven.plugins 85 | maven-compiler-plugin 86 | 3.8.1 87 | 88 | 23 89 | --enable-preview 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-surefire-plugin 96 | 3.0.0-M5 97 | 98 | 99 | --enable-native-access jpassport --enable-preview 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | central 137 | file://tmp 138 | 139 | 140 | central 141 | 142 | file://tmp 143 | 144 | 145 | 146 | 147 | release-sign-artifacts 148 | 149 | 150 | performRelease 151 | true 152 | 153 | 154 | 155 | 156 | 157 | org.apache.maven.plugins 158 | maven-javadoc-plugin 159 | 160 | 161 | 162 | attach-javadocs 163 | package 164 | 165 | jar 166 | 167 | 168 | 169 | 170 | 171 | org.apache.maven.plugins 172 | maven-source-plugin 173 | 2.2.1 174 | 175 | 176 | attach-sources 177 | package 178 | 179 | jar-no-fork 180 | 181 | 182 | 183 | 184 | 185 | 186 | org.apache.maven.plugins 187 | maven-gpg-plugin 188 | 3.1.0 189 | 190 | 191 | sign-artifacts 192 | verify 193 | 194 | sign 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | -------------------------------------------------------------------------------- /src/main/java/jpassport/FunctionPtr.java: -------------------------------------------------------------------------------- 1 | package jpassport; 2 | 3 | import java.lang.foreign.Arena; 4 | import java.lang.foreign.MemorySegment; 5 | 6 | /** 7 | * If you make a callback function, this will be the class returned. Since this 8 | * derives from GenericPointer, you can pass it to a method. 9 | */ 10 | public class FunctionPtr extends GenericPointer { 11 | 12 | private final Arena arena; //The arena that was used to allocate the function pointer 13 | 14 | /** 15 | * 16 | * @param arena The arena used to allocate the function pointer 17 | * @param ptr The function pointer 18 | */ 19 | FunctionPtr(Arena arena, MemorySegment ptr) 20 | { 21 | super(ptr); 22 | this.arena = arena; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/jpassport/GenericPointer.java: -------------------------------------------------------------------------------- 1 | package jpassport; 2 | 3 | 4 | import java.lang.foreign.MemorySegment; 5 | 6 | /** 7 | * This class should be extended if you have a method that works with an opaque pointer 8 | * class. You could just declare that your interface returns a MemoryAddress, but this 9 | * allows you to give a type to the address, which aids in readability. 10 | * ex. 11 | *
12 |  * C
13 |  * GATEWAY* openGateway();
14 |  * void closeGateway(GATEWAY* g);
15 |  *
16 |  * Java
17 |  * class Gateway extends GenericPointer
18 |  * {}
19 |  *
20 |  * interface MyGateway
21 |  * {
22 |  *     Gateway openGateway();
23 |  *     void closeGateway(Gateway g);
24 |  * }
25 |  * 
26 | */ 27 | public class GenericPointer { 28 | protected MemorySegment ptr; 29 | 30 | public GenericPointer(MemorySegment addr) 31 | { 32 | ptr = addr; 33 | } 34 | 35 | public MemorySegment getPtr() 36 | { 37 | return ptr; 38 | } 39 | 40 | public boolean isNull() 41 | { 42 | return ptr.equals(MemorySegment.NULL); 43 | } 44 | 45 | /** 46 | * A convenience method for a NULL value. 47 | */ 48 | public static GenericPointer NULL() { 49 | return new GenericPointer(MemorySegment.NULL); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/jpassport/MemoryBlock.java: -------------------------------------------------------------------------------- 1 | package jpassport; 2 | 3 | import java.lang.foreign.Arena; 4 | import java.lang.foreign.MemorySegment; 5 | 6 | /** 7 | * A very common idiom in C is to pass a pointer to some allocated memory 8 | * and the function fills the memory. This class is used to handle this 9 | * for strings specifically. You can pass char[] if you want, but the 10 | * C function then needs to handle 2 byte chars. 11 | * NOTE: this class is always assumed to be a RefArg, i.e. it is always 12 | * read back after the native call. 13 | */ 14 | public class MemoryBlock { 15 | final private long sizeInBytes; 16 | private MemorySegment ptr = null; 17 | private String readBack = null; 18 | private byte[] buffer = null; 19 | 20 | public MemoryBlock(long bytes) 21 | { 22 | sizeInBytes = bytes; 23 | } 24 | 25 | public long size() 26 | { 27 | return sizeInBytes; 28 | } 29 | 30 | public MemorySegment toPtr(Arena scope) 31 | { 32 | if (ptr == null) 33 | ptr = scope.allocate(sizeInBytes); 34 | return ptr; 35 | } 36 | 37 | public void readBack() 38 | { 39 | if (ptr != null) 40 | { 41 | String[] args = new String[1]; 42 | args[0] = Utils.readString(ptr); 43 | readBack = args[0]; 44 | 45 | var bb = ptr.asByteBuffer(); 46 | buffer = new byte[bb.limit()]; 47 | bb.get(buffer, 0, buffer.length); 48 | } 49 | } 50 | 51 | public byte[] getBytes() 52 | { 53 | return buffer; 54 | } 55 | 56 | public void setString(String testing_only) 57 | { 58 | readBack = testing_only; 59 | } 60 | 61 | public String toString() 62 | { 63 | if (readBack != null) 64 | return readBack; 65 | return "No memory allocated"; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/jpassport/NamedLookup.java: -------------------------------------------------------------------------------- 1 | package jpassport; 2 | 3 | import java.lang.foreign.MemorySegment; 4 | 5 | /** 6 | * Variables of this type declared in your interface will be loaded by the SymbolLookup. 7 | * 8 | *
 9 |  *     public interface LoadNames extends Passport
10 |  *     {
11 |  *        NamedLookup loadedName = new NamedLookup("LoadName");
12 |  *     }
13 |  * 
14 | * 15 | * The above code would load LoadName from the native library as a pointer. 16 | */ 17 | public class NamedLookup { 18 | final private String name; 19 | private MemorySegment addr; 20 | 21 | public NamedLookup(String name) 22 | { 23 | this.name = name; 24 | } 25 | 26 | protected void setAddress(MemorySegment addr) 27 | { 28 | this.addr = addr; 29 | } 30 | 31 | public MemorySegment addr() 32 | { 33 | return addr; 34 | } 35 | 36 | public String name() 37 | { 38 | return name; 39 | } 40 | 41 | public boolean equals(GenericPointer ptr) 42 | { 43 | return addr.equals(ptr.getPtr()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/jpassport/Passport.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport; 13 | 14 | import java.lang.foreign.MemorySegment; 15 | import java.lang.invoke.MethodHandle; 16 | import java.util.HashMap; 17 | 18 | /** 19 | * An interface needs to extend this interface in order to link to the foreign library. 20 | */ 21 | public interface Passport { 22 | HashMap m_methods = new HashMap<>(); 23 | HashMap m_loadedNames = new HashMap<>(); 24 | 25 | /** 26 | * Lets you know if a specific method was found or not. Generally, all methods must be found 27 | * when loading the library. However, if some methods use the @NotRequired annotation 28 | * then those methods may not be present at runtime. 29 | * 30 | * @param name The exact name of a method in your interface. 31 | * @return True if the native link was made, false if it was not. If this returns false 32 | * then a call to the interface method in question is going to throw a java.lang.Error. 33 | */ 34 | default boolean hasMethod(String name) 35 | { 36 | return m_methods.containsKey(name); 37 | } 38 | 39 | default boolean hasName(String name) 40 | { 41 | return m_loadedNames.containsKey(name); 42 | } 43 | 44 | default Object readStruct(MemorySegment segment, Object rec) 45 | { 46 | throw new RuntimeException("Not implemented"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/jpassport/PassportBuilder.java: -------------------------------------------------------------------------------- 1 | package jpassport; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.lang.classfile.*; 7 | import java.lang.classfile.constantpool.*; 8 | import java.lang.constant.ClassDesc; 9 | import java.lang.constant.ConstantDesc; 10 | import java.lang.constant.ConstantDescs; 11 | import java.lang.constant.MethodTypeDesc; 12 | import java.lang.foreign.Arena; 13 | import java.lang.invoke.MethodHandle; 14 | import java.lang.invoke.MethodHandles; 15 | import java.lang.reflect.Method; 16 | import java.util.*; 17 | import java.util.function.Consumer; 18 | 19 | public class PassportBuilder extends ClassLoader{ 20 | 21 | private String fullName; 22 | private byte[] classBytes; 23 | 24 | private static int Class_ID = 1; //Used to make unique package names 25 | 26 | public PassportBuilder(Class interfaceClass) 27 | { 28 | this(interfaceClass, "jpassport.called_" + Class_ID++, interfaceClass.getSimpleName() + "_impl"); 29 | } 30 | 31 | ClassDesc thisClassDesc; 32 | private HashMap methodHandles = new HashMap<>(); 33 | 34 | public PassportBuilder(Class interfaceClass, String packageName, String className) 35 | { 36 | thisClassDesc = ClassDesc.of(packageName, className); 37 | List interfaceMethods = PassportFactory.getDeclaredMethods(interfaceClass); 38 | 39 | classBytes = ClassFile.of().build(thisClassDesc, clb -> 40 | { 41 | var entry = ConstantPoolBuilder.of().classEntry(ClassDesc.of("jpassport", "Passport")); 42 | var mainInterface = ConstantPoolBuilder.of().classEntry(ClassDesc.of(interfaceClass.getName())); 43 | clb.withInterfaces(entry, mainInterface); 44 | clb.withFlags(ClassFile.ACC_PUBLIC); 45 | 46 | var classDescHM = ClassDesc.ofInternalName("java/util/HashMap"); 47 | var descMap = ClassDesc.ofInternalName("java/util/Map"); 48 | clb.withField("methods", classDescHM, ClassFile.ACC_PUBLIC); 49 | 50 | var nte = clb.constantPool().nameAndTypeEntry("methods", classDescHM); 51 | var methodsfield = clb.constantPool().fieldRefEntry(clb.constantPool().classEntry(thisClassDesc), nte); 52 | 53 | 54 | var desc = MethodTypeDesc.of(ConstantDescs.CD_void, classDescHM); 55 | var descputAll = MethodTypeDesc.of(ConstantDescs.CD_void, descMap); 56 | 57 | clb.withMethod(ConstantDescs.INIT_NAME, desc, 58 | ClassFile.ACC_PUBLIC, mb -> mb.withCode( 59 | 60 | // ** call Object. 61 | cob -> cob.aload(0) 62 | .invokespecial(ConstantDescs.CD_Object, 63 | ConstantDescs.INIT_NAME, ConstantDescs.MTD_void) 64 | // **done Object. 65 | .aload(0) 66 | .new_(classDescHM) 67 | .dup() 68 | .invokespecial(classDescHM, ConstantDescs.INIT_NAME, ConstantDescs.MTD_void, false) 69 | .putfield(methodsfield) 70 | .aload(0) 71 | .getfield(methodsfield) 72 | .aload(1) 73 | .invokevirtual(classDescHM, "putAll", descputAll) 74 | .aload(0) 75 | .invokevirtual(thisClassDesc, "init", ConstantDescs.MTD_void) 76 | .return_())); 77 | 78 | var methodTypeDesc = ClassDesc.ofInternalName("java/lang/invoke/MethodHandle"); 79 | for (Method m : interfaceMethods) 80 | { 81 | String fieldName = "m_" + m.getName(); 82 | clb.withField(fieldName, methodTypeDesc, ClassFile.ACC_PRIVATE); 83 | nte = clb.constantPool().nameAndTypeEntry(fieldName, methodTypeDesc); 84 | var fieldRefEntry = clb.constantPool().fieldRefEntry(clb.constantPool().classEntry(thisClassDesc), nte); 85 | methodHandles.put(fieldName, fieldRefEntry); 86 | } 87 | 88 | var cdObject = ClassDesc.ofInternalName("java/lang/Object"); 89 | clb.withMethod("init", MethodTypeDesc.of(ConstantDescs.CD_void), 90 | ClassFile.ACC_PUBLIC, mb -> mb.withCode( 91 | cob -> { 92 | var start = cob.newBoundLabel(); 93 | 94 | // cob.labelBinding(start); 95 | for (String name : methodHandles.keySet()) 96 | { 97 | String fieldName = "m_" + name; 98 | cob.ldc(name.substring(2)) // clip off "m_" 99 | .astore(1) 100 | .aload(0) 101 | .aload(0) 102 | .getfield(methodsfield) 103 | .aload(1) 104 | .invokevirtual(classDescHM, "get", MethodTypeDesc.of(cdObject, cdObject)) 105 | .typeCheckInstruction(Opcode.CHECKCAST, ClassDesc.ofInternalName("java/lang/invoke/MethodHandle")) 106 | .putfield(methodHandles.get(name)); 107 | } 108 | var end = cob.endLabel(); 109 | cob.localVariable(1, "key", ClassDesc.ofInternalName("java/lang/String"), start, end); 110 | cob.return_(); 111 | } 112 | )); 113 | 114 | for (Method m : interfaceMethods) 115 | { 116 | if (needsArena(m)) 117 | addMethodWithArena(clb, m); 118 | else 119 | addMethod(clb, m); 120 | } 121 | } 122 | ); 123 | 124 | fullName = packageName + "." + className; 125 | } 126 | 127 | private boolean needsArena(Method m) 128 | { 129 | var arena = Arena.ofConfined().getClass(); 130 | return Arrays.stream(m.getParameterTypes()).anyMatch(c -> c.isArray() || c.isRecord() || c.equals(arena)); 131 | } 132 | 133 | public T build(Map methods) throws Throwable 134 | { 135 | Class foreignImpl = (Class)defineClass(fullName, classBytes, 0, classBytes.length); 136 | 137 | return foreignImpl.getDeclaredConstructor(methods.getClass()).newInstance(methods); 138 | } 139 | 140 | private ClassBuilder addMethod(ClassBuilder clb, Method iMethod) 141 | { 142 | var params = Arrays.stream(iMethod.getParameterTypes()).map(PassportBuilder::toDesc).toList(); 143 | var methodSig = MethodTypeDesc.of(toDesc(iMethod.getReturnType()), params); 144 | var methodTypeDesc = ClassDesc.ofInternalName("java/lang/invoke/MethodHandle"); 145 | clb.withMethod(iMethod.getName(), methodSig, ClassFile.ACC_PUBLIC, 146 | mb -> mb.withCode(cob -> { 147 | List keepers = classifyParams(cob, iMethod); 148 | var start = cob.newLabel(); 149 | cob.labelBinding(start); 150 | 151 | cob.aload(0) 152 | .getfield(methodHandles.get("m_" + iMethod.getName())); 153 | int idx = 0; 154 | for (var k : keepers) 155 | k.loadParam(cob); 156 | 157 | cob.invokevirtual(methodTypeDesc, "invokeExact", methodSig ); 158 | storeParam(cob, idx, iMethod.getReturnType()); 159 | loadParam(cob, idx, iMethod.getReturnType()); 160 | returnParam(cob, iMethod.getReturnType()); 161 | var error = ClassDesc.ofInternalName("java/lang/Error"); 162 | var end = cob.newLabel(); 163 | cob.labelBinding(end); 164 | var handler = cob.newLabel(); 165 | cob.labelBinding(handler) 166 | .astore(5) 167 | .new_(error) 168 | .dup() 169 | .aload(5) 170 | .invokespecial(error, ConstantDescs.INIT_NAME, MethodTypeDesc.of(ConstantDescs.CD_void, ClassDesc.ofInternalName("java/lang/Throwable")), false) 171 | .throwInstruction(); 172 | var handlerEnd = cob.newLabel(); 173 | cob.labelBinding(handlerEnd); 174 | cob.exceptionCatchAll(start, end, handler); 175 | } 176 | )); 177 | return clb; 178 | } 179 | 180 | enum ParamType 181 | { 182 | doubleType(2), longType(2), 183 | floatType(1), 184 | intType(1), shortType(1), byteType(1), charType(1), 185 | addressType(1); 186 | 187 | int slot_count = 1; 188 | 189 | ParamType(int slots) 190 | { 191 | slot_count = slots; 192 | } 193 | } 194 | 195 | static class ParamKeeper 196 | { 197 | ParamType type; 198 | int stored; 199 | 200 | ParamKeeper(ParamType t, int slot) 201 | { 202 | type = t; 203 | stored = slot; 204 | } 205 | 206 | public String toString() 207 | { 208 | return type + "(" + stored + ")"; 209 | } 210 | 211 | void loadParam(CodeBuilder cob) 212 | { 213 | switch(type) 214 | { 215 | case doubleType: 216 | cob.dload(stored); 217 | break; 218 | case longType: 219 | cob.lload(stored); 220 | break; 221 | case floatType: 222 | cob.fload(stored); 223 | break; 224 | case intType: 225 | cob.iload(stored); 226 | break; 227 | case shortType: 228 | cob.iload(stored); 229 | break; 230 | case addressType: 231 | cob.aload(stored); 232 | break; 233 | } 234 | } 235 | } 236 | 237 | private List classifyParams(CodeBuilder cob, Method iMethod) 238 | { 239 | ArrayList keepers = new ArrayList<>(); 240 | int slot = 0; 241 | for (Class c : iMethod.getParameterTypes()) 242 | { 243 | if (c.equals(double.class)) 244 | keepers.add(new ParamKeeper(ParamType.doubleType, cob.parameterSlot(slot))); 245 | else if (c.equals(long.class)) 246 | keepers.add(new ParamKeeper(ParamType.longType, cob.parameterSlot(slot))); 247 | else if (c.equals(float.class)) 248 | keepers.add(new ParamKeeper(ParamType.floatType, cob.parameterSlot(slot))); 249 | else if (c.equals(int.class)) 250 | keepers.add(new ParamKeeper(ParamType.intType, cob.parameterSlot(slot))); 251 | else if (c.equals(short.class)) 252 | keepers.add(new ParamKeeper(ParamType.shortType, cob.parameterSlot(slot))); 253 | else 254 | keepers.add(new ParamKeeper(ParamType.addressType, cob.parameterSlot(slot))); 255 | slot++; 256 | } 257 | return keepers; 258 | } 259 | 260 | private ClassBuilder addMethodWithArena(ClassBuilder clb, Method iMethod) 261 | { 262 | 263 | var params = Arrays.stream(iMethod.getParameterTypes()).map(PassportBuilder::toDesc).toList(); 264 | var paramsVirt = Arrays.stream(iMethod.getParameterTypes()).map(PassportBuilder::toDescVirt).toList(); 265 | var methodSig = MethodTypeDesc.of(toDesc(iMethod.getReturnType()), params); 266 | var methodSigVirt = MethodTypeDesc.of(toDesc(iMethod.getReturnType()), paramsVirt); 267 | var methodTypeDesc = ClassDesc.ofInternalName("java/lang/invoke/MethodHandle"); 268 | var arenaDesc = ClassDesc.ofInternalName("java/lang/foreign/Arena"); 269 | var MemorySegment = ClassDesc.ofInternalName("java/lang/foreign/MemorySegment"); 270 | var error = ClassDesc.ofInternalName("java/lang/Error"); 271 | 272 | clb.withMethod(iMethod.getName(), methodSig, ClassFile.ACC_PUBLIC, 273 | mb -> mb.withCode(cob -> { 274 | List keepers = classifyParams(cob, iMethod); 275 | int used = Arrays.stream(iMethod.getParameterTypes()).mapToInt(this::paramSize).sum(); 276 | used += 1; 277 | int arenaSlot = used++; 278 | var start = cob.newLabel(); 279 | cob.labelBinding(start); 280 | var iv = getCodeTemplate(); 281 | if (iv.isPresent()) 282 | cob.accept(iv.get()); 283 | cob.astore(arenaSlot); 284 | var startAutoClose = cob.newLabel(); 285 | cob.labelBinding(startAutoClose); 286 | 287 | int ii = -1; 288 | for (Class t : iMethod.getParameterTypes()) { 289 | ii++; 290 | if (!t.isArray()) 291 | continue; 292 | 293 | cob.aload(arenaSlot).aload(keepers.get(ii).stored).iconst_0(); 294 | cob.invokestatic(ClassDesc.ofInternalName("jpassport/Utils"), "toMS", 295 | MethodTypeDesc.of(MemorySegment, 296 | arenaDesc, toDesc(t), ConstantDescs.CD_boolean)); 297 | used++; 298 | cob.astore(used); 299 | keepers.get(ii).stored = used; 300 | keepers.get(ii).type = ParamType.addressType; 301 | } 302 | cob.aload(0).getfield(methodHandles.get("m_" + iMethod.getName())); 303 | 304 | //after here is not updated 305 | for (ParamKeeper k : keepers) 306 | { 307 | if (k.type == ParamType.addressType) 308 | { 309 | var mtd = MethodTypeDesc.of(MemorySegment, MemorySegment); 310 | k.loadParam(cob); 311 | cob.invokestatic(ClassDesc.ofInternalName("jpassport/Utils"), "toAddr", mtd); 312 | used++; 313 | cob.astore(used).aload(used); 314 | k.stored = used; 315 | } 316 | else 317 | k.loadParam(cob); 318 | } 319 | 320 | cob.invokevirtual(methodTypeDesc, "invokeExact", methodSigVirt ); 321 | int newUsed = storeParam(cob, used, iMethod.getReturnType()); 322 | cob.aload(arenaSlot); 323 | cob.invokeinterface(ClassDesc.ofInternalName("java/lang/foreign/Arena"), "close", MethodTypeDesc.of(ConstantDescs.CD_void)); 324 | loadParam(cob, used, iMethod.getReturnType()); 325 | used = newUsed; 326 | returnParam(cob, iMethod.getReturnType()); 327 | var end = cob.newLabel(); 328 | cob.labelBinding(end); 329 | var handler = cob.newLabel(); 330 | cob.labelBinding(handler) 331 | .astore(5) 332 | .new_(error) 333 | .dup() 334 | .aload(5) 335 | .invokespecial(error, ConstantDescs.INIT_NAME, MethodTypeDesc.of(ConstantDescs.CD_void, ClassDesc.ofInternalName("java/lang/Throwable")), false) 336 | .throwInstruction(); 337 | var handlerEnd = cob.newLabel(); 338 | cob.labelBinding(handlerEnd); 339 | cob.exceptionCatchAll(start, end, handler); 340 | } 341 | )); 342 | return clb; 343 | } 344 | 345 | 346 | 347 | private int paramSize(Class c) 348 | { 349 | if (c.equals(double.class) || c.equals(long.class)) 350 | return 2; 351 | return 1; 352 | } 353 | 354 | private CodeBuilder loadParam(CodeBuilder cob, int idx, Class c) 355 | { 356 | if (c.equals(double.class)) 357 | cob.dload(idx); 358 | else if (c.equals(long.class)) 359 | cob.lload(idx); 360 | else if (c.equals(float.class)) 361 | cob.fload(idx); 362 | else if (c.equals(int.class) || c.equals(short.class)) 363 | cob.iload(idx ); 364 | else 365 | cob.aload(idx); 366 | return cob; 367 | } 368 | 369 | private int storeParam(CodeBuilder cob, int idx, Class c) 370 | { 371 | if (c.equals(double.class)) 372 | { 373 | cob.dstore(idx); 374 | return idx + 2; 375 | } 376 | else if (c.equals(long.class)) 377 | { 378 | cob.lstore(idx); 379 | return idx + 2; 380 | } 381 | else if (c.equals(float.class)) 382 | cob.fstore(idx); 383 | else if (c.equals(int.class) || c.equals(short.class)) 384 | cob.istore(idx); 385 | else 386 | cob.astore(idx); 387 | return idx+1; 388 | } 389 | 390 | private CodeBuilder returnParam(CodeBuilder cob, Class c) 391 | { 392 | if (c.equals(double.class)) 393 | cob.dreturn(); 394 | else if (c.equals(long.class)) 395 | cob.lreturn(); 396 | else if (c.equals(float.class)) 397 | cob.freturn(); 398 | else if (c.equals(int.class) || c.equals(short.class)) 399 | cob.ireturn(); 400 | else 401 | cob.areturn(); 402 | return cob; 403 | } 404 | 405 | public static ClassDesc toDesc(Class c) 406 | { 407 | if (c.isArray()) 408 | { 409 | var atype = c.getComponentType(); 410 | if (atype.equals(double.class)) 411 | return ConstantDescs.CD_double.arrayType(); 412 | 413 | } 414 | if (c.equals(double.class)) 415 | return ConstantDescs.CD_double; 416 | else if (c.equals(long.class)) 417 | return ConstantDescs.CD_long; 418 | else if (c.equals(int.class)) 419 | return ConstantDescs.CD_int; 420 | else if (c.equals(short.class)) 421 | return ConstantDescs.CD_short; 422 | else if (c.equals(byte.class)) 423 | return ConstantDescs.CD_byte; 424 | else if (c.equals(char.class)) 425 | return ConstantDescs.CD_char; 426 | return ClassDesc.of(c.getName()); 427 | } 428 | 429 | public static ClassDesc toDescVirt(Class c) 430 | { 431 | if (c.isArray()) 432 | { 433 | return ClassDesc.ofInternalName("java/lang/foreign/MemorySegment"); 434 | } 435 | if (c.equals(double.class)) 436 | return ConstantDescs.CD_double; 437 | else if (c.equals(long.class)) 438 | return ConstantDescs.CD_long; 439 | else if (c.equals(int.class)) 440 | return ConstantDescs.CD_int; 441 | else if (c.equals(short.class)) 442 | return ConstantDescs.CD_short; 443 | else if (c.equals(byte.class)) 444 | return ConstantDescs.CD_byte; 445 | else if (c.equals(char.class)) 446 | return ConstantDescs.CD_char; 447 | return ClassDesc.of(c.getName()); 448 | } 449 | 450 | private Optional getCodeTemplate() 451 | { 452 | ByteArrayOutputStream bout = new ByteArrayOutputStream(); 453 | try { 454 | InputStream in = PassportBuilder.class.getResourceAsStream("Utils.class"); 455 | byte data[] = new byte[4096]; 456 | int len; 457 | 458 | while ((len = in.read(data)) > 0) 459 | bout.write(data, 0, len); 460 | in.close(); 461 | } 462 | catch (IOException ex) 463 | { 464 | ex.printStackTrace(); 465 | } 466 | ClassModel cm = ClassFile.of().parse(bout.toByteArray()); 467 | 468 | for (var mm : cm.methods()) 469 | { 470 | if (!mm.methodName().stringValue().equals("templatedMethod")) 471 | continue; 472 | 473 | for (CodeElement ce : mm.code().get().elements()) 474 | { 475 | if (ce.toString().contains("ofConfined")) 476 | return Optional.of(ce); 477 | } 478 | } 479 | 480 | return Optional.empty(); 481 | } 482 | 483 | } 484 | -------------------------------------------------------------------------------- /src/main/java/jpassport/PassportException.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport; 13 | 14 | public class PassportException extends Error 15 | { 16 | public PassportException(String msg) { 17 | super(msg); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/jpassport/PassportFactory.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport; 13 | 14 | 15 | import jpassport.annotations.NotRequired; 16 | import jpassport.annotations.Critical; 17 | 18 | import java.io.File; 19 | import java.lang.foreign.*; 20 | import java.lang.invoke.MethodHandle; 21 | import java.lang.invoke.MethodHandles; 22 | import java.lang.invoke.MethodType; 23 | import java.lang.reflect.Field; 24 | import java.lang.reflect.Method; 25 | import java.lang.reflect.Modifier; 26 | import java.lang.reflect.Proxy; 27 | import java.util.*; 28 | 29 | public class PassportFactory 30 | { 31 | /** 32 | * Call this method to generate the library linkage. This version of the method will write the java file and compile 33 | * it. As a result, the start-up is a bit slower than {@link #proxy(String, Class) proxy()}, but the implementation 34 | * is a bit quicker. 35 | * 36 | *

This version also supports Record -> struct conversions. 37 | * 38 | * @param libraryName The library name (the file name of the shared library without extension on all platforms, 39 | * without lib prefix on Linux and Mac). Use null to load system method calls (ex. malloc) 40 | * @param interfaceClass The class to wrap. 41 | * @param Any interface that extends Passport 42 | * @return A class linked to call into a DLL or SO using the Foreign Linker. 43 | */ 44 | public synchronized static T link(String libraryName, Class interfaceClass) throws Throwable 45 | { 46 | if (!Passport.class.isAssignableFrom(interfaceClass)) { 47 | throw new IllegalArgumentException( 48 | String.format("Interface (%s) of library=%s does not extend %s", 49 | interfaceClass.getSimpleName(), libraryName, Passport.class.getSimpleName())); 50 | } else { 51 | return buildClass(libraryName, interfaceClass); 52 | } 53 | } 54 | 55 | /** 56 | * Call this method to generate the library linkage. This version of the method will write the java file and compile 57 | * it. As a result, the start-up is a bit slower than {@link #proxy(String, Class) proxy()}, but the implementation 58 | * is a bit quicker. 59 | * 60 | *

This version also supports Record -> struct conversions. 61 | * 62 | * @param libraryName The library name (the file name of the shared library without extension on all platforms, 63 | * without lib prefix on Linux and Mac). Use null to load system method calls (ex. malloc) 64 | * @param interfaceClass The class to wrap. 65 | * @param Any interface that extends Passport 66 | * @return A class linked to call into a DLL or SO using the Foreign Linker. 67 | */ 68 | public synchronized static T link_experimental(String libraryName, Class interfaceClass) throws Throwable 69 | { 70 | if (!Passport.class.isAssignableFrom(interfaceClass)) { 71 | throw new IllegalArgumentException( 72 | String.format("Interface (%s) of library=%s does not extend %s", 73 | interfaceClass.getSimpleName(), libraryName, Passport.class.getSimpleName())); 74 | } else { 75 | return buildClassExperimental(libraryName, interfaceClass); 76 | } 77 | } 78 | 79 | /** 80 | * Call this method to generate the library linkage. This version of the method uses a dynamic proxy to handle native 81 | * calls. As a result, the start-up is faster than {@link #link(String, Class) link()}, but the implementation is a bit slower. 82 | * 83 | *

This method does not support Record -> struct conversions.

84 | * 85 | * @param libraryName The library name (the file name of the shared library without extension on all platforms, 86 | * without lib prefix on Linux and Mac). Use null to load system method calls (ex. malloc) 87 | * @param interfaceClass The class to wrap. 88 | * @param Any interface that extends Passport 89 | * @return A class linked to call into a DLL or SO using the Foreign Linker. 90 | */ 91 | public static T proxy(String libraryName, Class interfaceClass) throws Throwable 92 | { 93 | if (!Passport.class.isAssignableFrom(interfaceClass)) { 94 | throw new IllegalArgumentException("Interface (" + interfaceClass.getSimpleName() + ") of library=" + libraryName + " does not extend " + Passport.class.getSimpleName()); 95 | } 96 | 97 | var methods = loadMethodHandles(libraryName, interfaceClass); 98 | var handler = new PassportInvocationHandler(methods, interfaceClass); 99 | return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), 100 | new Class[] { interfaceClass }, 101 | handler); 102 | } 103 | 104 | private static T buildClass(String libName, Class interfaceClass) throws Throwable 105 | { 106 | HashMap handles = loadMethodHandles(libName, interfaceClass); 107 | PassportWriter classWriter = new PassportWriter<>(interfaceClass); 108 | 109 | return classWriter.build(handles); 110 | } 111 | 112 | private static T buildClassExperimental(String libName, Class interfaceClass) throws Throwable { 113 | HashMap handles = loadMethodHandles(libName, interfaceClass); 114 | PassportBuilder classWriter = new PassportBuilder<>(interfaceClass); 115 | 116 | return classWriter.build(handles); 117 | } 118 | 119 | /** 120 | * This method looks up the methods in the requested native library that match non-static 121 | * methods in the given interface class. 122 | * 123 | * @param libName Name of the native library to load, of null if the methods will be system method (ex. malloc). 124 | * @param interfaceClass The interface class to use as a reference for loading methods. 125 | * @return A map of Name to method handle pairs for the methods in the interface class. 126 | */ 127 | public static HashMap loadMethodHandles(String libName, Class interfaceClass) 128 | { 129 | if (libName != null) { 130 | if (Utils.getPlatform().equals(Utils.Platform.Windows) && !libName.endsWith(".dll")) 131 | libName = libName + ".dll"; 132 | 133 | File libPath = new File(libName); 134 | System.load(libPath.getAbsolutePath()); 135 | } 136 | Linker cLinker = Linker.nativeLinker(); 137 | 138 | List interfaceMethods = getDeclaredMethods(interfaceClass); 139 | HashMap methodMap = new HashMap<>(); 140 | 141 | //if no library name is given then it must be a system library 142 | SymbolLookup lookup = libName == null ? cLinker.defaultLookup() : SymbolLookup.loaderLookup(); 143 | 144 | for (Method method : interfaceMethods) { 145 | Class retType = method.getReturnType(); 146 | Class[] parameters = method.getParameterTypes(); 147 | 148 | for (int n = 0; n < parameters.length; ++n) { 149 | if (!parameters[n].isPrimitive() && !Arena.class.equals(parameters[n])) 150 | parameters[n] = MemorySegment.class; 151 | } 152 | 153 | MemoryLayout[] memoryLayout = Arrays.stream(parameters).filter(p -> !Arena.class.equals(p)). 154 | map(PassportFactory::classToMemory).toArray(MemoryLayout[]::new); 155 | 156 | FunctionDescriptor fd; 157 | if (void.class.equals(retType)) 158 | fd = FunctionDescriptor.ofVoid(memoryLayout); 159 | else 160 | fd = FunctionDescriptor.of(classToMemory(retType), memoryLayout); 161 | 162 | var addr = lookup.find(method.getName()).orElse(null); 163 | if (addr == null && method.getAnnotation(NotRequired.class) == null) 164 | throw new PassportException("Could not find method in library: " + method.getName()); 165 | 166 | if (addr != null) { 167 | 168 | MethodHandle methodHandle; 169 | 170 | if (method.getAnnotation(Critical.class) == null) 171 | methodHandle = cLinker.downcallHandle(addr, fd); 172 | else 173 | methodHandle = cLinker.downcallHandle(addr, fd, Linker.Option.critical(false)); 174 | 175 | methodMap.put(method.getName(), methodHandle); 176 | } 177 | } 178 | loadNames(interfaceClass); 179 | return methodMap; 180 | } 181 | 182 | /** 183 | * This methods looks up all of the methods in the requested native library that match non-static 184 | * methods in the given interface class. 185 | * 186 | * @param interfaceClass The interface class to use as a reference for loading methods. 187 | */ 188 | private static void loadNames(Class interfaceClass) 189 | { 190 | List names = getDeclaredNames(interfaceClass); 191 | 192 | SymbolLookup lookup = SymbolLookup.loaderLookup(); 193 | 194 | for (Field field : names) { 195 | try { 196 | var named = ((NamedLookup)field.get(interfaceClass)); 197 | var addr = lookup.find(named.name()); 198 | if (addr.isEmpty() && field.getAnnotation(NotRequired.class) == null) 199 | throw new PassportException("Could not find field in library: " + named.name()); 200 | addr.ifPresent(named::setAddress); 201 | } catch (IllegalAccessException e) { 202 | throw new PassportException("Could not find field in library: " + field.getName()); 203 | } 204 | } 205 | } 206 | 207 | /** 208 | * Given an object and method name this will return a memory address that 209 | * corresponds to a method pointer that can be passed to native code. 210 | * The method cannot be static. 211 | * 212 | * @param ob The object that the method belongs to. 213 | * @param methodName The name of the method. 214 | * @return A pointer to a method handle that can be passed to native code. 215 | * @throws IllegalArgumentException if there is no method with the given name, or there is more 216 | * than 1 method with the given name. 217 | */ 218 | public static FunctionPtr createCallback(Object ob, String methodName) 219 | { 220 | var methods = getDeclaredMethods(ob.getClass()); 221 | 222 | methods = methods.stream().filter(m -> m.getName().equals(methodName)).toList(); 223 | if (methods.isEmpty()) 224 | throw new IllegalArgumentException("Could not find method " + methodName + " in class " + ob.getClass().getName()); 225 | else if (methods.size() > 1) 226 | throw new IllegalArgumentException("Multiple overloads of method " + methodName + " in class " + ob.getClass().getName()); 227 | 228 | Method callbackMethod = methods.get(0); 229 | 230 | Class retType = callbackMethod.getReturnType(); 231 | Class[] parameters = callbackMethod.getParameterTypes(); 232 | 233 | if (!retType.isPrimitive() && retType != MemorySegment.class) 234 | throw new IllegalArgumentException("Callback method must return void, primitives, or MemoryAddress, not " + retType.getName()); 235 | 236 | 237 | for (Class parameter : parameters) { 238 | if (!parameter.isPrimitive() && parameter != MemorySegment.class) 239 | throw new IllegalArgumentException("Callback parameters must be primitives or MemoryAddress, not " + parameter.getName()); 240 | } 241 | 242 | MemoryLayout[] memoryLayout = Arrays.stream(parameters).map(PassportFactory::classToMemory).toArray(MemoryLayout[]::new); 243 | FunctionDescriptor fd; 244 | if (void.class.equals(retType)) 245 | fd = FunctionDescriptor.ofVoid(memoryLayout); 246 | else 247 | fd = FunctionDescriptor.of(classToMemory(retType), memoryLayout); 248 | 249 | try { 250 | var handle = MethodHandles.publicLookup().findVirtual(ob.getClass(), methodName, MethodType.methodType(retType, parameters)); 251 | var handleToCall = handle.bindTo(ob); 252 | 253 | var scope = Arena.ofAuto(); 254 | return new FunctionPtr(scope, Linker.nativeLinker().upcallStub(handleToCall, fd, scope)); 255 | } 256 | catch (NoSuchMethodException | IllegalAccessException ex) 257 | { 258 | throw new Error("Failed to create callback method", ex); 259 | } 260 | } 261 | 262 | static List getDeclaredMethods(Class interfaceClass) { 263 | Method[] methods = interfaceClass.getDeclaredMethods(); 264 | return Arrays.stream(methods). 265 | filter(method -> !Modifier.isStatic(method.getModifiers())). 266 | filter(method -> !method.isDefault()).toList(); 267 | } 268 | 269 | static List getDeclaredNames(Class interfaceClass) { 270 | Field[] fields = interfaceClass.getDeclaredFields(); 271 | return Arrays.stream(fields). 272 | filter(field -> field.getType().equals(NamedLookup.class)).toList(); 273 | } 274 | 275 | 276 | private static MemoryLayout classToMemory(Class type) 277 | { 278 | if (double.class.equals(type)) 279 | return ValueLayout.JAVA_DOUBLE; 280 | if (int.class.equals(type)) 281 | return ValueLayout.JAVA_INT; 282 | if (float.class.equals(type)) 283 | return ValueLayout.JAVA_FLOAT; 284 | if (short.class.equals(type)) 285 | return ValueLayout.JAVA_SHORT; 286 | if (byte.class.equals(type)) 287 | return ValueLayout.JAVA_BYTE; 288 | if (long.class.equals(type)) 289 | return ValueLayout.JAVA_LONG; 290 | if (boolean.class.equals(type)) 291 | return ValueLayout.JAVA_BOOLEAN; 292 | 293 | return ValueLayout.ADDRESS; 294 | } 295 | 296 | } 297 | -------------------------------------------------------------------------------- /src/main/java/jpassport/PassportInvocationHandler.java: -------------------------------------------------------------------------------- 1 | package jpassport; 2 | 3 | import java.lang.annotation.Annotation; 4 | import java.lang.foreign.Arena; 5 | import java.lang.foreign.MemorySegment; 6 | import java.lang.invoke.MethodHandle; 7 | import java.lang.reflect.InvocationHandler; 8 | import java.lang.reflect.Method; 9 | import java.lang.reflect.Parameter; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.HashMap; 13 | 14 | import static jpassport.PassportWriter.isGenericPtr; 15 | 16 | public class PassportInvocationHandler implements InvocationHandler { 17 | HashMap handles; 18 | boolean allArraysAreReadBack = false; 19 | 20 | PassportInvocationHandler( HashMap methods, Class interfaceClass) 21 | { 22 | handles = methods; 23 | allArraysAreReadBack = PassportWriter.isRefArg(interfaceClass.getAnnotations()); 24 | } 25 | 26 | @Override 27 | public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 28 | 29 | Class retType = method.getReturnType(); 30 | Class[] parameters = method.getParameterTypes(); 31 | var params = method.getParameters(); 32 | var mh = handles.get(method.getName()); 33 | Annotation[][] paramAnnotations = method.getParameterAnnotations(); 34 | 35 | if (method.getName().equals("hasMethod")) 36 | return handles.containsKey(args[0].toString()); 37 | if (mh == null) 38 | throw new Error("Method does not exist"); 39 | 40 | var passedArena = Arrays.stream(args).filter(a -> a instanceof Arena).findFirst(); 41 | 42 | try (var scope = Arena.ofConfined()) { 43 | Arena useScope = (Arena) passedArena.orElse(scope); 44 | var largs = new ArrayList(); 45 | for (int i = 0; i < parameters.length; ++i) 46 | { 47 | if (Arena.class.equals(parameters[i])) 48 | continue; 49 | largs.add(toArg(parameters[i], args[i], useScope, paramAnnotations[i], params[i])); 50 | } 51 | 52 | var ret = mh.invokeWithArguments(largs); 53 | 54 | for (int i = 0; i < largs.size(); ++i) 55 | { 56 | if (PassportWriter.isRefArg(paramAnnotations[i]) || 57 | MemoryBlock.class.equals(parameters[i]) || 58 | (parameters[i].isArray() && allArraysAreReadBack)) 59 | readBack(args[i], largs.get(i)); 60 | } 61 | 62 | if (String.class.equals(retType)) 63 | return Utils.readString((MemorySegment) ret); 64 | else if (isGenericPtr(retType)) 65 | { 66 | var cons = retType.getConstructor(MemorySegment.class); 67 | return cons.newInstance((MemorySegment)ret); 68 | } 69 | return ret; 70 | } 71 | } 72 | 73 | 74 | Object toArg(Class type, Object value, Arena arena, Annotation[] annotations, Parameter p) 75 | { 76 | if (type.isRecord() || (type.isArray() && type.getComponentType().isRecord())) 77 | throw new IllegalArgumentException("Record types not supported"); 78 | 79 | if (type.isPrimitive()) 80 | return value; 81 | 82 | if (PassportWriter.isPtrPtrArg(annotations)) 83 | { 84 | return switch (value) 85 | { 86 | case null -> MemorySegment.NULL; 87 | case byte[][] i -> Utils.toPtrPTrMS(arena, i); 88 | case char[][] i -> Utils.toPtrPTrMS(arena, i); 89 | case short[][] i -> Utils.toPtrPTrMS(arena, i); 90 | case int[][] i -> Utils.toPtrPTrMS(arena, i); 91 | case long[][] i -> Utils.toPtrPTrMS(arena, i); 92 | case float[][] i -> Utils.toPtrPTrMS(arena, i); 93 | case double[][] i -> Utils.toPtrPTrMS(arena, i); 94 | 95 | default -> value; 96 | }; 97 | } 98 | 99 | var readBack = PassportWriter.isRefArgReadBackOnly(p); 100 | return switch (value) 101 | { 102 | case null -> MemorySegment.NULL; 103 | case GenericPointer g -> g.getPtr(); 104 | case GenericPointer[] g -> Utils.toMS(arena, g,readBack); 105 | case String i -> Utils.toCString(i, arena); 106 | case MemoryBlock i -> i.toPtr(arena); 107 | case String[] i -> Utils.toCString(i, arena); 108 | case byte[] i -> Utils.toMS(arena, i, readBack); 109 | case char[] i -> Utils.toMS(arena, i, readBack); 110 | case short[] i -> Utils.toMS(arena, i, readBack); 111 | case int[] i -> Utils.toMS(arena, i, readBack); 112 | case long[] i -> Utils.toMS(arena, i, readBack); 113 | case float[] i -> Utils.toMS(arena, i, readBack); 114 | case double[] i -> Utils.toMS(arena, i, readBack); 115 | case byte[][] i -> Utils.toMS(arena, i, readBack); 116 | case char[][] i -> Utils.toMS(arena, i, readBack); 117 | case short[][] i -> Utils.toMS(arena, i, readBack); 118 | case int[][] i -> Utils.toMS(arena, i, readBack); 119 | case long[][] i -> Utils.toMS(arena, i, readBack); 120 | case float[][] i -> Utils.toMS(arena, i, readBack); 121 | case double[][] i -> Utils.toMS(arena, i, readBack); 122 | 123 | default -> value; 124 | }; 125 | } 126 | 127 | void readBack(Object value, Object called) 128 | { 129 | if (value == null) 130 | return; 131 | 132 | switch (value) 133 | { 134 | // case null -> GenericPointer.NULL(); 135 | //case GenericPointer g -> new GenericPointer(); 136 | 137 | case byte[] i -> Utils.toArr(i, (MemorySegment) called); 138 | case char[] i -> Utils.toArr(i, (MemorySegment) called); 139 | case short[] i -> Utils.toArr(i, (MemorySegment) called); 140 | case int[] i -> Utils.toArr(i, (MemorySegment) called); 141 | case long[] i -> Utils.toArr(i, (MemorySegment) called); 142 | case float[] i -> Utils.toArr(i, (MemorySegment) called); 143 | case double[] i -> Utils.toArr(i, (MemorySegment) called); 144 | 145 | case GenericPointer[] g -> Utils.toArr(g, (MemorySegment) called); 146 | case String[] g -> Utils.fromCString((MemorySegment) called, g); 147 | case MemoryBlock fs -> fs.readBack(); 148 | // case byte[][] i -> Utils.toArr(i, (MemorySegment) called); 149 | // case short[][] i -> Utils.toMS(arena, i, false); 150 | // case int[][] i -> Utils.toMS(arena, i, false); 151 | // case float[][] i -> Utils.toMS(arena, i, false); 152 | // case double[][] i -> Utils.toMS(arena, i, false); 153 | 154 | default -> { 155 | break; 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/jpassport/Pointer.java: -------------------------------------------------------------------------------- 1 | package jpassport; 2 | 3 | import java.lang.foreign.MemorySegment; 4 | 5 | public class Pointer extends GenericPointer{ 6 | public Pointer(MemorySegment addr) { 7 | super(addr); 8 | } 9 | 10 | public Pointer() { 11 | super(MemorySegment.NULL); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/jpassport/Utils.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport; 13 | 14 | 15 | import jpassport.annotations.Array; 16 | import jpassport.annotations.Ptr; 17 | 18 | import java.io.IOException; 19 | import java.lang.annotation.Annotation; 20 | import java.lang.foreign.*; 21 | import java.lang.invoke.MethodHandle; 22 | import java.lang.reflect.Field; 23 | import java.nio.file.FileVisitResult; 24 | import java.nio.file.Files; 25 | import java.nio.file.Path; 26 | import java.nio.file.SimpleFileVisitor; 27 | import java.nio.file.attribute.BasicFileAttributes; 28 | import java.util.ArrayList; 29 | import java.util.Arrays; 30 | 31 | 32 | public class Utils { 33 | 34 | public static MemorySegment toAddr(MemorySegment seg) { 35 | if (seg == null) 36 | return MemorySegment.NULL; 37 | 38 | return seg; 39 | } 40 | 41 | /* Double ///////////////////////////////////////////////////////////////// */ 42 | public static MemorySegment toMS(SegmentAllocator scope, double[] arr, boolean isReadBackOnly) { 43 | if (arr == null) 44 | return null; 45 | return isReadBackOnly ? scope.allocate((long)arr.length * Double.BYTES) : 46 | scope.allocateFrom(ValueLayout.JAVA_DOUBLE, arr); 47 | } 48 | 49 | public static MemorySegment toMS(Arena scope, double[] arr, boolean isReadBackOnly) { 50 | if (arr == null) 51 | return null; 52 | return isReadBackOnly ? scope.allocate((long)arr.length * Double.BYTES) : 53 | scope.allocateFrom(ValueLayout.JAVA_DOUBLE, arr); 54 | } 55 | 56 | public static MemorySegment toMS(SegmentAllocator scope, double[][] arr, boolean isReadBackOnly) { 57 | if (arr == null) 58 | return null; 59 | 60 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Double.BYTES); 61 | int n = 0; 62 | for (double[] row : arr) { 63 | segment.asSlice(n, (long) row.length * Double.BYTES).copyFrom(MemorySegment.ofArray(row)); 64 | n += row.length * Double.BYTES; 65 | } 66 | 67 | return segment; 68 | } 69 | 70 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, double[][] arr) { 71 | if (arr == null) 72 | return null; 73 | 74 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES); 75 | int n = 0; 76 | for (double[] a : arr) { 77 | MemorySegment subSeg = scope.allocateFrom(ValueLayout.JAVA_DOUBLE, a); 78 | segment.setAtIndex(ValueLayout.ADDRESS, n++, subSeg); 79 | } 80 | return segment; 81 | } 82 | 83 | public static void toArr(double[] arr, MemorySegment segment) { 84 | if (arr == null) 85 | return; 86 | 87 | MemorySegment.copy(segment, ValueLayout.JAVA_DOUBLE, 0, arr, 0, arr.length); 88 | } 89 | 90 | public static double[] toArr(ValueLayout.OfDouble layout, MemorySegment seg, MemorySegment addr, int count) { 91 | if (MemorySegment.NULL.equals(addr)) 92 | return null; 93 | 94 | return slice(seg, addr, count * layout.byteSize()).toArray(layout); 95 | } 96 | 97 | public static double[] toArr(ValueLayout.OfDouble layout, MemorySegment addr, int count) { 98 | if (MemorySegment.NULL.equals(addr)) 99 | return null; 100 | 101 | if (addr.byteSize() == 0) 102 | { 103 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret((long)Double.BYTES * count); 104 | return seg.toArray(ValueLayout.JAVA_DOUBLE); 105 | } 106 | 107 | return addr.asSlice(0, (long) count * Long.BYTES).toArray(layout); 108 | } 109 | 110 | /* Float ///////////////////////////////////////////////////////////////// */ 111 | 112 | public static MemorySegment toMS(SegmentAllocator scope, float[] arr, boolean isReadBackOnly) { 113 | if (arr == null) 114 | return null; 115 | 116 | return isReadBackOnly ? scope.allocate(arr.length * Float.BYTES) : 117 | scope.allocateFrom(ValueLayout.JAVA_FLOAT, arr); 118 | } 119 | 120 | public static MemorySegment toMS(SegmentAllocator scope, float[][] arr, boolean isReadBackOnly) { 121 | if (arr == null) 122 | return null; 123 | 124 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Float.BYTES); 125 | int n = 0; 126 | for (float[] row : arr) { 127 | segment.asSlice(n, (long) row.length * Float.BYTES).copyFrom(MemorySegment.ofArray(row)); 128 | n += row.length * Float.BYTES; 129 | } 130 | 131 | return segment; 132 | } 133 | 134 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, float[][] arr) { 135 | if (arr == null) 136 | return null; 137 | 138 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES); 139 | int n = 0; 140 | for (float[] a : arr) { 141 | MemorySegment subSeg = scope.allocateFrom(ValueLayout.JAVA_FLOAT, a); 142 | segment.setAtIndex(ValueLayout.ADDRESS, n++, subSeg); 143 | } 144 | return segment; 145 | } 146 | 147 | public static void toArr(float[] arr, MemorySegment segment) { 148 | if (arr == null) 149 | return; 150 | 151 | MemorySegment.copy(segment, ValueLayout.JAVA_FLOAT, 0, arr, 0, arr.length); 152 | } 153 | 154 | public static float[] toArr(ValueLayout.OfFloat layout, MemorySegment seg, MemorySegment addr, int count) { 155 | if (MemorySegment.NULL.equals(addr)) 156 | return null; 157 | 158 | return slice(seg, addr, count * layout.byteSize()).toArray(layout); 159 | } 160 | 161 | public static float[] toArr(ValueLayout.OfFloat layout, MemorySegment addr, int count) { 162 | if (MemorySegment.NULL.equals(addr)) 163 | return null; 164 | 165 | if (addr.byteSize() == 0) 166 | { 167 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret((long)Float.BYTES * count); 168 | return seg.toArray(ValueLayout.JAVA_FLOAT); 169 | } 170 | 171 | return addr.asSlice(0, (long) count * Long.BYTES).toArray(layout); 172 | } 173 | 174 | /* Pointers ///////////////////////////////////////////////////////////////// */ 175 | 176 | public static MemorySegment toMS(SegmentAllocator scope, GenericPointer[] arr, boolean isReadBackOnly) { 177 | if (arr == null) 178 | return null; 179 | 180 | var pass = Arrays.stream(arr).mapToLong(gp -> gp == null ? MemorySegment.NULL.address() : gp.getPtr().address()).toArray(); 181 | return isReadBackOnly ? scope.allocate((long)arr.length * Long.BYTES) : 182 | scope.allocateFrom(ValueLayout.JAVA_LONG, pass); 183 | } 184 | public static void toArr(GenericPointer[] arr, MemorySegment segment) { 185 | if (arr == null) 186 | return; 187 | 188 | var asLong = new long[arr.length]; 189 | toArr(asLong, segment); 190 | 191 | for (int n = 0; n < asLong.length; ++n) 192 | { 193 | var addr = MemorySegment.ofAddress(asLong[n]); 194 | if (arr[n] == null) 195 | { 196 | try { 197 | var cons = arr.getClass().getComponentType().getConstructor(MemorySegment.class); 198 | arr[n] = (GenericPointer) cons.newInstance(addr); 199 | } 200 | catch(Exception ex) 201 | { 202 | ex.printStackTrace(); 203 | } 204 | } 205 | else 206 | arr[n].ptr = addr; 207 | } 208 | } 209 | 210 | /* Long ///////////////////////////////////////////////////////////////// */ 211 | 212 | public static MemorySegment toMS(SegmentAllocator scope, long[] arr, boolean isReadBackOnly) { 213 | if (arr == null) 214 | return null; 215 | return isReadBackOnly ? scope.allocate((long)arr.length * Long.BYTES) : 216 | scope.allocateFrom(ValueLayout.JAVA_LONG, arr); 217 | } 218 | 219 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, long[][] arr) { 220 | if (arr == null) 221 | return null; 222 | 223 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES); 224 | int n = 0; 225 | for (long[] a : arr) { 226 | MemorySegment subSeg = scope.allocateFrom(ValueLayout.JAVA_LONG, a); 227 | segment.setAtIndex(ValueLayout.ADDRESS, n++, subSeg); 228 | } 229 | return segment; 230 | } 231 | 232 | public static MemorySegment toMS(SegmentAllocator scope, long[][] arr, boolean isReadBackOnly) { 233 | if (arr == null) 234 | return null; 235 | 236 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Long.BYTES); 237 | int n = 0; 238 | for (long[] row : arr) { 239 | segment.asSlice(n, (long) row.length * Long.BYTES).copyFrom(MemorySegment.ofArray(row)); 240 | n += row.length * Long.BYTES; 241 | } 242 | 243 | return segment; 244 | } 245 | 246 | public static void toArr(long[] arr, MemorySegment segment) { 247 | if (arr == null) 248 | return; 249 | 250 | MemorySegment.copy(segment, ValueLayout.JAVA_LONG, 0, arr, 0, arr.length); 251 | } 252 | 253 | public static long[] toArr(ValueLayout.OfLong layout, MemorySegment seg, MemorySegment addr, int count) { 254 | if (MemorySegment.NULL.equals(addr)) 255 | return null; 256 | 257 | return slice(seg, addr, count * layout.byteSize()).toArray(layout); 258 | } 259 | 260 | public static long[] toArr(ValueLayout.OfLong layout, MemorySegment addr, int count) { 261 | if (MemorySegment.NULL.equals(addr)) 262 | return null; 263 | 264 | if (addr.byteSize() == 0) 265 | { 266 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret((long)Long.BYTES * count); 267 | return seg.toArray(ValueLayout.JAVA_LONG); 268 | } 269 | 270 | return addr.asSlice(0, (long) count * Long.BYTES).toArray(layout); 271 | } 272 | 273 | /* Int ///////////////////////////////////////////////////////////////// */ 274 | 275 | public static MemorySegment toMS(SegmentAllocator scope, int[] arr, boolean isReadBackOnly) { 276 | if (arr == null) 277 | return null; 278 | 279 | return isReadBackOnly ? scope.allocate(arr.length * Integer.BYTES) : 280 | scope.allocateFrom(ValueLayout.JAVA_INT, arr); 281 | } 282 | 283 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, int[][] arr) { 284 | if (arr == null) 285 | return null; 286 | 287 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES); 288 | int n = 0; 289 | for (int[] a : arr) { 290 | segment.setAtIndex(ValueLayout.ADDRESS, n++, scope.allocateFrom(ValueLayout.JAVA_INT, a)); 291 | } 292 | return segment; 293 | } 294 | 295 | public static MemorySegment toMS(SegmentAllocator scope, int[][] arr, boolean isReadBackOnly) { 296 | if (arr == null) 297 | return null; 298 | 299 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Integer.BYTES); 300 | int n = 0; 301 | for (int[] row : arr) { 302 | segment.asSlice(n, (long) row.length * Integer.BYTES).copyFrom(MemorySegment.ofArray(row)); 303 | n += row.length * Integer.BYTES; 304 | } 305 | 306 | return segment; 307 | } 308 | 309 | public static void toArr(int[] arr, MemorySegment segment) { 310 | if (arr == null) 311 | return; 312 | 313 | MemorySegment.copy(segment, ValueLayout.JAVA_INT, 0, arr, 0, arr.length); 314 | } 315 | 316 | public static int[] toArr(ValueLayout.OfInt layout, MemorySegment seg, MemorySegment addr, int count) { 317 | if (MemorySegment.NULL.equals(addr)) 318 | return null; 319 | 320 | return slice(seg, addr, count * layout.byteSize()).toArray(layout); 321 | } 322 | 323 | public static int[] toArr(ValueLayout.OfInt layout, MemorySegment addr, int count) { 324 | if (MemorySegment.NULL.equals(addr)) 325 | return null; 326 | 327 | if (addr.byteSize() == 0) 328 | { 329 | return MemorySegment.ofAddress(addr.address()). 330 | reinterpret((long)Integer.BYTES * count).toArray(ValueLayout.JAVA_INT); 331 | } 332 | 333 | return addr.asSlice(0, (long) count * Integer.BYTES).toArray(layout); 334 | } 335 | 336 | 337 | /* Short ///////////////////////////////////////////////////////////////// */ 338 | 339 | public static MemorySegment toMS(SegmentAllocator scope, short[] arr, boolean isReadBackOnly) { 340 | if (arr == null) 341 | return null; 342 | 343 | return isReadBackOnly ? scope.allocate((int) (arr.length * Short.BYTES)) : 344 | scope.allocateFrom(ValueLayout.JAVA_SHORT, arr); 345 | } 346 | 347 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, short[][] arr) { 348 | if (arr == null) 349 | return null; 350 | 351 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES); 352 | int n = 0; 353 | for (short[] a : arr) { 354 | segment.setAtIndex(ValueLayout.ADDRESS, n++, scope.allocateFrom(ValueLayout.JAVA_SHORT, a)); 355 | } 356 | return segment; 357 | } 358 | 359 | public static MemorySegment toMS(SegmentAllocator scope, short[][] arr, boolean isReadBackOnly) { 360 | if (arr == null) 361 | return null; 362 | 363 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Short.BYTES); 364 | int n = 0; 365 | for (short[] row : arr) { 366 | segment.asSlice(n, (long) row.length * Short.BYTES).copyFrom(MemorySegment.ofArray(row)); 367 | n += row.length * Short.BYTES; 368 | } 369 | 370 | return segment; 371 | } 372 | 373 | public static void toArr(short[] arr, MemorySegment segment) { 374 | if (arr == null) 375 | return; 376 | MemorySegment.copy(segment, ValueLayout.JAVA_SHORT, 0, arr, 0, arr.length); 377 | } 378 | 379 | public static short[] toArr(ValueLayout.OfShort layout, MemorySegment seg, MemorySegment addr, int count) { 380 | if (MemorySegment.NULL.equals(addr)) 381 | return null; 382 | 383 | return slice(seg, addr, count * layout.byteSize()).toArray(layout); 384 | } 385 | 386 | public static short[] toArr(ValueLayout.OfShort layout, MemorySegment addr, int count) { 387 | if (MemorySegment.NULL.equals(addr)) 388 | return null; 389 | 390 | if (addr.byteSize() == 0) 391 | { 392 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret((long)Short.BYTES * count); 393 | return seg.toArray(ValueLayout.JAVA_SHORT); 394 | } 395 | 396 | return addr.asSlice(0, (long) count * Long.BYTES).toArray(layout); 397 | } 398 | 399 | /* Byte ///////////////////////////////////////////////////////////////// */ 400 | 401 | public static MemorySegment toMS(SegmentAllocator scope, byte[] arr, boolean isReadBackOnly) { 402 | if (arr == null) 403 | return null; 404 | 405 | return isReadBackOnly ? scope.allocate(arr.length) : scope.allocateFrom(ValueLayout.JAVA_BYTE, arr); 406 | } 407 | 408 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, byte[][] arr) { 409 | if (arr == null) 410 | return null; 411 | 412 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES); 413 | int n = 0; 414 | for (byte[] a : arr) 415 | segment.setAtIndex(ValueLayout.ADDRESS, n++, scope.allocateFrom(ValueLayout.JAVA_BYTE, a)); 416 | return segment; 417 | } 418 | 419 | public static MemorySegment toMS(SegmentAllocator scope, byte[][] arr, boolean isReadBackOnly) { 420 | if (arr == null) 421 | return null; 422 | 423 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Byte.BYTES); 424 | int n = 0; 425 | for (byte[] row : arr) { 426 | segment.asSlice(n, row.length * Byte.BYTES).copyFrom(MemorySegment.ofArray(row)); 427 | n += row.length * Byte.BYTES; 428 | } 429 | 430 | return segment; 431 | } 432 | 433 | public static void toArr(byte[] arr, MemorySegment segment) { 434 | if (arr == null) 435 | return; 436 | MemorySegment.copy(segment, ValueLayout.JAVA_BYTE, 0, arr, 0, arr.length); 437 | } 438 | 439 | public static byte[] toArr(ValueLayout.OfByte layout, MemorySegment seg, MemorySegment addr, int count) { 440 | if (MemorySegment.NULL.equals(addr)) 441 | return null; 442 | 443 | return slice(seg, addr, count * layout.byteSize()).toArray(layout); 444 | } 445 | 446 | public static byte[] toArr(ValueLayout.OfByte layout, MemorySegment addr, int count) { 447 | if (MemorySegment.NULL.equals(addr)) 448 | return null; 449 | 450 | if (addr.byteSize() == 0) 451 | { 452 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret(count); 453 | return seg.toArray(ValueLayout.JAVA_BYTE); 454 | } 455 | 456 | return addr.asSlice(0, count).toArray(layout); 457 | } 458 | 459 | /* Char ///////////////////////////////////////////////////////////////// */ 460 | 461 | public static MemorySegment toMS(SegmentAllocator scope, char[] arr, boolean isReadBackOnly) { 462 | if (arr == null) 463 | return null; 464 | 465 | return isReadBackOnly ? scope.allocate(Character.BYTES * arr.length) : scope.allocateFrom(ValueLayout.JAVA_CHAR, arr); 466 | } 467 | 468 | public static MemorySegment toPtrPTrMS(SegmentAllocator scope, char[][] arr) { 469 | if (arr == null) 470 | return null; 471 | 472 | MemorySegment segment = scope.allocate((long) arr.length * Long.BYTES); 473 | int n = 0; 474 | for (char[] a : arr) 475 | segment.setAtIndex(ValueLayout.ADDRESS, n++, scope.allocateFrom(ValueLayout.JAVA_CHAR, a)); 476 | return segment; 477 | } 478 | 479 | public static MemorySegment toMS(SegmentAllocator scope, char[][] arr, boolean isReadBackOnly) { 480 | if (arr == null) 481 | return null; 482 | 483 | MemorySegment segment = scope.allocate((long) arr.length * arr[0].length * Byte.BYTES); 484 | int n = 0; 485 | for (char[] row : arr) { 486 | segment.asSlice(n, (int)(row.length * Character.BYTES)).copyFrom(MemorySegment.ofArray(row)); 487 | n += row.length * Byte.BYTES; 488 | } 489 | 490 | return segment; 491 | } 492 | 493 | public static void toArr(char[] arr, MemorySegment segment) { 494 | if (arr == null) 495 | return; 496 | MemorySegment.copy(segment, ValueLayout.JAVA_CHAR, 0, arr, 0, arr.length); 497 | } 498 | 499 | public static char[] toArr(ValueLayout.OfChar layout, MemorySegment seg, MemorySegment addr, int count) { 500 | if (MemorySegment.NULL.equals(addr)) 501 | return null; 502 | 503 | return slice(seg, addr, count * layout.byteSize()).toArray(layout); 504 | } 505 | 506 | public static char[] toArr(ValueLayout.OfChar layout, MemorySegment addr, int count) { 507 | if (MemorySegment.NULL.equals(addr)) 508 | return null; 509 | 510 | if (addr.byteSize() == 0) 511 | { 512 | var seg = MemorySegment.ofAddress(addr.address()).reinterpret(count); 513 | return seg.toArray(ValueLayout.JAVA_CHAR); 514 | } 515 | 516 | return addr.asSlice(0, count).toArray(layout); 517 | } 518 | 519 | /*///////////////////////////////////////////////////////////////// */ 520 | 521 | public static MemorySegment slice(MemorySegment scope, MemorySegment addr, long bytes) { 522 | if (addr.byteSize() == 0) 523 | return MemorySegment.ofAddress(addr.address()).reinterpret(bytes).asSlice(0, bytes); 524 | 525 | return MemorySegment.ofAddress(addr.address()).asSlice(0, bytes); 526 | } 527 | 528 | public static String readString(MemorySegment addr) { 529 | if (MemorySegment.NULL.equals(addr)) 530 | return null; 531 | 532 | if (addr.byteSize() == 0) 533 | { 534 | // This is slightly horrible. I can't find a better way. Use C's strlen to figure out 535 | //how big the memory segment really is. 536 | return addr.reinterpret(strLen(addr)+1).getString(0); 537 | } 538 | 539 | return addr.getString(0); 540 | } 541 | 542 | public static MemorySegment resize(MemorySegment addr, long bytes) 543 | { 544 | if (addr.byteSize() == 0) 545 | addr = addr.reinterpret(bytes); 546 | return addr; 547 | } 548 | 549 | static MethodHandle strlen = null; 550 | 551 | private static long strLen(MemorySegment seg) 552 | { 553 | if (strlen == null) 554 | { 555 | Linker linker = Linker.nativeLinker(); 556 | SymbolLookup stdlib = linker.defaultLookup(); 557 | strlen = linker.downcallHandle( 558 | stdlib.find("strlen").get(), 559 | FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS) 560 | ); 561 | 562 | } 563 | try { 564 | return (long)strlen.invokeExact(seg); 565 | } catch (Throwable e) { 566 | return 0; 567 | } 568 | } 569 | 570 | public static MemorySegment toCString(String[] s, Arena scope) { 571 | var segment = scope.allocate(ValueLayout.JAVA_LONG.byteSize() * s.length); 572 | for (int i = 0; i < s.length; ++i) 573 | { 574 | segment.setAtIndex(ValueLayout.ADDRESS, i, toCString(s[i], scope)); 575 | } 576 | return segment; 577 | } 578 | 579 | public static void fromCString(MemorySegment mem, String[] s) 580 | { 581 | for (int n = 0; n < s.length; ++n) 582 | s[n] = readString(mem.getAtIndex(ValueLayout.ADDRESS, n)); 583 | } 584 | 585 | public static MemorySegment toCString(String s, Arena scope) { 586 | return scope.allocateFrom(s); 587 | } 588 | 589 | public static MemorySegment toCString(String s, SegmentAllocator scope) { 590 | return scope.allocateFrom(s); 591 | } 592 | 593 | /** 594 | * Given a folder or file this will recursively delete it. 595 | * 596 | * @param folder The root folder to recursively delete everything under. 597 | * @return True if everything was deleted. 598 | */ 599 | public static boolean deleteFolder(Path folder) { 600 | if (!Files.exists(folder)) 601 | return true; 602 | 603 | try { 604 | Files.walkFileTree(folder, new SimpleFileVisitor<>() { 605 | @Override 606 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) 607 | throws IOException { 608 | Files.delete(file); 609 | return FileVisitResult.CONTINUE; 610 | } 611 | 612 | @Override 613 | public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { 614 | if (exc != null) 615 | throw exc; 616 | 617 | Files.delete(dir); 618 | return FileVisitResult.CONTINUE; 619 | } 620 | }); 621 | } catch (IOException e) { 622 | e.printStackTrace(); 623 | } 624 | return true; 625 | } 626 | 627 | public static Path getBuildFolder() { 628 | if (System.getProperty("jpassport.build.home") != null) 629 | return Path.of(System.getProperty("jpassport.build.home")); 630 | return Path.of(System.getProperty("java.io.tmpdir"), "jpassport"); 631 | } 632 | 633 | public enum Platform {Windows, Mac, Linux, Unknown} 634 | 635 | public static Platform getPlatform() { 636 | String os = System.getProperty("os.name").toLowerCase(); 637 | 638 | if (os.contains("win")) 639 | return Platform.Windows; 640 | if (os.contains("mac")) 641 | return Platform.Mac; 642 | if ((os.contains("nix") || os.contains("nux") || os.contains("aix"))) 643 | return Platform.Linux; 644 | 645 | return Platform.Unknown; 646 | } 647 | 648 | private static long typeToSize(Class type) 649 | { 650 | 651 | if (type.equals(byte.class)) return ValueLayout.JAVA_CHAR.byteSize(); 652 | if (type.equals(short.class)) return ValueLayout.JAVA_SHORT.byteSize(); 653 | if (type.equals(int.class)) return ValueLayout.JAVA_INT.byteSize(); 654 | if (type.equals(long.class)) return ValueLayout.JAVA_LONG.byteSize(); 655 | if (type.equals(float.class)) return ValueLayout.JAVA_FLOAT.byteSize(); 656 | if (type.equals(double.class)) return ValueLayout.JAVA_DOUBLE.byteSize(); 657 | if (type.isRecord()) return size_of(type); 658 | throw new IllegalArgumentException("Cannot get size for non-primative"); 659 | }; 660 | public static long size_of(Class c) 661 | { 662 | if (!c.isRecord()) 663 | throw new IllegalArgumentException("Can only get size of records, not " + c.getName()); 664 | 665 | int size = 0; 666 | for (Field f : c.getDeclaredFields()) 667 | { 668 | size += PassportWriter.getPaddingBytes(f); 669 | 670 | Class type = f.getType(); 671 | if (type.isPrimitive()) 672 | size += typeToSize(type); 673 | else if (type.isRecord()) 674 | { 675 | boolean isPtr = f.getAnnotationsByType(Ptr.class).length > 0; 676 | if (isPtr) 677 | size += ValueLayout.ADDRESS.byteSize(); 678 | else 679 | size += size_of(type); 680 | } 681 | else if (String.class.equals(type)) 682 | size += ValueLayout.ADDRESS.byteSize(); 683 | else if (type.isArray()) 684 | { 685 | Annotation[] arrays = f.getAnnotationsByType(Array.class); 686 | boolean isPointer = f.getAnnotationsByType(Ptr.class).length > 0; 687 | 688 | if (arrays.length > 0) 689 | { 690 | int length = ((Array) arrays[0]).length(); 691 | size += length * typeToSize(type.getComponentType()); 692 | } 693 | else if (isPointer) 694 | size += ValueLayout.ADDRESS.byteSize(); 695 | } 696 | } 697 | return size; 698 | } 699 | 700 | /** 701 | * Called by generated code to build the memory layout for a struct. This automatically 702 | * tries to figure out where padding is needed in the struct in order to get proper byte 703 | * alignment. 704 | * @param layout The members of the struct 705 | * @return The full GroupLayout of the struct. 706 | */ 707 | public static GroupLayout makeStruct(MemoryLayout ... layout) 708 | { 709 | int byteBarrier = System.getProperty("sun.arch.data.model").contains("64") ? 8 : 4; 710 | ArrayList memLayout = new ArrayList<>(); 711 | memLayout.add(layout[0]); 712 | long nextBarrier = byteBarrier; 713 | 714 | var curSize = memLayout.stream().mapToLong(MemoryLayout::byteSize).sum(); 715 | while (nextBarrier <= curSize) nextBarrier += byteBarrier; 716 | 717 | for (int n = 1; n < layout.length; ++n) 718 | { 719 | curSize = memLayout.stream().mapToLong(MemoryLayout::byteSize).sum(); 720 | long nextItemSize = layout[n].byteSize(); 721 | 722 | //if an array is next, then we only need to byte align the first element of the array 723 | if (layout[n] instanceof SequenceLayout seq) 724 | nextItemSize = seq.byteSize() / seq.elementCount(); 725 | 726 | // If the next piece of memory we are adding crosses the byte alignement barrier 727 | // then we need to pad the struct to alow byte alignment 728 | if (curSize + nextItemSize > nextBarrier) 729 | memLayout.add(MemoryLayout.paddingLayout(nextBarrier - curSize)); 730 | memLayout.add(layout[n]); 731 | 732 | curSize = memLayout.stream().mapToLong(MemoryLayout::byteSize).sum(); 733 | while (nextBarrier <= curSize) nextBarrier += byteBarrier; 734 | } 735 | 736 | 737 | return MemoryLayout.structLayout(memLayout.toArray(new MemoryLayout[0])); 738 | } 739 | 740 | public void templatedMethod() 741 | { 742 | try (var scope = Arena.ofConfined();) { 743 | 744 | } 745 | catch(Throwable th) 746 | { 747 | throw new Error(th); 748 | } 749 | } 750 | 751 | } 752 | -------------------------------------------------------------------------------- /src/main/java/jpassport/Version.java: -------------------------------------------------------------------------------- 1 | package jpassport; 2 | 3 | import java.util.Arrays; 4 | import java.util.ResourceBundle; 5 | 6 | public class Version 7 | { 8 | private static ResourceBundle m_res; 9 | 10 | static 11 | { 12 | try 13 | { 14 | m_res = ResourceBundle.getBundle("jpassport.version"); 15 | } 16 | catch (Exception ex) 17 | { 18 | ex.printStackTrace(); 19 | } 20 | } 21 | 22 | public static String getVersion() 23 | { 24 | return m_res.getString("version"); 25 | } 26 | 27 | public static int[] getVersionParts() 28 | { 29 | String v = getVersion(); 30 | 31 | return Arrays.stream(v.split("\\.")).mapToInt(Integer::parseInt).toArray(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/jpassport/annotations/Array.java: -------------------------------------------------------------------------------- 1 | package jpassport.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * This annotation can be used with record members to declare the size of an array. For example 10 | *
11 |  * public record PassingArrays(
12 |  *         @Array(length = 5) double[] s_double)
13 |  *  
14 | * 15 | * In the above example, if PassingArrays is annotated with RefArg to indicate that it should 16 | * be read back after the native call, then the Array annotation indicates that s_double[] 17 | * should always be read as 5 doubles. 18 | */ 19 | @Retention(RetentionPolicy.RUNTIME) 20 | @Target({ElementType.RECORD_COMPONENT, ElementType.FIELD}) 21 | public @interface Array { 22 | int length() default 1; 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/jpassport/annotations/Critical.java: -------------------------------------------------------------------------------- 1 | package jpassport.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * This annotation enables the MethodHandle hint "critical". This reduces some of the 10 | * overhead that is required when calling a MethodHandle. From the definition of critical: 11 | * 12 | * A critical function is a function that has an extremely short running time in all cases (similar to calling an empty 13 | * function), and does not call back into Java (e.g. using an upcall stub). Using this linker option is a hint which 14 | * some implementations may use to apply optimizations that are only valid for critical functions. Using this linker 15 | * option when linking non-critical functions is likely to have adverse effects, such as loss of performance, or JVM crashes. 16 | */ 17 | @Retention(RetentionPolicy.RUNTIME) 18 | @Target(ElementType.METHOD) 19 | public @interface Critical { 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/jpassport/annotations/NotRequired.java: -------------------------------------------------------------------------------- 1 | package jpassport.annotations; 2 | 3 | 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | 9 | /** 10 | * This annotation is for methods that may or may not be available at run-time. 11 | * For instance, if you want to use multiple library versions that do not contain 12 | * the same foreign functions you can use this annotation. All Passport interfaces contain 13 | * the method hasMethod(String) where you can ask if the native method was found. 14 | * This can be used like old school C #IFDEF's to use or avoid certain calls. 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.METHOD, ElementType.FIELD}) 18 | public @interface NotRequired { 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/jpassport/annotations/Ptr.java: -------------------------------------------------------------------------------- 1 | package jpassport.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * This is used to annotate a member of a Record that is also a Record and should be 10 | * treated as a pointer in the struct. Ex 11 | * 12 | * struct MyStruct1 13 | * { 14 | * ...... 15 | * } 16 | * 17 | * struct MyStruct2 18 | * { 19 | * struct MyStruct1* ptrToStruct; 20 | * struct MyStruct1 regStruct; 21 | * } 22 | * 23 | * public record MyStruct1( .... ){}; 24 | * public record MyStruct2(@Ptr MyStruct1 ptrToStruct, MyStruct1 regStruct) { 25 | * } 26 | */ 27 | @Retention(RetentionPolicy.RUNTIME) 28 | @Target({ElementType.RECORD_COMPONENT, ElementType.FIELD}) 29 | public @interface Ptr { 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/jpassport/annotations/PtrPtrArg.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport.annotations; 13 | 14 | import java.lang.annotation.ElementType; 15 | import java.lang.annotation.Retention; 16 | import java.lang.annotation.RetentionPolicy; 17 | import java.lang.annotation.Target; 18 | 19 | /** 20 | * This annotation is for a 2-D array that should be passed as a pointer to a list of pointers. Without 21 | * this annotation a 2-D array will be copied as a single large memory block in row major order. 22 | * double sumMatD(int rows, int cols, double mat[rows][cols]) - C function 23 | * double sumMatD(int rows, int cols, double[][] mat); - Java interface 24 | * double sumMatD(const int rows, const int cols, const double** mat) - C function 25 | * double sumMatD(int rows, int cols, @PtrPtrArg double[][] mat); - Java interface 26 | * This annotation is only observed for array arguments. 27 | */ 28 | @Retention(RetentionPolicy.RUNTIME) 29 | @Target(ElementType.PARAMETER) 30 | public @interface PtrPtrArg { 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/jpassport/annotations/RefArg.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport.annotations; 13 | 14 | import java.lang.annotation.ElementType; 15 | import java.lang.annotation.Retention; 16 | import java.lang.annotation.RetentionPolicy; 17 | import java.lang.annotation.Target; 18 | 19 | /** 20 | * This annotation is for an array that will be changed in the foreign library and 21 | * therefore should be read back in after the library call. 22 | * 23 | * This annotation is observed for array arguments and the class as a whole. When 24 | * used on a class it means that ALL arrays in interface methods are RefArgs. 25 | * 26 | * If the read_back_only parameter is false then the value in the java array is copied 27 | * into the native memory that is passed to the function call. If read_back_only is true 28 | * then blank memory is allocated and passed to the function call. For large 29 | * blocks of memory where you will only ever receive data back (eg. a read call) 30 | * read_back_only = true can save quite a bit of time. 31 | */ 32 | @Retention(RetentionPolicy.RUNTIME) 33 | @Target({ElementType.PARAMETER, ElementType.TYPE}) 34 | public @interface RefArg { 35 | boolean read_back_only() default false; 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/jpassport/annotations/StructPadding.java: -------------------------------------------------------------------------------- 1 | package jpassport.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * This should be used to annotate the members of a Record class that are converted to a C Struct. 10 | * The idea is that the values in a struct are often padded. At least chars, and shorts are often 11 | * padded to 32 bits. The trouble is that the exact amount of padding is dependent on the compiler 12 | * and the platform. As such, it's nearly impossible to know ahead of time what the padding should 13 | * be on a given platform with a given compiler. As the developer, you need to tell JPassport how 14 | * much padding there should be. You can specify different padding for different platforms. 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target({ElementType.RECORD_COMPONENT, ElementType.FIELD}) 18 | public @interface StructPadding { 19 | 20 | int NO_VALUE = Integer.MIN_VALUE; 21 | 22 | /** The number of bytes of padding to add. */ 23 | int bytes() default 0; 24 | int windowsBytes() default NO_VALUE; 25 | int macBytes() default NO_VALUE; 26 | int linuxBytes() default NO_VALUE; 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/jpassport/version.properties: -------------------------------------------------------------------------------- 1 | #Sat, 12 Mar 2022 19:20:26 -0500 2 | version=0.6.1 3 | -------------------------------------------------------------------------------- /src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module jpassport { 2 | requires jdk.compiler; 3 | 4 | 5 | exports jpassport; 6 | exports jpassport.annotations; 7 | } -------------------------------------------------------------------------------- /src/main/resources/jpassport/version.properties: -------------------------------------------------------------------------------- 1 | #Sat, 12 Mar 2022 19:20:26 -0500 2 | version=1.0.0 3 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/PureJava.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport.test; 13 | 14 | 15 | import jpassport.MemoryBlock; 16 | import jpassport.Pointer; 17 | import jpassport.annotations.RefArg; 18 | 19 | import java.lang.foreign.Arena; 20 | import java.lang.foreign.MemorySegment; 21 | 22 | public class PureJava implements TestLink 23 | { 24 | 25 | @Override 26 | public void functionDoesNotExist(double v) { 27 | 28 | } 29 | 30 | @Override 31 | public double sumD(double d, double d2) { 32 | return d + d2; 33 | } 34 | 35 | @Override 36 | public double sumArrD(double[] d, int len) 37 | { 38 | double ret = 0; 39 | for (int n = 0; n < len; ++n) 40 | ret += d[n]; 41 | return ret; 42 | } 43 | 44 | @Override 45 | public double sumArrDD(double[] d, double[] d2, int len) { 46 | double ret = 0; 47 | for (int n = 0; n < len; ++n) 48 | { 49 | ret += d[n]; 50 | ret += d2[n]; 51 | } 52 | return ret; 53 | } 54 | 55 | @Override 56 | public void readD(double[] d, int set) { 57 | d[0] = set; 58 | } 59 | 60 | @Override 61 | public float sumArrF(float[] d, int len) 62 | { 63 | float ret = 0; 64 | for (int n = 0; n < len; ++n) 65 | ret += d[n]; 66 | return ret; 67 | } 68 | 69 | @Override 70 | public void readF(float[] d, float set) { 71 | d[0] = set; 72 | } 73 | 74 | @Override 75 | public long sumArrL(long[] d, long len) 76 | { 77 | long ret = 0; 78 | for (int n = 0; n < len; ++n) 79 | ret += d[n]; 80 | return ret; 81 | } 82 | 83 | @Override 84 | public void readL(long[] d, long set) { 85 | d[0] = set; 86 | } 87 | 88 | @Override 89 | public int sumArrI(int[] d, int len) 90 | { 91 | int ret = 0; 92 | for (int n = 0; n < len; ++n) 93 | ret += d[n]; 94 | return ret; 95 | } 96 | 97 | @Override 98 | public void readI(int[] d, int set) { 99 | d[0] = set; 100 | } 101 | 102 | @Override 103 | public short sumArrS(short[] d, short len) 104 | { 105 | short ret = 0; 106 | for (int n = 0; n < len; ++n) 107 | ret += d[n]; 108 | return ret; 109 | } 110 | 111 | @Override 112 | public void readS(short[] d, short set) { 113 | d[0] = set; 114 | } 115 | 116 | @Override 117 | public byte sumArrB(byte[] d, byte len) 118 | { 119 | byte ret = 0; 120 | for (int n = 0; n < len; ++n) 121 | ret += d[n]; 122 | return ret; 123 | } 124 | 125 | @Override 126 | public void readB(byte[] d, byte set) { 127 | d[0] = set; 128 | } 129 | 130 | @Override 131 | public double sumMatD(int rows, int cols, double[][] mat) 132 | { 133 | double total = 0; 134 | for (int y = 0; y < rows; ++y) 135 | { 136 | for (double i : mat[y]) 137 | total += i; 138 | } 139 | return total; 140 | } 141 | 142 | @Override 143 | public double sumMatDPtrPtr(int rows, int cols, double[][] mat) { 144 | return sumMatD(rows, cols, mat); 145 | } 146 | 147 | @Override 148 | public float sumMatF(int rows, int cols, float[][] mat) 149 | { 150 | float total = 0; 151 | for (int y = 0; y < rows; ++y) 152 | { 153 | for (float i : mat[y]) 154 | total += i; 155 | } 156 | return total; 157 | } 158 | 159 | @Override 160 | public float sumMatFPtrPtr(int rows, int cols, float[][] mat) { 161 | return sumMatF(rows, cols, mat); 162 | } 163 | 164 | @Override 165 | public long sumMatL(int rows, int cols, long[][] mat) 166 | { 167 | long total = 0; 168 | for (int y = 0; y < rows; ++y) 169 | { 170 | for (long i : mat[y]) 171 | total += i; 172 | } 173 | return total; 174 | } 175 | 176 | @Override 177 | public long sumMatLPtrPtr(int rows, int cols, long[][] mat) { 178 | return sumMatL(rows, cols, mat); 179 | } 180 | 181 | @Override 182 | public int sumMatI(int rows, int cols, int[][] mat) 183 | { 184 | int total = 0; 185 | for (int y = 0; y < rows; ++y) 186 | { 187 | for (int i : mat[y]) 188 | total += i; 189 | } 190 | return total; 191 | } 192 | 193 | @Override 194 | public int sumMatIPtrPtr(int rows, int cols, int[][] mat) { 195 | return sumMatI(rows, cols, mat); 196 | } 197 | 198 | @Override 199 | public int sumMatS(int rows, int cols, short[][] mat) 200 | { 201 | int total = 0; 202 | for (int y = 0; y < rows; ++y) 203 | { 204 | for (int i : mat[y]) 205 | total += i; 206 | } 207 | return total; 208 | } 209 | 210 | @Override 211 | public int sumMatSPtrPtr(int rows, int cols, short[][] mat) { 212 | return sumMatS(rows, cols, mat); 213 | } 214 | 215 | @Override 216 | public int sumMatB(int rows, int cols, byte[][] mat) 217 | { 218 | int total = 0; 219 | for (int y = 0; y < rows; ++y) 220 | { 221 | for (int i : mat[y]) 222 | total += i; 223 | } 224 | return total; 225 | } 226 | 227 | @Override 228 | public int sumMatBPtrPtr(int rows, int cols, byte[][] mat) { 229 | return sumMatB(rows, cols, mat); 230 | } 231 | 232 | @Override 233 | public int cstringLength(String s) 234 | { 235 | return s.length(); 236 | } 237 | 238 | @Override 239 | public String mallocString(String origString) { 240 | return new String(origString); 241 | } 242 | 243 | @Override 244 | public MemorySegment mallocDoubles(int count) { 245 | return null; 246 | } 247 | 248 | @Override 249 | public void freeMemory(MemorySegment address) { 250 | } 251 | 252 | @Override 253 | public boolean hasMethod(String name) { 254 | return true; 255 | } 256 | 257 | public void readPointer(Pointer[] val, long set) 258 | { 259 | val[0] = new Pointer(MemorySegment.ofAddress(set)); 260 | } 261 | public Pointer getPointer(Pointer[] val, long set) 262 | { 263 | readPointer(val, set); 264 | return val[0]; 265 | } 266 | 267 | public int swapStrings(@RefArg String[] vals, int i, int j) 268 | { 269 | var s = vals[i]; 270 | vals[i] = vals[j]; 271 | vals[j] = s; 272 | return vals[i].length() + vals[j].length(); 273 | } 274 | 275 | public int fillChars(Arena a, MemoryBlock fillThis, int sizemax) 276 | { 277 | String s= "hello world"; 278 | fillThis.setString(s); 279 | return s.length(); 280 | } 281 | public int passChars(char[] fillThis, int sizemax) 282 | { 283 | int s = 0; 284 | for (char c : fillThis) 285 | s += c; 286 | return s; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/TestJPassport.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport.test; 13 | 14 | import java.lang.foreign.Arena; 15 | import java.util.stream.IntStream; 16 | 17 | import jpassport.MemoryBlock; 18 | import jpassport.PassportBuilder; 19 | import jpassport.Pointer; 20 | import org.junit.jupiter.api.BeforeAll; 21 | import jpassport.PassportFactory; 22 | 23 | import org.junit.jupiter.api.Test; 24 | 25 | import static jpassport.test.TestLinkHelp.getLibName; 26 | import static org.junit.jupiter.api.Assertions.*; 27 | 28 | 29 | public class TestJPassport 30 | { 31 | static TestLink testClass[]; 32 | 33 | @BeforeAll 34 | public static void startup() throws Throwable 35 | { 36 | System.setProperty("jpassport.build.home", "out/testing"); 37 | System.setProperty("jna.library.path", System.getProperty("java.library.path")); 38 | testClass = new TestLink[] {PassportFactory.link(getLibName(), TestLink.class), 39 | PassportFactory.proxy(getLibName(), TestLink.class)}; 40 | 41 | new PassportBuilder(TestLink.class, "none.none", "testlinkImpl"); 42 | } 43 | 44 | @Test 45 | public void testNoPresent() 46 | { 47 | for (TestLink testLink : testClass) { 48 | assertFalse(testLink.hasMethod("functionDoesNotExist")); 49 | assertThrows(Error.class, () -> testLink.functionDoesNotExist(1)); 50 | } 51 | } 52 | @Test 53 | public void testAllocString() 54 | { 55 | for (TestLink testLink : testClass) { 56 | String orig = "hello"; 57 | String ret = testLink.mallocString(orig); 58 | assertEquals(orig, ret); 59 | } 60 | } 61 | 62 | @Test 63 | public void testNulls() 64 | { 65 | for (TestLink testFL : testClass) { 66 | assertNull(testFL.mallocString(null)); 67 | assertEquals(0, testFL.sumArrD(null, 10)); 68 | assertTrue(TestLinkHelp.testMallocDouble(testFL)); 69 | } 70 | } 71 | 72 | @Test 73 | public void testD() 74 | { 75 | for (TestLink testFL : testClass) { 76 | assertEquals(4 + 5, testFL.sumD(4, 5)); 77 | assertEquals(1 + 2 + 3, testFL.sumArrD(new double[]{1, 2, 3}, 3)); 78 | assertEquals(1 + 2 + 3 + 4 + 5 + 6, testFL.sumArrDD(new double[]{1, 2, 3}, new double[]{4, 5, 6}, 3)); 79 | 80 | double[] v = new double[1]; 81 | testFL.readD(v, 5); 82 | assertEquals(5, v[0]); 83 | } 84 | } 85 | 86 | @Test 87 | public void testF() 88 | { 89 | for (TestLink testFL : testClass) { 90 | assertEquals(1 + 2 + 3, testFL.sumArrF(new float[]{1, 2, 3}, 3)); 91 | 92 | float[] v = new float[1]; 93 | testFL.readF(v, 5); 94 | assertEquals(5, v[0]); 95 | } 96 | } 97 | 98 | 99 | @Test 100 | public void testL() 101 | { 102 | for (TestLink testFL : testClass) { 103 | assertEquals(1 + 2 + 3, testFL.sumArrL(new long[]{1, 2, 3}, 3)); 104 | 105 | long[] v = new long[1]; 106 | testFL.readL(v, 5); 107 | assertEquals(5, v[0]); 108 | } 109 | } 110 | 111 | 112 | @Test 113 | public void testI() 114 | { 115 | int[] testRange = IntStream.range(1, 5).toArray(); 116 | int correct = IntStream.range(1, 5).sum(); 117 | 118 | for (TestLink testFL : testClass) { 119 | assertEquals(correct, testFL.sumArrI(testRange, testRange.length)); 120 | 121 | int[] v = new int[1]; 122 | testFL.readI(v, 5); 123 | assertEquals(5, v[0]); 124 | } 125 | } 126 | 127 | 128 | @Test 129 | public void testS() 130 | { 131 | for (TestLink testFL : testClass) { 132 | assertEquals(1 + 2 + 3, testFL.sumArrS(new short[]{1, 2, 3}, (short) 3)); 133 | 134 | short[] v = new short[1]; 135 | testFL.readS(v, (short) 5); 136 | assertEquals(5, v[0]); 137 | } 138 | } 139 | 140 | 141 | @Test 142 | public void testB() 143 | { 144 | for (TestLink testFL : testClass) { 145 | assertEquals(1 + 2 + 3, testFL.sumArrB(new byte[]{1, 2, 3}, (byte) 3)); 146 | 147 | byte[] v = new byte[1]; 148 | testFL.readB(v, (byte) 5); 149 | assertEquals(5, v[0]); 150 | } 151 | } 152 | 153 | @Test 154 | public void testSumMatD() 155 | { 156 | for (TestLink testFL : testClass) { 157 | double[][] mat = new double[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}; 158 | int correct = IntStream.range(1, 13).sum(); 159 | assertEquals(correct, testFL.sumMatD(mat.length, mat[0].length, mat)); 160 | assertEquals(correct, testFL.sumMatDPtrPtr(mat.length, mat[0].length, mat)); 161 | } 162 | } 163 | 164 | @Test 165 | public void testSumMatF() 166 | { 167 | for (TestLink testFL : testClass) { 168 | float[][] mat = new float[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}; 169 | int correct = IntStream.range(1, 13).sum(); 170 | assertEquals(correct, testFL.sumMatF(mat.length, mat[0].length, mat)); 171 | assertEquals(correct, testFL.sumMatFPtrPtr(mat.length, mat[0].length, mat)); 172 | } 173 | } 174 | 175 | @Test 176 | public void testSumMatL() 177 | { 178 | for (TestLink testFL : testClass) { 179 | long[][] mat = new long[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}; 180 | int correct = IntStream.range(1, 13).sum(); 181 | assertEquals(correct, testFL.sumMatL(mat.length, mat[0].length, mat)); 182 | assertEquals(correct, testFL.sumMatLPtrPtr(mat.length, mat[0].length, mat)); 183 | } 184 | } 185 | 186 | @Test 187 | public void testSumMatI() 188 | { 189 | for (TestLink testFL : testClass) { 190 | int[][] mat = new int[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}; 191 | int correct = IntStream.range(1, 13).sum(); 192 | assertEquals(correct, testFL.sumMatI(mat.length, mat[0].length, mat)); 193 | assertEquals(correct, testFL.sumMatIPtrPtr(mat.length, mat[0].length, mat)); 194 | } 195 | } 196 | 197 | @Test 198 | public void testSumMatS() 199 | { 200 | for (TestLink testFL : testClass) { 201 | short[][] mat = new short[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}; 202 | int correct = IntStream.range(1, 13).sum(); 203 | assertEquals(correct, testFL.sumMatS(mat.length, mat[0].length, mat)); 204 | assertEquals(correct, testFL.sumMatSPtrPtr(mat.length, mat[0].length, mat)); 205 | } 206 | } 207 | 208 | @Test 209 | public void testSumMatB() 210 | { 211 | for (TestLink testFL : testClass) { 212 | byte[][] mat = new byte[][]{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}}; 213 | int correct = IntStream.range(1, 13).sum(); 214 | assertEquals(correct, testFL.sumMatB(mat.length, mat[0].length, mat)); 215 | assertEquals(correct, testFL.sumMatBPtrPtr(mat.length, mat[0].length, mat)); 216 | } 217 | } 218 | 219 | @Test 220 | public void testStrLen() 221 | { 222 | for (TestLink testFL : testClass) { 223 | assertEquals(5, testFL.cstringLength("12345")); 224 | } 225 | } 226 | 227 | // @Test 228 | // public void testReturnPointer() 229 | // { 230 | // TestLink.calling(testFL); 231 | // } 232 | 233 | @Test 234 | public void testPointerPassing() 235 | { 236 | 237 | for (TestLink testFL : testClass) { 238 | var pt = new Pointer[1]; 239 | pt[0] = new Pointer(); 240 | 241 | testFL.readPointer(pt, 5); 242 | assertEquals(5, pt[0].getPtr().address()); 243 | 244 | try (var scope = Arena.ofConfined();) { 245 | var mem = scope.allocate(8); 246 | pt[0] = new Pointer(); 247 | var ret = testFL.getPointer (pt, mem.address()); 248 | 249 | assertEquals(mem.address(), pt[0].getPtr().address()); 250 | assertEquals(mem.address(), ret.getPtr().address()); 251 | 252 | } 253 | } 254 | } 255 | 256 | @Test 257 | public void testStringArr() 258 | { 259 | for (TestLink testFL : testClass) { 260 | String[] var= new String[] {"hello", "Goodbye"}; 261 | 262 | var len = testFL.swapStrings(var, 0, 1); 263 | assertEquals(var[0].length() + var[1].length(), len); 264 | assertEquals("hello".length(), var[1].length()); 265 | assertEquals("Goodbye".length(), var[0].length()); 266 | } 267 | } 268 | 269 | @Test 270 | public void testCharArgs() 271 | { 272 | String expected = "hello world"; 273 | 274 | for (TestLink testFL : testClass) { 275 | MemoryBlock fill = new MemoryBlock(100); 276 | try (Arena a = Arena.ofConfined()) { 277 | int ll = testFL.fillChars(a, fill, (int) fill.size()); 278 | assertEquals(expected.length(), ll); 279 | assertEquals(expected, fill.toString()); 280 | 281 | int s = 0; 282 | for (char c : expected.toCharArray()) 283 | s += c; 284 | 285 | assertEquals(s, testFL.passChars(fill.toString().toCharArray(), (int) expected.length() * 2)); 286 | } 287 | } 288 | 289 | } 290 | 291 | } 292 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/TestLink.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport.test; 13 | 14 | import com.sun.jna.Library; 15 | import jpassport.MemoryBlock; 16 | import jpassport.Passport; 17 | import jpassport.Pointer; 18 | import jpassport.annotations.NotRequired; 19 | import jpassport.annotations.PtrPtrArg; 20 | import jpassport.annotations.RefArg; 21 | 22 | import java.lang.foreign.Arena; 23 | import java.lang.foreign.MemorySegment; 24 | 25 | 26 | public interface TestLink extends Passport, Library { 27 | 28 | default double SUMD(double d, double d2) 29 | { 30 | return this.sumD(d, d2); 31 | } 32 | 33 | @NotRequired 34 | void functionDoesNotExist(double v); 35 | 36 | double sumD(double d, double d2); 37 | double sumArrD(double[] d, int len); 38 | double sumArrDD(double[] d, double[] d2, int len); 39 | void readD(@RefArg double[] d, int set); 40 | 41 | float sumArrF(float[] i, int len); 42 | void readF(@RefArg float[] d, float set); 43 | 44 | long sumArrL(long[] i, long len); 45 | void readL(@RefArg long[] d, long set); 46 | 47 | int sumArrI(int[] i, int len); 48 | void readI(@RefArg int[] d, int set); 49 | 50 | short sumArrS(short[] i, short len); 51 | void readS(@RefArg short[] d, short set); 52 | 53 | byte sumArrB(byte[] i, byte len); 54 | void readB(@RefArg byte[] d, byte set); 55 | 56 | double sumMatD(int rows, int cols, double[][] mat); 57 | double sumMatDPtrPtr(int rows, int cols, @PtrPtrArg double[][] mat); 58 | float sumMatF(int rows, int cols, float[][] mat); 59 | float sumMatFPtrPtr(int rows, int cols, @PtrPtrArg float[][] mat); 60 | 61 | long sumMatL(int rows, int cols, long[][] mat); 62 | long sumMatLPtrPtr(int rows, int cols, @PtrPtrArg long[][] mat); 63 | int sumMatI(int rows, int cols, int[][] mat); 64 | int sumMatIPtrPtr(int rows, int cols, @PtrPtrArg int[][] mat); 65 | int sumMatS(int rows, int cols, short[][] mat); 66 | int sumMatSPtrPtr(int rows, int cols, @PtrPtrArg short[][] mat); 67 | int sumMatB(int rows, int cols, byte[][] mat); 68 | int sumMatBPtrPtr(int rows, int cols, @PtrPtrArg byte[][] mat); 69 | 70 | int cstringLength(String s); 71 | 72 | String mallocString(String origString); 73 | MemorySegment mallocDoubles(int count); 74 | void freeMemory(MemorySegment address); 75 | 76 | void readPointer(@RefArg Pointer[] val, long set); 77 | Pointer getPointer(@RefArg Pointer[] val, long set); 78 | 79 | int swapStrings(@RefArg String[] vals, int i, int j); 80 | 81 | int fillChars(Arena a, MemoryBlock fillThis, int sizemax); 82 | int passChars(char[] fillThis, int sizemax); 83 | 84 | // static void calling(TestLink tl) 85 | // { 86 | // double[] values = new double[5]; 87 | // MemoryAddress address = tl.mallocDoubles(values.length); 88 | // MemorySegment segment = address.asSegmentRestricted(values.length * Double.BYTES); 89 | // Utils.toArr(values, segment); 90 | // 91 | // assertArrayEquals(new double[] {0, 1, 2, 3, 4}, values); 92 | // 93 | // tl.freeMemory(address); 94 | // } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/TestLinkHelp.java: -------------------------------------------------------------------------------- 1 | package jpassport.test; 2 | 3 | 4 | import java.lang.foreign.MemorySegment; 5 | import java.util.Locale; 6 | 7 | public class TestLinkHelp { 8 | 9 | public static boolean testMallocDouble(TestLink link) 10 | { 11 | return MemorySegment.NULL.equals(link.mallocDoubles(0)); 12 | } 13 | 14 | 15 | public static String getLibName() 16 | { 17 | if (System.getProperty("os.name").equalsIgnoreCase("linux")) 18 | return "libpassport_test.so"; 19 | if (System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows")) 20 | return "libpassport_test"; 21 | 22 | throw new IllegalArgumentException("Unknown OS: " + System.getProperty("os.name")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/TestLinkJNADirect.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport.test; 13 | 14 | import com.sun.jna.Native; 15 | import jpassport.MemoryBlock; 16 | import jpassport.Pointer; 17 | import jpassport.annotations.RefArg; 18 | import jpassport.test.performance.PerfTest; 19 | 20 | import java.lang.foreign.Arena; 21 | import java.lang.foreign.MemorySegment; 22 | 23 | public class TestLinkJNADirect 24 | { 25 | public static native double sumD(double d, double d2); 26 | public static native double sumArrD(double[] d, int len); 27 | public static native double sumArrDD(double[] d, double[] dd, int len); 28 | public static native void readD(double[] d, int set); 29 | 30 | public static native float sumArrF(float[] d, int len); 31 | public static native void readF(float[] d, float set); 32 | 33 | public static native long sumArrL(long[] i, long len); 34 | public static native void readL(long[] d, long set); 35 | 36 | public static native int sumArrI(int[] i, int len); 37 | public static native void readI(int[] d, int set); 38 | 39 | public static native short sumArrS(short[] i, short len); 40 | public static native void readS(short[] d, short set); 41 | 42 | public static native byte sumArrB(byte[] i, byte len); 43 | public static native void readB(byte[] d, byte set); 44 | // public static native int[] mallocInts(int count); 45 | 46 | static 47 | { 48 | Native.register("passport_test"); 49 | } 50 | 51 | public static class JNADirect implements TestLink, PerfTest { 52 | @Override 53 | public void functionDoesNotExist(double v) { 54 | 55 | } 56 | 57 | 58 | 59 | @Override 60 | public double sumD(double d, double d2) { 61 | return TestLinkJNADirect.sumD(d, d2); 62 | } 63 | 64 | @Override 65 | public double sumArrD(double[] d, int len) { 66 | return TestLinkJNADirect.sumArrD(d, len); 67 | } 68 | 69 | @Override 70 | public double sumArrDD(double[] d, double[] d2, int len) { 71 | return TestLinkJNADirect.sumArrDD(d, d2, len); 72 | } 73 | 74 | @Override 75 | public void readD(double[] d, int set) { 76 | TestLinkJNADirect.readD(d, set); 77 | } 78 | 79 | @Override 80 | public float sumArrF(float[] i, int len) { 81 | return TestLinkJNADirect.sumArrF(i, len); 82 | } 83 | 84 | @Override 85 | public void readF(float[] d, float set) { 86 | TestLinkJNADirect.readF(d, set); 87 | } 88 | 89 | @Override 90 | public long sumArrL(long[] i, long len) { 91 | return TestLinkJNADirect.sumArrL(i, len); 92 | } 93 | 94 | @Override 95 | public void readL(long[] d, long set) { 96 | TestLinkJNADirect.readL(d, set); 97 | } 98 | 99 | @Override 100 | public int sumArrI(int[] i, int len) { 101 | return TestLinkJNADirect.sumArrI(i, len); 102 | } 103 | 104 | @Override 105 | public void readI(int[] d, int set) { 106 | TestLinkJNADirect.readI(d, set); 107 | } 108 | 109 | @Override 110 | public short sumArrS(short[] i, short len) { 111 | return TestLinkJNADirect.sumArrS(i, len); 112 | } 113 | 114 | @Override 115 | public void readS(short[] d, short set) { 116 | TestLinkJNADirect.readS(d, set); 117 | } 118 | 119 | @Override 120 | public byte sumArrB(byte[] i, byte len) { 121 | return TestLinkJNADirect.sumArrB(i, len); 122 | } 123 | 124 | @Override 125 | public void readB(byte[] d, byte set) { 126 | TestLinkJNADirect.readB(d, set); 127 | } 128 | 129 | @Override 130 | public double sumMatD(int rows, int cols, double[][] mat) { 131 | return 0; 132 | } 133 | 134 | @Override 135 | public double sumMatDPtrPtr(int rows, int cols, double[][] mat) { 136 | return 0; 137 | } 138 | 139 | @Override 140 | public float sumMatF(int rows, int cols, float[][] mat) { 141 | return 0; 142 | } 143 | 144 | @Override 145 | public float sumMatFPtrPtr(int rows, int cols, float[][] mat) { 146 | return 0; 147 | } 148 | 149 | @Override 150 | public long sumMatL(int rows, int cols, long[][] mat) { 151 | return 0; 152 | } 153 | 154 | @Override 155 | public long sumMatLPtrPtr(int rows, int cols, long[][] mat) { 156 | return 0; 157 | } 158 | 159 | @Override 160 | public int sumMatI(int rows, int cols, int[][] mat) { 161 | return 0; 162 | } 163 | 164 | @Override 165 | public int sumMatIPtrPtr(int rows, int cols, int[][] mat) { 166 | return 0; 167 | } 168 | 169 | @Override 170 | public int sumMatS(int rows, int cols, short[][] mat) { 171 | return 0; 172 | } 173 | 174 | @Override 175 | public int sumMatSPtrPtr(int rows, int cols, short[][] mat) { 176 | return 0; 177 | } 178 | 179 | @Override 180 | public int sumMatB(int rows, int cols, byte[][] mat) { 181 | return 0; 182 | } 183 | 184 | @Override 185 | public int sumMatBPtrPtr(int rows, int cols, byte[][] mat) { 186 | return 0; 187 | } 188 | 189 | @Override 190 | public int cstringLength(String s) { 191 | return 0; 192 | } 193 | 194 | @Override 195 | public String mallocString(String orig) { 196 | return new String(orig); 197 | } 198 | 199 | @Override 200 | public MemorySegment mallocDoubles(int count) { 201 | return null; 202 | } 203 | 204 | @Override 205 | public void freeMemory(MemorySegment address) { 206 | 207 | } 208 | 209 | @Override 210 | public boolean hasMethod(String name) { 211 | return true; 212 | } 213 | 214 | @Override 215 | public Object readStruct(MemorySegment segment, Object rec) { 216 | return null; 217 | } 218 | 219 | public void readPointer(Pointer[] val, long set) 220 | {} 221 | 222 | public Pointer getPointer(Pointer[] val, long set) 223 | { 224 | return null; 225 | } 226 | 227 | public int swapStrings(@RefArg String[] vals, int i, int j) 228 | { 229 | return 0; 230 | } 231 | 232 | public int fillChars(Arena a, MemoryBlock fillThis, int sizemax) 233 | { 234 | String s= "hello world"; 235 | fillThis.setString(s); 236 | return s.length(); 237 | } 238 | public int passChars(char[] fillThis, int sizemax) 239 | { 240 | int s = 0; 241 | for (char c : fillThis) 242 | s += c; 243 | return s; 244 | } 245 | 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/callback/CallbackNative.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.callback; 2 | 3 | import jpassport.FunctionPtr; 4 | import jpassport.Passport; 5 | 6 | import java.lang.foreign.MemorySegment; 7 | 8 | public interface CallbackNative extends Passport { 9 | int call_CB(FunctionPtr fn, int v, double v2); 10 | void call_CBArr(FunctionPtr fn, int[] vals, int count); 11 | } 12 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/callback/CallbackObj.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.callback; 2 | 3 | import jpassport.FunctionPtr; 4 | import jpassport.PassportFactory; 5 | import jpassport.Utils; 6 | 7 | import java.lang.foreign.MemorySegment; 8 | import java.lang.foreign.ValueLayout; 9 | import java.util.Arrays; 10 | 11 | public class CallbackObj { 12 | public int calls = 0; 13 | 14 | public int callback(int n, double m) { 15 | calls++; 16 | return (int) (n + m); 17 | } 18 | 19 | public FunctionPtr getAsFunctionPtr() 20 | { 21 | return PassportFactory.createCallback(this, "callback"); 22 | } 23 | 24 | public int sum = 0; 25 | 26 | // public void callbackArr(MemorySegment ptr, int count) { 27 | // var vals = Utils.toArr(ValueLayout.JAVA_INT, ptr, ptr.address(), count); 28 | // sum = Arrays.stream(vals).sum(); 29 | // } 30 | 31 | public void callbackArr(MemorySegment ptr, int count) { 32 | var vals = Utils.toArr(ValueLayout.JAVA_INT, ptr, count); 33 | sum = Arrays.stream(vals).sum(); 34 | } 35 | 36 | public FunctionPtr getAsFunctionArrPtr() 37 | { 38 | return PassportFactory.createCallback(this, "callbackArr"); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/callback/TestCallback.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.callback; 2 | 3 | import jpassport.PassportFactory; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | 8 | import static jpassport.test.TestLinkHelp.getLibName; 9 | import static org.junit.jupiter.api.Assertions.assertEquals; 10 | 11 | public class TestCallback { 12 | 13 | @Test 14 | public void testCallback() throws Throwable { 15 | var callBack = PassportFactory.link(getLibName(), CallbackNative.class); 16 | 17 | var myCB = new CallbackObj(); 18 | 19 | int ret = callBack.call_CB(myCB.getAsFunctionPtr(), 5, 1); 20 | assertEquals(5, myCB.calls); 21 | assertEquals(30, ret); 22 | 23 | int[] test = {1, 2, 3 ,4 ,5}; 24 | callBack.call_CBArr(myCB.getAsFunctionArrPtr(), test, test.length); 25 | assertEquals(Arrays.stream(test).sum(), myCB.sum); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/performance/JPassportMicroBenchmark.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.performance; 2 | 3 | import com.sun.jna.Native; 4 | import jpassport.PassportFactory; 5 | import jpassport.test.PureJava; 6 | import jpassport.test.TestLink; 7 | import jpassport.test.TestLinkJNADirect; 8 | import org.openjdk.jmh.annotations.*; 9 | import org.openjdk.jmh.runner.Runner; 10 | import org.openjdk.jmh.runner.options.Options; 11 | import org.openjdk.jmh.runner.options.OptionsBuilder; 12 | 13 | 14 | import java.util.stream.IntStream; 15 | 16 | @State(Scope.Benchmark) 17 | public class JPassportMicroBenchmark 18 | { 19 | public static void main(String[] args) throws Exception { 20 | Options opt = new OptionsBuilder() 21 | .include(JPassportMicroBenchmark.class.getSimpleName()) 22 | .forks(1) 23 | .build(); 24 | 25 | new Runner(opt).run(); 26 | } 27 | 28 | static TestLink testFL; 29 | static TestLink testJNA; 30 | static TestLink testJNADirect; 31 | static TestLink testJava; 32 | 33 | @Param({"1024", "2048", "16384", "262144"}) 34 | public int array_size; 35 | 36 | public double[] test_arr; 37 | 38 | @Setup(Level.Trial) 39 | public void updateArray() 40 | { 41 | test_arr = IntStream.range(0, array_size).mapToDouble(i -> i).toArray(); 42 | 43 | } 44 | 45 | 46 | @Setup() 47 | public void setUp() throws Throwable 48 | { 49 | System.setProperty("jna.library.path", System.getProperty("java.library.path")); 50 | testFL = PassportFactory.link("libforeign_link", TestLink.class); 51 | testJNA = Native.load("libforeign_link.dll", TestLink.class); 52 | testJNADirect = new TestLinkJNADirect.JNADirect(); 53 | testJava = new PureJava(); 54 | } 55 | 56 | 57 | @Benchmark 58 | @Fork(value = 2, warmups = 1) 59 | public void sumTestArrDJava() 60 | { 61 | testJava.sumArrD(test_arr, test_arr.length); 62 | } 63 | 64 | @Benchmark 65 | @Fork(value = 2, warmups = 1) 66 | public void sumTestArrDJNA() 67 | { 68 | testJNA.sumArrD(test_arr, test_arr.length); 69 | } 70 | 71 | @Benchmark 72 | @Fork(value = 2, warmups = 1) 73 | public void sumTestArrDJNADirect() 74 | { 75 | testJNADirect.sumArrD(test_arr, test_arr.length); 76 | } 77 | 78 | @Benchmark 79 | @Fork(value = 2, warmups = 1) 80 | public void sumTestArrDJPassport() 81 | { 82 | testFL.sumArrD(test_arr, test_arr.length); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/performance/PerfTest.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport.test.performance; 13 | 14 | import com.sun.jna.Library; 15 | import jpassport.Passport; 16 | import jpassport.annotations.Critical; 17 | 18 | public interface PerfTest extends Passport, Library { 19 | @Critical 20 | double sumD(double d, double d2); 21 | @Critical 22 | double sumArrD(double[] d, int len); 23 | float sumArrF(float[] d, int len); 24 | int sumArrI(int[] d, int len); 25 | } 26 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/performance/PerformanceTest.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport.test.performance; 13 | 14 | import com.sun.jna.Native; 15 | import jpassport.PassportFactory; 16 | import jpassport.test.TestLinkJNADirect; 17 | import jpassport.test.util.CSVOutput; 18 | 19 | import java.io.IOException; 20 | import java.nio.file.Path; 21 | import java.util.Arrays; 22 | import java.util.stream.IntStream; 23 | 24 | 25 | public class PerformanceTest 26 | { 27 | static PerfTest testFL; 28 | static PerfTest testFLP; 29 | static PerfTest testJNA; 30 | static PerfTest testJNADirect; 31 | static PerfTest testJava; 32 | 33 | 34 | public static void startup() throws Throwable 35 | { 36 | System.setProperty("jpassport.build.home", "out/testing"); 37 | System.setProperty("jna.library.path", System.getProperty("java.library.path")); 38 | testFL = PassportFactory.link("libpassport_test", PerfTest.class); 39 | testFLP = PassportFactory.proxy("libpassport_test", PerfTest.class); 40 | testJNA = Native.load("passport_test", PerfTest.class); 41 | testJNADirect = new TestLinkJNADirect.JNADirect(); 42 | testJava = new PureJavaPerf(); 43 | } 44 | 45 | public static void main(String[] str) throws Throwable 46 | { 47 | startup(); 48 | 49 | PerfTest[] tests = new PerfTest[] {testJava, testJNA, testJNADirect, testFL, testFLP}; 50 | 51 | try(var csv = new CSVOutput(Path.of("performance", "doubles_add_2.csv"))) 52 | { 53 | csv.add("iteration", "pure java", "JNA", "JNA Direct", "JPassport", "Proxy").endLine(); 54 | 55 | for (int loops = 1000; loops < 100000; loops += 1000) { 56 | 57 | double[][] results = new double[5][5]; 58 | for (int n = 0; n < 5; ++n) { 59 | for (int m = 0; m < tests.length; ++m) 60 | results[m][n] = sumTest(tests[m], loops); 61 | } 62 | 63 | csv.addF(loops); 64 | for (double[] arr : results) 65 | { 66 | Arrays.sort(arr); 67 | csv.addF(arr[arr.length/2]); 68 | } 69 | csv.endLine(); 70 | System.out.println("loops: " + loops); 71 | } 72 | } 73 | catch (IOException ex) 74 | { 75 | ex.printStackTrace(); 76 | } 77 | 78 | try(var csv = new CSVOutput(Path.of("performance", "double_arr_add.csv"))) 79 | { 80 | csv.add("array size", "pure java", "JNA", "JNA Direct", "JPassport", "Proxy").endLine(); 81 | for (int size = 1024; size <= 1024*256; size += 1024) 82 | { 83 | double[][] results = new double[5][5]; 84 | 85 | for (int n = 0; n < 5; ++n) { 86 | for (int m = 0; m < tests.length; ++m) 87 | results[m][n] = sumTestArrD(tests[m], 100, size);; 88 | } 89 | 90 | csv.addF(size); 91 | for (double[] arr : results) 92 | { 93 | Arrays.sort(arr); 94 | csv.addF(arr[arr.length/2]); 95 | } 96 | csv.endLine(); 97 | System.out.println("array size: " + size); 98 | } 99 | } 100 | catch (IOException ex) 101 | { 102 | ex.printStackTrace(); 103 | } 104 | 105 | } 106 | 107 | 108 | static double sumTest(PerfTest testLib, int count) { 109 | long start = System.nanoTime(); 110 | double m = 0; 111 | for (double n = 0; n < count; ++n) { 112 | m = testLib.sumD(n, m); 113 | } 114 | return (System.nanoTime() - start) / 1e9; 115 | } 116 | 117 | static double sumTestArrD(PerfTest testLib, int count, int arrSize) { 118 | double[] d = IntStream.range(0, arrSize).mapToDouble(i -> i).toArray(); 119 | long start = System.nanoTime(); 120 | double m = 0; 121 | for (double n = 0; n < count; ++n) { 122 | m = testLib.sumArrD(d, arrSize); 123 | } 124 | return (System.nanoTime() - start) / 1e9; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/performance/PureJavaPerf.java: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2021 Duncan McLean, All Rights Reserved 2 | * 3 | * The contents of this file is dual-licensed under the 4 | * Apache License 2.0. 5 | * 6 | * You may obtain a copy of the Apache License at: 7 | * 8 | * http://www.apache.org/licenses/ 9 | * 10 | * A copy is also included in the downloadable source code. 11 | */ 12 | package jpassport.test.performance; 13 | 14 | import jpassport.test.structs.TestStruct; 15 | 16 | public class PureJavaPerf implements PerfTest{ 17 | @Override 18 | public double sumD(double d, double d2) { 19 | return d + d2; 20 | } 21 | 22 | @Override 23 | public double sumArrD(double[] d, int len) 24 | { 25 | double ret = 0; 26 | for (int n = 0; n < len; ++n) 27 | ret += d[n]; 28 | return ret; 29 | } 30 | 31 | @Override 32 | public int sumArrI(int[] d, int len) 33 | { 34 | int ret = 0; 35 | for (int n = 0; n < len; ++n) 36 | ret += d[n]; 37 | return ret; 38 | } 39 | 40 | @Override 41 | public float sumArrF(float[] d, int len) 42 | { 43 | float ret = 0; 44 | for (int n = 0; n < len; ++n) 45 | ret += d[n]; 46 | return ret; 47 | } 48 | 49 | public double passStruct(TestStruct simpleStruct) { 50 | return simpleStruct.s_int() + simpleStruct.s_long() + simpleStruct.s_float() + simpleStruct.s_double(); 51 | } 52 | 53 | @Override 54 | public boolean hasMethod(String name) { 55 | return true; 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/structs/ComplexStruct.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.structs; 2 | 3 | import jpassport.annotations.Ptr; 4 | 5 | /** 6 | * This record is meant to match the ComplexPassing struct in the C code. 7 | * This is considered complex in that it contains references to other records. 8 | */ 9 | public record ComplexStruct( 10 | int ID, 11 | TestStruct ts, 12 | @Ptr TestStruct tsPtr, 13 | String string) 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/structs/PassingArrays.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.structs; 2 | 3 | import jpassport.annotations.Array; 4 | import jpassport.annotations.Ptr; 5 | 6 | public record PassingArrays( 7 | @Array(length = 5) double[] s_double, 8 | @Array(length = 8) long[] s_long, 9 | long s_doublePtrCount, 10 | @Ptr double[] s_doublePtr, 11 | long s_longPtrCount, 12 | @Ptr long[] s_longPtr) 13 | { 14 | } 15 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/structs/StructWithPrt.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.structs; 2 | 3 | import jpassport.annotations.StructPadding; 4 | 5 | import java.lang.foreign.MemorySegment; 6 | 7 | public record StructWithPrt(@StructPadding(bytes = 4)int n, MemorySegment addr, float f) { 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/structs/TestStruct.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.structs; 2 | 3 | import jpassport.annotations.StructPadding; 4 | 5 | public record TestStruct( 6 | @StructPadding(bytes = 4) int s_int, 7 | long s_long, 8 | @StructPadding(bytes = 4) float s_float, 9 | double s_double) { 10 | } 11 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/structs/TestStructCalls.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.structs; 2 | 3 | import jpassport.Passport; 4 | import jpassport.annotations.NotRequired; 5 | import jpassport.annotations.RefArg; 6 | 7 | public interface TestStructCalls extends Passport { 8 | double passStruct(TestStruct address); 9 | double passComplex(@RefArg ComplexStruct[] complexStruct); 10 | double passStructWithArrays(@RefArg PassingArrays[] arrays); 11 | @NotRequired 12 | void testAddrCall(StructWithPrt test); 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/structs/TestUsingStructs.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.structs; 2 | 3 | import jpassport.PassportFactory; 4 | import jpassport.Utils; 5 | import org.junit.jupiter.api.BeforeAll; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import java.util.stream.IntStream; 9 | 10 | import static java.lang.foreign.MemoryLayout.PathElement.groupElement; 11 | import static java.lang.foreign.ValueLayout.*; 12 | import static jpassport.test.TestLinkHelp.getLibName; 13 | import static org.junit.jupiter.api.Assertions.assertArrayEquals; 14 | import static org.junit.jupiter.api.Assertions.assertEquals; 15 | 16 | public class TestUsingStructs { 17 | 18 | static TestStructCalls PassingStructs; 19 | 20 | @BeforeAll 21 | public static void startup() throws Throwable 22 | { 23 | System.setProperty("jpassport.build.home", "out/testing"); 24 | PassingStructs = PassportFactory.link(getLibName(), TestStructCalls.class); 25 | // PassingStructs = new TestStructCalls_impl(PassportFactory.loadMethodHandles("libpassport_test.dll", TestStructCalls.class)); 26 | } 27 | 28 | @Test 29 | public void testSimpleStruct() 30 | { 31 | assertEquals(4 * JAVA_LONG.byteSize(), Utils.size_of(TestStruct.class)); 32 | assertEquals(JAVA_INT.byteSize() + Utils.size_of(TestStruct.class) + 33 | ADDRESS.byteSize() * 2, Utils.size_of(ComplexStruct.class)); 34 | 35 | 36 | assertEquals(2+3+4+5, PassingStructs.passStruct(new TestStruct(2, 3, 4, 5))); 37 | // long[] times = new long[1000]; 38 | // 39 | // for (int n = 0; n < times.length; n++) 40 | // { 41 | // long t = System.currentTimeMillis(); 42 | // 43 | // for (int m = 0; m < 10000; ++m) 44 | // { 45 | // PassingStructs.passStruct(new TestStruct(m, m+1, m+2, m+3)); 46 | // } 47 | // 48 | // times[n] = System.currentTimeMillis() - t; 49 | // System.out.printf("%d = %d ms\n", n, times[n]); 50 | // } 51 | // 52 | // times = Arrays.copyOfRange(times, 5, times.length); 53 | // System.out.println("Mean = " + Arrays.stream(times).average().getAsDouble()); 54 | } 55 | 56 | @Test 57 | public void testComplexStruct() 58 | { 59 | TestStruct ts = new TestStruct(1, 2, 3, 4); 60 | TestStruct tsPtr = new TestStruct(5, 6, 7, 8); 61 | ComplexStruct[] complex = new ComplexStruct[] {new ComplexStruct(55, ts, tsPtr, "hello")}; 62 | 63 | double d = PassingStructs.passComplex(complex); 64 | assertEquals(IntStream.range(1, 9).sum(), d); 65 | assertEquals(65, complex[0].ID()); 66 | assertEquals(11, complex[0].ts().s_int()); 67 | assertEquals(25, complex[0].tsPtr().s_int()); 68 | assertEquals("HELLO", complex[0].string()); 69 | // 70 | // long[] times = new long[500]; 71 | // 72 | // for (int n = 0; n < times.length; n++) 73 | // { 74 | // long t = System.currentTimeMillis(); 75 | // 76 | // for (int m = 0; m < 10000; ++m) 77 | // { 78 | // complex = new ComplexStruct[] {new ComplexStruct(55, ts, tsPtr, "hello")}; 79 | // PassingStructs.passComplex(complex); 80 | // } 81 | // 82 | // times[n] = System.currentTimeMillis() - t; 83 | // System.out.printf("%d = %d ms\n", n, times[n]); 84 | // } 85 | // 86 | // times = Arrays.copyOfRange(times, 5, times.length); 87 | // System.out.println("Mean = " + Arrays.stream(times).average().getAsDouble()); 88 | } 89 | 90 | @Test 91 | public void testStructsWithArrays() 92 | { 93 | int expected = IntStream.range(1, 21).sum(); 94 | 95 | var doublesArr = new double[] {1, 2, 3, 4, 5}; 96 | var longArr = new long[] {6, 7, 8, 9, 10, 11, 12, 13}; 97 | var doublePtr = new double[] {14, 15, 16}; 98 | var longPtr = new long[] {17,18,19,20}; 99 | 100 | PassingArrays pa = new PassingArrays(doublesArr, longArr, doublePtr.length, doublePtr, longPtr.length, longPtr); 101 | PassingArrays[] regArg = new PassingArrays[] {pa}; 102 | assertEquals(expected, PassingStructs.passStructWithArrays(regArg)); 103 | 104 | assertArrayEquals(new double[] {14,15,16,4,5}, regArg[0].s_double()); 105 | assertArrayEquals(new long[] {6,7,8,9}, regArg[0].s_longPtr()); 106 | assertArrayEquals(longArr, regArg[0].s_long()); 107 | assertArrayEquals(doublePtr, regArg[0].s_doublePtr()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/jpassport/test/util/CSVOutput.java: -------------------------------------------------------------------------------- 1 | package jpassport.test.util; 2 | 3 | import org.apache.commons.csv.CSVFormat; 4 | import org.apache.commons.csv.CSVPrinter; 5 | 6 | import java.io.IOException; 7 | import java.nio.charset.Charset; 8 | import java.nio.file.Files; 9 | import java.nio.file.Path; 10 | import java.util.ArrayList; 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | public class CSVOutput implements AutoCloseable 15 | { 16 | private final List m_curLine = new ArrayList<>(); 17 | private final CSVPrinter m_printer; 18 | 19 | public CSVOutput(Path path) throws IOException 20 | { 21 | if (!Files.exists(path.getParent())) 22 | Files.createDirectories(path.getParent()); 23 | 24 | m_printer = CSVFormat.DEFAULT.print(path, Charset.defaultCharset()); 25 | } 26 | 27 | public void close() throws IOException 28 | { 29 | m_printer.close(); 30 | } 31 | 32 | public CSVOutput add(String ... value) 33 | { 34 | m_curLine.addAll(Arrays.asList(value)); 35 | return this; 36 | } 37 | 38 | public CSVOutput addF(double ...value) 39 | { 40 | Arrays.stream(value).forEach(dd -> m_curLine.add(Double.toString(dd))); 41 | return this; 42 | } 43 | 44 | public CSVOutput endLine() throws IOException 45 | { 46 | m_printer.printRecord(m_curLine); 47 | m_curLine.clear(); 48 | return this; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | module test.passport { 2 | requires jpassport; 3 | requires com.sun.jna; 4 | requires com.sun.jna.platform; 5 | 6 | requires org.junit.jupiter.api; 7 | requires org.junit.platform.engine; 8 | requires jmh.core; 9 | requires jmh.generator.annprocess; 10 | 11 | requires commons.csv; 12 | 13 | exports jpassport.test; 14 | exports jpassport.test.performance; 15 | exports jpassport.test.structs; 16 | exports jpassport.test.callback; 17 | } --------------------------------------------------------------------------------