marlersoft / zigwin32

Zig bindings for Win32 generated by https://github.com/marlersoft/zigwin32gen
MIT License
234 stars 30 forks source link

would you mind adding a tutorial ? #9

Closed yvz5 closed 6 months ago

yvz5 commented 2 years ago

Hi all, I am pretty new to zig and I wanted to use win32 lib to create a window. How would I go about doing it using this lib ?

marler8997 commented 2 years ago

I did a Zig Showtime talk on this project here: https://www.youtube.com/watch?v=HsnWZxrf5VE

The first part talks alot about how I created the project. More towards the end of the talk I show how it can be used.

Ciantic commented 2 weeks ago

Maybe even just a simple MsgBox example? It would still be useful!

I'm new to zig, I did this:

zig fetch git+https://github.com/marlersoft/zigwin32/ --save

But then what? Editor can't autocomplete with:

const win32 = @import("win32");
// or
const win32 = @import("zigwin32");

It just appears as an untyped item with no autocompletion etc.

Thanks.

BanacialPC2 commented 2 weeks ago

I keep the entire zigwin32 folder within my src folder and simply import("win32.zig") or the everything.zig file. You can search and replace this import with a build-file implemented module defintion / module import -> const win32 = import("win32"); when sharing with others or just dont.

i.e.:

const std = @import("std");
const builtin = @import("builtin");
const win32 = @import("win32.zig").everything;
const win32_zig = @import("win32.zig").zig;
const win32_std = std.os.windows;
const Allocator = std.mem.Allocator;

pub fn wWinMain(
    instance: win32.HINSTANCE,
    prevInstance: ?win32.HINSTANCE,
    commandLine: [*:0]u16,
    showCode: u32,
) callconv(WINAPI) i32 {
    _ = commandLine;
    _ = prevInstance;

    var windowClass: win32.WNDCLASSW = .{
        .style = win32.WNDCLASS_STYLES.initFlags(.{ .HREDRAW = 1, .VREDRAW = 1 }),
        .lpfnWndProc = win32MainWindowCallback,
        .cbClsExtra = 0,
        .cbWndExtra = 0,
        .hInstance = instance,
        .hIcon = null,
        .hCursor = null,
        .hbrBackground = null,
        .lpszMenuName = win32.L("SomeMenuName"),
        .lpszClassName = win32.L("SomeOtherName"),
    };

    if (win32.RegisterClassW(&windowClass) != 0) { ... }}

For ZLS to be able to peek into all defined modules in build.zig it would have to compile your build.zig and somehow look into the definitions made there, as to display / use the imported module in the correct manner. Maybe this is a WIP by ZLS or just not intended due to security issues with executing each modules own build.zig and potentially running malicious code.

When building your executable, to my knowledge, zig first runs a "sub-process" to compile your build fn in build.zig. This is when your imported module is being added to the list of available modules / actually being made available for the rest of the compilation process.

BanacialPC2 commented 2 weeks ago

Maybe even just a simple MsgBox example? It would still be useful! Thanks.

This is an old file of mine, when I tried to follow Molly Rockets "HandMadeHero" Playlist.

Still runs on my pc tho -> >zig run "path_to_file_containing_this"

const std = @import("std");
const win32 = @import("win32.zig").everything;
const win32_zig = @import("win32.zig").zig;
const win32_std = std.os.windows;
const Allocator = std.mem.Allocator;

pub const UNICODE = true;
const WINAPI = win32_std.WINAPI;

// ###########################################################
//
//  Global structs
//
// ###########################################################

const Win32_offscreen_buffer = struct {
    info: win32.BITMAPINFO,
    memory: ?*anyopaque = null,
    width: u32,
    height: u32,
    bytesPerPixel: u32,
    pitch: u32,
};
const Win32_window_dimension = struct {
    height: u32,
    width: u32,
};

const win32_sound_output = struct {
    samplesPerSecond: u32,
    toneHz: u32,
    volume: f32,
    runningSampleIndex: u32,
    wavePeriod: u32,
    bytesPerSample: u32,
    bufferSize: u32,
    latencySampleCount: u32,
};

// ###########################################################
//
//  Global variables
//
// ###########################################################

var running = true;
var global_buffer: Win32_offscreen_buffer = std.mem.zeroInit(Win32_offscreen_buffer, .{});
var global_window: Win32_window_dimension = .{ .height = 720, .width = 1280 };
var global_secondary_buffer: ?*win32.IDirectSoundBuffer = undefined;

