Opaque Pointer leads to Segfault in zig (works in c)

There is a library (that comes with a header.h) that runs fine with the c example, but segfaults in zig.

The C Code:

#include <string.h>
#include "irohnet.h"

int main() {
  Endpoint_t * ep = endpoint_default();
  endpoint_free(ep); // does not segfault

  return 0;
}

the zig code (snippet):

const std = @import("std");
const iroh = @import("iroh");
const dprint = std.debug.print;
const DISCOVERY_CONFIG_ALL: c_int = 3;

pub fn main(init: std.process.Init) !void {
    const path = "D:\\Code\\Zig\\SystemExamples\\iroh_p2p_dynamic_linking";

    var irohny = iroh{
        .lib = try .init(init.gpa, path, "iroh_c_ffi"), // dynamic lib because duplicate symbols otherwise
    };
    defer irohny.lib.close();

    const endpoint: ?*iroh.Endpoint_t = irohny.endpoint_default();
    irohny.endpoint_free(endpoint); //segfaults here
}

Due to duplicate symbols being a problem I opted to link the dynamic library instead of dealing with that headache. The type definitions and such did come from the translate-c of the header file. iroh.Endpoint_t is defined as an opaque type. As that appears to be the largest

Also (informed by print debugging) irohny.endpoint_default() does not crash and so I believe that the dynamic library is being filled correctly.

In case I am wrong the functions are filled in this manner :

var lib = try load_lib.open(alloc, path, lib_name);
errdefer lib.close();
return .{
    .endpoint_bind = lib.lookup(*const fn ([*c]const EndpointConfig_t, ?*SocketAddrV4_t, ?*SocketAddrV6_t, [*c]const ?*Endpoint_t) EndpointResult_t, "endpoint_bind") orelse return error.FunctionNotFound,
    .endpoint_free = lib.lookup(*const fn (?*Endpoint_t) void, "endpoint_free") orelse return error.FunctionNotFound,
    // and other functions too
};

the lookup function resolves to:

pub fn lookup(self: *Self, T: type, name: [:0]const u8) ?T {
    return switch (os) {
        // this is run on windows
        .windows => @ptrCast(GetProcAddress(self.lib, name.ptr) orelse return null),
        else => self.lib.lookup(T, name),
    };
}

The library was (before being compiled to a static/dynamic library) written in rust. Any help or advice would be appreciated.

Probably smth wrong with your dynamic loading code. Or there is something that must happen at load time and you don’t do with your manual loading.

Honestly your dynamic loading seems fishy, I would try to start by fixing the build system to load with regular tools.

I am uncertain by what you mean by “fixing the build system to load with regular tools”. Linking leads to a Duplicate Symbols error (the library comes from rust and ships a duplicate copy of some lib-c {or something along those lines}). This is a bug with the zig compiler as noted here. https://codeberg.org/ziglang/zig/issues/31182 do you mean to suggest that I fix this inside the compiler?

I have thought about this, but I have been told that the compilation pipeline is under heavy development due incremental compilation being added, and it does not, to me, seem trivial to fix it given my lack of knowledge of the zig internals. I figured that it would just be easier to load the library dynamically rather than attempting to fix zig source. I have also have not found any tool or resource that can systematically rename/prefix all the symbols in the source .lib or otherwise fix the duplicate-symbols error.

The dynamic loading code that I use I found at DynLib no longer supports Windows, and what is the intended solution? (Zonion’s post I believe, with the memory leak patched). If you want to know the architecture of how I do dynamic linking in more details it is this:

WrapperStruct
LibWithFunctionsStruct

WrapperStruct (irohny) has an internal LibWithFunctionsStruct (lib_iroh). lib_iroh has the type definitions generated from translate-c being called on the library-header.h, but other than that it is just a list of function pointers that is filled by the following snippet:

var lib = try load_lib.open(alloc, path, lib_name);
errdefer lib.close();
return .{
    .endpoint_bind = lib.lookup(*const fn ([*c]const EndpointConfig_t, ?*SocketAddrV4_t, ?*SocketAddrV6_t, [*c]const ?*Endpoint_t) EndpointResult_t, "endpoint_bind") orelse return error.FunctionNotFound,
    .endpoint_free = lib.lookup(*const fn (?*Endpoint_t) void, "endpoint_free") orelse return error.FunctionNotFound,
    // and other functions too
};

WrapperStruct exports the type definitions from lib_iroh and has wrappers defined for the various functions that end up being used.

pub fn endpoint_default(WS: *WrapperStruct) ?*Endpoint_t {
    return WS.lib.endpoint_default();
}

pub fn endpoint_free(WS: *WrapperStruct, ep: ?*Endpoint_t) void {
    return WS.lib.endpoint_free(ep);
}

I can paste the entirety of my source code here if you think it would be helpful. WrapperStruct is imported simply as “iroh” in the mainfile which I have already shown.

Thanks for responding btw; it’s nice to know that people read these. Any ideas are appreciated if you have them (I have pretty much run out myself)