Error Running jextract on d3d12.h

Jorn Vernee jorn.vernee at oracle.com
Mon Sep 14 13:28:50 UTC 2020


I've tried it out on my machine as well (I'm also on Windows). Here is a 
little bit more complete example that prints out the adapter description

If you have extracted the shared/dxgi.h and the shared/guiddef.h headers 
as well, you can do something like this:


import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.NativeScope;
import org.jextract.dxgi_h;
import org.jextract.guiddef_h;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.nio.charset.StandardCharsets;

import static jdk.incubator.foreign.CSupport.*;
import static org.jextract.dxgi_h.*;

public class Main {

     public static void main(String[] args)throws Throwable {
         try (NativeScope scope =NativeScope.unboundedScope()) {
             // IDXGIFactory1** dxgiFactory;
var ppDxgiFactory =IDXGIFactory1.allocatePointer(scope);
             // HRESULT = CreateDXGIFactory1(_uuid(dxgiFactory), &dxgiFactory)) 
checkResult(dxgi_h.CreateDXGIFactory1(IID_IDXGIFactory1,ppDxgiFactory));
             // IDXGIFactory1* MemorySegment pDxgiFactory =asSegment(MemoryAccess.getAddress(ppDxgiFactory),IDXGIFactory1.$LAYOUT());

             // (This)->lpVtbl MemorySegment vtbl =asSegment(IDXGIFactory1.lpVtbl$get(pDxgiFactory),IDXGIFactory1Vtbl.$LAYOUT());
             // lpVtbl->EnumAdapters1 MemoryAddress addrEnumAdapters =IDXGIFactory1Vtbl.EnumAdapters1$get(vtbl);

             // link the pointer MethodHandle MH_EnumAdapters1 =getSystemLinker().downcallHandle(
                 addrEnumAdapters,
                 MethodType.methodType(int.class,MemoryAddress.class,int.class,MemoryAddress.class),
                 FunctionDescriptor.of(C_INT,C_POINTER,C_INT,C_POINTER));

             /* [annotation][out] _COM_Outptr_ IDXGIAdapter1** */ MemorySegment ppOut =IDXGIAdapter1.allocatePointer(scope);
             checkResult((int)MH_EnumAdapters1.invokeExact(pDxgiFactory.address(),0,ppOut.address()));// IDXGIAdapter1* MemorySegment pAdapter =asSegment(MemoryAccess.getAddress(ppOut),IDXGIAdapter1.$LAYOUT());

             // (This)->lpVtbl MemorySegment vtbl2 =asSegment(IDXGIAdapter1.lpVtbl$get(pAdapter),IDXGIAdapter1Vtbl.$LAYOUT());
             // lpVtbl->EnumAdapters1 // 
HRESULT(*)(IDXGIAdapter1*,DXGI_ADAPTER_DESC1*) MemoryAddress addrGetDesc1 =IDXGIAdapter1Vtbl.GetDesc1$get(vtbl2);

             // link the pointer MethodHandle MH_GetDesc1 =getSystemLinker().downcallHandle(
                 addrGetDesc1,
                 MethodType.methodType(int.class,MemoryAddress.class,MemoryAddress.class),
                 FunctionDescriptor.of(C_INT,C_POINTER,C_POINTER));

             /* DXGI_ADAPTER_DESC1* */ MemorySegment pDesc =DXGI_ADAPTER_DESC1.allocate(scope);
             checkResult((int)MH_GetDesc1.invokeExact(pAdapter.address(),pDesc.address()));

