仅使用指针调用dll函数

本文关键字:dll 函数 调用 指针 | 更新日期: 2023-09-27 18:11:40

我正在尝试使用openVR dll从delphi。但是,这个dll只导出了有限的函数,很多函数都留在接口中。

因为有一些使用openVR的示例,所以我看了看c版本头和c#版本头,看看他们是如何做到的。

我没有从c头中获得足够的知识,而在c#头中,我注意到他们正在使用一些结构体(如delphi中的接口)来存储函数表,并有一个类(如delphi中的实现类)用于该结构体,类内部有一个创建函数,似乎可以hack所有这些函数的指针。

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

pInterface指针在一个包含一组实现类的大类上给出。

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;
}

其中OpenVRInterop.GetGenericInterface是dll导出的函数之一。

所以我的问题是:

(1) delphi可以做c#做的事情吗?似乎他调用这些函数只是通过原始指针(地址?抵消吗?)我搜索delphi处理dll,只有两种方式(静态和动态)都需要函数名。

function someFunction(a : integer) :integer; stdcall; external ’someDll.dll’;
dllHandle := LoadLibrary(’someDll.dll’);
@someFunction := GetProcAddress(dllHandle,'someFunction');

(2) c头文件是如何加载的?

仅使用指针调用dll函数

多亏了Remy的建议,我想我找到了解决办法。

我把c#头文件翻译成delphi,现在工作得很好。

我以VRSystem为例。

首先,我们需要一些基本的enum, const, struct translate

enum确实需要一个Z4标签来使大小匹配c样式enum。

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

对于结构体,记录是完全匹配的。

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

然后我们需要为接口内部的每个函数声明委托函数,像这样

_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;

和保存它们的结构体,但是这次我们需要一个完美的大小匹配所以我们需要打包记录

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

,最后一个类保存该结构体,并通过给出指向该结构体的指针对其进行初始化。

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;

那么现在我们可以通过调用CVRSystem内部的函数来使用这些函数,这些函数直接指向FNTable内部的函数

通过这种方式,我们使用结构体作为函数表,我想知道是否会有更棘手的方法来破解虚拟方法表