I'm trying to get display information through Win32 APIs. So far I've managed EnumDisplayDevicesA just fine, but EnumDisplaySettingsA is giving me trouble.
No matter how I set the first two variables, the function returns zero (indicating failure), with no extra information on why it's failing.
Here's a cut down version of my code with just the function in question;
package main
import (
"fmt"
"syscall"
"unsafe"
)
var (
dll = syscall.MustLoadDLL("user32.dll")
enumDisplaySettings = dll.MustFindProc("EnumDisplaySettingsA")
)
type devMode struct {
dmDeviceName [32]uint16
dmSpecVersion uint16
dmDriverVersion uint16
dmSize uint16
dmDriverExtra uint16
dmFields uint32
dmOrientation int16
dmPaperSize int16
dmPaperLength int16
dmPaperWidth int16
dmScale int16
dmCopies int16
dmDefaultSource int16
dmPrintQuality int16
dmColor int16
dmDuplex int16
dmYResolution int16
dmTTOption int16
dmCollate int16
dmFormName [32]uint16
dmLogPixels uint16
dmBitsPerPel uint32
dmPelsWidth uint32
dmPelsHeight uint32
dmDisplayFlags uint32
dmDisplayFrequency uint32
dmICMMethod uint32
dmICMIntent uint32
dmMediaType uint32
dmDitherType uint32
dmReserved1 uint32
dmReserved2 uint32
dmPanningWidth uint32
dmPanningHeight uint32
}
func queryDisplaySettings() devMode {
out := devMode{}
out.dmSize = uint16(unsafe.Sizeof(out))
outptr := uintptr(unsafe.Pointer(&out))
namePtr := uintptr(unsafe.Pointer(nil))
var iModeNum uint32 = 4294967295
enumCurrentSettings := uintptr(unsafe.Pointer(&iModeNum))
ret, _, _ := enumDisplaySettings.Call(namePtr, enumCurrentSettings, outptr)
if ret == 0 {
fmt.Printf("Failed EnumDisplaySettings")
}
return out
}
func main() {
settings := queryDisplaySettings()
fmt.Printf("\n%v\n", settings.dmPelsWidth)
fmt.Printf("%v\n", settings.dmPelsHeight)
fmt.Printf("%v\n\n", settings.dmDisplayFrequency)
}
My Sources:
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaysettingsw
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-devmodea
https://github.com/JamesHovious/w32/blob/74b38b9b07b520e0f84a5eec5daada6c7b6a2471/typedef.go#L363
https://docs.rs/winapi/0.2.0/i686-pc-windows-msvc/winapi/constant.ENUM_CURRENT_SETTINGS.html
There are multiple issues with the code here.
First off your type definition of devMode is for DEVMODEW but you are calling EnumDisplaySettingsA. However you shouldn't be calling that in the first place (its the ANSI version), so use EnumDisplaySettingsW instead (UNICODE).
Next, the second parameter for EnumDisplaySettingsA/EnumDisplaySettingsW is a DWORD (uint32), however instead of passing the value, you are passing the address to it.
So replace
var iModeNum uint32 = 4294967295
enumCurrentSettings := uintptr(unsafe.Pointer(&iModeNum))
With just
iModeNum := uintptr(4294967295)
And it should all just work.
I'm using syscall.Syscall(...) to call a C method in a dll.
This is the C method signature:
SENSEI_API HSENSEI SENSEI_open(const char* sensigrafo, const char* options, SENSEI_ERR* se);
This is the SENSEI_ERR struct:
typedef struct
{
int code;
char* error_string;
} SENSEI_ERR;
In my GO program I declared a struct:
type senseiErr struct {
code int
error_string *byte
}
And tried to call the method:
var nargs uintptr = 3
var err senseiErr
ret, _, callErr := syscall.Syscall(uintptr(senseiOpen),
nargs,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("en"))),
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(""))),
uintptr(unsafe.Pointer(&err)),
)
As you may have guessed, the SENSEI_open method fill the SENSEI_ERR argument with the code and the text of the error.
Now I need to read the content of that error.
err.code actually has the correct value.
About err.error_string I don't know. I'm new to GO and i have some questions:
Since the C struct has the field char* error_string, is error_string *byte in my GO struct correct?
Should I use []byte or something else?
How do I read the content of the error_string field?
fmt.Println(err.error_string) prints the memory address
fmt.Println(*err.error_string) prints always "101"
1) I doubt that cost char* meant to be UTF16 encoded. So all what you need is just getting raw data:
sensigrafo := "en\000" // \000 = 0 = null termination, \0 does not valid
options := "\000"
...
uintptr(*(*unsafe.Pointer)(unsafe.Pointer(&sensigrafo))
uintptr(*(*unsafe.Pointer)(unsafe.Pointer(&options))
// *(*unsafe.Pointer) are accessing the first field of string header:
type string struct {
data *byte
len int
}
// same with slices
// but for them there's less ugly way:
sensigrafo := []byte("en\000")
options := []byte("\000")
uintptr(unsafe.Pointer(&sensigrafo[0]))
uintptr(unsafe.Pointer(&options[0]))
2) C's int and Golang's int might have different sizeof, so this requires cgo declaration (C.int) or manual matching with random selection (try also int32, int64 if you don't want to use cgo)
type senseiErr struct {
code C.int /* Golang's int32/int64 */
error_string *byte // pointer types are same as C's void* or Golang's unsafe.Pointer
}
Wrong offset might cause error_string be empty or point to random addr.
3) To read content you have to use same methods as C does (read data until null terminated byte, considering that *byte points to first element of string), but I propose to use already implemented runtime functions:
//go:linkname gostringn runtime.gostringn
func gostringn(p *byte, l int) string
//go:linkname findnull runtime.findnull
//go:nosplit
func findnull(s *byte) int
...
error_string := gostringn(err.error_string, findnull(err.error_string))
// or cgo one:
type senseiErr struct {
code C.int
error_string *C.char
}
...
error_string := C.GoString(err.error_string)
I wanna make push subscription to Windows Event Log in Golang
How exactly should I pass a callback function?
EVT_SUBSCRIBE_CALLBACK is the pointer of function, like
typedef DWORD ( WINAPI *EVT_SUBSCRIBE_CALLBACK)(
EVT_SUBSCRIBE_NOTIFY_ACTION Action,
PVOID UserContext,
EVT_HANDLE Event
);
So, my variant looks like this:
func logCallback() syscall.Handle {
cb := func(_ uintptr, _ uintptr, _ uintptr) uint64 {
fmt.Printf("callback called %v", data)
return 0
}
ptr := syscall.NewCallback(cb)
return syscall.Handle(ptr) // type syscall.Handle uintptr
}
I get successfully subscribed handler with no errors, but it still doesn't work.
Any ideas why? Where should I look first?
When using syscall make sure the to include import "C" at the top of your file. Glad it helped you.
I'm trying to call SHGetImageList in Go in order to extract icons from .EXE files. The problem is that I don't know how to create/pass an "IImageList2 interface" in Go, which is something SHGetImageList requires.
I've tried miscellaneous things for a few hours, but it all results in the same E_NOINTERFACE error. Basically they're all "shots in the dark" ([]byte array to see if I could receive any "data" at all, an actual interface{} in Go containing the same functions as the IImagelist2 interface defined by MSDN, etc). If it is of any relevance, I do have a working version of this in C# using something along the lines of http://www.pinvoke.net/default.aspx/shell32.shgetimagelist, but I simply have no real clue on how to "translate" that to Go. Any help would be much appreciated.
Example Go code below, with some info and links to MSDN in the comments.
package main
import (
"fmt"
"syscall"
"unsafe"
)
var (
shell32 = syscall.MustLoadDLL("shell32.dll")
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb762179(v=vs.85).aspx
procSHGetFileInfo = shell32.MustFindProc("SHGetFileInfoW")
//https://msdn.microsoft.com/en-us/library/windows/desktop/bb762185(v=vs.85).aspx
procSHGetImageList = shell32.MustFindProc("SHGetImageList")
)
func main() {
someExeFile := `c:\windows\explorer.exe`
iconIndex := GetIconIndex(someExeFile)
// The problem:
HRESULT, _, _ := procSHGetImageList.Call(
uintptr(SHIL_JUMBO),
uintptr(unsafe.Pointer(&IID_IImageList2)),
// I don't know how pass/create an "IImageList interface" in Go,
// or if it's even possible without relying on CGO.
// IImageList interface:
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb761419(v=vs.85).aspx
// Currently there's just a pointer to an empty []byte so that the code will compile.
// HRESULT naturally contains the error code E_NOINTERFACE (2147500034),
// which makes sense seeing as I'm not passing a valid interface.
uintptr(unsafe.Pointer(&[]byte{})),
)
fmt.Println(iconIndex, HRESULT)
}
const SHIL_JUMBO = 0x4
const shGetFileInfoLen = 3
const shGetFileInfoFlags = 16400 //(SysIconIndex|LargeIcon|UseFileAttributes)
// use SHGetFileInfo to get the icon index (only value we care about)
func GetIconIndex(fileName string) int {
buf := make([]uint16, shGetFileInfoLen)
ret, _, _ := procSHGetFileInfo.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))),
0,
uintptr(unsafe.Pointer(&buf[0])),
shGetFileInfoLen,
shGetFileInfoFlags,
)
if ret != 0 && buf[2] > 0 {
return int(buf[2])
}
return 0
}
// From: "192B9D83-50FC-457B-90A0-2B82A8B5DAE1"
var IID_IImageList2 = &GUID{0x192b9d83, 0x50fc, 0x457b, [8]byte{0x90, 0xa0, 0x2b, 0x82, 0xa8, 0xb5, 0xda, 0xe1}}
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373931.aspx
type GUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
UPDATE
The problem now is that it's exiting with an error(0xC0000005) when calling the Syscall to get the actual icon pointer.
package main
import (
"fmt"
"syscall"
"unsafe"
)
var (
shell32 = syscall.MustLoadDLL("shell32.dll")
// https://msdn.microsoft.com/en-us/library/windows/desktop/bb762179(v=vs.85).aspx
procSHGetFileInfo = shell32.MustFindProc("SHGetFileInfoW")
//https://msdn.microsoft.com/en-us/library/windows/desktop/bb762185(v=vs.85).aspx
procSHGetImageList = shell32.MustFindProc("SHGetImageList")
ole32 = syscall.MustLoadDLL("ole32.dll")
procCoInitialize = ole32.MustFindProc("CoInitialize")
)
func main() {
someExeFile := `c:\windows\explorer.exe`
procCoInitialize.Call()
iconIndex := GetIconIndex(someExeFile)
var imglist *IImageList
hr, _, _ := procSHGetImageList.Call(
uintptr(SHIL_JUMBO),
uintptr(unsafe.Pointer(&IID_IImageList)),
uintptr(unsafe.Pointer(&imglist)),
)
// These look OK
fmt.Println(iconIndex, hr, imglist.Vtbl.GetIcon)
var hIcon uintptr
// GetIcon: https://msdn.microsoft.com/en-us/library/windows/desktop/bb761463(v=vs.85).aspx
hr, _, _ = syscall.Syscall(imglist.Vtbl.GetIcon,
uintptr(unsafe.Pointer(imglist)),
uintptr(iconIndex),
getIconFlags,
uintptr(unsafe.Pointer(&hIcon)),
)
// Errors: "Process finished with exit code -1073741819 (0xC0000005)"
fmt.Println("hIcon:", hIcon) // Never reaches this
}
// ILD_TRANSPARENT | ILD_IMAGE
const getIconFlags = 0x00000001 | 0x00000020
const SHIL_JUMBO = 0x4
const shGetFileInfoLen = 3
const shGetFileInfoFlags = 16400 //(SysIconIndex|LargeIcon|UseFileAttributes)
// use SHGetFileInfo to get the icon index (only value we care about)
func GetIconIndex(fileName string) int {
buf := make([]uint16, shGetFileInfoLen)
ret, _, _ := procSHGetFileInfo.Call(
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))),
0,
uintptr(unsafe.Pointer(&buf[0])),
shGetFileInfoLen,
shGetFileInfoFlags,
)
if ret != 0 && buf[2] > 0 {
return int(buf[2])
}
return 0
}
// From: "46EB5926-582E-4017-9FDF-E8998DAA0950"
var IID_IImageList = GUID{0x46eb5926, 0x582e, 0x4017, [8]byte{0x9f, 0xdf, 0xe8, 0x99, 0x8d, 0xaa, 0x09, 0x50}}
// http://msdn.microsoft.com/en-us/library/windows/desktop/aa373931.aspx
type GUID struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
type IImageList struct {
Vtbl *IImageListVtbl
}
type IImageListVtbl struct {
Add uintptr
ReplaceIcon uintptr
SetOverlayImage uintptr
Replace uintptr
AddMasked uintptr
Draw uintptr
Remove uintptr
GetIcon uintptr
GetImageInfo uintptr
Copy uintptr
Merge uintptr
Clone uintptr
GetImageRect uintptr
GetIconSize uintptr
SetIconSize uintptr
GetImageCount uintptr
SetImageCount uintptr
SetBkColor uintptr
GetBkColor uintptr
BeginDrag uintptr
EndDrag uintptr
DragEnter uintptr
DragLeave uintptr
DragMove uintptr
SetDragCursorImage uintptr
DragShowNolock uintptr
GetDragImage uintptr
GetItemFlags uintptr
GetOverlayImage uintptr
}
Oh, I see the actual problem now.
uintptr(unsafe.Pointer(&IID_IImageList2)),
...
var IID_IImageList2 = &GUID{0x192b9d83, 0x50fc, 0x457b, [8]byte{0x90, 0xa0, 0x2b, 0x82, 0xa8, 0xb5, 0xda, 0xe1}}
Your IID_IImageList2 is already a pointer. In your call, you're taking a pointer to that pointer, which means the address is used as the GUID. You should either do
uintptr(unsafe.Pointer(&IID_IImageList2)),
...
var IID_IImageList2 = GUID{0x192b9d83, 0x50fc, 0x457b, [8]byte{0x90, 0xa0, 0x2b, 0x82, 0xa8, 0xb5, 0xda, 0xe1}}
or
uintptr(unsafe.Pointer(IID_IImageList2)),
...
var IID_IImageList2 = &GUID{0x192b9d83, 0x50fc, 0x457b, [8]byte{0x90, 0xa0, 0x2b, 0x82, 0xa8, 0xb5, 0xda, 0xe1}}
That way, the GUID itself is used as the GUID, not its location in memory.
Interfacing COM with Go is going to be painful.
A COM interface like IImageList2 is a list of function pointers, and you can't use those C function pointers from Go directly; you have to use syscall.Syscall() and its siblings (depending on the number of arguments the individual function takes) to call them.
At its core, a COM interface instance is a structure whose first and only field is a pointer to this list of methods. So it'd be something along the lines of
type IImageList2Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
|
type IImageList2 struct {
Vtbl *IImageList2Vtbl
}
When you pass a pointer to a variable of type IImageList2 to the Windows API function you call to create that object, be it SHGetImageList(), CoCreateInstance(), D3D11CreateDeviceAndSwapChain(), or what have you, the system will fill in the Vtbl entry with a pointer in read-only system memory that contains the list of functions.
So the first thing you have to do is make sure the methods are in the right order, so the Go structure IImageList2Vtbl and the list that Windows will give you match. Unfortunately, MSDN isn't good at this; you'll have to spelunk through the header files. Here's what I get:
type IImageList2Vtbl struct {
QueryInterface uintptr
AddRef uintptr
Release uintptr
Add uintptr
ReplaceIcon uintptr
SetOverlayImage uintptr
Replace uintptr
AddMasked uintptr
Draw uintptr
Remove uintptr
GetIcon uintptr
GetImageInfo uintptr
Copy uintptr
Merge uintptr
Clone uintptr
GetImageRect uintptr
GetIconSize uintptr
SetIconSize uintptr
GetImageCount uintptr
SetImageCount uintptr
SetBkColor uintptr
GetBkColor uintptr
BeginDrag uintptr
EndDrag uintptr
DragEnter uintptr
DragLeave uintptr
DragMove uintptr
SetDragCursorImage uintptr
DragShowNolock uintptr
GetDragImage uintptr
GetItemFlags uintptr
GetOverlayImage uintptr
Resize uintptr
GetOriginalSize uintptr
SetOriginalSize uintptr
SetCallback uintptr
GetCallback uintptr
ForceImagePresent uintptr
DiscardImages uintptr
PreloadImages uintptr
GetStatistics uintptr
Initialize uintptr
Replace2 uintptr
ReplaceFromImageList uintptr
}
I'm sure I got this wrong; please report any errors. :)
This comes from commoncontrols.h. Go with the C-style, not C++-style, interface definitions (if there are more than one), as that will have all the methods, including those of interfaces that IImageList2 derives from (IUnknown, which all interfaces derive from, and IImageList).
Er wait, I lied: Windows doesn't expect you to give it the memory for the IImageList2. That's because COM interfaces are just like Go interfaces: they're a set of methods that any implementation can implement. So in reality you have to let Windows give you a pointer to an IImageList2, not an IImageList2Vtbl.
So what do we do? We store each instance as a pointer to the interface, and then pass a pointer to that to the creation functions.
So from that, we have
var imglist *IImageList2
hr, _, _ := procSHGetImageList.Call(
uintptr(SHIL_JUMBO),
uintptr(unsafe.Pointer(&IID_IImageList2)),
uintptr(unsafe.Pointer(&imglist)),
)
Notice that we pass a pointer to the IImageList2. I'll call this the instance, and the Vtbl member the vtable.
Now when you want to call a method, you have to add an extra first parameter: the instance itself. You can look on MSDN for the return types and other parameters:
// HRESULT EndDrag(void);
hr, _, _ = syscall.Syscall(instance.Vtbl.EndDrag,
uintptr(unsafe.Pointer(imglist)))
// HRESULT SetOverlayImage(int, int);
hr, _, _ = syscall.Syscall(instance.Vtbl.SetOverlayImage,
uintptr(unsafe.Pointer(imglist)),
4,
2)
Notice that we pass imglist this time, not &imglist.
Now because C and C++ don't support multiple returns like Go does, functions typically return a HRESULT (the COM equivalent of Go's error) and have you pass in pointers to the rest of the return values at the end of the argument list. For other COM interfaces, we follow the rules above. For other type, we refer to the Windows Data Types page to see what each named type represents, and remember that C int is always Go int32 on Windows, even on 64-bit systems.
// HRESULT AddMasked(HBITMAP, COLORREF, (OUT) int *);
// we'll use uintptr for HBITMAP and int32 for COLORREF
var index int32
hr, _, _ = syscall.Syscall(instance.Vtbl.AddMasked,
uintptr(unsafe.Pointer(instance)),
hbitmap,
mask,
uintptr(unsafe.Pointer(&index)))
Finally, COM requires us to call the Release() method when we're done with an object. This takes no extra parameters and its return value is irrelevant. You can stuff this in a defer if you want.
syscall.Syscall(instance.Vtbl.Release,
uintptr(unsafe.Pointer(instance)))
Note: I don't know if SHGetImageList() requires COM to be initialized. If it doesn't, ignore this part until you need some other COM interface.
Oh but that's not enough, because you also have to initialize COM. And COM can operate in a number of threading models. Only two are important: single-threaded apartment, which is used for anything related to GUI, and multithreaded apartment, which is used for special purposes.
The CoInitialize(), CoInitializeEx(), and CoUninitialize() functions all handle COM initialization and uninitialization. You already know how to call them; they're just normal DLL functions.
But beware! If you need a single-threaded apartment, you have to use runtime.LockOSThread() to ensure Go doesn't move the current goroutine to another OS thread from under you. If you don't do this, things will break in bizarre ways.
This is all a load of work, and you have to do it for every interface you're going to use. You might as well use a package that someone already made that does the heavy lifting for you. There are several packages, such as go-ole, that do the basic stuff. I don't see one that provides IImageList2, but you might be able to piggyback off an existing one. There seems to be only one reference for IImageList.
Good luck!
Im currently trying to use user32.dll EnumWindows on Go but seems to not be working
var(
user32 = syscall.NewLazyDLL("user32.dll")
procEnumWindows = user32.NewProc("EnumWindows")
)
func EnumWindows() int {
ret, _, _ := procEnumWindows.Call(
syscall.NewCallback(enumWindowsProc),
uintptr(0),
)
return int(ret)
}
func enumWindowsProc(hwnd syscall.Handle, lparam uintptr) bool {
return true
}
Calling EnumWindows will give the following error:
panic: compileCallback: output parameter size is wrong
Im not sure how should I use the syscall package... I cant seem to find enough documentation on it
On the MSDN doc page it says that the callback should return a BOOL and thats what I am doing?
BOOL in WinAPI is declared as typedef int BOOL. So it doesn't match Go's bool. Specifications doesn't even mention what's the size it has. It's probably 1 byte but it doesn't say it. You should use int32 instead.