             // print description MemorySegment descStr =DXGI_ADAPTER_DESC1.Description$slice(pDesc);
             String str =new String(descStr.toByteArray(),StandardCharsets.UTF_16LE);
             System.out.println(str);
         }
     }

     public static MemorySegment asSegment(MemoryAddress addr,MemoryLayout layout) {
         return MemorySegment.ofNativeRestricted(addr, layout.byteSize(),Thread.currentThread(),null,null);
     }

     static final MemorySegment IID_IDXGIFactory1 =GUID(
             (int)0x770aae78,
             (short)0xf26f,
             (short)0x4dba,
             new byte[]{(byte)0xa8,0x29,0x25,0x3c, (byte)0x83, (byte)0xd1, (byte)0xb3, (byte)0x87});

     static final MemorySegment GUID(int Data1,short Data2,short Data3,byte[] Data4) {
         MemorySegment ms =MemorySegment.allocateNative(guiddef_h.GUID.$LAYOUT());
         guiddef_h.GUID.Data1$set(ms, Data1);
         guiddef_h.GUID.Data2$set(ms, Data2);
         guiddef_h.GUID.Data3$set(ms, Data3);
         guiddef_h.GUID.Data4$slice(ms).copyFrom(MemorySegment.ofArray(Data4));
         return ms;
     }

     private static final int S_OK =0x00000000;

     private static void checkResult(int result) {
         switch (result) {
             case S_OK -> {}
             default ->throw new IllegalStateException("Unknown result: " +String.format("%X8", result));
         }
     }
}


Which on my machine prints: NVIDIA GeForce GTX 1050 Ti

HTH,
Jorn

