COMMUNITY

HxCPP Externs, Help With Inline Functions and Pointers to Pointers

Hello, I’ve been working on creating some hxcpp externs for several directx libraries for myself and have ran into some small issues surrounding directx’s pointer usage and hxcpp inline function generation.

Since directx is rather pointer heavy I’ve been wrapping my extern classes in ::cpp::Pointer and pretty much exclusively using inline functions and untyped to make the haxe externs cleaner and easier to use.

Sample ID3D11Device and ID3D11Buffer externs, used in examples later on.


@:unreflective
@:native("::cpp::Pointer<ID3D11Device>")
@:include("d3d11.h")
extern class Device extends IUnknown
{
    inline function createBuffer(_description : BufferDescription, _buffer : Buffer) : Int
    {
        return untyped __cpp__('{0}->ptr->CreateBuffer(&{1}, nullptr, (ID3D11Buffer**)&{2})', this, _description, _buffer);
    }
}

@:unreflective
@:native("::cpp::Pointer<ID3D11Buffer>")
@:include("d3d11.h")
extern class Buffer extends IUnknown
{
    //
}

This has been working fine for the most part and I’m happy with how nice the haxe externs are to use but I’ve run into a small problem surrounding haxe inline functions and directx functions which take a pointer to a pointer.

Say I have a class and a member variable vertexBuffer which is of the Buffer extern type and in some function I call device.createBuffer passing vertexBuffer as the last argument to create a buffer. Despite that function returning S_OK (0) vertexBuffer will still be null instead of an address due to how hxcpp is generating inline functions.

    // Haxe
    if (device.createBuffer(bufferDesc, vertexBuffer) != 0)
    {
        throw 'Failed to create vertex buffer';
    }

    // Generated C++
    HXLINE( 245)		 cpp::Pointer<ID3D11Device> _this9 = this->device;
    HXDLIN( 245)		 cpp::Pointer<ID3D11Buffer> _buffer = this->vertexBuffer;
    HXDLIN( 245)		if (hx::IsNotEq( _this9->ptr->CreateBuffer(&bufferDesc, nullptr, (ID3D11Buffer**)&_buffer),0 )) {
    HXLINE( 247)			HX_STACK_DO_THROW(HX_("Failed to create vertex buffer",9a,75,75,96));
                        }

It seems like hxcpp creates local variables for inline functions arguments and then uses them. So in this case _buffer is assigned to the member variable vertexBuffer and that is used instead of using vertexBuffer directly. Since the directx createBuffer function takes a pointer to a pointer that temporary pointer variable will contains the address of the created buffer while vertexBuffer remains null.
I can work around this creating my own temporary pointer variable on the haxe side and reassign the member pointer afterwards and that seems to satisfy hxcpp for the inline function argument, but this makes the code messier and the externs harder to work with.

    // With temporary variable pointer

    // Haxe
    var tmpVtxBuffer : Buffer = null;
    if (device.createBuffer(bufferDesc, tmpVtxBuffer) != 0)
    {
        throw 'Failed to create vertex buffer';
    }
    vertexBuffer = tmpVtxBuffer;

    // Generated C++
    HXLINE( 244)		 cpp::Pointer<ID3D11Buffer> tmpVtxBuffer = null();
    HXLINE( 245)		 cpp::Pointer<ID3D11Device> _this9 = this->device;
    HXDLIN( 245)		if (hx::IsNotEq( _this9->ptr->CreateBuffer(&bufferDesc, nullptr, (ID3D11Buffer**)&tmpVtxBuffer),0 )) {
    HXLINE( 247)			HX_STACK_DO_THROW(HX_("Failed to create vertex buffer",9a,75,75,96));
                        }
    HXLINE( 249)		this->vertexBuffer = tmpVtxBuffer;

Doing the above everything works fine and vertexBuffer has the buffer address but I’d rather not have to create extra code for this work around. Directx has quite a few of these pointer to a pointer functions for creating resources so if anyone has any advice on how I can change the externs to avoid this it’d be greatly appreciated.

Thanks, and sorry for the long post.

