How to initiate a C struct that includes a pointer from zig

I’m sorry for the bold font here below but I couldn’t understand how to get rid of it.

I want zig to import a C file and let zig compile it for me. The C file also include a .h file that defines a struct. I have simplified my real files here but the problem is the same.
----------------------------------simple.h------------------------

struct simple
{
    unsigned long* ptr_to_val;
};

unsigned long add_value( struct simple *s_ptr );

----------------------------------simple.c------------------------------------------

#include "simple.h"

unsigned long add_value( struct simple *s_ptr )
{
    return *(s_ptr->ptr_to_val) + 1;
}

I want to call add_value() from zig with a legal value of s_ptr->ptr_to_val .
I declare my struct variable in the test part in main.zig like this.
-------------------------------------------------main.zig-----------------------------

const test_file = @cImport(
{
   @cInclude("simple.c");
});

test "simple test" {
   var ret: c_ulong = undefined;
   var st = test_file.simple{.dummy = 2, .ptr_to_val = undefined};

  // How to give the st.ptr_to_val a value that I can use when calling add_test() ? 
   ret = test_file.add_value(st.ptr_to_val)
}

1 Like

.ptr_to_val = &ret should work. And of course you should initialize ret to some value before you call the the C function.

Oh and also, it is generally a bad idea to @cInclude non-header files.
The zig internal parser/translator for C files which gets invoked on @cImport has sometimes trouble translating more complex implementations.
Instead the recommended way is to add C source files to the build.zig using exe.addCSourceFile and only @cInclude the header files.

2 Likes

Thanks for the hint to not use @cInclude non header files, I will try that.
Shouldn’t the

var st = test_file.simple{.dummy = 2, .ptr_to_val = undefined}

assign an address to the pointer? And what the pointer is pointed to is undefined. That was my intention anyway.

Using undefined means that you make a promise to the zig compiler that you are going to set the value somehow later.
Zig compiler can put nothing (leave it uninitialized) or might set it to alternating 0 and 1 bits (0xAA bytes) in debug mode.
See: undefined in Zig Language Reference

Hello again and thanks. I got it to work with your trick with &ret.
I then include the .h, in main.zig, and adding my C-file in build.zig like this.
As you can see I’m only interested to build and run the test.

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});
    const unit_tests = b.addTest(.{
        .root_source_file = .{ .path = "src/main.zig" },
        .target = target,
        .optimize = optimize,
    });
    
    const CFlags = &.{};
    unit_tests.addCSourceFile(.{
        .file = .{
            .path = "c-src/MainTimer.cpp",
        },
        .flags = CFlags,
    });

    unit_tests.addIncludePath(.{ .path = "c-src" });
    const run_unit_tests = b.addRunArtifact(unit_tests);
    const test_step = b.step("test", "Run unit tests");
    test_step.dependOn(&run_unit_tests.step);
}

Now I got link errors like (error: lld-link: undefined symbol: MainTimer_Init)
To me it looks that I miss to link my C file or perhaps it is not even compiled.
Can you see what I’m doing wrong?

The only thing I see is that you are missing unit_tests.linkLibC(); and also linkLibCpp(). You need to explicitly link the C and C++ standard libraries if you use them.
But that wouldn’t explain this particular linker error. Are there other errors before that in the output?

If MainTimer_Init is in a .cpp file you must prefix the definition with extern "C" to get C linkage (C++ compiler does not mangle the name).

1 Like

The MainTimer.cpp file doesn’t use anything external so it should not be necessary to link the standard libs. There is no errors before the one I describe here above.
Dimdin can you elaborate around how to prefix the definition with extern “C”. I don’t understand how to do it.
By the way, MainTimer.cpp is a real production file and it is a pure C file. I don’t know why it is named .cpp. I guess it has something to do with our build system.

If that function lives in a .cpp file, it is being compiled as C++, and its name is being mangled. This is why you need to prefix it with extern C.

Thanks gonzo, now I know it is compiled with C++ compiler.
As you can understand I’m new to zig, can you please show me, from my build.zig here above, how to prefix it with extern C.
When I try I only got errors.

Prefixing with extern C is something you do in your C++ code. For example, if you have a file foo.cpp:

int bilbo() { return 5; }

extern "C" int frodo() { return 9; }

function bilbo will not be callable from C code (because its name will be mangled by the C++ compiler) but function frodo will be.

You could also compile file MainTimer.cpp with a C compiler, instead of a C++ compiler.

Thanks gonzo, but then I have to change in the production files and that is not so god. I realize that is bad practice to name a C file .cpp but we have a lot of them unfortunately. Do you know of any trick that can be used in build.zig instead?

You can add another c++ file: api.cpp and add the functions you need to call from zig there:

extern "C" void MainTimerInit() {
    // call C++ MainTimer_Init from here
}
1 Like

Keep in mind we are really talking about tricks here. As @leso suggested, you could export your symbol from a separate C++ file. You could also create blah.c as a symlink pointing to your C++ file, and compile that with a C compiler.

Nothing very elegant, I am afraid.