pub fn win32InitSound(window: win32.HWND, samplesPerSecond: u32, buffersize: u32) void {
    var directSound: ?*win32.IDirectSound = undefined;
    if (win32_zig.SUCCEEDED(win32.DirectSoundCreate(null, &directSound, null))) {
        var waveFormat: win32.WAVEFORMATEX = .{
            .wFormatTag = win32.WAVE_FORMAT_PCM,
            .nChannels = 2,
            .nSamplesPerSec = samplesPerSecond,
            .wBitsPerSample = 16,
            .nBlockAlign = 4, // (WaveFormat.nChannels)*(WaveFormat.wBitsPerSample)/8;
            .nAvgBytesPerSec = 4 * samplesPerSecond,
            .cbSize = 0,
        };
        if (win32_zig.SUCCEEDED(directSound.?.IDirectSound_SetCooperativeLevel(window, win32.DSSCL_PRIORITY))) {
            var buffer1Description: win32.DSBUFFERDESC = std.mem.zeroInit(win32.DSBUFFERDESC, .{
                .dwSize = @sizeOf(win32.DSBUFFERDESC),
                .dwFlags = win32.DSBCAPS_PRIMARYBUFFER,
            });
            var primaryBuffer: ?*win32.IDirectSoundBuffer = undefined;
            if (win32_zig.SUCCEEDED(directSound.?.IDirectSound_CreateSoundBuffer(&buffer1Description, &primaryBuffer, null))) {
                if (win32_zig.SUCCEEDED(primaryBuffer.?.IDirectSoundBuffer_SetFormat(&waveFormat))) {
                    std.log.debug("Primary Buffer Format was set", .{});
                }
            }
        }
        var buffer2Description: win32.DSBUFFERDESC = std.mem.zeroInit(win32.DSBUFFERDESC, .{
            .dwSize = @sizeOf(win32.DSBUFFERDESC),
            .dwFlags = 0,
            .dwBufferBytes = buffersize,
            .lpwfxFormat = &waveFormat,
        });
        if (win32_zig.SUCCEEDED(directSound.?.IDirectSound_CreateSoundBuffer(&buffer2Description, &global_secondary_buffer, null))) {
            std.log.debug("Secondary Buffer created sucessfully.", .{});
        }
    }
}

pub fn win32FillSoundBuffer(soundOutPut: *win32_sound_output, bytesToLock: u32, bytesToWrite: u32) void {
    var region1: ?*anyopaque = undefined;
    var region2: ?*anyopaque = undefined;
    var regionSize1: u32 = 0;
    var regionSize2: u32 = 0;
    if (win32_zig.SUCCEEDED(global_secondary_buffer.?.IDirectSoundBuffer_Lock(
        bytesToLock,
        bytesToWrite,
        &region1,
        &regionSize1,
        &region2,
        &regionSize2,
        0,
    ))) {
        var sampleOut: ?[*]i16 = @as(?[*]i16, @ptrCast(@alignCast(region1)));
        var place: usize = 0;
        const region1SampleCount: u32 = regionSize1 / soundOutPut.bytesPerSample;
        var sampleIndex1: usize = 0;
        while (sampleIndex1 < region1SampleCount) : (sampleIndex1 += 1) {
            const t: f32 = 2 * std.math.pi * @as(f32, @floatFromInt(soundOutPut.runningSampleIndex)) / @as(f32, @floatFromInt(soundOutPut.wavePeriod));
            const sinValue: f32 = std.math.sin(t);
            const sampeValue: i16 = @intFromFloat(sinValue * soundOutPut.volume);
            sampleOut.?[place] = sampeValue;
            sampleOut.?[place + 1] = sampeValue;
            place += 2;
            soundOutPut.runningSampleIndex += 1;
        }

        var sampleOut2: ?[*]i16 = @as(?[*]i16, @ptrCast(@alignCast(region2)));
        place = 0;
        const region2SampleCount: u32 = regionSize2 / soundOutPut.bytesPerSample;
        var sampleIndex2: usize = 0;
        while (sampleIndex2 < region2SampleCount) : (sampleIndex2 += 1) {
            const t: f32 = 2 * std.math.pi * @as(f32, @floatFromInt(soundOutPut.runningSampleIndex)) / @as(f32, @floatFromInt(soundOutPut.wavePeriod));
            const sinValue: f32 = std.math.sin(t);
            const sampeValue: i16 = @intFromFloat(sinValue * soundOutPut.volume);
            sampleOut2.?[place] = sampeValue;
            sampleOut2.?[place + 1] = sampeValue;
            place += 2;
            soundOutPut.runningSampleIndex += 1;
        }
        _ = global_secondary_buffer.?.IDirectSoundBuffer_Unlock(
            region1,
            regionSize1,
            region2,
            regionSize2,
        );
    }
}

pub inline fn getWindowDimension(window: win32.HWND) void {
    var clientRect: win32.RECT = undefined;
    _ = win32.GetClientRect(window, &clientRect);

    global_window.height = @as(u32, @bitCast((clientRect.bottom - clientRect.top)));
    global_window.width = @as(u32, @bitCast((clientRect.right - clientRect.left)));
}

/// fill Buffer in TODO optimizable way
pub fn win32RenderWeird(buffer: *Win32_offscreen_buffer, xOffset: u32, yOffset: u32) void {
    _ = yOffset;

    var row: [*]u8 = @as([*]u8, @ptrCast(buffer.memory));

    var y: u32 = 0;
    while (y < buffer.height) : (y += 1) {
        var pixel: [*]u32 = @as([*]u32, @ptrCast(@alignCast(row)));
        var x: u32 = 0;
        while (x < buffer.width) : (x += 1) {
            const blue: u8 = @truncate(x +% xOffset);
            const green: u8 = @truncate(y +% xOffset);
            const red: u8 = @truncate((y +% x));
            const place: usize = @as(usize, x);
            pixel[place] = @as(u32, red) << 16 | @as(u32, green) << 8 | @as(u32, blue);
        }
        row += @as(usize, buffer.pitch);
    }
}

pub fn win32RenderMonochrome(buffer: *Win32_offscreen_buffer, color: u32) void {
    var row: [*]u32 = @as([*]u32, @alignCast(@ptrCast(buffer.memory)));
    var y: usize = 0;
    while (y < buffer.width * buffer.height) : (y += 1) {
        row[y] = color;
    }
}

/// display buffercontent on current windowsize
pub fn win32DisplayBufferInWindow(buffer: *Win32_offscreen_buffer, deviceContext: win32.HDC, windowHeight: u32, windowWidth: u32) void {
    _ = win32.StretchDIBits(
        deviceContext,
        0,
        0,
        @as(i32, @bitCast(windowWidth)),
        @as(i32, @bitCast(windowHeight)),
        0,
        0,
        @as(i32, @bitCast(buffer.width)),
        @as(i32, @bitCast(buffer.height)),
        buffer.memory,
        &(buffer.info),
        win32.DIB_RGB_COLORS,
        win32.SRCCOPY,
    );
}

pub fn win32ResizeDIBSection(buffer: *Win32_offscreen_buffer, width: u32, height: u32) void {
    if (buffer.memory) |mem| {
        std.log.debug("win32ResizeDIBSection: buffer.memory is not null", .{});
        const deleted = win32.VirtualFree(mem, 0, win32.MEM_RELEASE);
        if (deleted > 0) std.log.debug("Successfull virtualfree call", .{});
    } else std.log.debug("win32ResizeDIBSection: buffer.memory is null !!!!", .{});

    buffer.height = height;
    buffer.width = width;
    buffer.bytesPerPixel = 4;
    buffer.pitch = buffer.width *% buffer.bytesPerPixel;

    buffer.info.bmiHeader.biSize = @sizeOf(@TypeOf(buffer.info.bmiHeader));
    buffer.info.bmiHeader.biWidth = @as(i32, @bitCast(buffer.width));
    buffer.info.bmiHeader.biHeight = -@as(i32, @bitCast(buffer.height));
    buffer.info.bmiHeader.biPlanes = 1;
    buffer.info.bmiHeader.biBitCount = 32;
    buffer.info.bmiHeader.biCompression = win32.BI_RGB;

    const bitMapMemorySize: usize = @as(usize, buffer.width * buffer.height * buffer.bytesPerPixel);
    const allocationtype = win32.VIRTUAL_ALLOCATION_TYPE.initFlags(.{ .RESERVE = 1, .COMMIT = 1 });
    buffer.memory = win32.VirtualAlloc(
        null,
        bitMapMemorySize,
        allocationtype,
        win32.PAGE_READWRITE,
    );
    std.log.debug("win32ResizeDIBSection: VirtualAllocated", .{});
}