On 14/09/2020 12:52, Maurizio Cimadamore wrote:
> I think there's something off in here:
>
>> MemorySegment segment =
>> MemorySegment.allocateNative(dxgi_h.IDXGIFactory1Vtbl.$LAYOUT().byteSize()); 
>>
>> MemoryAddress address = segment.address().addOffset(64);
> I see a couple of issues:
>
> 1) how do you determine that the offset of the function address you 
> want to obtain is 64 byte from the start? Didn't jextract give you a 
> getter for that?
>
> 2) Even assuming 64 byte is the correct offset, note that the segment 
> will be initialized to all zeros - I presume this vtable must be 
> filled in somehow, or perhaps obtained from a global variable?
>
> From your earlier email I see this:
>
>> interface IDXGIFactory
>>       {
>>           CONST_VTBL struct IDXGIFactoryVtbl *lpVtbl;
>>       };
> So... perhaps once yo create a IDXGIFactory, you can just grab the 
> pointer to the vtable by accessing its `lpVtbl` field - no need to 
> allocate a new segment (the library has done the allocation for you).
>
> If I'm correct, this can be achieved with something like (not sure if 
> the names are going to be 100% correct since I don't have a jextracted 
> version of the API with me - so beware, I have NOT tested this :-) ):
>
> var dxgiFactoryPtr = 
> MemoryAccess.getAddress(MemorySegment.ofNativeRestricted(), dxgiFactory);
> var lpVtbl = 
> MemoryAccess.getAddress(MemorySegment.ofNativeRestricted(), 
> dxgiFactoryPtr);
> var vtable = RuntimeHelper.ofArrayRestricted(lpVtbl, 
> IDXGIFactory1Vtbl.$LAYOUT(), 1); //1
> var address = IDXGIFactory1Vtbl$EnumAdapters$get(vtable);
>
>
> And this should give the address pointer you are looking for. We need 
> to smooth the edges especially around (1) - e.g. to turn a random 
> native pointer into a struct with given layout (jextract should 
> generate a static helper for that, see [1]) - but with the code above 
> you should be able to get there, I hope.
>
> P.S.
> In these situations, my suggestion is, especially when attacking a 
> native library for the first time, to always try the library directly 
> using C and see what happens, to understand exactly what kind of 
> pattern/idiom the library requires. I did that many times and realized 
> my assumptions on how library worked were wrong; other users have 
> posted similar instances in this mailing list. Once you get things 
> working in C, porting the code in Java using the Panama API is 
> typically a trivial matter.
>
> Maurizio
>
> [1] - https://bugs.openjdk.java.net/browse/JDK-8253102
>
>
> On 13/09/2020 02:37, Michael Ennen wrote:
>> Well, progress of a kind? This crashes the VM instead of not 
>> compiling or
>> throwing an exception:
>>
>> // IDXGIFactory1** dxgiFactory;
>> var dxgiFactory = scope.allocate(C_POINTER);
>> // HRESULT = CreateDXGIFactory1(_uuid(dxgiFactory), &dxgiFactory))
>> int hresult = dxgi_h.CreateDXGIFactory1(GUID(IID.IID_IDXGIFactory1),
>> dxgiFactory);
>> System.out.println("hresult: " + hresult);
>> var dxgiAdapter = scope.allocate(C_POINTER);
>> System.out.println("IDXGIFactory1Vtbl byte size: " +
>> dxgi_h.IDXGIFactory1Vtbl.$LAYOUT().byteSize()); //112
>> MemorySegment segment =
>> MemorySegment.allocateNative(dxgi_h.IDXGIFactory1Vtbl.$LAYOUT().byteSize()); 
>>
>> MemoryAddress address = segment.address().addOffset(64);
>> FunctionDescriptor functionDescriptor = FunctionDescriptor.of(C_INT, 
>> C_INT,
>> C_POINTER);
>> MethodType methodType = MethodType.methodType(int.class, int.class,
>> MemoryAddress.class);
>> MethodHandle methodHandle = getSystemLinker().downcallHandle(address,
>> methodType, functionDescriptor);
>> try {
>> methodHandle.invokeWithArguments(0, dxgiAdapter.address());
>> } catch (Throwable throwable) {
>> throwable.printStackTrace();
>> }
>>
>> On Sat, Sep 12, 2020 at 6:34 PM Michael Ennen <mike.ennen at gmail.com> 
>> wrote:
>>
>>> I think I see why. It's because I am passing in a MemorySegment, 
>>> which is
>>> the dxgiAdapter which I initialize thusly:
>>>
>>> var dxgiAdapter = scope.allocate(C_POINTER);
>>>
>>> I guess I need to make it a MemoryAddress instead.
>>>
>>> On Sat, Sep 12, 2020 at 2:25 PM Michael Ennen <mike.ennen at gmail.com>
>>> wrote:
>>>
>>>> Thanks so much Ty. That's super helpful. I think I was able to make 
>>>> quite
>>>> a bit of progress:
>>>>
>>>> var dxgiAdapter = scope.allocate(C_POINTER);
>>>> System.out.println("IDXGIFactory1Vtbl byte size: " +
>>>> dxgi_h.IDXGIFactory1Vtbl.$LAYOUT().byteSize());
>>>> MemorySegment segment =
>>>> MemorySegment.allocateNative(dxgi_h.IDXGIFactory1Vtbl.$LAYOUT().byteSize()); 
>>>>
>>>> MemoryAddress address = segment.address().addOffset(64);
>>>> FunctionDescriptor functionDescriptor = FunctionDescriptor.of(C_INT,
>>>> C_INT, C_POINTER);
>>>> MethodType methodType = MethodType.methodType(int.class, int.class,
>>>> MemoryAddress.class);
>>>> MethodHandle methodHandle = getSystemLinker().downcallHandle(address,
>>>> methodType, functionDescriptor);
>>>> try {
>>>> methodHandle.invokeWithArguments(0, dxgiAdapter);
>>>> } catch (Throwable throwable) {
>>>> throwable.printStackTrace();
>>>> }
>>>>
>>>> Note that the EnumAdapters1 function signature is actually:
>>>>
>>>> HRESULT EnumAdapters1(
>>>>    UINT          Adapter,
>>>>    IDXGIAdapter1 **ppAdapter
>>>> );
>>>>
>>>>
>>>> So that's why I went with:
>>>>
>>>> FunctionDescriptor functionDescriptor = FunctionDescriptor.of(C_INT,
>>>> C_INT, C_POINTER);
>>>>
>>>> and then
>>>>
>>>> methodHandle.invokeWithArguments(0, dxgiAdapter);
>>>>
>>>> I did some fancy stuff as per your suggestion which I feel should be
>>>> getting the address of the EumAdapters1 function
>>>> pointer (basically start at the base address of the struct and then 
>>>> move
>>>> 64 bytes past, which should be the 8th function
>>>> pointer which in this case is EnumAdapters1).
>>>>
>>>> I do get the following exception on invoking the method handle, 
>>>> though:
>>>>
>>>> hresult: 0
>>>> factory: MemorySegment{ id=0x3898fd8a limit: 8 }
>>>> IDXGIFactory1Vtbl byte size: 112
>>>> java.lang.ClassCastException: Cannot cast
>>>> jdk.internal.foreign.NativeMemorySegmentImpl to
>>>> jdk.incubator.foreign.MemoryAddress
>>>>          at java.base/java.lang.Class.cast(Class.java:3816)
>>>>          at
>>>> java.base/java.lang.invoke.MethodHandle.invokeWithArguments(MethodHandle.java:733) 
>>>>
>>>>          at com.dx12.DX12.main(DX12.java:91)
>>>>          at
>>>> java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native
>>>> Method)
>>>>          at
>>>> java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:64) 
>>>>
>>>>          at
>>>> java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
>>>>
>>>>          at java.base/java.lang.reflect.Method.invoke(Method.java:564)
>>>>          at
>>>> jdk.compiler/com.sun.tools.javac.launcher.Main.execute(Main.java:415)
>>>>          at
>>>> jdk.compiler/com.sun.tools.javac.launcher.Main.run(Main.java:192)
>>>>          at
>>>> jdk.compiler/com.sun.tools.javac.launcher.Main.main(Main.java:132)
>>>> vh: VarHandle[varType=jdk.incubator.foreign.MemoryAddress,
>>>> coord=[interface jdk.incubator.foreign.MemorySegment]]
>>>>
>>>> Any more assistance would be greatly appreciate but undeserved :).
>>>>
>>>>
>>>> On Sat, Sep 12, 2020 at 1:41 AM Ty Young <youngty1997 at gmail.com> 
>>>> wrote:
>>>>
>>>>> Not a JDK developer, but if you want to do this by hand(aka no
>>>>> jextract), I had to do this for NVAPI which obfuscates function 
>>>>> pointers
>>>>> using keys, e.g.:
>>>>>
>>>>>
>>>>> nvapi_h.nvapi_QueryInterface(0x0150e828);
>>>>>
>>>>>
>>>>> where "0x0150e828" is the "NvAPI_Initialize" function key as per the
>>>>> Open Source headers Nvidia released sometime ago.
>>>>>
>>>>>
>>>>> The only difference is that, since the function pointer is in a 
>>>>> struct,
>>>>> you need to get the segment in the struct containing the function
>>>>> pointer(see "slice" MemorySegment method) and actually read from it
>>>>> using a VarHandle. You then take that MemoryAddress, with a
>>>>> FunctionDescriptor and MethodType, and create a MethodHandle. For
>>>>> reference, this my working code("NativeFunction" and "NativeTypes" do
>>>>> not exist in Panama, those are mine but they dont matter) for the
>>>>> NvAPI_Initalize API:
>>>>>
>>>>>
>>>>> public class NvAPI_Initialize implements NativeFunction
>>>>> {
>>>>>       private final MemoryAddress address;
>>>>>       private final FunctionDescriptor descriptor;
>>>>>       private final MethodType type;
>>>>>
>>>>>       private final MethodHandle handle;
>>>>>
>>>>>       public NvAPI_Initialize() throws NoSuchMethodException, 
>>>>> Throwable
>>>>>       {
>>>>>           this.address = nvapi_h.nvapi_QueryInterface(0x0150e828);
>>>>>
>>>>>           this.descriptor = FunctionDescriptor.of(NativeTypes.INT);
>>>>>
>>>>>           this.type = MethodType.methodType(int.class);
>>>>>
>>>>>           this.handle =
>>>>> CSupport.getSystemLinker().downcallHandle(this.address, this.type,
>>>>> this.descriptor);
>>>>>       }
>>>>>
>>>>>       // etc
>>>>>
>>>>> }
>>>>>
>>>>>
>>>>> It looks like in your case the FunctionDescriptor would be:
>>>>>
>>>>>
>>>>> this.descriptor = FunctionDescriptor.of(CSupport.C_POINTER,
>>>>> CSupport.C_INT, CSupport.C_POINTER);
>>>>>
>>>>>
>>>>> for MethodType:
>>>>>
>>>>>
>>>>> MethodType.methodType(MemoryAddress.class, int.class,
>>>>> MemoryAddress.class);
>>>>>
>>>>>
>>>>> where the first argument takes in a IDXGIFactory, the second an 
>>>>> unsigned
>>>>> int, and the third a pointer to what looks like an opaque device that
>>>>> you pass to other functions.
>>>>>
>>>>>
>>>>> If you haven't already, it might help to make bindings with a 
>>>>> smaller,
>>>>> less complicated library just to get a feel with how things work 
>>>>> under
>>>>> the hood.
>>>>>
>>>>>
>>>>> Hope this helps.
>>>>>
>>>>>
>>>>> On 9/12/20 2:23 AM, Michael Ennen wrote:
>>>>>> Looking into this a bit further I think I chose a really bad 
>>>>>> library to
>>>>>> start with as the function I need to call, EnumAdapters, is inside
>>>>>> some type of "VTbl" struct that is trying to emulate a C++ class 
>>>>>> with
>>>>>> virtual functions. What jextract is returning is most likely 
>>>>>> *correct*
>>>>>> but in this case so complicated because of how it is laid out in 
>>>>>> C. It
>>>>> is
>>>>>> not clear at all how to grab the EnumAdapters function from
>>>>>> this mess, and it is probably above my head.
>>>>>>
>>>>>> EXTERN_C const IID IID_IDXGIFactory;
>>>>>>
>>>>>> #if defined(__cplusplus) && !defined(CINTERFACE)
>>>>>>
>>>>>> MIDL_INTERFACE("7b7166ec-21c7-44ae-b21a-c9ae321ae369")
>>>>>>       IDXGIFactory : public IDXGIObject
>>>>>>       {
>>>>>>       public:
>>>>>>           virtual HRESULT STDMETHODCALLTYPE EnumAdapters(
>>>>>>               /* [in] */ UINT Adapter,
>>>>>>               /* [annotation][out] */
>>>>>>               _COM_Outptr_  IDXGIAdapter **ppAdapter) = 0;
>>>>>>
>>>>>>           virtual HRESULT STDMETHODCALLTYPE MakeWindowAssociation(
>>>>>>               HWND WindowHandle,
>>>>>>               UINT Flags) = 0;
>>>>>>
>>>>>>           virtual HRESULT STDMETHODCALLTYPE GetWindowAssociation(
>>>>>>               /* [annotation][out] */
>>>>>>               _Out_  HWND *pWindowHandle) = 0;
>>>>>>
>>>>>>           virtual HRESULT STDMETHODCALLTYPE CreateSwapChain(
>>>>>>               /* [annotation][in] */
>>>>>>               _In_  IUnknown *pDevice,
>>>>>>               /* [annotation][in] */
>>>>>>               _In_  DXGI_SWAP_CHAIN_DESC *pDesc,
>>>>>>               /* [annotation][out] */
>>>>>>               _COM_Outptr_  IDXGISwapChain **ppSwapChain) = 0;
>>>>>>
>>>>>>           virtual HRESULT STDMETHODCALLTYPE CreateSoftwareAdapter(
>>>>>>               /* [in] */ HMODULE Module,
>>>>>>               /* [annotation][out] */
>>>>>>               _COM_Outptr_  IDXGIAdapter **ppAdapter) = 0;
>>>>>>
>>>>>>       };
>>>>>>
>>>>>>
>>>>>> #else /* C style interface */
>>>>>>
>>>>>>       typedef struct IDXGIFactoryVtbl
>>>>>>       {
>>>>>>           BEGIN_INTERFACE
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
>>>>>>               IDXGIFactory * This,
>>>>>>               /* [in] */ REFIID riid,
>>>>>>               /* [annotation][iid_is][out] */
>>>>>>               _COM_Outptr_  void **ppvObject);
>>>>>>
>>>>>>           ULONG ( STDMETHODCALLTYPE *AddRef )(
>>>>>>               IDXGIFactory * This);
>>>>>>
>>>>>>           ULONG ( STDMETHODCALLTYPE *Release )(
>>>>>>               IDXGIFactory * This);
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *SetPrivateData )(
>>>>>>               IDXGIFactory * This,
>>>>>>               /* [annotation][in] */
>>>>>>               _In_  REFGUID Name,
>>>>>>               /* [in] */ UINT DataSize,
>>>>>>               /* [annotation][in] */
>>>>>>               _In_reads_bytes_(DataSize)  const void *pData);
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *SetPrivateDataInterface )(
>>>>>>               IDXGIFactory * This,
>>>>>>               /* [annotation][in] */
>>>>>>               _In_  REFGUID Name,
>>>>>>               /* [annotation][in] */
>>>>>>               _In_opt_  const IUnknown *pUnknown);
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *GetPrivateData )(
>>>>>>               IDXGIFactory * This,
>>>>>>               /* [annotation][in] */
>>>>>>               _In_  REFGUID Name,
>>>>>>               /* [annotation][out][in] */
>>>>>>               _Inout_  UINT *pDataSize,
>>>>>>               /* [annotation][out] */
>>>>>>               _Out_writes_bytes_(*pDataSize)  void *pData);
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *GetParent )(
>>>>>>               IDXGIFactory * This,
>>>>>>               /* [annotation][in] */
>>>>>>               _In_  REFIID riid,
>>>>>>               /* [annotation][retval][out] */
>>>>>>               _COM_Outptr_  void **ppParent);
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *EnumAdapters )(
>>>>>>               IDXGIFactory * This,
>>>>>>               /* [in] */ UINT Adapter,
>>>>>>               /* [annotation][out] */
>>>>>>               _COM_Outptr_  IDXGIAdapter **ppAdapter);
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *MakeWindowAssociation )(
>>>>>>               IDXGIFactory * This,
>>>>>>               HWND WindowHandle,
>>>>>>               UINT Flags);
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *GetWindowAssociation )(
>>>>>>               IDXGIFactory * This,
>>>>>>               /* [annotation][out] */
>>>>>>               _Out_  HWND *pWindowHandle);
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *CreateSwapChain )(
>>>>>>               IDXGIFactory * This,
>>>>>>               /* [annotation][in] */
>>>>>>               _In_  IUnknown *pDevice,
>>>>>>               /* [annotation][in] */
>>>>>>               _In_  DXGI_SWAP_CHAIN_DESC *pDesc,
>>>>>>               /* [annotation][out] */
>>>>>>               _COM_Outptr_  IDXGISwapChain **ppSwapChain);
>>>>>>
>>>>>>           HRESULT ( STDMETHODCALLTYPE *CreateSoftwareAdapter )(
>>>>>>               IDXGIFactory * This,
>>>>>>               /* [in] */ HMODULE Module,
>>>>>>               /* [annotation][out] */
>>>>>>               _COM_Outptr_  IDXGIAdapter **ppAdapter);
>>>>>>
>>>>>>           END_INTERFACE
>>>>>>       } IDXGIFactoryVtbl;
>>>>>>
>>>>>>       interface IDXGIFactory
>>>>>>       {
>>>>>>           CONST_VTBL struct IDXGIFactoryVtbl *lpVtbl;
>>>>>>       };
>>>>>>
>>>>>>
>>>>
>>>> -- 
>>>> Michael Ennen
>>>>
>>>
>>> -- 
>>> Michael Ennen
>>>
>>


More information about the panama-dev mailing list