[HN Gopher] How to Use the Foreign Function API in Java 22 to Ca...
       ___________________________________________________________________
        
       How to Use the Foreign Function API in Java 22 to Call C Libraries
        
       Author : pjmlp
       Score  : 201 points
       Date   : 2024-05-06 08:40 UTC (3 days ago)
        
 (HTM) web link (ifesunmola.com)
 (TXT) w3m dump (ifesunmola.com)
        
       | xyst wrote:
       | What's the use case here? Developing drivers with Java?
        
         | invalidname wrote:
         | Invoking native code has always been necessary in Java. In the
         | past it was done via JNI which has many issues. These new APIs
         | solve the issues and simplify the API. The use case is
         | interacting with anything that isn't written in Java.
        
           | xtracto wrote:
           | Blast from the past! I remember doing JNI integration in Java
           | around 2003! It's been so long I don't remember details but
           | you had to declare some interfaces in java, then some
           | middleware .h or .c and then call the native library iirc.
           | 
           | Glad to see things are progressing!!
        
         | neonsunset wrote:
         | Same use case as to why .NET has low/zero-cost FFI.
         | 
         | This is similar, except more boilerplate and much, much slower.
        
           | pron wrote:
           | The FFM downcalls in OpenJDK compile down to argument
           | shuffling + a CALL instruction (in "critical" linker mode),
           | i.e. the same machine code gcc/clang would generate for a
           | call from a C program.
        
             | neonsunset wrote:
             | This is what it is compiled to in .NET[0] today more or
             | less[1]. What does OpenJDK compile these to? (edit: misread
             | as _could_ compile. Hmm, I wonder how much the difference
             | will there be in average FFI cost with newer APIs vs direct
             | calls)
             | 
             | [0] Objects that need pinning are pinned(by toggling a bit
             | in object header), byrefs are pinned by simply storing them
             | on the stack, arguments that need marshalling involve
             | calling corresponding marshalling code. That code can
             | allocate intermediate data on heap, on stack or call
             | NativeMemory.Alloc/.Free C-style.
             | 
             | [1] Overhead can be further reduced by 1. annotating FFI
             | calls with [SuppressGCTransition] which saves on possible
             | arguments stack spills and GC helper call, replacing the
             | call with a single flag check and optional call into GC in
             | epilog, 2. in NativeAOT, p/invokes can be "direct" which
             | saves on initialization checks and indirections (though
             | they are reduced in JIT as it can bake data directly into
             | codegen after static init has finished on recompilation).
             | This has a tradeoff as system's dynamic loader will be used
             | at application startup instead of regular lazy
             | initialization and 3. direct p/invokes can be upgraded to
             | static linking, which transforms them into direct calls
             | identical to regular C calls save for the same GC flag
             | check in post-condition. This comes with compiling .NET
             | executables and libraries into a single statically linked
             | binary (well, statically linked for the native dependencies
             | the user has opted into linking this way).
        
             | int_19h wrote:
             | What does it do if you need to pass a struct that contains
             | another struct?
        
               | pron wrote:
               | The same, but you need to define the layout
               | appropriately. The JEP covers the basics:
               | https://openjdk.org/jeps/454. As I explained in another
               | comment [1], we didn't want to trade off performance or
               | limit the runtime, so the API for describing native
               | layouts is more elaborate.
               | 
               | [1]: https://news.ycombinator.com/item?id=40303242
        
           | pjmlp wrote:
           | I still have some hopes that it will evolve towards a
           | P/Invoke like experience.
           | 
           | While a step closer to Valhala, the whole dev experience is
           | still quite lacking versus what .NET offers.
           | 
           | Currently is too much like making direct use of
           | InteropServices.
        
             | pron wrote:
             | > I still have some hopes that it will evolve towards a
             | P/Invoke like experience.
             | 
             | Doubtful, given that this is something we worked hard to
             | avoid. To be efficient, a P/Invoke-like model places
             | restrictions on the runtime, which inhibits optimisation
             | and flexibility and this cost is worth it only when native
             | calls are relatively common. In Java they are rare and
             | easily abstracted away, so we opted for a model that offers
             | full control without giving up on abstraction, given that
             | only a very small number of experts (<1%) would directly
             | write native calls and then hide them as implementation
             | details. I'm not saying this approach is the right one for
             | all languages, but it's clearly the right one for Java
             | given the frequency of native calls and who makes them.
             | 
             | Of course, you can wrap FFM with a higher-level P/Invoke-
             | like mechanism, but it won't give you as much control.
        
               | pjmlp wrote:
               | Well, for developers like myself that feel at home with
               | JNI, the current development experience, even with
               | jpackage, is too much to ask for.
               | 
               | I will rather keep writing C++ with JNI, instead of
               | enduring the current boilerplate, specially if I already
               | need to manually create header files to feed into
               | jpackage, for basic stuff like struct definitions, which
               | I don't feel like writing by hand.
               | 
               | As for performance, this is something I agree with
               | neonsunset, unless we see Techpowerbenchmarks level of
               | Panama beating P/Invoke, it is pretty much theoretical
               | stuff at the expense of developer convience.
        
               | _old_dude_ wrote:
               | s/jpackage/jextract/g
        
               | pron wrote:
               | We can't tailor every feature to the widely disparate
               | preferences of so many developers nor do we try to
               | convince every last developer of the merit of our
               | approach -- this is both impractical and a losing
               | strategy. Rather, we rely on our experience designing a
               | highly successful language and platform, and consult with
               | companies -- each employing thousands of Java developers
               | -- and authors of some of the most popular relevant Java
               | libraries to ensure that we meet their requirements. Of
               | course, we also look at what other languages have done
               | and the tradeoffs they've accepted (some of which may not
               | be appropriate for Java [1]), but there are always many
               | possible designs and we don't adopt one from a less
               | successful language _just_ because it, too, has its fans.
               | 
               | I would encourage those who think that we're
               | _consistently_ making suboptimal choices for Java
               | compared to choices made by significantly less successful
               | languages to consider whether it is possible that their
               | preferences are not aligned with those of the software
               | market at large. Java is and aims to continue being the
               | world 's most popular language for serious server
               | software, and that requires tailoring designs to a very
               | large audience.
               | 
               | I always notice a certain lack of respect on forums such
               | as HN for the world's most consistently successful and
               | popular languages -- JS, Java, and Python. Different
               | programmers have different preferences and I'm all for
               | rooting for the underdog now and again, but you simply
               | cannot consistently make wrong decisions over a very long
               | period of time and yet consistently win. What we do may
               | not be everyone's cup of tea (no language is), but it is
               | clearly that of a whole lot of people. We work to offer
               | value to them.
               | 
               | [1]: E.g. the design of native interop has significantly
               | impacted that of user-mode threads (or lack thereof:
               | https://github.com/dotnet/runtimelab/issues/2398) in both
               | .NET and Go, and we weren't willing to make such
               | tradeoffs in either performance or programming model.
        
               | pjmlp wrote:
               | I can say that in my bubble we reach out for Java,
               | because of Spring, AEM and Android.
               | 
               | That is it, other use cases, have other programming
               | stacks.
               | 
               | As such our native libraries are written in consideration
               | to be consumed at very least, across .NET (P/Invoke,
               | C++/CLI, COM), Java (JNI), nodejs (C++ addons), Swift.
               | 
               | So to move the existing development workflow from JNI to
               | Panama, it must be an easy sell why we should budget
               | rewrites to start with.
               | 
               | Also in regards to "hate", if all decisions were that
               | great there wouldn't be needed to create a new library
               | support group to help Java ecosystem actually move
               | forward and adopt new Java versions, as I learned from
               | JFokus related content.
        
               | pron wrote:
               | You shouldn't! We're not trying to "sell" any rewrite
               | from JNI to FFM. Since FFM is both significantly easier
               | to use and offers better performance, most people would
               | choose to write _new_ interop code with FFM; that is an
               | easy sell. But that 's not to say that these benefits
               | justify a rewrite of _existing_ code, and we have no plan
               | to remove JNI. JNI and FFM can coexist in same program
               | (and even in the same class). However, we are about to
               | place the same protections on JNI as those we have on FFM
               | to ensure that Java programs are free of undefined
               | behaviour by default, and that modules that may introduce
               | undefined behaviour are clearly acknowledged by the
               | application so that the application owners may give them
               | closer scrutiny if they wish [1].
               | 
               | To elaborate just a bit more on what I wrote in my
               | previous comment, to get a straightforward interop with C
               | you need to place certain restrictions on the runtime
               | which limit your ability to implement certain
               | abstractions such as moving GCs and user-mode threads.
               | Because native interop requires special care anyway due
               | to native memory management, which makes it significantly
               | more complex than ordinary code and so less suitable for
               | direct exposure to application developers -- so it's best
               | done by experts in the area -- and on top of that native
               | calls in Java aren't common, we decided not to sacrifice
               | the runtime in favour of more direct interop. As a
               | result, native interop is somewhat more elaborate to
               | code, but as it requires some special expertise and so
               | should be hidden away from application developers anyway,
               | we decided it's better to place the extra burden on the
               | experts doing the interop rather than trade off runtime
               | capabilities and performance. We think this is the better
               | tradeoff for Java. Consequently, we have both compacting
               | collectors and no performance penalty for native calls on
               | virtual threads. Other languages made whatever tradeoffs
               | they thought were right for them, but they did very
               | clearly sacrifice something.
               | 
               | [1]: https://openjdk.org/jeps/472
        
               | int_19h wrote:
               | A "certain lack of respect" comes from having to work
               | with these languages for literally decades, and knowing
               | their warts (and how those warts compare to some other
               | similarly popular languages).
               | 
               | In general, being successful and popular had little to do
               | with how well a PL is designed. Visual Basic, PHP, and
               | even C are some historical examples that I have plenty of
               | personal experience with.
        
               | pron wrote:
               | > In general, being successful and popular had little to
               | do with how well a PL is designed.
               | 
               | Perhaps, but it is fairly easy to design a product for a
               | small, self-selecting group of fans who find the
               | aesthetics appealing and so declare the design good for
               | their taste. Unless a language becomes heavily used in
               | codebases that are maintained for years by a large
               | variety of programmers, it's hard to tell how well it is
               | actually designed as a mass-appeal product.
               | 
               | Two of the three languages you mentioned weren't able to
               | attain nearly the same success as Java for as long a
               | duration. I'd give C a similar success score because what
               | it lacks in popularity it still makes up for in
               | longevity, being almost twice as old. There are good
               | reasons for why C is still as popular as it is. For
               | example, in its domain -- which requires compilation to
               | exotic architectures -- "good design" entails being able
               | to easily implement efficient compilers.
        
               | int_19h wrote:
               | Of course, when you compare languages, you have to
               | compare them to contemporary ones that also target the
               | same niche. In case of C, that would be e.g. Modula-2.
               | The consequences of the industry making an expedient but
               | wrong choice then - 45 years ago! - are still with us:
               | C++ only just got proper modules, and even then most C++
               | code written today is still mostly using #include...
               | 
               | And to be clear, I'm not advocating for aesthetics here.
               | It's not like C# is a model of purity, either; but I
               | would say that their choices over the years have been
               | more pragmatic overall from the perspective of someone
               | who needs to write readable, good quality code without
               | jumping through too many hoops or getting lost in the
               | verbiage.
        
               | kaba0 wrote:
               | > C# [..] but I would say that their choices over the
               | years have been more pragmatic overall from the
               | perspective of someone who needs to write readable, good
               | quality code without jumping through too many hoops or
               | getting lost in the verbiage.
               | 
               | I personally don't agree with that, C# is very
               | "impulsive" at adding new features, which sounds cool in
               | isolation, but makes the language significantly more
               | complex to understand, and has non-intuitive interactions
               | with other features.
               | 
               | I think C# is quick at going the C++ way, and there is no
               | return from there if we guarantee compatibility.
               | 
               | I much prefer Java's approach, where yeah, at times one
               | might lack some syntactic sugar/nicety (often greatly
               | overcome by IDE/tooling's advancements), but over time
               | they do add important ones, but only commit to features
               | that have been earnestly tried and sustainable.
        
               | pron wrote:
               | Modula-2 wasn't really a contemporary of C's. By the time
               | it was released, C had already taken over the world.
               | Plus, it's yet another case of something that looks good
               | but has never really been tested. While not quite
               | Modula-2, in the early oughts I was working on a large
               | project that was half written in C++ and half in Ada.
               | We're talking millions of lines of code in both languages
               | here. The Ada code looked nice but we were cursing when
               | we had to work with it for two reasons: we had to consult
               | thick Ada manuals to grapple with language intricacies,
               | and compilation times were frustratingly slow. With C++
               | we could spend more time thinking about the algorithms as
               | there was less "language lawyering", and we could run
               | more tests (ironically, C++ now suffers from both of
               | these problems). Perhaps that's why to this day I prefer
               | smaller languages with short compilation times (I like
               | Clojure but dislike Scala; I like Zig but dislike Rust).
               | 
               | My point is that when people say that one language is
               | technically superior to another, what they really mean is
               | that it's superior in the technical aspects that _they
               | themselves value_ more than the aspects where the other
               | language is technically superior. This is all fine,
               | except that these personal preferences aren 't
               | distributed equally. This is a little like the Betamax
               | vs. VHS debate. Sure, Betamax had a superior picture
               | quality that some valued, but VHS had a superior
               | recording time, which others valued but that latter group
               | was bigger.
               | 
               | As for C# -- strong disagree there. I think they're
               | making the classic mistake of trying to solve every
               | problem in the language and soon, resulting in a pretty
               | haphazard collection of features, quite a few of them are
               | anti-features, making up a pretty complicated language.
               | For example, they have both properties and records, while
               | in Java we figured that by adding records we'll both
               | direct people toward a more data-oriented form of
               | programming and at the same time make the problem of
               | writing setters so much less annoying to the point it
               | shouldn't be addressed by the language (while properties
               | have the opposite effect of encouraging mutation).
               | They've painted themselves into a very tight corner with
               | async/await (the same with P/Invoke, which constrained
               | their runtime's design making it harder to add user-mode
               | threads), and I think they've made a big security mistake
               | with interpolation -- something we're trying to avoid
               | with a substantially different design. Also, while richer
               | languages do have a lot of fans, all else being equal
               | more people seem to prefer languages with fewer features.
               | Our hypothesis is that it's better to spend a few years
               | thinking how to avoid adding a language feature (like
               | async/await or properties) than to spend a few months
               | adding it.
               | 
               | Also, every feature you add constrains you a little in
               | the future (and every language makes this tradeoff early
               | when it's trying to acquire users, but once it's
               | established you need to be more careful). That's why we
               | try to keep the abstraction level high at the expense of
               | a quicker and tighter fit to a particular environment.
               | This delays some things, but we believe it keeps us more
               | flexible to adapt to future changes. It's like having an
               | adaptation budget that you don't want to fully spend on
               | your current environment (I think P/Invoke and properties
               | are such examples of overfitting that pays well in the
               | short term and make you less adaptable in the long term).
               | The complexity budget is another you want to conserve.
               | Add a language feature to make every problem easier, and
               | over time you find yourself not only constrained, but
               | with a pretty complex language that few want to learn.
        
               | neonsunset wrote:
               | Async/await is not a tight corner as showcased by a
               | multitude of languages adopting the pattern: Rust,
               | Python, JavaScript and Swift. It is a clean abstraction
               | where future progress is possible while retaining the
               | convenience of its concurrency syntax and task
               | composition.
               | 
               | Green threads experiment proved net negative in terms of
               | benefit but the follow-up work on modernizing the
               | implementation details of async/await itself was very
               | successful:
               | 
               | Issue https://github.com/dotnet/runtime/issues/94620
               | 
               | Technical details https://github.com/dotnet/runtimelab/bl
               | ob/feature/async2-exp...
               | 
               | The result is such that regardless of p/invoke existence
               | green threads would have been a worse tradeoff.
               | 
               | It also seems that common practices in Java indicate that
               | properties are not a mistake as showcased by popularity
               | of Lombok and dozens of other libraries to generate
               | builders and property-like methods (or, worse, Java
               | developers having to write them by hand). In addition,
               | properties existed in C# since its inception,
               | that's...not a few years.
               | 
               | Not entirely sure about string interpolation but if you
               | are alluding to `var text = $"Time: {DateTime.Now}";`,
               | then it's a non-issue - APIs that care about it in
               | complex contexts like querying a DB or logging can handle
               | it with interpolated string handler API which allows to
               | pass string interpolation expression to methods accepting
               | interpolated string handler types, which can then, for
               | example, generate parametrized query with sanitized
               | inputs, without any friction for the user. Something that
               | Java does not seem to sufficiently appreciate.
               | 
               | Example: https://learn.microsoft.com/en-
               | us/ef/core/querying/sql-queri...
        
               | pron wrote:
               | These are all valid and well-known opinions, but that's
               | my point: there is nothing even remotely close to a
               | consensus on them (never mind that even results don't
               | extrapolate well from one language to another), and
               | different choices appeal to different people.
               | 
               | We put a lot of thought into which features we want to
               | add to the Java platform and in what form, and also
               | consider what other languages have done. Sometimes we
               | choose to make different tradeoffs based on what we think
               | are the right tradeoffs for most Java users (a tradeoff
               | that's right for language X may be wrong for language Y
               | [1]), and sometimes we disagree on aesthetics or
               | technical merit. But the choices we've made have worked
               | well for Java. We're well aware of differing opinions,
               | but it seems that we're managing to align with the
               | majority opinions (don't confuse "popular" with
               | "majority"; something like Lombok is quite popular in
               | absolute terms, but is still liked by a minority, i.e. it
               | is less popular than not using it; Kotlin is also quite
               | popular, but it is still more than ten times less popular
               | than Java so does that mean we should follow its
               | decisions?). At the adoption levels enjoyed by JS,
               | Python, and Java, something could be hugely popular in
               | absolute terms yet liked by a minority.
               | 
               | In our primary domain of serious server-side software, no
               | other language has done better (or as well), and we and
               | our users are happy, for the most part, with the choices
               | we've made (except maybe for choices made very early on,
               | but that's true for all languages). The mere fact that
               | sometimes not everyone agrees with our choices (let's be
               | honest, programmers rarely agree on anything) doesn't
               | mean we should change them, especially as languages that
               | go a different way don't seem to be doing as well. Still,
               | different programmers will continue liking different
               | things, and most will continue insisting that their
               | preferences -- however popular -- are somehow
               | "objectively" better with or without bottom-line metrics
               | to support their beliefs.
               | 
               | In general, thinking about a programming language from
               | the perspective of a programmer situated in specific
               | circumstances can be quite different from thinking about
               | a programming language from the perspective of the
               | language maintainer, who needs to take into account
               | different and often conflicting needs of many programmers
               | situated in a variety of different circumstances. The
               | wider the market you're targeting, the more aspects there
               | are to consider and the closer attention needs to be paid
               | to the distribution of programmer preferences.
               | 
               | [1]: E.g. the technical constraints that impact the
               | design and performance of user-mode threads in Rust or
               | C++ are fundamentally different from those that affect
               | Java (re. e.g. the cost of allocating memory, and where
               | pointers are allowed to point). The constraints around
               | async/await in JS -- where a lot of code is already
               | written under the assumption of no intervention -- are
               | also very different from those in Java, where threads
               | have existed from day one.
        
               | jacques_chester wrote:
               | > _It is a clean abstraction_
               | 
               | Ah, that must be why I see FooMethod and FooMethodAsync
               | side-by-side in C# all the time.
        
               | cesarb wrote:
               | > [...] and this cost is worth it only when native calls
               | are relatively common. In Java they are rare and easily
               | abstracted away, [...] but it's clearly the right one for
               | Java given the frequency of native calls [...]
               | 
               | Native calls are rare in Java _because they 're such a
               | pain_. If it wasn't so hard to do native calls in Java,
               | it would be common even for non-experts to make use of
               | non-Java libraries.
        
               | pron wrote:
               | I don't think so, given that there are more popular Java
               | libraries than popular libraries with a C ABI. There is a
               | small number of very popular C libraries that result in
               | the majority of native call uses. But in any event,
               | calling native libraries in Java is now no longer a pain
               | thanks to FFM (and jextract [1]) so we'll see.
               | 
               | Note that interaction with native libraries often
               | requires a more careful management of native memory that,
               | though much easier now with FFM, is still significantly
               | trickier (and more dangerous in terms of introducing
               | undefined behaviour) than interacting with Java code
               | regardless of how that interaction is declared in code.
               | In Java, as in Python, interaction with native code -- in
               | the vast majority of cases -- is best encapsulated inside
               | a Java library and not often directly exposed to
               | application programmers.
               | 
               | [1]: https://github.com/openjdk/jextract
        
               | kaba0 wrote:
               | Interestingly enough, this actually turned into a
               | positive over time -- also, java was usually fast enough
               | (compared to python) to avoid reaching for native all the
               | time, so it wasn't as big a pain point, it managed to
               | create an almost completely pure, 99.9% java ecosystem.
               | This means that even very very complex java apps will
               | basically work on every OS, unlike python and to a
               | smaller extent nodejs, where some cryptic dependency is
               | only for windows/linux, etc.
        
           | miffy900 wrote:
           | > This is similar, except more boilerplate and much, much
           | slower.
           | 
           | That's JNI, which really was truly terrible. Java 22
           | introducing FFM is finally an admission that JNI was crap and
           | a dead end.
        
             | peterashford wrote:
             | JNI worked it just wasnt as ergonomic as it could have been
             | - which was on purpose. I disagree that Java should have
             | discouraged use of JNI in that way but it was hardly "crap
             | and a dead end"
        
               | miffy900 wrote:
               | > JNI worked it just wasnt as ergonomic as it could have
               | been - which was on purpose
               | 
               | There's a reason they're calling it the 'FFM' API and not
               | JNI v2. The API devs were correct in rethinking the
               | approach to native interop.
               | 
               | This just proves my point; being crappy ON PURPOSE is why
               | it's a dead end; it's very difficult to improve something
               | that's been deliberately designed badly.
               | 
               | Besides that, no Java dev in their right mind is going to
               | continue to use JNI once they upgrade to Java 22 and
               | realise FFM exists.
        
               | peterashford wrote:
               | There's no reason to use it now, yes. It wasn't a dead
               | end because people absolutely could and were using it
               | just fine (if not happily)
        
               | pjmlp wrote:
               | I will keep using JNI, first of all because I cannot
               | stand its boilerplate instead of having something nice
               | like on .NET side, even jextract can't make up for it.
               | 
               | Secondly, our libraries also land on Android
               | applications, and lets see if FFM ever lands on ART.
        
       | marginalia_nu wrote:
       | What I'm missing is a model for building/distributing those C
       | libraries with a java application.
       | 
       | Every ffi example I've found seem to operate on the assumption
       | that you want to invoke syscalls or libc, which (with possibly
       | the exception of like madvise and aioring) Java already mostly
       | has decent facilities to interact with even without native calls.
        
         | ruslan_talpa wrote:
         | Put them in a jar?
        
         | pjmlp wrote:
         | You do it the standard way, package them inside the jar file.
        
           | marginalia_nu wrote:
           | Oh, does this actually work?
           | 
           | I was on the assumption that it was dynamically linking the
           | libarary with the OS dynamic linker, which in no OS I'm aware
           | of is capable of loading libraries inside of zip files.
           | 
           | Not sure where I got that notion. Maybe I was overthinking
           | this.
        
             | zten wrote:
             | Yes. Check out a library like zstd-jni. You'll find native
             | libraries inside it. It'll load from the classpath first,
             | and then ask the OS linker to find it.
        
               | marginalia_nu wrote:
               | Sounds promising.
               | 
               | I have some extremely unwieldy off-heap operations
               | currently implemented in Java (like quicksort for 128 bit
               | records) that would be very nice to offload as FFI calls
               | to the corresponding a single-line C++ function.
        
               | neonsunset wrote:
               | Why not give C# a try instead? It has everything you ask
               | for and then some.
        
               | neonsunset wrote:
               | Wow, you all are sure mad enough to go out of your way
               | and downvote my comments elsewhere.
               | 
               | Stay in the swamp :)
        
               | coldtea wrote:
               | Because "some inconvenience/unmet requirement" from a
               | language is not an invitation to "throw out the whole
               | platform and your existing code and tooling, and
               | learn/adopt/use an entirely different, single-vendor
               | platform".
               | 
               | Except if we're talking about some college student or
               | hobbyist picking their first language and exploring the
               | language space...
        
               | imtringued wrote:
               | He would still have to call out to the C++ function.
        
               | neonsunset wrote:
               | Assuming it is "sort for 128bit records", that's
               | something C# does really well - writing optimized code
               | with structs / Vector128<T> / pointer arithmetic when
               | really needed without going through FFI and having to
               | maintain separate build step and project parts for a
               | different platform.
               | 
               | But even if it was needed, such records can be commonly
               | represented by the same structs both at C#'s and C++'s
               | sides without overhead.
               | 
               | An array of such could be passed as is as a pointer, or
               | vice versa - a buffer of struts allocated in C/C++ can be
               | wrapped in a Span<Record128> and transparently interact
               | with the rest of standard library without having to touch
               | unsafe (aside from eventually freeing it, should that be
               | necessary).
        
               | maksut wrote:
               | I'd like to learn how they do it. Because last time I've
               | looked at this, the suggested solution was to copy the
               | binaries from claspath (eg: the jar) into a temporary
               | folder then load it from there. It feels icky :)
        
               | renewiltord wrote:
               | EDIT: Disregard. I am wrong. Original below.
               | 
               | You can just load as a resource. We do this internally
               | since much of network stack is C. But we use JNI because
               | code is older than Java 22.
        
               | maksut wrote:
               | You made me search it again. And still I don't see how
               | that's possible. `Runtime.load` requires a regular file
               | with an absolute path[0].
               | 
               | Stackoverflow is full of "copy it into a temp file"
               | solutions. ChatGPT keeps saying "sorry" but still insists
               | on copying it into a temp file :)
               | 
               | [0] - https://docs.oracle.com/en%2Fjava%2Fjavase%2F22%2Fd
               | ocs%2Fapi...
        
               | renewiltord wrote:
               | Embarrassing of me to give you wrong answer. I went and
               | checked my old code and:                    new
               | FileOutputStream(tmpFile)
               | 
               | Apologies.
        
               | zten wrote:
               | Yep, you're right, they do exactly that. Apologies for
               | the confusion.
               | 
               | Decompiled class file:                   try {
               | var4 = File.createTempFile("libzstd-jni-1.5.0-4", "." +
               | libExtension(), var0);             var4.deleteOnExit();
        
               | sedro wrote:
               | This wouldn't work on Windows, because you can't delete a
               | DLL while it's in use
        
               | BenjiWiebe wrote:
               | Couldn't you: Extract DLL Load DLL Unload DLL Delete DLL
               | ?
               | 
               | Though in the example given, I do see your point now.
               | You'd have to make sure the DLL was unloaded before the
               | delete-on-exit happened.
        
               | tadfisher wrote:
               | You can install a shutdown hook to do cleanup like this.
               | Runtime.getRuntime().addShutdownHook(...)
        
               | sedro wrote:
               | That's how java.io.File#deleteOnExit works under the
               | hood. The DLL is still loaded at that point and can't be
               | deleted.
        
               | tadfisher wrote:
               | Ah, looking through the docs [1]; you have to use your
               | own ClassLoader (so it can be garbage-collected), and
               | statically-link with a JNI library which is unloaded when
               | the ClassLoader is garbage-collected.
               | 
               | 1: https://docs.oracle.com/en/java/javase/22/docs/specs/j
               | ni/inv...
        
               | sedro wrote:
               | According to JNA it's not safe to unload the DLL:
               | 
               | https://github.com/java-native-
               | access/jna/blob/40f0a1249b5ad...                 Do NOT
               | force the class loader to unload the native library,
               | since       that introduces issues with cleaning up any
               | extant JNA bits       (e.g. Memory) which may still need
               | use of the library before shutdown.
               | 
               | Following the blame back to 2011, they did unload DLLs
               | before https://github.com/java-native-
               | access/jna/commit/71de662675b...                 Remove
               | any automatically unpacked native library.  Forcing the
               | class       loader to unload it first is only required on
               | Windows, since the       temporary native library is
               | still "in use" and can't be deleted until       the
               | native library is removed from its class loader.  Any
               | deferred       execution we might install at this point
               | would prevent the Native       class and its class loader
               | from being GC'd, so we instead force        the native
               | library unload just a little bit prematurely.
               | 
               | Users reported occasional access violation errors during
               | shutdown.
        
               | electrum wrote:
               | You might be able to use FILE_FLAG_DELETE_ON_CLOSE, but
               | this would likely require calling the Windows API
               | functions directly.
        
               | zten wrote:
               | Hmm, interesting. They do have DLLs in the JAR...
        
             | brabel wrote:
             | I remember using Sqlite Java and not having to install
             | sqlite on the image. Then I looked inside the Sqlite-java's
             | jar and they just packed the sqlite binaries for the
             | different OSs in the jar!!
        
               | DannyB2 wrote:
               | I once (2016 ish) used a serial-port library for Java.
               | Needed to be cross platform desktop app for Linux,
               | Windows and Mac (in that order, all on x86/64). And it
               | was. I have forgotten the name of the library project I
               | included, but it included DLL binaries for the platforms
               | we were targeting.
        
               | dehrmann wrote:
               | Not sure if this is still the case, but one of the Java
               | Sqlite driver used something called NestedVM to run
               | Sqlite in the JVM when a native library wasn't available.
               | It worked by cross-compiling the code to mips, then
               | transpiling the mips assembly to Java byte code. I can't
               | remember if it bridged system calls or libc calls to Java
               | for things like file IO.
        
               | KptMarchewa wrote:
               | That's a common solution. I do the same.
        
             | saagarjha wrote:
             | Android knows how to do this, actually.
        
           | fire_lake wrote:
           | Is there a solution when the binaries are 500mb+ per
           | platform?
        
             | pjmlp wrote:
             | People seem pretty happy when Go and Rust do the same with
             | static linking, advocating how great it happens to be.
        
               | cesarb wrote:
               | Static linking in Go and Rust includes compiled code only
               | for the target platform. It does not include compiled
               | code for every possible architecture, including 32-bit
               | MacOS and Solaris on PowerPC.
        
               | SJC_Hacker wrote:
               | Solaris... now thats a name I have not heard in a long
               | time. A long time.
        
               | pjmlp wrote:
               | Hence why you end up with 300MB x platforms, and tricks
               | like upx.
        
               | yw3410 wrote:
               | You can't be as aggressive at removing functions in Java
               | than in Rust though since it's dynamic dispatch (e.g., if
               | you use toString once in your code, you need to keep all
               | implementations of toString which are reachable even if
               | users don't use reflection).
        
               | neonsunset wrote:
               | .NET's trimmer/linker deals with this quite well, only
               | referenced or otherwise observable .ToString()
               | implementations are rooted.
               | 
               | Without it 1.6-2MiB-sized AOT binaries would not have
               | been possible (most space is occupied by standard
               | library/runtime bits and GC)
        
               | pjmlp wrote:
               | Except that is what jlinker, and GraalVM/OpenJ9 (among
               | other AOT toolchains) do in practice.
        
               | fire_lake wrote:
               | In Java libraries are shared precompiled so the package
               | manager either needs to be platform aware or distribute
               | fat bundles.
        
               | pjmlp wrote:
               | Only for those that are yet to learn how to use jlinker.
        
             | mike_hearn wrote:
             | Libraries like JCEF have support tools to download the
             | libraries either at runtime or during the build, to offload
             | from Maven Central.
        
         | pron wrote:
         | The recommended distribution model for Java applications is a
         | jlinked runtime image [1], which supports including native
         | libraries in the image.
         | 
         | [1]: Technically, this is the only distribution model because
         | all Java runtimes as of JDK 9 are created with jlink, including
         | the runtime included in the JDK (which many people use as-is),
         | but I mean a custom runtime packaged with the application.
        
           | maksut wrote:
           | Is that still true when distributing libraries?
        
             | pron wrote:
             | If you distribute libraries as jmod files, which few
             | libraries do (in that case, jlink would automatically
             | extract the native libraries and place them in the
             | appropriate location).
        
             | brabel wrote:
             | Absolutely not. jlink is used to distribute applications
             | (it includes your code, the Java libs you use, i.e. their
             | jars, and the trimmed-down JVM with the modules you're
             | using so that your distribution is not so big - typically
             | around 30MB).
             | 
             | Java libraries are still obtained from Maven repositories
             | via Maven/Gradle/Ant/Bazel/etc.
        
         | sedro wrote:
         | Native libraries are typically packaged inside a jar so that
         | everything works over the existing build and dependency
         | management systems.
         | 
         | For example, each these jars named "native-$os-$arch.jar"
         | contain a .dll/.so/.dylib:
         | https://repo1.maven.org/maven2/com/aayushatharva/brotli4j/
         | 
         | JNA will extract the appropriate native library (using os.name
         | and os.arch system properties), save the library to a temp
         | file, then load it.
        
           | throwaway2037 wrote:
           | > JNA will extract the appropriate native library ..., save
           | the library to a temp file, then load it.
           | 
           | JNA does this?
           | 
           | FYI: JNA = Java Native Access project:
           | https://github.com/java-native-access/jna
        
             | sedro wrote:
             | It does! Code is here https://github.com/java-native-
             | access/jna/blob/40f0a1249b5ad...
        
         | gwbas1c wrote:
         | > Every ffi example I've found seem to operate on the
         | assumption that you want to invoke syscalls or libc ... Java
         | already mostly has decent facilities to interact with even
         | without native calls.
         | 
         | Because you would use ffi to interact with libraries that don't
         | have Java wrappers yet: IE, you're writing the wrapper.
         | 
         | Using syscalls or libc is a way to write an example against a
         | known library that you're probably familiar with.
        
         | aardvark179 wrote:
         | So, other people have already answered this, but this does seem
         | to be a gap where many developers lack some piece of knowledge
         | to chain the whole solution together. You normally package this
         | sort of thing by putting the native library in a jar,
         | extracting it to a tmp file that will be deleted on exit, and
         | opening that dynamic library.
         | 
         | I've met many perfectly reasonable developers who do know all
         | those steps can be done but can't put them all together - maybe
         | because it just hasn't clicked that you can store a library in
         | a jar. It feels like something tutorials should cover, but I
         | think falls into the, "surely everyone can work it out?"
         | category.
        
           | chii wrote:
           | > extracting it to a tmp file
           | 
           | i wonder if there's a way to do this entirely in memory?
           | Because some deployment scenarios might not have disk space
           | at all.
        
             | MobiusHorizons wrote:
             | /tmp is often a RAM disk in such cases
        
             | xxs wrote:
             | not with java - it needs a path. Of course if you have a
             | ram disk (e.g. /tmp) it'd do the job.
        
             | rwmj wrote:
             | Technically memfd_create will let you create a file
             | descriptor backed by a memory region. However in Linux I
             | don't believe there's a way to dlopen that. (Maybe dlopen
             | /dev/fd/... might work?) In FreeBSD there's a fdlopen
             | library function.
             | 
             | Edit: glibc proposal which was never accepted:
             | https://sourceware.org/bugzilla/show_bug.cgi?id=11767
        
           | sedro wrote:
           | You would also need to learn about Maven profiles and
           | activation. And for other build tools, you'll be delighted to
           | know they have _partial_ support.
        
           | xxs wrote:
           | >exract it to a tmp file that will be deleted on _exit_ ,
           | 
           | actually you delete it immediately (after load) on anything
           | that's not windows... even then but it's likely to return
           | false.
           | 
           | deleteOnExit just stores the path to delete and uses a
           | shutdownHook to actually call delete. Nothing really special
           | about it
        
         | mike_hearn wrote:
         | If your app is open source, or you're willing to buy a
         | commercial tool, then you could try Conveyor from my company
         | [1]. It will:
         | 
         | - Find all the shared libraries in your JARs or configured app
         | inputs (files in your build/source tree)
         | 
         | - Sniff them to figure out what OS and CPU arch they are for
         | 
         | - Bundle them into the right package for each platform that it
         | makes, in the right place to be found by System.loadLibrary()
         | 
         | - Sign them if necessary
         | 
         | - Delete them from the JARs now they are extracted. Optionally
         | extract them from library JARs, sign them and then put them
         | back if your library refuses to load the shared library from
         | disk instead of unpacking it (most libs don't need this)
         | 
         | - JLink a bundled JVM for your app for each platform you
         | target, using jdeps to figure out the right set of modules, and
         | combine that with your shared libs.
         | 
         | When building Debian/Ubuntu packages it will also:
         | 
         | - Read the .so library dependencies, look up the packages that
         | contain those other shared libraries and add package
         | dependencies on those packages, so "apt install" will do the
         | right thing.
         | 
         | So that makes it a lot easier to distribute Java apps that use
         | native code.
         | 
         | [1] https://www.hydraulic.dev/
        
       | alex_suzuki wrote:
       | Wonder if this will make JNA (Java Native Access) redundant at
       | some point: https://github.com/java-native-access/jna
       | 
       | Very useful, especially the prebundled platform bindings.
        
       | xyproto wrote:
       | Does this mean that one can use SDL2 together with Java without
       | bending over backwards?
        
         | neonsunset wrote:
         | It seems it will make it somewhat easier.
         | 
         | But if you want to use SDL2 from something higher-level, you
         | will be _much_ better served by C# which will give you minimal
         | FFI cost and most data structures you want to express in C as-
         | is.
        
           | maksut wrote:
           | I don't know much about C#. It certainly looks more popular
           | in gamedev circles.
           | 
           | When I played with this new java api. I wasn't worried about
           | the FFI cost. It seemed fast enough to me. My toy application
           | was performing about 0.77x of pure C equivalent. I think
           | Java's memory model and heavy heap use might hurt more.
           | Hopefully Java will catch up when it gets value objects with
           | Project Valhalla. Next decade or so :)
        
             | neonsunset wrote:
             | Genuine curiosity - what would be your motivation to use
             | Java over C# here aside from familiarity (which is
             | perfectly understandable)? The latter takes heavy focus on
             | making sure to provide features like structs and pointers
             | with little to no friction, you can even AOT compile it and
             | statically link SDL2 into a single executable.
             | 
             | In improbable case you may want to try it out, then all it
             | needs is
             | 
             | - SDK from https://dot.net/download (or package manager of
             | your choice if you are on Linux e.g. `sudo apt-get install
             | dotnet-sdk-8.0`, !do not! use Homebrew if you are on macOS
             | however, use .pkg installer)
             | 
             | - C# extension for VS Code (DevKit is not needed)
             | 
             | - SDL2 abstraction: https://github.com/dotnet/Silk.NET
             | (there are all sorts of alternate bindings depending on
             | your preferences)
        
               | lazide wrote:
               | Not the original poster, but most folks have little
               | choice what ecosystem they're using.
               | 
               | And once you have enough momentum, switching isn't
               | usually worth it.
               | 
               | (As someone who has done Perl, C, Java, C#, Kotlin, JS,
               | and Python professionally - god help me. Maybe a million
               | lines of code all in now?)
        
               | neonsunset wrote:
               | Fair enough
        
               | spullara wrote:
               | Java now has <1ms max pause time garbage collectors with
               | TB heaps. If you are writing a game GC matters a lot.
        
               | neonsunset wrote:
               | Interesting! What games did you write code for?
        
               | stoperaticless wrote:
               | Not the op, but at some point I did choose between the
               | two paths/jobs assuming I will get more proficient in
               | only one of them each year (which is true, I stayed
               | junior in C#).
               | 
               | Why I chose Java boils down to two reasons:
               | 
               | - runs on linux (I know there is some version of c# that
               | eventually opened up, but I kind of expect it to have lot
               | of conditions for being cross platform, I assume that
               | standard c# code is not crossplatform due to some reason
               | (e.g. Com usage might be standard way of doing stuff),
               | which would make finding crossplatform answers tedious)
               | 
               | - whole ecosystem is more open source and more involved
               | parties (which I interpreted as abit less controlled by
               | the corporate overlord, so if corporate overlord went
               | rogue, greater chance that language would survive
               | somehow)
               | 
               | Never needed to call into C though..
        
               | neonsunset wrote:
               | Neither point is true today FWIW.
               | 
               | Neither point was ever true in the last ~10 years when it
               | comes to gamedev (or where you want to use SDL) where
               | Java was and continues to be a _much_ weaker choice.
        
               | kaba0 wrote:
               | Java's ecosystem is just vastly bigger. In many
               | categories, Java has multiple open-source offerings vs
               | .NET's single, proprietary one that is often just a bad
               | copy of one of the former libraries.
        
               | 5e92cb50239222b wrote:
               | Despite what some fanatics may claim, operating systems
               | other than Windows are still second class citizens
               | (saying this after five years of doing .NET development
               | almost exclusively on Linux), especially for dev, and
               | operating systems other than the big three are not
               | supported at all. So no BSDs (even FreeBSD) or Solaris if
               | you ever need it.
               | 
               | Since the open .NET is pretty young, and they still have
               | trouble with community perception due to their past
               | actions, finding high quality FOSS libraries may pose a
               | problem depending on what you're doing. Pretty much
               | everything from MS is open and high quality, but they
               | don't provide everything under the sun.
               | 
               | And with Java you always have alternative runtimes in
               | case this Oracle deal goes sideways for any reason.
               | 
               | So you're all good, don't worry about it.
        
               | neonsunset wrote:
               | FreeBSD: `pkg install lang/dotnet` (from
               | https://www.freshports.org/lang/dotnet)
               | 
               | GObject (GTK4 and similar):
               | https://github.com/gircore/gir.core (significantly better
               | and faster than Java alternatives, this is just one
               | example among many)
               | 
               | Young: first OSS version was released 8 years ago
               | 
               | Solaris: might as well say "it runs COBOL but not .NET"
               | 
               | It's funny that everyone missed the initial context of
               | the question and jumped onto parroting the same arguments
               | as years ago, without actually addressing the matter at
               | hand or saying anything of substance. Unsurprising show
               | of ignorance by Java community. Please stay this way -
               | will help the industry move on faster.
               | 
               | The premise is always the same - if something is missing
               | in {technology I don't like}, it's a deal-breaker, and
               | when it's not or was always there - it never mattered, or
               | is harmful actually, that is, until {technology I like}
               | gets it as well.
        
               | pooya72 wrote:
               | Interesting, in what ways is it a second class citizen? I
               | tried googling it, but didn't find much.
        
               | maksut wrote:
               | It was a learning exercise. Just playing around with
               | clojure, raylib and this new api. I know all these can
               | also be done with C# with some pros & cons.
               | 
               | I wasn't advocating java for gamedev. Just pointing that,
               | this new api is a nice addition. And I am glad that jvm
               | ecosystem is improving.
               | 
               | To be fair, if I was starting a game project I wouldn't
               | stay in Java/C# level. Depending on the project,
               | something like C, C++, zig might be more practical.
               | Ironically I believe they would be easier for iterating
               | ideas and deploy into different platforms (mobile, wasm
               | etc.).
        
               | neonsunset wrote:
               | A little bit sad* but understandable, thank you.
               | 
               | *C/C++ tooling and verbosity pains, what PL dev progress
               | is for? C# feels more modern than some of the "modern"
               | alternatives but eh.
        
           | p0w3n3d wrote:
           | You're are (or were) right. Java has (had) an awful
           | performance of a foreign API call, and I wonder was this
           | fixed in this release, because as I heard, fixing it was the
           | main reason of the upcoming functionality
        
             | neonsunset wrote:
             | It could bring Java closer in FFI overhead but not
             | necessarily match. There are still missing features like
             | structs, C pointers (though in C# they are superseded quite
             | a bit by byrefs aka `ref T` syntax, e.g. used by Span<T>),
             | stack allocated buffers, etc.
             | 
             | C# also has function pointers (managed/unmanaged) and C
             | exports with NativeAOT.
        
         | marginalia_nu wrote:
         | Oh man that's a cool idea.
         | 
         | Might just build a SDL2-wrapper for ffi just as an FFI and FMI-
         | exercise.
        
         | maksut wrote:
         | I have played with raylib bindings for clojure by using the new
         | foreign function api. It was a lot of fun. SDL might be a
         | better fit because it prefers pass by reference arguments [0].
         | 
         | [0]
         | https://gist.github.com/raysan5/17392498d40e2cb281f5d09c0a4b...
        
       | creativeSlumber wrote:
       | Not directly related to the artcile,but is there any article that
       | explain how memory management (stack/heap) work when using FFI in
       | java. Also when a call is made though FFI to a C library, is
       | there a separate java and C call stack? I haven't found a good
       | article yet on what happens under the hood.
        
         | dzaima wrote:
         | Don't have an article, but the gist on stacks is that Java
         | still uses the regular architecture stack (rsp on x86, etc)
         | that the FFI'd code will, and on exit to/entry from FFI it'd
         | have to store its stack end/start pointer (or otherwise be able
         | to figure the range out) such that GC knows what to scan.
        
           | kgeist wrote:
           | I wonder how it works when you use virtual threads. In Go,
           | goroutines have resizable stacks which notoriously
           | complicates FFI because C has no idea about resizable stacks
           | (IIRC they have to temporarily switch to a separate, special
           | C stack).
        
             | mike_hearn wrote:
             | When it's running a virtual thread is using a physical OS
             | level thread, and if you call into C then that virtual
             | thread won't suspend. It pins the OS thread. So it's all
             | transparent.
        
         | w10-1 wrote:
         | For the heap, JEP 454 is reasonably detailed:
         | https://openjdk.org/jeps/454
         | 
         | It describes how to adopt memory from C and have C adopt memory
         | you allocate, and gives control over how memory is allocated in
         | an arena.
         | 
         | The arena has lifecycle boundaries, and allocations determine
         | the memory space available. Java guarantees (only) that you
         | can't use unallocated memory or memory outside the arena, and
         | if you access via a (correct) value layout, you should be able
         | to navigate structure correctly.
         | 
         | The interesting stuff is passing function pointers back and
         | forth - look for `downcall method handles`.
        
       | thefaux wrote:
       | I am sort of surprised that there isn't a widely used tool that
       | uses codegen to generate jni bindings sort of like what the jna
       | does but at build time. You could go meta and bundle a builder in
       | a jar that looks for the shared library in a particular place and
       | shells out to build and install the native library if it is
       | missing on the host computer. This would run once pretty similar
       | I think to bundling native code in npm.
       | 
       | I have bundled shared libraries for five or six platforms in a
       | java library that needs to make syscalls. It works but it is a
       | pain if anything ever changes or a new platform needs to be
       | brought up. Checking in binaries always feels icky but is
       | necessary if not all targets can be built on a single machine.
       | 
       | The problem with the new api is that people upgrade java very
       | slowly in most contexts. For an oss library developer, I see very
       | little value add in this feature because I'm still stuck for all
       | of my users who are using an older version of java. If I
       | integrate the new ffi api, now I have to support both it and the
       | jni api.
        
         | MaxBarraclough wrote:
         | > I am sort of surprised that there isn't a widely used tool
         | that uses codegen to generate jni bindings sort of like what
         | the jna does but at build time
         | 
         | There are several, including SWIG.
        
           | andoando wrote:
           | Which is still a pita to use unless maybe you really know
           | what youre doing.
        
             | TillE wrote:
             | Binding generation is really difficult to approach as a
             | general problem, which is why I've found that SWIG
             | unfortunately doesn't help much in non-trivial cases.
             | 
             | All the good bindings I use are generated by custom systems
             | (ie, usually some Python scripts) tailored for the specific
             | way their library works.
        
         | lelanthran wrote:
         | There is SWIG, which does bings to and from C for almost every
         | language that exists.
        
         | PaulHoule wrote:
         | Back in 1998 I wrote a code generator to make JNI stubs for
         | LAPACK. It's the kind of programming that goes that way.
        
         | gudzpoz wrote:
         | There is a library called jnigen [1], mainly used by the libGDX
         | framework [2]. But I don't see it used in many other projects
         | though. Personally I use it to maintain a set of Lua C API
         | bindings for some platforms [3] and it works sort of OK once
         | you manage to somehow set up a workflow for building and
         | testing the binaries.
         | 
         | > It works but it is a pain if anything ever changes or a new
         | platform needs to be brought up. Checking in binaries always
         | feels icky but is necessary if not all targets can be built on
         | a single machine.
         | 
         | It is definitely a pain when you cannot test all changes on a
         | single local machine. But I would argue that it is true
         | whenever multiple platforms (or maybe even multiple glibc
         | versions) are involved, regardless of what
         | languages/libraries/tools you use.
         | 
         | [1] https://libgdx.com/wiki/utils/jnigen [2]
         | https://github.com/libgdx/libgdx [3]
         | https://github.com/gudzpoz/luajava
        
       | iso8859-1 wrote:
       | Calling C is easy. But how do you call C++? Shiboken has a
       | language that let's you express ownership properties on C++ data
       | structures/methods/functions. It's tailored to generating Python
       | FFI bindings though. It would be so nice if there were a cross-
       | platform language to do this.
        
         | secondcoming wrote:
         | You put your C++ behind a C API.
        
         | p0w3n3d wrote:
         | This is something new. Before it you had to create a native-
         | compatible shared library that returns jString/jObject instead
         | or use a proxy which did this for you (JNA). Let's see what
         | happens next, maybe even shiboken
        
         | qweqwe14 wrote:
         | The answer is basically you don't. It's impossible to make a
         | sane, stable FFI for a language unless you put it behind a C
         | ABI, which is relatively basic, but this is exactly why it's
         | most suitable for FFI: implementing support for calling C
         | functions is way more trivial than figuring out how to call the
         | latest C++/Rust/etc monstrosity.
        
           | zozbot234 wrote:
           | > The answer is basically you don't. It's impossible to make
           | a sane, stable FFI for a language unless you put it behind a
           | C ABI
           | 
           | The Swift folks have put a lot of effort into attaining a
           | stable ABI that's native to their language. They can achieve
           | that because Swift is the officially endorsed language for
           | development on Mac OS and iOS, so it (together with the
           | platform itself) can set a standard that other languages will
           | have to live with.
           | 
           | In a way, software VM's like the JVM and CLR can also be said
           | to define 'ABIs' of sorts within their runtime, that every
           | language implementation on these runtimes will have to deal
           | with.
        
           | Dwedit wrote:
           | There do exist ABIs that aren't the C ABI. But saying "use
           | the C ABI" is far more portable than anything else.
           | 
           | I can also point to the GCC Inline Assembler as an excellent
           | way to call arbitrary functions whether they implement the
           | standard C procedure call standard or not. By providing the
           | list of arguments and what register they correspond to, along
           | with the clobber list, you know everything you need to know
           | to call the function. So it's more suitable for "fastcall"
           | type functions where you need the arguments to correspond to
           | particular registers.
           | 
           | But of course, ASM isn't portable.
        
           | neonsunset wrote:
           | Swift ABI proves this to be wrong, but also showcases the
           | complexity that goes with ABI of such kind.
        
         | mike_hearn wrote:
         | There's javacpp which can do that.
        
         | imtringued wrote:
         | I don't know why people don't know this, but you can just use
         | GObject.
        
       | p0w3n3d wrote:
       | Last time I checked (ca. 2017-9) every call to foreign API in
       | Java had to create a memory barrier causing flush of all CPU
       | cache. This was different to using normal JVM interfaces and when
       | I asked some guy on a Java conference, he told me they cheated
       | during writing of calls to JVM API, but other people need to
       | adhere to rules. I wonder what happened in this matter in Java
       | 22, as this change was highly expected
        
         | ryanpetrich wrote:
         | Memory barriers don't force a flush of all CPU cache. They will
         | enforce the ordering of memory operations issued before and
         | after the barrier instruction, preserving the contents of the
         | CPU's various caches.
        
       | Dwedit wrote:
       | C# does a much better job of calling into C Code. All the
       | programmer has to do is either write a extern function with the
       | "DllImport" attribute, or they can turn a raw function pointer
       | into a delegate. (Or even directly use a function pointer in
       | newer versions of C#)
        
       | petesergeant wrote:
       | I can't see why I'd ever reach for it, but I do like knowing that
       | Java is actively being improved over time
        
       | stevefan1999 wrote:
       | Compared to .NET's P/Invoke this is still way too convoluted. Of
       | course Java has its own domain problem such as treating
       | everything as a reference (and thus pointer, there is a reason
       | Java has NullPointerException rather than NullReferenceException)
       | and the lack of stack value types (everything lives on heap
       | unless escape analysis allows some data to stay on stack, but it
       | is uncontrollable anyway) makes translation of Plain-Old-Data
       | (POD) types in Java very difficult, which is mostly a no-op with
       | C#. That's why JNI exists as a mediator between native code and
       | Java VM.
       | 
       | In C# I can just do something like this conceptual code:
       | 
       | ```
       | 
       | // FILE *fopen(const char *filename, const char *mode)
       | 
       | [DllImport("libc")] public unsafe extern nint
       | fopen([MarshalAs(UnmanagedType.LPStr)] string filename,
       | [MarshalAs(UnmanagedType.LPStr)] string mode);
       | 
       | // char *fgets(char *str, int n, FILE *stream)
       | 
       | [DllImport("libc")] public unsafe extern nint
       | fgets([MarshalAs(UnmanagedType.LPStr)] string str, int n, nint
       | stream);
       | 
       | // int fclose(FILE *stream)
       | 
       | [DllImport("libc")] public unsafe extern int fclose(nint stream);
       | 
       | ```
       | 
       | So much less code, and so much more precise than any of the Java
       | JNI and FFI stuff.
        
         | neonsunset wrote:
         | Can be even simpler now (you can declare it as a local function
         | in a method, so this works when copied to Program.cs as is):
         | var text = "Hello, World!"u8;         write(1, text,
         | text.Length);              [DllImport("libc")]         static
         | extern nint write(nint fd, ReadOnlySpan<byte> buf, nint count);
         | 
         | (note: it's recommended to use [LibraryImport] instead for
         | p/invoke declarations that require marshalling as it does not
         | require JIT/runtime marshalling but just generates (better)
         | p/invoke stub at build time)
        
         | pjmlp wrote:
         | Yep, that is my main complaint, and why I will rather reach to
         | JNI instead.
        
         | the-alchemist wrote:
         | Java's FFI is currently a very low-level. As the article points
         | you, you don't actually have to do this: the jextract tool will
         | generate the bindings for you from header files.
         | 
         | I'm sure someone will come along and write annotations to do
         | exactly as you describe there. The Java language folks tend to
         | be very conservative about putting stuff in the official API,
         | cuz they know it'll have to stay there for 30+ years. They
         | prefer to let the community write something like annotations
         | over low-level APIs.
         | 
         | Anyway, the GraalVM folks don't have quite the same limitations
         | as Java, so they have annotations already
         | (https://yyhh.org/blog/2021/02/writing-c-code-in-
         | javaclojure-...):                   @CStruct("MDB_val")
         | public interface MDB_val extends PointerBase {
         | @CField("mv_size")             long get_mv_size();
         | @CField("mv_size")             void set_mv_size(long value);
         | @CField("mv_data")             VoidPointer get_mv_data();
         | @CField("mv_data")             void set_mv_data(VoidPointer
         | value);         }
        
       | jakjak123 wrote:
       | I had a C library I needed to ideally use from Java directly. The
       | new FFI API looks great, but unfortunately the C API relied
       | heavily on macros and void* arguments, making it incredibly
       | difficult to model from Java.
        
         | the-alchemist wrote:
         | I would give the jextract tool a try. I believe it uses LLVM to
         | parse the header files, so the generated bindings might
         | actually be pretty good.
        
       ___________________________________________________________________
       (page generated 2024-05-09 23:02 UTC)