Calling a C routine which expects a pointer to a string (in order to write it)

I’m trying to call a routine in a C library. The C library is this one: Index of /software/libidn/libidn2 The routine I want to use is idn2_to_ascii_8z, documented in Libidn2 2.3.4 As you can see, it will write in the parameter named output. There is an example in C Libidn2 2.3.4 but note that, unlike what the documentation requests, the variable named p is not allocated (and the C example works nevertheless).

Zig sees this routine as pub extern fn idn2_to_ascii_8z(input: [*c]const u8, output: [*c][*c]u8, flags: c_int) c_int; I’m quite lost about how to call that and I find no examples on various Zig forums. This code compiles:

var dest = try allocator.alloc(u8, maxsize);
const result = idn2.idn2_to_ascii_8z(orig, @alignCast(@ptrCast(&dest)), 0); 

but segfaults. I tried different variants but without any success. Any example of calling a routine with [*c][*c]u8 parameters?

the way I understood the documentation is that it’s the C function that will allocate the memory for the output. You just have to declare a viariable to hold the pointer to the new memory the C function will create.

var output: [*:0]u8 = undefined;
// then pass &output to the function

For freeing you will have to use malloc (ie the C allocator), which you can get by adding stdlib.h to your imports. Zig also exposes it from the stdlib but that interface requires you to pass in Zig slices, not just pointers-to-many, and that might make things awkward for you.

In any case, start by making the first part work and then think about freeing memory later.

1 Like

Well, it still segfaults. I wonder if the problem is not instead with the input parameter. I passed the working C example through zig translate-c , it still works but when I try to write a Zig program doing the same, it segfaults. Here is a minimum example:

const std = @import("std");
const idn2 = @cImport(@cInclude("idn2.h")); 

pub fn main() !void {
    const maxsize = 256;
    var orig: [maxsize]u8 = undefined; 
    orig[0] = 116;
    orig[1] = 0;
    var dest: [*c]u8 = undefined;
    var orig_ptr: [*c]u8 = @as([*c]u8, @ptrCast(@alignCast(&orig)));
    const result: c_int = idn2.idn2_to_ascii_8z(orig_ptr, &dest, idn2.IDN2_NONTRANSITIONAL);
    std.debug.print("Punycode({s}) = {s} (return code is {d})\n", .{ orig, dest, result});
}

And:

% zig build-exe tmp.zig -lidn2  && ./tmp 
Segmentation fault at address 0x0
???:?:?: 0x0 in ??? (???)
zsh: IOT instruction (core dumped)  ./tmp

I think you’re overthinking this with all these casts and C pointers and whatnot.

Try this:

$ cat main.zig
const std = @import("std");
const c = @cImport(@cInclude("idn2.h"));

pub fn main() !u8 {
    const stderr = std.io.getStdErr().writer();
    const stdin = std.io.getStdIn().reader();

    try stderr.writeAll("Input: ");

    var input: [256]u8 = undefined;
    const length = try stdin.readAll(&input);

    const end = std.mem.lastIndexOfNone(u8, input[0..length], &(std.ascii.whitespace ++ .{0})) orelse length;
    input[end + 1] = 0;

    var output: [*:0]u8 = undefined;
    if (c.idn2_to_ascii_8z(
        &input,
        @ptrCast(&output),
        c.IDN2_NONTRANSITIONAL,
    ) != c.IDN2_OK) {
        std.log.err("idn2_to_ascii_8z() failed!", .{});
        return 1;
    }
    defer c.idn2_free(output);

    std.debug.print("Punycode(\"{s}\") = \"{s}\"\n", .{ input, output });
    return 0;
}

$  zig build-exe -lc -lidn2 -femit-bin=./2074 main.zig && ./2074
Input: βόλος.com
Punycode("βόλος.com") = "xn--nxasmm1c.com"

On amd64, it crashes the same:

% ./zig-out/bin/to-ascii     
Input: βόλος.com
Segmentation fault at address 0x0
???:?:?: 0x0 in ??? (???)
zsh: IOT instruction  ./zig-out/bin/to-ascii
% zig version
0.12.0-dev.1594+7048e9366

It compiles and runs on RISC-V.

I tested the above code on an amd64 machine and it worked fine.

Maybe you can run it through a debugger and see exactly where it segfaults?

EDIT: I missed the fact you are running on a recent nightly, while I was testing this on the 0.11 stable release of Zig. Nevertheless, I just tested it with the most recently nightly and it still works for me:

$ ./zig-linux-x86_64-0.12.0-dev.1595+70d8baaec/zig version
0.12.0-dev.1595+70d8baaec

$ ./zig-linux-x86_64-0.12.0-dev.1595+70d8baaec/zig build-exe -lc -lidn2 -femit-bin=./2074 main.zig && ./2074
Input: βόλος.com
Punycode("βόλος.com") = "xn--nxasmm1c.com"

When building, rather than zig build-exe tmp.zig -lidn2, try zig build-exe tmp.zig -lc -lidn2 to explicitly link libc. With the former command, it segfaults for me as well, but with the latter command, it works.

Unfortunately, the backtrace I get from LLDB is not helpful for this particular segfault (GDB’s is no better either):

⬢[ian@toolbox tmp]$ lldb tmp
(lldb) target create "tmp"
Current executable set to '/var/home/ian/tmp/tmp' (x86_64).
(lldb) r
Process 123849 launched: '/var/home/ian/tmp/tmp' (x86_64)
Process 123849 stopped
* thread #1, name = 'tmp', stop reason = signal SIGSEGV: address not mapped to object (fault address: 0x0)
    frame #0: 0x0000000000000000
error: memory read failed for 0x0
(lldb) bt
* thread #1, name = 'tmp', stop reason = signal SIGSEGV: address not mapped to object (fault address: 0x0)
  * frame #0: 0x0000000000000000

This looks like the same sort of behavior reported here: zig build-exe not warning for missing -lc parameter · Issue #10410 · ziglang/zig · GitHub

4 Likes

Indeed, it works, many thanks. (Alternative: add exe.linkLibC(); to build.zig.) What is strange is that there is no error message when running zig build...

2 Likes