Pointer becomes 0 when passed to C functions

I need some more help with the zig binding to zflecs :smile:
For some reason a pointer inside a struct that is then passed to a zflecs function in C becomes 0x0000000000000000. I used a debugger to check the values and the struct has the field correctly filled, then it disappears once in the C function.

To give some context:

There is this struct system_desc_t in the C library which holds a callback field:

typedef struct ecs_system_desc_t {
// ...
    ecs_entity_t entity; // just an i32
    ecs_iter_action_t callback; // the pointer that disappears
// ...
} ecs_system_desc_t;

That ecs_iter_action_t is :

typedef void (*ecs_iter_action_t)(
    ecs_iter_t *it);

And the corresponding Zig representation:


pub const system_desc_t = extern struct {
// ...
    entity: entity_t = 0,
    callback: ?iter_action_t = null,
// ...
};

with the iter_action_t like this:

pub const iter_action_t = *const fn (it: *iter_t) callconv(.C) void;

Then in this code I pass the system_desc to the zflecs C function:

pub fn SYSTEM(..., system_desc: *system_desc_t) void { <-- received desc has callback field
    var entity_desc = entity_desc_t{};
    ...
    system_desc.entity = entity_init(world, &entity_desc); 
    _ = system_init(world, system_desc); <-- Inside here desc.callback is 0!
}

The entity field value in system_desc is actually there in the C function, meanwhile the callback field value became 0x0000000000000000.

Can you do me a favor? Make a small example where you pass in a pointer to that function but call it directly against the import (no function pointer between the two) and one where you go through a function pointer that is exactly declared like it is above. I’m very curious if the issue still persists.

Try printing the callback pointer right before system_init. If the pointer is valid, then the problem is inside the C library. If it’s already invalid, then you’re thrashing it somewhere in your Zig code.

@AndrewCodeDev I’m not sure what you meant with “call it directly against the import”. Instead of passing a *system_desc to the SYSTEM() function and then the input is passed to system_init, I call system_init directly with a newly created system_desc?

If you mean that the result is the same.

Also I went and tried with @cImport(@cInclude…)) and the C system_init has a value of anon_xxxx (some number) instead of a nullptr, I imagine it’s some internal representation (although it was then crashing when trying to invoke the callback anyway)

I can’t manage to have any output printed when running tests unfortunately, but from the debugger I can see the values and the callback pointer seems ok before system_init

Add a debugger watchpoint to break when the variable change value.

  • gdb: watch system_desc.callback
  • lldb: watchpoint set variable system_desc.callback

Maybe watching for a memory location is better: watchpoint commands

2 Likes

I’m looking for an issue with invalid register/stack setup. I’ve run across an issue where going against something directly imported from the cImport vs a function pointer to the same thing can have different results. This may not be your issue at all, just wanting to check :slight_smile:

const c = @cImoort(...);

c.foo(x, y, z); // this may succeed while...

const foo: *const @TypeOf(c.foo) = &c.foo;
foo(x, y, z); // this might fail...