Funneling Objects through void*

Johannes Kuhn info at j-kuhn.de
Thu Jun 15 19:30:27 UTC 2023



On 15-Jun-23 21:04, Jorn Vernee wrote:
> Hey Johannes,
> 
> Some thoughts:
> 
>  > Of course it is trivial to write such functionally using a counter 
> and a ConcurrentHashMap, but that is not the only possible implementation.
> 
>  > In fact the JNI functions `newGlobalRef` and `deleteGlobalRef` would 
> provide an adequate implementation.
> 
> I'll note that newGlobalRef/deleteGlobalRef is implemented with a global 
> store (look e.g. for OopStorage::allocate [1]), and the object handle is 
> a pointer into that store. I.e. it is not too different from having a 
> global Map<Long, Object> somewhere, then passing the `long` (analogous 
> to the JNI handle) around through native code, and 'resolving' it 
> through Map::get when getting back into Java. Global refs are also a 
> generically applicable mechanism, and I suspect that most use cases 
> would benefit from a more custom tailored solution for that particular 
> use case. Though, I agree that the problem exists (and it's something 
> we've run into as well [2])

Yes, that is what I mean by "a counter and a ConcurrentHashMap".

WRT Exceptions. I did notice that throwing an exception in an upcall 
crashes the JVM. Not so great. Means I need to do some extra work 
catching them, checking for pending exceptions (there should be none, 
but you never know) and return the appropriate "abort enumerating" constant.

Not sure if this could be improved by specifying an "exception return 
value" when creating the upcall and then rethrow the exception when the 
underlying downcall returns.

(BTW: When calling an JNI function such as FindClass with an invalid 
class name, the downcall will throw a CNFE.)

> 
> I think we mostly discussed adding direct support for Object -> JNI 
> handle conversions in order to interoperate with native functions that 
> depend on JNI (such as the native AWT API from that SO question you 
> linked).

An other option is exposing such things through the Java API. 
sun.awt.windows.WComponentPeer has the useful method `public long getHWnd()`

I guess in the future it might get more and more common for people to 
ask for some "native identifier", be it the SSID for UserPrincipal, the 
thread id (which is different from java.lang.Thread.threadId()), and 
probably other things.

It is quite unfortunate if the identifier you need is there, sitting in 
a private field of some Object, but without an official, supported way 
to obtain that.

> 
>  > Currently I have to create a new upcall stub for every invocation
> 
> What is the problem with doing this? Is it cumbersome? Or does it seem 
> wasteful? WRT the latter, we still have some options for making upcall 
> stub allocation with the same FunctionDescriptor share the same VM stub 
> (mostly). Though, we already share the Java wrapper class at the moment.
> 

Yes, it does seem wasteful. Might not be that bad, but it feels wrong, 
especially if you got a void* parameter for that kind of thing for free.

> In both of these cases, I wonder if the best solution we can provide is 
> a more intuitive/convenient way of addressing these use cases, rather 
> than something which necessarily has a better implementation than what a 
> user can do today.
> 
> Jorn
> 
> [1]: 
> https://github.com/openjdk/jdk/blob/3e0bbd290c534b0f9729c54cd45308d505907797/src/hotspot/share/gc/shared/oopStorage.cpp#L436
> [2]: 
> https://github.com/openjdk/jextract/blob/cf3afe9ca71592c8ebb32f219707285dd1d5b28a/src/main/java/org/openjdk/jextract/clang/Cursor.java#L194
> 
> On 15/06/2023 18:05, Johannes Kuhn wrote:
>> When trying to help this StackOverflow question[1], I tried to use 
>> `EnumWindows`[2]. EnumWindows takes a callback and a void* parameter 
>> and invokes the callback using the toplevel HWND & the passed void* 
>> parameter.
>>
>> Currently I have to create a new upcall stub for every invocation, but 
>> if I could funnel some java.lang.Object through the void* parameter, I 
>> would only need to create a single upcall, where I retrieve the 
>> object, cast it and call some method on it.
>>
>> Example (pseudo) code:
>>
>>     public interface EnumWindowsCallback {
>>         int callback(long hwnd);
>>     }
>>     private static int enumWindowsUpcall(long hwnd, MemoryAddress obj) {
>>         return (int) ((EnumWindowsCallback) 
>> toObject(obj)).callback(hwnd);
>>     }
>>
>>     public static int enumWindows(EnumWindowsCallback cb) throws 
>> Throwable {
>>         try (Arena arena = Arena.ofConfined()) {
>>             return (int) 
>> ENUM_WINDOWS_MH.invokeExact(ENUM_WINDOWS_UPCALL, toPointer(arena, cb));
>>         }
>>     }
>>
>> One problem is that the arena is not accessible within the callback.
>>
>>
>> Of course it is trivial to write such functionally using a counter and 
>> a ConcurrentHashMap, but that is not the only possible implementation.
>>
>> In fact the JNI functions `newGlobalRef` and `deleteGlobalRef` would 
>> provide an adequate implementation.
>>
>> - Johannes
>>
>> [1]: 
>> https://stackoverflow.com/questions/75620948/how-do-i-get-the-hwnd-of-a-canvas-using-panama/
>> [2]: 
>> https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumwindows


More information about the panama-dev mailing list