How to reimplement Win32 MAKEINTRESOURCE macro in Zig

Hi! I am currently making a simple windowing library, and am working on the win32 part of it now. I have run into an issue that I have struggles with overcoming.
I am writing my own user32.zig implementation, and with that I am trying to convert the original MAKEINTRESOURCE macro from MINGW winuser.h, which you can see here:

#define MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))
#define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))

My attempt to make something similar in Zig looks like this:
Note: LPCSTR is equivalent to [*:0]const u8 and LPCWSTR is equivalent to [*:0]const u16.

pub fn makeIntResourceA(i: comptime_int) LPCSTR {
    return @as(windows.LPCSTR, @ptrFromInt(@as(usize, @intCast(i))));
}

pub fn makeIntResourceW(i: comptime_int) LPCWSTR {
    const address: usize = @intCast(i);
    const ptr: LPCWSTR = @ptrFromInt(address);
    return ptr;
}

This works fine for the makeIntResourceA function, since the LPCSTR just has a 1 byte alignment, the issues is when I am trying to use the makeIntResourceW function.
The makeIntResourceW works fine with values such as IDC_ARROW = 32512 because that has a 2 byte alignment, but with icons such as IDC_BEAM=32513 I get the following error, understandably.

install
└─ install opengl-example
   └─ zig build-exe opengl-example Debug native 1 errors
src\user32.zig:1480:38: error: pointer type '[*:0]const u16' requires aligned address
    const ptr: LPCWSTR = @ptrFromInt(address);

This code is then later used from my window implementation and is passed to the win32 LoadCursorW function. My question then becomes how can I even do something like this in Zig, or do I have to write my own winuser.h file that only includes the constants and the macro and use that instead?

2 Likes

Hello @Skarsh, welcome to the forum.

While LPCWSTR declaration remains the same, zig type system is not going to let you have a misaligned value.
You can use MAKEINTRESOURCEA with LoadCursorA, and use LoadCursorW when you have a string name.

Please note that there are windows bindings automatically generated from windows metadata

There was a similar issue with HWND_TOPMOST passed as a pointer with value -1:

2 Likes

Thanks for the reply @dimdin!

Yes, that’s a good point, and I think it should work fine for my usecase at least.

1 Like

My initial thought would be something like this:

const std = @import("std");
const windows = std.os.windows;

// LPCWSTR but with align(1)
const ResourceNamePtrW = [*:0]align(1) const windows.WCHAR;

extern "user32" fn LoadIconW(
    hInstance: ?windows.HINSTANCE,
    lpIconName: ResourceNamePtrW,
) callconv(windows.WINAPI) ?windows.HICON;

extern "user32" fn LoadCursorW(
    hInstance: ?windows.HINSTANCE,
    lpCursorName: ResourceNamePtrW,
) callconv(windows.WINAPI) ?windows.HCURSOR;

// Resource ordinals are limited to u16
fn makeIntResourceW(id: u16) ResourceNamePtrW {
    return @ptrFromInt(@as(usize, id));
}

const IDC_BEAM = 32513;
const beam_resource_name = makeIntResource(IDC_BEAM);

pub fn main() !void {
    // Load the beam cursor using the integer ID
    const beam_cursor = LoadCursorW(null, beam_resource_name);
    std.debug.print("cursor={*}\n", .{beam_cursor});

    // Load an icon from the .exe just to show that string names also work
    const icon_name = std.unicode.utf8ToUtf16LeStringLiteral("foo");
    const instance: windows.HINSTANCE = @ptrCast(windows.kernel32.GetModuleHandleW(null));
    const icon = LoadIconW(instance, icon_name);
    std.debug.print("icon={*}\n", .{icon});
}

with an .rc file like:

FOO ICON "test.ico"

Built via (on master version of Zig for .rc compilation support):

zig build-exe test.zig test.rc

and running it results in:

cursor=*os.windows.HCURSOR__opaque_2421@10007
icon=*os.windows.HICON__opaque_2812@d40c63

Another option is to make ResourceNamePtr an *opaque {} which means that you’d have to @ptrCast string names when calling the Load functions, but would make it so that you wouldn’t need separate makeIntResourceA/makeIntResourceW functions.

2 Likes