1

I am trying to use openVR dll from delphi. However, this dll got only limited function exported, a lot of function is stay inside interfaces.

since there are some samples for using openVR, so I take a look at c version header and a c# version header to see how they do that.

I do not get quite knowledge from the c header, while in the c# header, I notice they are using some struct(like interface in delphi) to store the function table, and have an class (like implementation class in delphi) for that struct, inside the class there is an create function witch seems like hack the pointer to all these functions.

IVRSystem FnTable;
internal CVRSystem(IntPtr pInterface)
{
    FnTable = (IVRSystem)Marshal.PtrToStructure(pInterface, typeof(IVRSystem));
}

The pInterface pointer is given at a big class which contains a set of the implementation class.

public CVRSystem VRSystem()
{
    CheckClear();
    if (m_pVRSystem == null)
    {
        var eError = EVRInitError.None;
        var pInterface = OpenVRInterop.GetGenericInterface(FnTable_Prefix+IVRSystem_Version, ref eError);
        if (pInterface != IntPtr.Zero && eError == EVRInitError.None)
            m_pVRSystem = new CVRSystem(pInterface);
    }
    return m_pVRSystem;
}

where OpenVRInterop.GetGenericInterface is one of the exported function by dll.

So my problem are :

(1) can delphi did something like what C# does? it seems like he call these functions just by the raw pointer (address? offset?) I searched for delphi deal with dll, only for two ways(static and dynamics) both require function names.

function someFunction(a : integer) :integer; stdcall; external ’someDll.dll’;

dllHandle := LoadLibrary(’someDll.dll’);
@someFunction := GetProcAddress(dllHandle,'someFunction');

(2) How is the c header did the library load? I did not find related code there.

Mengchao
  • 81
  • 7
  • 3
    One thing: due to the fact that stuff must be marshalled between C# and the DLL, it is always a little harder to translate than a nice plain C header. C and Delphi understand each other very well. So instead of using the C# demo, try the C demo. And yes, it will probably use pointers. – Rudy Velthuis Oct 06 '16 at 07:08
  • 3
    Copying C# marshalling isn't going to be useful. You need to gain understanding. No short cuts. – David Heffernan Oct 06 '16 at 07:28
  • 1
    The OpenVR API is using non-COM interfaces based on C++ abstract classes. Delphi does not support consuming C++ classes, it uses a different model for its interface support. The C# code gets around this issue by marshaling plain structs that contain simulated vtable pointers to match the C++ class's memory layouts. C users have to do the same thing when using C++-based COM interfaces. You will have to do something similar in Delphi as well, so translating the C# code instead of the C code makes more sense in this situation. – Remy Lebeau Oct 06 '16 at 19:12

1 Answers1

1

Thanks to Remy's advice, I think I worked out a solution for that.

I do translate the C# header to delphi and it works fine now.

I will use VRSystem as example.

First, we need some basic enum, const, struct translate.

enum do need a Z4 tag to make the size match c style enum.

{$Z4}
ETrackingResult = (
    ETrackingResult_Uninitialized = 1,
    ETrackingResult_Calibrating_InProgress = 100,
    ETrackingResult_Calibrating_OutOfRange = 101,
    ETrackingResult_Running_OK = 200,
    ETrackingResult_Running_OutOfRange = 201
);

for struct, record is perfect match.

TrackedDevicePose_t = record
    mDeviceToAbsoluteTracking : HmdMatrix34_t;
    vVelocity : HmdVector3_t;
    vAngularVelocity : HmdVector3_t;
    eTrackingResult : ETrackingResult;
    bPoseIsValid : boolean;
    bDeviceIsConnected : boolean;
end;

and then we need to declear delegate functions for every function inside the inside the interface like this.

_GetRecommendedRenderTargetSize = procedure(var pnWidth : uint32; var pnHeight : uint32); stdcall;
_GetProjectionMatrix = function(eEye : EVREye; fNearZ : single; fFarZ : single; eProjType : EGraphicsAPIConvention) : HmdMatrix44_t; stdcall;
...
_AcknowledgeQuit_UserPrompt = procedure(); stdcall;

and a struct to hold them, but this time we need a perfect size match so we need packed record

PIVRSystem = ^IVRSystem;
IVRSystem = packed record
    GetRecommendedRenderTargetSize : _GetRecommendedRenderTargetSize;
    GetProjectionMatrix : _GetProjectionMatrix;
    ....
    AcknowledgeQuit_UserPrompt : _AcknowledgeQuit_UserPrompt;
end;

and finally a class holds the struct and will init this struct by giving an pointer to it.

CVRSystem = class
    FnTable : PIVRSystem;
    Constructor Create(FNPointer : IntPtr);

    procedure GetRecommendedRenderTargetSize(var pnWidth : uint32; var pnHeight : uint32);
    function GetProjectionMatrix(eEye : EVREye; fNearZ : single; fFarZ : single; eProjType : EGraphicsAPIConvention) : HmdMatrix44_t;
    ...
    procedure AcknowledgeQuit_UserPrompt();
end;

so now we can use these functions by calling the function inside CVRSystem which directly point to the function inside the FNTable

By this way, we use a struct as function table,I wonder if there will be a more tricky way to hack with the virtual method tables.

Mengchao
  • 81
  • 7