gocgo

How can I get a c variable from cgo?


package main

/*
#include <malloc.h>
#include <windows.h>
HDC *hdcArr

BOOL CALLBACK EnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
    for (int i = 0; i < (_msize(hdcArr) / sizeof(HDC)); i++) {
        if (hdcArr[i] == NULL) {
            hdcArr[i] = hdcMonitor;
            break;
        }
    }
    return TRUE;
}
void Init() {
    int count = GetSystemMetrics(SM_CMONITORS);
    hdcArr = (HDC*)malloc(sizeof(HDC) * count);
    memset(hdcArr, 0, sizeof(HDC) * count);
}
HDC* GetHDC() {
    return *hdcArr;
}
*/
import "C"
import (
    "fmt"
    "reflect"
    "unsafe"
    ".../w32"
)
func main() {
    var hdc w32.HDC
    hdc = w32.GetDC(0)
    C.Init()
    w32.EnumDisplayMonitors(hdc, nil, reflect.ValueOf(C.EnumProc).Pointer(), 0)
    t := (*[]w32.HDC)(unsafe.Pointer(&C.hdcArr))
    cx := w32.GetDeviceCaps((*t)[0], w32.HORZRES)
    fmt.Println(cx)
}

I wrote the source code as above.

What I want is to import cgo's HDC array into a w32.HDC array to know the width and height values of each monitor.
However, if you import t: = (* [] w32.HDC) unsafe.Pointer (& C.hdcArr)) and call cx: = w32.GetDeviceCaps ((* t) [0], w32.HORZRES) Only 0 is returned.

How can I use cgo to find the width and height of multiple monitors?


Solution

  • package main
    
    /*
    #cgo LDFLAGS: -lgdi32
    #include <stdio.h>
    #include <stdlib.h>
    #include <malloc.h>
    #include <windows.h>
    HDC *hdcArr;
    int count;
    
    BOOL CALLBACK EnumProc(HMONITOR hMonitor, HDC hdcMonitor, LPRECT lprcMonitor, LPARAM dwData) {
        int i;
        for (i = 0; i < (_msize(hdcArr) / sizeof(HDC)); i++) {
            if (hdcArr[i] == NULL) {
                hdcArr[i] = CreateCompatibleDC(hdcMonitor);
                break;
            }
        }
        return TRUE;
    }
    void Init() {
        count = GetSystemMetrics(SM_CMONITORS);
        hdcArr = (HDC*)malloc(sizeof(HDC) * count);
        memset(hdcArr, 0, sizeof(HDC) * count);
    }
    */
    import "C"
    
    import (
        "fmt"
        "reflect"
        "unsafe"
    
        "github.com/JamesHovious/w32"
    )
    
    func main() {
        C.Init()
        hdc := w32.GetDC(0)
        w32.EnumDisplayMonitors(hdc, nil, reflect.ValueOf(C.EnumProc).Pointer(), 0)
        w32.ReleaseDC(0, hdc)
        t := unsafe.Slice(C.hdcArr, C.count)
        for _, dc := range t {
            cx := w32.GetDeviceCaps(dc, w32.HORZRES)
            fmt.Println(cx)
            w32.DeleteDC(dc)
        }
        C.free(unsafe.Pointer(C.hdcArr))
    }
    

    It's very important for you to understand, that a pointer to a C-Array is just a memory address without any information about size (hence why your t array was empty). This is why you have to use unsafe.Slice function, which returns a slice whose underlying array starts at hdcArr and whose length and capacity are count.

    That's why i made count a global variable.

    The other issue you ran into was that the hdc handles passed to EnumProc were only valid inside the callback. To make them permanent and usable outside the callback scope, you have to call CreateCompatibleDC(hdcMonitor);.
    To use this function with cgo you will have to include the lib gdi32 via #cgo LDFLAGS: -lgdi32

    Once you are done using these DC's you have to free them again with w32.DeleteDC(dc)

    Also don't forget to free your malloced array with C.free(unsafe.Pointer(C.hdcArr))

    My advice: Whenever you use a WinApi, carefully read the msdn documentation. It takes some time, but saves you lots of trouble

    You can also do this entirely in golang without cgo:

    package main
    
    import (
        "fmt"
        "syscall"
    
        "github.com/JamesHovious/w32"
    )
    
    func EnumProc(hMonitor w32.HMONITOR, hdcMonitor w32.HDC, lprcMonitor *w32.RECT, dwData w32.LPARAM) uintptr {
        fmt.Println(w32.GetDeviceCaps(hdcMonitor, w32.HORZRES))
        return w32.TRUE
    }
    
    func main() {
        hdc := w32.GetDC(0)
        w32.EnumDisplayMonitors(hdc, nil, syscall.NewCallback(EnumProc), 0)
        w32.ReleaseDC(0, hdc)
    }
    

    or even smoother:

    func EnumProc(hMonitor w32.HMONITOR, hdcMonitor w32.HDC, lprcMonitor *w32.RECT, dwData w32.LPARAM) uintptr {
        horzres := lprcMonitor.Right - lprcMonitor.Left
        fmt.Println(horzres)
        return w32.TRUE
    }
    
    func main() {
        w32.EnumDisplayMonitors(nil, nil, syscall.NewCallback(EnumProc), 0)
    }