String madness + libC

I’m trying to call some C functions, but I’m constantly running into troubles with strings.

In a nutshell, I’m trying to pass a string (filename) to C function, but I get this error:

error: expected type '[2053]u8', found '[*]const u8'

Is there a good resource with examples to sort out the string/slice stuff?

You’re going to need to provide more context.

One way I can think of to reproduce this error would be

fn foo(bar: [2053]u8) void {
    _ = bar;
}

pub fn main() !void {
    const slice: []const u8 = &.{};
    foo(slice.ptr);
}

but I have no idea how you’d be running into this scenario when working with C code, and in fact [2053]u8 as a parameter is not allowed for functions with the .c calling convention, so I’m doubly confused.

I think one of the most important things is to understand the difference between arrays and pointers in Zig.

In Zig, arrays are value types, not pointer types. This means that e.g. [5]u8 takes up 5 actual bytes of memory, and, in your example, [2053]u8 is a value made up of 2053 bytes of memory.

For pointers, the language reference does a good job of explaining the different pointer types, and notes that slices (which are also a pointer, but carry an associated length, too) should generally be preferred in Zig code.

When working with C from Zig, my general advice would be to use sentinel-terminated slices on the Zig side (e.g. [:0]u8 or [:0]const u8; many standard library functions will have a Z suffixed function that will give you a sentinel-terminated result, e.g. std.fs.path.joinZ, etc) and then you can pass those as sentinel-terminated pointers when calling into C functions that expect null-termination. Note that string literals are already pointers to sentinel terminated arrays (*const [N:0]u8), so you can pass them directly to C functions without issue (e.g. your_c_function("foo");).

2 Likes

When I do translate-c, I can observer that the C code is converted to struct where the filename has type [2053]u8 (I guess it will be converted to [*c]u8 before executing the function.

I tried just passing the pointed (.ptr) to the field, but I still get an error:

error: expected type '[2053]u8', found '[*]const u8'
    image_info.*.filename = filename.ptr;

It sounds like it should be a simple task, but I’ve spent a whole day trying to figure this out…

Can you post the C code that is being converted (just the part that’s getting converted to a struct with a [2053]u8 field)?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <magick/api.h>
#include <magick/pixel_cache.h>

// Generate clangd JSON:
// bear -- gcc c_image_convert.c `GraphicsMagick-config --cppflags --ldflags --libs`
//
// Compile
// gcc -o c_image_convert c_image_convert.c -O `GraphicsMagick-config --cppflags --ldflags --libs`

...
{
  Image
    *image = (Image *) NULL;

  Image
    *image_resize = (Image *) NULL;

  char
    infile[MaxTextExtent],
    outfile[MaxTextExtent];

  int
    arg = 1,
    exit_status = 0;

  ImageInfo
    *imageInfo;

  ExceptionInfo
    exception;

  InitializeMagick(NULL);
  imageInfo=CloneImageInfo(NULL);
  GetExceptionInfo(&exception);

  if (argc != 5)
    {
      (void) fprintf ( stderr, "Usage: %s infile outfile WIDTH HEIGHT\n", argv[0] );
      (void) fflush(stderr);
      exit_status = 1;
      goto program_exit;
    }

  (void) strncpy(infile, argv[arg], MaxTextExtent-1 );
  arg++;
  (void) strncpy(outfile, argv[arg], MaxTextExtent-1 );

// Here the filename is assigned and on the next line it is used to open image
  (void) strcpy(imageInfo->filename, infile);
  image = ReadImage(imageInfo, &exception);
  if (image == (Image *) NULL)
    {
      CatchException(&exception);
      exit_status = 1;
      goto program_exit;
    }

  QuantizeInfo* quantizeInfo;
  quantizeInfo  = CloneQuantizeInfo(NULL);
  quantizeInfo->number_colors = 32;
  QuantizeImage(quantizeInfo, image);

  image_resize = ResizeImage(image, atoi(argv[3]), atoi(argv[4]), LanczosFilter, 1.0, &exception);

  if (image_resize == (Image *) NULL)
    {
      CatchException(&exception);
      goto program_exit;
    }

  (void) strcpy(image_resize->filename, outfile);
  if (!WriteImage (imageInfo, image_resize))
    {
      CatchException(&image_resize->exception);
      exit_status = 1;
      goto program_exit;
    }

    printf("Number of colors: %d\n", image->colors);



  program_exit:

    if (image != (Image *) NULL)
      DestroyImage(image);

    if (image_resize != (Image *) NULL)
      DestroyImage(image_resize);

    if (imageInfo != (ImageInfo *) NULL)
      DestroyImageInfo(imageInfo);

  DestroyMagick();

  return exit_status;
}

I know that zig does not like goto, I have re-written that with return error_value;

it’s the ImageInfo field filename that is translated to [2053]u8

I assume this line is ultimately what you’re trying to translate to Zig:

strcpy(imageInfo->filename, infile);

It is copying the bytes from infile into imageInfo->filename (including the null terminator). The equivalent in Zig would be to use @memcpy:

// Copy the bytes
// @memcpy requires the dest and src lengths to match, so we
// slice the filename field to the length of `filename`
@memcpy(&image_info.filename[0..filename.len], filename);
// Write the null terminator
image_info.filename[filename.len] = 0;

See what I said about arrays in this comment for some more explanation.

1 Like

This method of copying worked.

However, the first comment did not help me. I know what arrays are, I know how items are stored in arrays. That’s not where I have the problem. I am struggling with zig way of doing things at times. For example, how I can assign from one type to another similar type.

After you provided an example, it made sense!

1 Like

It’s a bit counterintuitive, but I would say that [2053]u8 and [*]const u8 are not actually similar types. To put it another way, how to convert between them is similar to how you’d go about converting between something like an integer type and [*]const u8.

1 Like

Yes, but some of these things feel more auto-magical in some of the other languages.

Just for reference, this is what zig translate-c suggested that I should do to assign the values:

    _ = strncpy(@as([*c]u8, @ptrCast(@alignCast(&infile))), (blk: {
        const tmp = arg;
        if (tmp >= 0) break :blk argv + @as(usize, @intCast(tmp)) else break :blk argv - ~@as(usize, @bitCast(@as(isize, @intCast(tmp)) +% -1));
    }).*, @as(c_ulong, @bitCast(@as(c_long, @as(c_int, 2053) - @as(c_int, 1)))));

This is not readable. At least not for me. (And somehow it failed to work… or I made some typo somewhere when transferring it the final code.)

translate-c is really only intended to be used with header files. Using it as a general C → Zig converter will give you entirely unreadable code, as you’ve seen.

3 Likes

It helps, sometimes. It’s easier for me to find what are the type translations that it does such as c_long to zig native type. But other than that…