I’m not sure you should use cpp.Pointer<NativeClass> in your native, it’s a hxcpp class that encapsulate a raw pointer not the raw pointer itself.

Afaik the best practice would be to have @:native("ID3D11Device") extern class Device and use cpp.Star<Device> to mean ID3D11Device* in your functions argments or class values.

But that would probably not change your issue here, for that you might want to use cpp.Pointer.addressOf instead of doing &{1} that might use the correct variable.
Or (possibly and) you might need to change your createBuffer function to take a cpp.Reference<BufferDescription> and cpp.Reference<Buffer> which should generate the correct code.

I think Valentine is right in the addressOf… I was recently writing hxcpp externs for Apache Cassandra, and one of the functions has this signature in C:

CASS_EXPORT void
cass_future_error_message(CassFuture* future,
                          const char** message,
                          size_t* message_length);

The extern looks like this:

@:native("cass_future_error_message") public static function future_error_message(future:RawPointer<CassFuture>, message:RawPointer<ConstCharStar>, message_length:RawPointer<SizeT>):Void;

and the way I use it is this:

var message:ConstCharStar = "";
var message_length:SizeT = 0;
Cassandra.future_error_message(statement_future, RawPointer.addressOf(message), RawPointer.addressOf(message_length));
trace(message.toString().substr(0, message_length));

All seems to work fine.

Cheers,
Ian

Thanks for the advice guys. I’ll have a mess around, try a few things out, and report back.

Ok, I’ve got something sorted out. I update the extern classes to not be warpped in hxcpp pointers and instead have a typedef for a cpp.Star.

typedef Device = Star<DeviceRef>;

@:unreflective
@:structAccess
@:native("ID3D11Device")
@:include("d3d11.h")
private extern class DeviceRef extends IUnknown {}

Then I use Pointer.addressOf and Star<Whatever> for the pointer to a pointer and it correctly generates inline functions properly.

if (D3D11.createDevice(adapter, cast Pointer.addressOf(device), cast Pointer.addressOf(context)) != 0)
{
    throw 'Failed to create D3D11 device';
}
if (factory.createSwapChain(device, description, cast Pointer.addressOf(swapchain)) != 0)
{
    throw 'Failed to create DXGI swapchain';
}
inline static function createDevice(_adapter : Adapter, _device : Star<Device>, _context : Star<DeviceContext>) : Int

Having to type Pointer.addressOf each time is a bit of a pain but it works and setting up typdefs allows me to do var device : Device = null; which saves having to manually type cpp.Star out loads of times.

You could add using cpp.Pointer; instead of importing it and change your Pointer.addressOf(myvar) to myvar.addressOf() which is slightly smaller.

1 Like

That’s true, I always forget you can use static extensions on any class. If its not StringTools or Lambda I always forget about them. Thanks for the help!

So heres something that could be kinda interesting: use an abstract to automatically get addressOf… I was just having a play around and came up with it (not sure if its a good idea tbh). So previous code:

var wcex:WNDCLASSEX = Windows.createWNDCLASSEX();
wcex.hInstance = hInst;
wcex.hIcon = null;
...
Windows.registerClassEx(RawPointer.addressOf(wcex));

Heres a method using an abstract:

abstract AutoPointer<T>(T) from T to T {
    public var type(get, never):T;
    private inline function get_type():T {
        return this;
    }
 
    @:to inline function toRawPointer<T>():RawPointer<T> {
        return RawPointer.addressOf(this);
    }
}

var wcex:AutoPointer<WNDCLASSEX> = Windows.createWNDCLASSEX();
wcex.type.hInstance = hInst;
wcex.type.hIcon = null;
...
Windows.registerClassEx(wcex)

The crappy part is using the type property on the AutoPointer to get the underlying fields, there very well be a way around that though - though i couldnt see anything obvious. Could be an interesting approach… not sure.

Ian

EDIT: the generated cpp looks a little funky so might be a bad idea:

::Dynamic* AutoPointer_Impl__obj::toRawPointer(::Dynamic this1){
    return &(this1);
}

Namely the Dynamic stuff and the fact it generates a warning C4172: returning address of local variable or temporary: this1 - oh well :slight_smile: