Closed vault-thirteen closed 1 year ago
CC @golang/windows
I don't thing there is a problem with Windows callbacks, but with your code @vault-thirteen. It could be that you are missing the message loop that processes incoming messages at the end.
Here is a code example that exercises the callback when the mouse is moves:
package main
import "C"
import (
"syscall"
"unsafe"
)
var (
moduser32 = syscall.NewLazyDLL("user32.dll")
procSetWindowsHookEx = moduser32.NewProc("SetWindowsHookExW")
procGetMessage = moduser32.NewProc("GetMessageW")
procTranslateMessage = moduser32.NewProc("TranslateMessage")
procDispatchMessage = moduser32.NewProc("DispatchMessageW")
)
const WH_MOUSE_LL = 14
type MSG struct {
Hwnd uint32
Message uint32
WParam uintptr
LParam uintptr
Time uint32
Pt [2]int32
}
func main() {
hook, _, _ := procSetWindowsHookEx.Call(WH_MOUSE_LL, uintptr(syscall.NewCallback(callbackFunc)), 0, 0)
println(hook)
var msg MSG
for {
procGetMessage.Call(uintptr(unsafe.Pointer(&msg)), 0, 0, 0)
procTranslateMessage.Call(uintptr(unsafe.Pointer(&msg)))
procDispatchMessage.Call(uintptr(unsafe.Pointer(&msg)))
}
}
func callbackFunc(code int, wParam uintptr, lParam uintptr) uintptr {
println("inside callbackFunc")
return 0
}
@qmuntal , why are you using 0 as a ThreadId ?
@qmuntal , why are you using 0 as a ThreadId ?
WH_MOUSE_LL
is a global hook, so you must pass 0 as threadid. See SetWindowsHookEx remarks.
I am testing it with WH_KEYBOARD
and provide a ThreadId
.
The code is blocked waiting for a message, i.e. the GetMessage
function waits forever and does not return.
Program's output is following.
Callback_Test.exe
ThreadId: 2604
hook: 64422195
The code is following.
package main
import "C"
import (
"fmt"
"log"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
var (
kernel32DLL = windows.NewLazySystemDLL("kernel32.dll")
user32DLL = windows.NewLazySystemDLL("user32.dll")
procGetCurrentThreadId = kernel32DLL.NewProc("GetCurrentThreadId")
procSetWindowsHookExW = user32DLL.NewProc("SetWindowsHookExW")
procUnhookWindowsHookEx = user32DLL.NewProc("UnhookWindowsHookEx")
procCallNextHookEx = user32DLL.NewProc("CallNextHookEx")
procGetMessageW = user32DLL.NewProc("GetMessageW")
procTranslateMessage = user32DLL.NewProc("TranslateMessage")
procDispatchMessageW = user32DLL.NewProc("DispatchMessageW")
)
var hook uintptr
const (
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw
WH_KEYBOARD = 2 // Scope: Global or Thread.
WH_MOUSE = 7 // Scope: Global or Thread.
WH_KEYBOARD_LL = 13 // Scope: Global.
WH_MOUSE_LL = 14 // Scope: Global.
)
type TagMSG struct {
hwnd uint32 // HWND hwnd;
message uint32 // UINT message;
wParam uintptr // WPARAM wParam;
lParam uintptr // LPARAM lParam;
time uint32 // DWORD time; typedef unsigned long DWORD;
pt Point // POINT pt;
}
type Point struct {
x int32 // LONG x; typedef long LONG;
y int32 // LONG y; typedef long LONG;
}
// https://learn.microsoft.com/en-us/cpp/cpp/data-type-ranges?view=msvc-170
func mustBeNoError(err error) {
if err != nil {
panic(err)
}
}
func main() {
var err = kernel32DLL.Load()
mustBeNoError(err)
defer func() {
derr := windows.FreeLibrary(windows.Handle(kernel32DLL.Handle()))
if derr != nil {
log.Println(derr)
}
}()
err = user32DLL.Load()
mustBeNoError(err)
defer func() {
derr := windows.FreeLibrary(windows.Handle(user32DLL.Handle()))
if derr != nil {
log.Println(derr)
}
}()
threadId, _, _ := procGetCurrentThreadId.Call()
fmt.Println("ThreadId:", threadId)
//callback := syscall.NewCallback(callbackFunc)
callback := windows.NewCallback(callbackFunc)
defer func() {
// Callback release is not available in Golang.
// What should I do when the limit is reached ? ...
}()
hook, _, _ = procSetWindowsHookExW.Call(WH_KEYBOARD, callback, 0, threadId)
fmt.Println("hook:", hook)
defer func() {
ret, _, _ := procUnhookWindowsHookEx.Call(hook)
fmt.Println("UnhookWindowsHookEx:", int32(ret))
}()
var msg TagMSG
for {
if GetMessage(&msg) == 0 {
fmt.Println("break")
break
}
fmt.Println("msg:", msg)
if TranslateMessage(&msg) == 0 {
fmt.Println("TranslateMessage error")
break
}
lResult := DispatchMessage(&msg)
fmt.Println("lResult:", lResult)
}
time.Sleep(time.Second * 5)
}
func callbackFunc(code, wParam, lParam uintptr) uintptr {
fmt.Println("inside callbackFunc")
ret, _, _ := procCallNextHookEx.Call(hook, code, wParam, lParam)
return ret
}
func GetMessage(msg *TagMSG) int32 { // BOOL; typedef int BOOL;
ret, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
return int32(ret)
}
func TranslateMessage(msg *TagMSG) int32 { // BOOL; typedef int BOOL;
ret, _, _ := procTranslateMessage.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
return int32(ret)
}
func DispatchMessage(msg *TagMSG) (lResult uintptr) { // LRESULT; typedef LONG_PTR LRESULT;
lResult, _, _ = procDispatchMessageW.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
return lResult
}
@qmuntal , please, use a WH_KEYBOARD
and provide a ThreadId
as in my previous post. Will it work on your machine ?
@qmuntal , please, use a WH_KEYBOARD and provide a ThreadId as in my previous post. Will it work on your machine ?
It doesn't work with WH_KEYBOARD on my machine, but I still think it's not due to a bug in the windows callback, but because the SetWindowsHookExW
is used in the wrong way (that API is awfully complicated...). Could you try creating a windows (i.e. using CreateWindowExW) and passing its handle as the hmod
parameter? It can be that SetWindowsHookExW
can't intercept messages created from terminal applications.
@qmuntal , thanks for the test. I will make this experiment. I need some time to read the Microsoft's API and I will check it.
It looks like something is going wrong. I get a NULL pointer after the creation of a Window. @qmuntal
ThreadId: 10816
sizeOfX: 80
RegisterClass: 49828
hWnd: 0
SetActiveWindow: 0
ShowWindow: 0
UpdateWindow: 0
hook: 256050767
The code is following.
package main
import (
"fmt"
"log"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
var (
kernel32DLL = windows.NewLazySystemDLL("kernel32.dll")
user32DLL = windows.NewLazySystemDLL("user32.dll")
procGetCurrentThreadId = kernel32DLL.NewProc("GetCurrentThreadId")
procSetWindowsHookExW = user32DLL.NewProc("SetWindowsHookExW")
procUnhookWindowsHookEx = user32DLL.NewProc("UnhookWindowsHookEx")
procCallNextHookEx = user32DLL.NewProc("CallNextHookEx")
procGetMessageW = user32DLL.NewProc("GetMessageW")
procTranslateMessage = user32DLL.NewProc("TranslateMessage")
procDispatchMessageW = user32DLL.NewProc("DispatchMessageW")
procRegisterClassExW = user32DLL.NewProc("RegisterClassExW")
procCreateWindowExW = user32DLL.NewProc("CreateWindowExW")
procDestroyWindow = user32DLL.NewProc("DestroyWindow")
procSetActiveWindow = user32DLL.NewProc("SetActiveWindow")
procShowWindow = user32DLL.NewProc("ShowWindow")
procUpdateWindow = user32DLL.NewProc("UpdateWindow")
)
var hook uintptr
const (
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw
WH_KEYBOARD = 2 // Scope: Global or Thread.
WH_MOUSE = 7 // Scope: Global or Thread.
WH_KEYBOARD_LL = 13 // Scope: Global.
WH_MOUSE_LL = 14 // Scope: Global.
)
const (
// https://learn.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
WS_EX_ACCEPTFILES uint32 = 0x00000010
WS_EX_APPWINDOW uint32 = 0x00040000
// https://learn.microsoft.com/en-us/windows/win32/winmsg/window-styles
WS_THICKFRAME uint32 = 0x00040000
WS_SIZEBOX uint32 = 0x00040000
WS_CAPTION uint32 = 0x00C00000
WS_VISIBLE uint32 = 0x10000000
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
SW_SHOWNORMAL int32 = 1
// https://learn.microsoft.com/ru-ru/windows/win32/winmsg/window-class-styles
CS_VREDRAW uint16 = 0x0001
CS_HREDRAW uint16 = 0x0002
CS_CLASSDC uint16 = 0x0040
)
/*
typedef struct tagWNDCLASSEXW {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW, *NPWNDCLASSEXW, *LPWNDCLASSEXW;
*/
// typedef _Null_terminated_ CONST CHAR *LPCSTR, *PCSTR;
type WNDCLASSEXW struct {
cbSize uint32
style uint32
lpfnWndProc uintptr // WNDPROC
cbClsExtra int32
cbWndExtra int32
hInstance HINSTANCE
hIcon HICON
hCursor HCURSOR
hbrBackground HBRUSH
lpszMenuName uintptr // LPCSTR
lpszClassName uintptr // LPCSTR
hIconSm HICON
}
type HINSTANCE = HANDLE
type HICON = HANDLE
type HCURSOR = HICON
type HBRUSH = HANDLE
type HANDLE = windows.Handle
type TagMSG struct {
hwnd uint32 // HWND hwnd;
message uint32 // UINT message;
wParam uintptr // WPARAM wParam;
lParam uintptr // LPARAM lParam;
time uint32 // DWORD time; typedef unsigned long DWORD;
pt Point // POINT pt;
}
type Point struct {
x int32 // LONG x; typedef long LONG;
y int32 // LONG y; typedef long LONG;
}
// https://learn.microsoft.com/en-us/cpp/cpp/data-type-ranges?view=msvc-170
func mustBeNoError(err error) {
if err != nil {
panic(err)
}
}
func main() {
var err = kernel32DLL.Load()
mustBeNoError(err)
defer func() {
derr := windows.FreeLibrary(windows.Handle(kernel32DLL.Handle()))
if derr != nil {
log.Println(derr)
}
}()
err = user32DLL.Load()
mustBeNoError(err)
defer func() {
derr := windows.FreeLibrary(windows.Handle(user32DLL.Handle()))
if derr != nil {
log.Println(derr)
}
}()
threadId, _, _ := procGetCurrentThreadId.Call()
fmt.Println("ThreadId:", threadId)
windowClassName := "MainWClass"
var x WNDCLASSEXW
sizeOfX := unsafe.Sizeof(x)
fmt.Println("sizeOfX:", sizeOfX)
x = WNDCLASSEXW{
cbSize: uint32(sizeOfX), // The size, in bytes, of this structure. Set this member to sizeof(WNDCLASSEX).
style: uint32(CS_VREDRAW | CS_HREDRAW | CS_CLASSDC),
lpfnWndProc: 0,
cbClsExtra: 0, // The number of extra bytes to allocate following the window-class structure.
cbWndExtra: 0, // The number of extra bytes to allocate following the window instance.
hInstance: 0,
hIcon: 0,
hCursor: 0,
hbrBackground: 0,
lpszMenuName: 0,
lpszClassName: uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(windowClassName))),
hIconSm: 0,
}
fmt.Println("RegisterClass:", RegisterClass(&x))
var hWnd = CreateWindow(
WS_EX_ACCEPTFILES|WS_EX_APPWINDOW,
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(windowClassName))),
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("Test Window"))),
WS_VISIBLE|WS_THICKFRAME|WS_CAPTION,
0, 0, 640, 480, 0, 0, 0, 0,
)
fmt.Println("hWnd:", hWnd)
defer func() {
fmt.Println("DestroyWindow:", DestroyWindow(hWnd))
}()
fmt.Println("SetActiveWindow:", SetActiveWindow(hWnd))
fmt.Println("ShowWindow:", ShowWindow(hWnd, SW_SHOWNORMAL))
fmt.Println("UpdateWindow:", UpdateWindow(hWnd))
//callback := syscall.NewCallback(callbackFunc)
callback := windows.NewCallback(callbackFunc)
defer func() {
// Callback release is not available in Golang.
// What should I do when the limit is reached ? ...
}()
hook, _, _ = procSetWindowsHookExW.Call(WH_KEYBOARD, callback, 0, threadId)
fmt.Println("hook:", hook)
defer func() {
ret, _, _ := procUnhookWindowsHookEx.Call(hook)
fmt.Println("UnhookWindowsHookEx:", int32(ret))
}()
var msg TagMSG
for {
if GetMessage(&msg) == 0 {
fmt.Println("break")
break
}
fmt.Println("msg:", msg)
if TranslateMessage(&msg) == 0 {
fmt.Println("TranslateMessage error")
break
}
lResult := DispatchMessage(&msg)
fmt.Println("lResult:", lResult)
}
time.Sleep(time.Second * 5)
}
func callbackFunc(code, wParam, lParam uintptr) uintptr {
fmt.Println("inside callbackFunc")
ret, _, _ := procCallNextHookEx.Call(hook, code, wParam, lParam)
return ret
}
func GetMessage(msg *TagMSG) int32 { // BOOL; typedef int BOOL;
ret, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
return int32(ret)
}
func TranslateMessage(msg *TagMSG) int32 { // BOOL; typedef int BOOL;
ret, _, _ := procTranslateMessage.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
return int32(ret)
}
func DispatchMessage(msg *TagMSG) (lResult uintptr) { // LRESULT; typedef LONG_PTR LRESULT;
lResult, _, _ = procDispatchMessageW.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
return lResult
}
// ATOM RegisterClassExW([in] const WNDCLASSEXW *unnamedParam1);
func RegisterClass(unnamedParam1 *WNDCLASSEXW) uintptr {
atom, _, _ := procRegisterClassExW.Call(uintptr(unsafe.Pointer(unnamedParam1)))
return atom
}
// HWND CreateWindowExW([in] DWORD dwExStyle, [in, optional] LPCWSTR lpClassName,[in, optional] LPCWSTR lpWindowName, [in] DWORD dwStyle, [in] int X, [in] int Y, [in] int nWidth, [in] int nHeight, [in, optional] HWND hWndParent, [in, optional] HMENU hMenu, [in, optional] HINSTANCE hInstance, [in, optional] LPVOID lpParam);
// typedef unsigned long DWORD; typedef _Null_terminated_ CONST WCHAR *LPCWSTR, *PCWSTR;
func CreateWindow(dwExStyle uint32, lpClassName uintptr, lpWindowName uintptr, dwStyle uint32, x int16, y int16, nWidth int16, nHeight int16, hWndParent uintptr, hMenu uintptr, hInstance uintptr, lpParam uintptr) uintptr {
hWnd, _, _ := procCreateWindowExW.Call()
return hWnd
}
// BOOL DestroyWindow([in] HWND hWnd); typedef int BOOL;
func DestroyWindow(hWnd uintptr) int32 {
ret, _, _ := procDestroyWindow.Call(hWnd)
return int32(ret)
}
// HWND SetActiveWindow([in] HWND hWnd);
func SetActiveWindow(hWnd uintptr) (topLevelWindow uintptr) {
topLevelWindow, _, _ = procSetActiveWindow.Call(hWnd)
return topLevelWindow
}
// BOOL ShowWindow([in] HWND hWnd,[in] int nCmdShow); typedef int BOOL;
func ShowWindow(hWnd uintptr, nCmdShow int32) int32 {
ret, _, _ := procShowWindow.Call(hWnd, uintptr(nCmdShow))
return int32(ret)
}
// BOOL UpdateWindow([in] HWND hWnd); typedef int BOOL;
func UpdateWindow(hWnd uintptr) int32 {
ret, _, _ := procUpdateWindow.Call(hWnd)
return int32(ret)
}
@vault-thirteen the CreateWindow
function is not setting the procCreateWindowExW
parameters and the the WNDCLASSEXW
is missing the mandatory lpfnWndProc
parameter. Something like this should be enough:
precDefWindowProc = user32DLL.NewProc("DefWindowProcW")
...
x = WNDCLASSEXW{
cbSize: uint32(sizeOfX), // The size, in bytes, of this structure. Set this member to sizeof(WNDCLASSEX).
lpszClassName: uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(windowClassName))),
lpfnWndProc: syscall.NewCallback(func(window uintptr, msg uintptr, w, l uintptr) uintptr {
ret, _, _ := precDefWindowProc.Call(window, msg, w, l)
return ret
}),
}
@qmuntal , thank you very much for help. It looks like, callbacks are working !
Callback_Test.exe ThreadId: 9340 sizeOfX: 80 RegisterClass: 49790 hWnd: 264238 SetActiveWindow: 264238 ShowWindow: 24 UpdateWindow: 1 hook: 329777 inside callbackFunc wParam: 13, lParam:3223060481 TranslateMessage: success inside callbackFunc wParam: 72, lParam:2293761 TranslateMessage: success inside callbackFunc wParam: 72, lParam:3223519233 TranslateMessage: success
I have one question left. I need to unhook the callback.
For some reason I am not receiving the WM_QUIT
(zero result of GetMessageW
function) when I close the application.
package main
import (
"fmt"
"log"
"unsafe"
"golang.org/x/sys/windows"
)
var (
kernel32DLL = windows.NewLazySystemDLL("kernel32.dll")
user32DLL = windows.NewLazySystemDLL("user32.dll")
procGetCurrentThreadId = kernel32DLL.NewProc("GetCurrentThreadId")
procCallNextHookEx = user32DLL.NewProc("CallNextHookEx")
procCreateWindowExW = user32DLL.NewProc("CreateWindowExW")
procDefWindowProcW = user32DLL.NewProc("DefWindowProcW")
procDestroyWindow = user32DLL.NewProc("DestroyWindow")
procDispatchMessageW = user32DLL.NewProc("DispatchMessageW")
procGetMessageW = user32DLL.NewProc("GetMessageW")
procRegisterClassExW = user32DLL.NewProc("RegisterClassExW")
procSetActiveWindow = user32DLL.NewProc("SetActiveWindow")
procSetWindowsHookExW = user32DLL.NewProc("SetWindowsHookExW")
procShowWindow = user32DLL.NewProc("ShowWindow")
procTranslateMessage = user32DLL.NewProc("TranslateMessage")
procUnhookWindowsHookEx = user32DLL.NewProc("UnhookWindowsHookEx")
procUpdateWindow = user32DLL.NewProc("UpdateWindow")
)
var hook uintptr
const (
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowshookexw
WH_KEYBOARD = 2 // Scope: Global or Thread.
WH_MOUSE = 7 // Scope: Global or Thread.
WH_KEYBOARD_LL = 13 // Scope: Global.
WH_MOUSE_LL = 14 // Scope: Global.
)
const (
// https://learn.microsoft.com/en-us/windows/win32/winmsg/extended-window-styles
WS_EX_ACCEPTFILES uint32 = 0x00000010
WS_EX_APPWINDOW uint32 = 0x00040000
// https://learn.microsoft.com/en-us/windows/win32/winmsg/window-styles
WS_THICKFRAME uint32 = 0x00040000
WS_SIZEBOX uint32 = 0x00040000
WS_CAPTION uint32 = 0x00C00000
WS_VISIBLE uint32 = 0x10000000
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-showwindow
SW_SHOWNORMAL int32 = 1
// https://learn.microsoft.com/ru-ru/windows/win32/winmsg/window-class-styles
CS_VREDRAW uint16 = 0x0001
CS_HREDRAW uint16 = 0x0002
CS_CLASSDC uint16 = 0x0040
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644975(v=vs.85)
HC_ACTION = 0
)
/*
typedef struct tagWNDCLASSEXW {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW, *NPWNDCLASSEXW, *LPWNDCLASSEXW;
*/
// typedef _Null_terminated_ CONST CHAR *LPCSTR, *PCSTR;
type WNDCLASSEXW struct {
cbSize uint32
style uint32
lpfnWndProc uintptr // WNDPROC
cbClsExtra int32
cbWndExtra int32
hInstance HINSTANCE
hIcon HICON
hCursor HCURSOR
hbrBackground HBRUSH
lpszMenuName uintptr // LPCSTR
lpszClassName uintptr // LPCSTR
hIconSm HICON
}
type HINSTANCE = HANDLE
type HICON = HANDLE
type HCURSOR = HICON
type HBRUSH = HANDLE
type HANDLE = windows.Handle
type LPARAM = uintptr
type WPARAM = uintptr
type LRESULT = uintptr
type HWND = uint32
type TagMSG struct {
hwnd HWND // HWND hwnd;
message uint32 // UINT message;
wParam WPARAM // WPARAM wParam;
lParam LPARAM // LPARAM lParam;
time uint32 // DWORD time; typedef unsigned long DWORD;
pt Point // POINT pt;
}
type Point struct {
x int32 // LONG x; typedef long LONG;
y int32 // LONG y; typedef long LONG;
}
// https://learn.microsoft.com/en-us/cpp/cpp/data-type-ranges?view=msvc-170
func mustBeNoError(err error) {
if err != nil {
panic(err)
}
}
func main() {
var err = kernel32DLL.Load()
mustBeNoError(err)
defer func() {
derr := windows.FreeLibrary(windows.Handle(kernel32DLL.Handle()))
if derr != nil {
log.Println(derr)
}
}()
err = user32DLL.Load()
mustBeNoError(err)
defer func() {
derr := windows.FreeLibrary(windows.Handle(user32DLL.Handle()))
if derr != nil {
log.Println(derr)
}
}()
threadId, _, _ := procGetCurrentThreadId.Call()
fmt.Println("ThreadId:", threadId)
windowClassName := "MainWClass"
var x WNDCLASSEXW
sizeOfX := unsafe.Sizeof(x)
fmt.Println("sizeOfX:", sizeOfX)
x = WNDCLASSEXW{
cbSize: uint32(sizeOfX), // The size, in bytes, of this structure. Set this member to sizeof(WNDCLASSEX).
style: uint32(CS_VREDRAW | CS_HREDRAW | CS_CLASSDC),
lpfnWndProc: windows.NewCallback(DefWindowProcW),
cbClsExtra: 0, // The number of extra bytes to allocate following the window-class structure.
cbWndExtra: 0, // The number of extra bytes to allocate following the window instance.
hInstance: 0,
hIcon: 0,
hCursor: 0,
hbrBackground: 0,
lpszMenuName: 0,
lpszClassName: uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(windowClassName))),
hIconSm: 0,
}
fmt.Println("RegisterClass:", RegisterClass(&x))
var hWnd = CreateWindow(
WS_EX_ACCEPTFILES|WS_EX_APPWINDOW,
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr(windowClassName))),
uintptr(unsafe.Pointer(windows.StringToUTF16Ptr("Test Window"))),
WS_VISIBLE|WS_THICKFRAME|WS_CAPTION,
0, 0, 640, 480, 0, 0, 0, 0,
)
fmt.Println("hWnd:", hWnd)
defer func() {
fmt.Println("DestroyWindow:", DestroyWindow(hWnd))
}()
fmt.Println("SetActiveWindow:", SetActiveWindow(hWnd))
fmt.Println("ShowWindow:", ShowWindow(hWnd, SW_SHOWNORMAL))
fmt.Println("UpdateWindow:", UpdateWindow(hWnd))
//callback := syscall.NewCallback(callbackFunc)
callback := windows.NewCallback(callbackFunc)
defer func() {
// Callback release is not available in Golang.
// What should I do when the limit is reached ? ...
}()
hook, _, _ = procSetWindowsHookExW.Call(WH_KEYBOARD, callback, 0, threadId)
fmt.Println("hook:", hook)
defer func() {
ret, _, _ := procUnhookWindowsHookEx.Call(hook)
fmt.Println("UnhookWindowsHookEx:", int32(ret))
}()
var msg TagMSG
for {
// If the function retrieves a message other than WM_QUIT, the return value is nonzero.
// If the function retrieves the WM_QUIT message, the return value is zero.
if GetMessage(&msg) == 0 {
fmt.Println("break")
break
}
if TranslateMessage(&msg) != 0 {
fmt.Println("TranslateMessage: success")
}
// Although its meaning depends on the message being dispatched, the return value generally is ignored.
_ = DispatchMessage(&msg)
}
fmt.Println("Fin")
}
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms644975(v=vs.85)
func callbackFunc(nCode int32, wParam WPARAM, lParam LPARAM) (ret uintptr) {
fmt.Println("inside callbackFunc")
if nCode == HC_ACTION {
fmt.Println(fmt.Sprintf("wParam: %v, lParam:%v", wParam, lParam))
}
ret, _, _ = procCallNextHookEx.Call(hook, uintptr(nCode), wParam, lParam)
return ret
}
func GetMessage(msg *TagMSG) int32 { // BOOL; typedef int BOOL;
ret, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
return int32(ret)
}
func TranslateMessage(msg *TagMSG) int32 { // BOOL; typedef int BOOL;
ret, _, _ := procTranslateMessage.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
return int32(ret)
}
// LRESULT DispatchMessageW([in] const MSG *lpMsg);
func DispatchMessage(msg *TagMSG) (lResult LRESULT) { // LRESULT; typedef LONG_PTR LRESULT;
lResult, _, _ = procDispatchMessageW.Call(uintptr(unsafe.Pointer(msg)), 0, 0, 0)
return lResult
}
// ATOM RegisterClassExW([in] const WNDCLASSEXW *unnamedParam1);
func RegisterClass(unnamedParam1 *WNDCLASSEXW) uintptr {
atom, _, _ := procRegisterClassExW.Call(uintptr(unsafe.Pointer(unnamedParam1)))
return atom
}
// HWND CreateWindowExW([in] DWORD dwExStyle, [in, optional] LPCWSTR lpClassName,[in, optional] LPCWSTR lpWindowName, [in] DWORD dwStyle, [in] int X, [in] int Y, [in] int nWidth, [in] int nHeight, [in, optional] HWND hWndParent, [in, optional] HMENU hMenu, [in, optional] HINSTANCE hInstance, [in, optional] LPVOID lpParam);
// typedef unsigned long DWORD; typedef _Null_terminated_ CONST WCHAR *LPCWSTR, *PCWSTR;
func CreateWindow(dwExStyle uint32, lpClassName uintptr, lpWindowName uintptr, dwStyle uint32, x int16, y int16, nWidth int16, nHeight int16, hWndParent uintptr, hMenu uintptr, hInstance uintptr, lpParam uintptr) uintptr {
hWnd, _, _ := procCreateWindowExW.Call(uintptr(dwExStyle), lpClassName, lpWindowName, uintptr(dwStyle), uintptr(x), uintptr(y), uintptr(nWidth), uintptr(nHeight), hWndParent, hMenu, hInstance, lpParam)
return hWnd
}
// BOOL DestroyWindow([in] HWND hWnd); typedef int BOOL;
func DestroyWindow(hWnd uintptr) int32 {
ret, _, _ := procDestroyWindow.Call(hWnd)
return int32(ret)
}
// HWND SetActiveWindow([in] HWND hWnd);
func SetActiveWindow(hWnd uintptr) (topLevelWindow uintptr) {
topLevelWindow, _, _ = procSetActiveWindow.Call(hWnd)
return topLevelWindow
}
// BOOL ShowWindow([in] HWND hWnd,[in] int nCmdShow); typedef int BOOL;
func ShowWindow(hWnd uintptr, nCmdShow int32) int32 {
ret, _, _ := procShowWindow.Call(hWnd, uintptr(nCmdShow))
return int32(ret)
}
// BOOL UpdateWindow([in] HWND hWnd); typedef int BOOL;
func UpdateWindow(hWnd uintptr) int32 {
ret, _, _ := procUpdateWindow.Call(hWnd)
return int32(ret)
}
// LRESULT DefWindowProcW([in] HWND hWnd, [in] UINT Msg, [in] WPARAM wParam, [in] LPARAM lParam);
func DefWindowProcW(hWnd uint32, Msg uint32, wParam WPARAM, lParam LPARAM) LRESULT {
lResult, _, _ := procDefWindowProcW.Call(uintptr(hWnd), uintptr(Msg), wParam, lParam)
return lResult
}
I have one question left. I need to unhook the callback. For some reason I am not receiving the WM_QUIT (zero result of GetMessageW function) when I close the application.
I don't have an answer off the top of my head, but probably Stack Overflow will know 😸.
Closing as no issue, thanks for reporting it anyway @vault-thirteen.
What version of Go are you using (
go version
)?1.20.3.
Does this issue reproduce with the latest release?
Yes.
What operating system and processor architecture are you using (
go env
)?Windows 10, Intel x86-64.
What did you do?
I create a callback and try to get it working. I create a hook method to listen to system's events from a keyboard. I send some keyboard events, but the callback is not started. When the callback should be "fired", it does not "fire".
Code example is following.
I tried:
callback := syscall.NewCallback(callbackFunc)
callback := windows.NewCallback(callbackFunc)
import "C"
import "C"
WH_KEYBOARD
WH_KEYBOARD_LL
but the result stays the same. It does not work.
What did you expect to see?
I expect to see working callbacks on Windows O.S. in Golang.
What did you see instead?
I see broken callbacks on Windows O.S. in Golang.
Program's output was: