Stdout for C interop?

Hi!,

I need to pass stdout to a C function I am using.
What is the best way to do that in 15.2 ?
Is it different for upcoming 16* ?
thanks!

–Paul

Is stdout an argument of the C function?
What does the C function do with stdout?
Do you expect some kind of communication between the zig program and the C function based on stdout?

1 Like

Yes (it is an arg to a c function).

the type is: (after running translate-c on the header)

cout: ?*FILE

The C type from the original header file is:

FILE *out

I don’t need to otherwise interact with it from zig.

thanks,

You would have to provide the handle field of a zig std.[fs|IO].File to fdopen to get a c FILE*.

1 Like

Since you already include stdio.h which contains both FILE and stdout, give the argument the symbol stdout, which is in the same zig namespace as FILE.

1 Like

I don’t quite follow this.
One of the things I tried was passing std.fs.File.stdout().

src/root.zig:31:59: error: expected type '?*mtemplate.struct___sFILE', found 'fs.File'
    _ = mt.mtemplate_run_stdio(t, data, std.fs.File.stdout(), errbuf, 512);

note: parameter type declared here

pub extern fn mtemplate_run_stdio(tmpl: ?*struct_mtemplate, ns: ?*struct_mobject, out: ?*FILE, ebuf: [*c]u8, elen: usize) c_int;

Maybe I just have to make a intermediate var and turn the std.fs.File.stdout()
into an Optional and a pointer ?

Propably the name is mtemplate.stdout

1 Like

I tried that too.

src/mtemplate.zig:1162:62: error: C pointers cannot point to opaque types
pub const stdout = @import("std").zig.c_translation.cast([*c]struct___sFILE, __stdout);
                                            

Maybe this is actually a problem with the translate-c of the mtemplate.h
header file.

That error points to this in the translated zig file:

   1162 pub const stdout = @import("std").zig.c_translation.cast([*c]struct___sFILE, __stdout);


This is actually kinda hard to do in a platform-neutral manner. Translate-C runs into problems interpreting macros in stdio.h. I recommend adding a small C file to your project where you can place helper functions for working around untranslatable macros. Something like this:

#include <stdio.h>

FILE* get_stdio_ptr(int fd) {
    switch (fd) {
        case 0: return stdin;
        case 1: return stdout;
        case 2: return stderr;
    }
}
extern fn get_stdio_ptr(c_int) *c.FILE;
2 Likes

Thanks.

That makes sense conceptually, but I haven’t mixed C and zig within
a module yet. Like Where does the extern fn declaration go?
(or where does that *c.FILE get defined?)
When I use C I put it in its own module in the build file, and it’s
just C, and then I import that module to the zig modules.

Do you know of on example repo that does this pattern so I can
see it working?

I tried variations of what you show in my project, but it still
bottoms out at seeing that *FILE as an opaque type and complaining.

Can you give me the untranslated declaration of mtemplate_run_stdio?

1 Like

Oh, I guess I wasn’t being clear. c is just a variable holding the output from @cImport():


const c = @cImport({
    @cInclude("foobar-api.h");
});

The thing about stdout and friends is that they’re not variables on certain like Windows. They’re macros that expand into function calls on certain platforms (e.g. Windows). They cannot be translated into Zig. You cannot touch such mistranslated items.

1 Like

int mtemplate_run_stdio(struct mtemplate *tmpl, struct mobject *ns,
    FILE *out, char *ebuf, size_t elen);


Does the translated file have a declaration for FILE?

1 Like

205 pub const struct___sFILE = opaque {};
206 pub const FILE = struct___sFILE;


Well, that explains it, the autotranslation defines FILE as a separate type than what it is normal C.

So while practically it’s the same thing, the compiler doesn’t know about that.

I guess a @ptrCast should work here, even if I’m not really happy with this, but I’m also not entirely sure how you could fix that (as a user; I guess a smarter translator could deal with that).

1 Like

This is the most portable way, and will work everywhere.

@pkw You put this definition (and other utilities) in its own foo.c file, put their prototypes on foo.h and do the cInclude / cTranslate game with only foo.h, so that you get a zig prototype for the function, which you can call directly from your zig code.

2 Likes

If you can update the c and h files that define mtemplate_run_stdio then put the following declaration to the header file:

int mtemplate_run_stdout(struct mtemplate *tmpl, struct mobject *ns,
    char *ebuf, size_t elen);

and the following code to the c file:

int mtemplate_run_stdout(struct mtemplate *tmpl, struct mobject *ns,
    char *ebuf, size_t elen)
{
    return mtemplate_run_stdio(tmpl, ns, stdout, ebuf, elen);
}

finally from your zig code call mtemplate_run_stdout.


If you cannot modify the c and h files then:

  1. Create a foo.h file with the mtemplate_run_stdout declaration and #include whatever other include files you include in your @cInclude.
  2. Create a foo.c file with the mtemplate_run_stdout definition, including foo.h
  3. In your cInclude include foo.h
  4. Use mtemplate_run_stdout
1 Like

Thanks all! :slight_smile:

I haven’t been able to get back to this yet, but
I took notes and conceptually I get it, so I’m pretty
sure it’s gonna work :slight_smile: (jynx city :P)