// export seems to be removable -> currently is
pub fn wWinMain(
    instance: win32.HINSTANCE,
    prevInstance: ?win32.HINSTANCE,
    commandLine: [*:0]u16,
    showCode: u32,
) callconv(WINAPI) i32 {
    _ = commandLine;
    _ = prevInstance;

    win32ResizeDIBSection(&global_buffer, 1280, 720);

    var windowClass: win32.WNDCLASSW = .{
        // redraw the whole window, when it is resized, not just the new section
        .style = win32.WNDCLASS_STYLES.initFlags(.{ .HREDRAW = 1, .VREDRAW = 1 }),
        .lpfnWndProc = win32MainWindowCallback,
        .cbClsExtra = 0,
        .cbWndExtra = 0,
        .hInstance = instance,
        .hIcon = null,
        .hCursor = null,
        .hbrBackground = null,
        .lpszMenuName = win32.L("SomeMenuName"),
        .lpszClassName = win32.L("HandmadeHeroWindowClass"),
    };

    if (win32.RegisterClassW(&windowClass) != 0) {

        // create the window

        const window: win32.HWND = win32.CreateWindowExW(
            win32.WINDOW_EX_STYLE.initFlags(.{}),
            windowClass.lpszClassName,
            win32.L("Apfel"),
            win32.WINDOW_STYLE.initFlags(.{ .TILEDWINDOW = 1 }),
            win32.CW_USEDEFAULT,
            win32.CW_USEDEFAULT,
            1280,
            720,
            null,
            null,
            instance,
            null,
        ).?;

        var soundOutPut: win32_sound_output = .{
            .samplesPerSecond = 48000,
            .toneHz = 256,
            .volume = 3000,
            .runningSampleIndex = 0,
            .wavePeriod = 48000 / 256,
            .bytesPerSample = @sizeOf(i16) * 2,
            .bufferSize = 48000 * @sizeOf(i16) * 2,
            .latencySampleCount = 48000 / 15,
        };
        win32InitSound(window, soundOutPut.samplesPerSecond, soundOutPut.bufferSize);
        win32FillSoundBuffer(&soundOutPut, 0, soundOutPut.latencySampleCount * soundOutPut.bytesPerSample);
        _ = global_secondary_buffer.?.IDirectSoundBuffer_Play(0, 0, win32.DSBPLAY_LOOPING);
        var playing: bool = false;

        var message: win32.MSG = std.mem.zeroInit(win32.MSG, .{});

        // The GetDC function retrieves a handle to a device context (DC) for the client area of a specified window or for the entire screen.
        // he put in in the running loop after Message-Treatment
        const deviceContext: win32.HDC = win32.GetDC(window).?;

        // has be to called to show the Window ??
        // Sets the specified window's show state.
        // The first time an application calls ShowWindow, it should use the WinMain function's nCmdShow parameter as its nCmdShow paramete
        _ = win32.ShowWindow(window, @enumFromInt(showCode));

        while (running) {
            // The only reason to use PeekMessage over GetMessage is to do stuff even though you have no messages to respond to.
            // GetMessage blocks, meaning every time you call it your program stays inside it until it recieves a message.
            //      -> ?!? doenst work with PeekMessage ?
            // PeekMessage(...) doesn't wait for a message to appear.  It's more common in apps where you're doing some processing
            //     while waiting for messages, and can't just sit there and wait forever for the next message. Real-time games and
            //     such easily fall into this category.
            // GetMessage(...) waits til there's a message, and gets it. It's more efficient CPUwise, cause it's not
            //     constantly polling, but it will pause if there aren't any messages. It's more common in formy apps
            //     and other programs that don't require constant real-time processing to be going on.
            while (win32.PeekMessageW(&message, null, 0, 0, win32.PM_REMOVE) != 0) {
                // while (win32.PeekMessageW(&message, null, 0, 0, win32.PM_REMOVE) != 0) {
                // if (message.message == win32.WM_QUIT) {
                //     running = false;
                // }
                // Translates virtual-key messages into character messages. The character messages are posted to the calling
                //     thread's message queue, to be read the next time the thread calls the GetMessage or PeekMessage function.
                //     The TranslateMessage function does not modify the message pointed to by the lpMsg parameter.
                _ = win32.TranslateMessage(&message);
                // Dispatches a message to a window procedure.
                _ = win32.DispatchMessageW(&message);
            }
            // used to run here: by why Render, when there is no change ?
            //win32RenderWeird(&global_buffer, xOffsett, yOffsett);

            var playCursor: u32 = 0;
            var writeCursor: u32 = 0;
            if (win32_zig.SUCCEEDED(global_secondary_buffer.?.IDirectSoundBuffer_GetCurrentPosition(&playCursor, &writeCursor))) {
                const bytesToLock: u32 = (soundOutPut.runningSampleIndex * soundOutPut.bytesPerSample) % soundOutPut.bufferSize;
                const taretCursor: u32 = (playCursor + soundOutPut.latencySampleCount * soundOutPut.bytesPerSample) % soundOutPut.bufferSize;
                var bytesToWrite: u32 = 0;
                if (bytesToLock > taretCursor) {
                    bytesToWrite = soundOutPut.bufferSize - bytesToLock;
                    bytesToWrite += taretCursor;
                } else {
                    bytesToWrite = taretCursor - bytesToLock;
                }
                win32FillSoundBuffer(&soundOutPut, bytesToLock, bytesToWrite);
            }
            if (!playing) {
                _ = global_secondary_buffer.?.IDirectSoundBuffer_Play(0, 0, win32.DSBPLAY_LOOPING);
                playing = true;
            }

            //getWindowDimension(window);
            //win32DisplayBufferInWindow(&global_buffer, deviceContext, global_window.height, global_window.width);

        }
        _ = win32.ReleaseDC(window, deviceContext);
    }
    return 0;
}

pub fn win32MainWindowCallback(
    window: win32.HWND,
    message: u32,
    wParam: win32.WPARAM,
    lParam: win32.LPARAM,
) callconv(WINAPI) win32.LRESULT {
    // var result: win32.LRESULT = 0;
    switch (@as(u32, message)) {
        win32.WM_SIZE => {
            std.log.debug("win32MainWindowCallback: WM_SIZE", .{});
        },
        win32.WM_DESTROY => {
            std.log.debug("win32MainWindowCallback: WM_DSTROY", .{});
            running = false;
            // TODO once mainloop is working, remove ?
            win32.PostQuitMessage(0);
            return 0;
        },
        win32.WM_CLOSE => {
            std.log.debug("win32MainWindowCallback: WM_ClOSE", .{});
            running = false;
        },
        win32.WM_KEYDOWN => {},
        win32.WM_KEYUP => {},
        win32.WM_SYSKEYDOWN => {},
        win32.WM_SYSKEYUP => {},
        win32.WM_ACTIVATEAPP => {
            const vKCode: usize = wParam;
            const wasDown: bool = ((lParam & (1 << 30)) != 0);
            const isDown: bool = ((lParam & (1 << 31)) != 0);
            if (wasDown != isDown) {
                if (vKCode == @as(usize, 0x57)) {
                    // W
                } else if (vKCode == @as(usize, 0x41)) {
                    // A
                } else if (vKCode == @as(usize, 0x53)) {
                    // S
                } else if (vKCode == @as(usize, 0x44)) {
                    // D
                } else if (vKCode == @as(usize, 0x51)) {
                    // Q
                } else if (vKCode == @as(usize, 0x45)) {
                    // E
                } // more
            }

            const altKeyWasDown: bool = (((1 << 29) & lParam) != 0);
            if ((vKCode == @as(usize, @intFromEnum(win32.VK_F4))) and altKeyWasDown) {
                running = false;
            }
        },
        win32.WM_PAINT => {
            // TODO
            var paint: win32.PAINTSTRUCT = std.mem.zeroInit(win32.PAINTSTRUCT, .{});
            const deviceContext: win32.HDC = win32.BeginPaint(window, &paint).?;

            win32RenderWeird(&global_buffer, 0, 0);
            getWindowDimension(window);
            win32DisplayBufferInWindow(&global_buffer, deviceContext, global_window.height, global_window.width);

            _ = win32.EndPaint(window, &paint);
            return 0;
        },
        else => {},
    }
    // This function ensures that every message is processed.
    return win32.DefWindowProcW(window, message, wParam, lParam);
}
Ciantic commented 2 weeks ago

Thanks for your effor, I got it functioning with these steps:

  1. Install using
zig fetch git+https://github.com/marlersoft/zigwin32/ --save
  1. edit build.zig after the exe line include this
    const mydeps = b.dependency("zigwin32", .{});
    exe.root_module.addImport("win32", mydeps.module("zigwin32"));
  1. MessageBox example, in the main.zig:
const std = @import("std");
const win32 = @import("win32").everything;

pub fn main() !void {
    const W = std.unicode.utf8ToUtf16LeStringLiteral;
    const caption = W("Hello, Windows!");
    const message = W("Hello, Windows from Zig!");
    _ = win32.MessageBoxW(null, caption, message, win32.MB_OK);
}

Though that's about it, ZLS seems to be very slow with everything binding, but it will still work.