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.
Related
show code:
package main
import (
"fmt"
"unsafe"
)
// copy form src/runtime/type.go
type _type struct {
size uintptr
ptrdata uintptr // size of memory prefix holding all pointers
hash uint32
tflag tflag
align uint8
fieldAlign uint8
kind uint8
equal func(unsafe.Pointer, unsafe.Pointer) bool
gcdata *byte
str nameOff
ptrToThis typeOff
}
type tflag uint8
type nameOff int32
type typeOff int32
// copy form src/runtime/runtime2.go
type eface struct {
_type *maptype
data unsafe.Pointer
}
// copy form src/runtime/type.go
type maptype struct {
typ _type
key *_type
elem *_type
bucket *_type // internal type representing a hash bucket
// function for hashing keys (ptr to key, seed) -> hash
hasher func(unsafe.Pointer, uintptr) uintptr
keysize uint8 // size of key slot
elemsize uint8 // size of elem slot
bucketsize uint16 // size of bucket
flags uint32
}
func main() {
var t interface{} = map[int]int{1: 1}
p := (*eface)(unsafe.Pointer(&t))
fmt.Println(p._type.typ.kind) // 53
}
print 53,
but you can find in src/runtime/typekind.go
const (
kindBool = 1 + iota
kindInt
kindInt8
kindInt16
kindInt32
kindInt64
kindUint
kindUint8
kindUint16
kindUint32
kindUint64
kindUintptr
kindFloat32
kindFloat64
kindComplex64
kindComplex128
kindArray
kindChan
kindFunc
kindInterface
kindMap // 21
kindPtr
kindSlice
kindString
kindStruct
kindUnsafePointer
kindDirectIface = 1 << 5
kindGCProg = 1 << 6
kindMask = (1 << 5) - 1
)
the map type is const 21. Similarly, chan is 50 instead of kindChan(18). Why?
If you look at the src/runtime/typekind.go, there is a function to check if the value is stored directly in an interface value, which applies to your case, as you are creating t of interface{} but storing a map type to it.
// isDirectIface reports whether t is stored directly in an interface value.
func isDirectIface(t *_type) bool {
return t.kind&kindDirectIface != 0
}
When using that on your value of p as isDirectIface(&p._type.typ) it returns true, because of the underlying bit value corresponding to interface type is set (kindDirectIface = 1 << 5, 32)
So in effect, the value 53 represents (decimal 21+32) a map type 21 (kindMap) stored as an interface type 32 (kindDirectIface)
I need to read a specific binary data format (https://www.usna.edu/Users/oceano/pguth/md_help/html/BT_file_format.htm). Go seems to be able to do so quite nicely:
// ...
f, _ := os.Open(filename)
var data struct {
Indicator [10]byte
Columns [4]byte
Rows [4]byte
DataSize [4]byte
UTMFlag [2]byte
UTMZone [2]byte
LeftExtend [4]byte
RightExtend [4]byte
BottomExtend [4]byte
TopExtend [4]byte
FloatingPointFlag [2]byte
}
_ = binary.Read(f, binary.LittleEndian, &data)
// ...
That seems to work since spew.dump(data.Indicator) for example return the correct data. What I do not understand is how to cast fixed slices like [2]byte to an integer I can actually work with. Any suggestions?
Declare the fields with fixed size numeric types:
var data struct {
Indicator [10]byte
Columns uint32
Rows uint32
DataSize uint32
UTMFlag uint16
UTMZone uint16
LeftExtend uint32
RightExtend uint32
BottomExtend uint32
TopExtend uint32
FloatingPointFlag uint16
}
I used unsigned integers here, but it's also OK to use signed integers. Use the type that matches the data.
https://play.golang.org/p/95yqMAYsWVR
Please, somebody, explain Go pointers magic
I try to call COM function
func (p *IServer) Authorize(user, pass string) error {
UserName := ole.SysAllocString(login.UserName)
defer ole.SysFreeString(UserName)
UserPsw := ole.SysAllocString(login.UserPsw)
defer ole.SysFreeString(UserPsw)
// HRESULT IServer::Authorize([in] BSTR UserName, [in] BSTR UserPsw, [out] VARIANT* SID, [out, retval] long* Result)
hr, _, _ := Call(p.VTable().Authorize,
uintptr(unsafe.Pointer(p)),
uintptr(unsafe.Pointer(UserName)),
uintptr(unsafe.Pointer(UserPsw)),
uintptr(unsafe.Pointer(sid)),
uintptr(unsafe.Pointer(&res)))
...
}
This code works well, but when I replace convertion to
UserName := syscall.StringToUTF16Ptr(login.UserName)
UserPsw := syscall.StringToUTF16Ptr(login.UserPsw)
It cause an ACCESS VIOLATION! Source from go-ole
func SysAllocString(v string) (ss *int16) {
pss, _, _ := procSysAllocString.Call(uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(v))))
ss = (*int16)(unsafe.Pointer(pss))
return
}
What am I doing wrong?
Update: In C/C++ wchar* pointer works corretly both 386/amd64
void Authorize(IServer* p wchar* user, wchar* pass) {
p.Authorize(user, pass ....
}
SysAllocString returns BSTR type, the com type object.
typedef struct {
#ifdef _WIN64
DWORD pad;
#endif
DWORD size;
union {
char ptr[1];
WCHAR str[1];
DWORD dwptr[1];
} u; // take it as a starting point of the string
} bstr_t;
In other words, it's the same utf16 encoded string but with the prefix in the form of its size (length of the Unicode characters multiplied by the size of wchar_t (2-4 byte)). For the optimization reason, it also has padding.
Because of its floating size, it's better to use ole package rather than reinventing the wheel. If you want to implement it yourself, and wchar_t has the size of int16 (2 bytes), then you have to do the following:
(half-pseudocode, I didn't test it)
type BSTR *uint16
func SysAllocString(str string) (result BSTR) {
// DWORD == int32 == rune
const padf = "\x00" // only for 64 bit system
const sizef = "\x00"
// int32 == 4 byte
// int16 == 2 byte
const wordSize = unsafe.Sizeof(int16(0))
utf16 := utf16.Encode([]rune(padf + sizef + str))
/* pad is on index 0 and 1 */
size := &utf16[2 /* 0 for 32 bit system */]
// set "size" field as unicode charachers length multypled by size of wchar_t
*(*rune)(unsafe.Pointer(size)) = rune((len(utf16)-2) * int(wordSize))
result = BSTR(&utf16[0])
return
}
// ...
bstr := SysAllocString(login.UserName)
uintptr(unsafe.Pointer(bstr))
I'v been trying to play with netapi32.dll, but I'm having mixed results.
The following works as expected
type SERVER_INFO_101 struct {
PlatformID uint32
Name *uint16
VersionMajor uint32
VersionMinor uint32
Type uint32
Comment *uint16
}
func NetServerGetInfo() {
info := &SERVER_INFO_101{}
ret, _, err := procNetServerGetInfo.Call(0, 101, uintptr(unsafe.Pointer(&info)))
if ret != 0 {
log.Fatal(err)
}
spew.Dump(info)
}
However, I'm not sure why info has to have & inside the unsafe.Pointer also.
The following does not work, and I can't seem to find out why. No error codes get thrown. Neither the struct or variables gets filled out.
type SESSION_INFO_10 struct {
Cname *uint16
Username *uint16
Time uint32
IdleTime uint32
}
func NetSessionEnum() {
info := &SESSION_INFO_10{}
var prefmaxlen int32 = -1
var entriesread uint32
var totalentries uint32
var resumehandle uint32
x, y, z := procNetSessionEnum.Call(0, 0, 0, 10, uintptr(unsafe.Pointer(info)), uintptr(prefmaxlen), uintptr(unsafe.Pointer(&entriesread)), uintptr(unsafe.Pointer(&totalentries)), uintptr(unsafe.Pointer(&resumehandle)))
fmt.Println(x, y, z)
fmt.Println(entriesread, totalentries)
spew.Dump(info)
}
…because you're not supposed to pass a pointer to your memory block there—to cite the manual:
This buffer is allocated by the system and must be freed using the NetApiBufferFree function.
The type of that pointer is misleading but you're supposed to pass a pointer to a pointer there, something like this:
func NetSessionEnum() {
var pinfo *SESSION_INFO_10
var prefmaxlen int32 = -1
var entriesread uint32
var totalentries uint32
var resumehandle uint32
x, y, z := procNetSessionEnum.Call(0, 0, 0, 10,
uintptr(unsafe.Pointer(&pinfo)), uintptr(prefmaxlen),
uintptr(unsafe.Pointer(&entriesread)),
uintptr(unsafe.Pointer(&totalentries)),
uintptr(unsafe.Pointer(&resumehandle)))
fmt.Println(x, y, z)
fmt.Println(entriesread, totalentries)
spew.Dump(info)
}
// Now use `*pinfo.Cname` etc
// Don't forget to later call `NetApiBufferFree()` on that pointer.
What happens here:
The variable pinfo is a pointer to a value of type SESSION_INFO_10.
You take the address of the memory block occupied by the value kept in that variable (which is a pointer) and pass it to NetSessionEnum().
That function allocates the buffer by itself and writes its address to the memory block pointed to by the address you have passed to the function.
Since you've passed an address of the pinfo variable, the address of the buffer ends up being written into the variable pinfo.
You then use that address stored in pinfo to access the memory allocated by NetSessionEnum().
That's called "double indirection" and is used in quite many places of Win32 API. Please read the manual page and study the code example it includes.
Update: as it turned out, there were more problems with the original code so I've took time to provide full solution—here is the gist (tested with Go 1.6 amd64 and i386 on Windows XP 32-bit, Windows 2003 R2 64-bit and Windows 8.1 64-bit).
Tries to call GetVolumeInformation function from golang. Want to get volume name.
Use spec's of api:
BOOL WINAPI GetVolumeInformation(
_In_opt_ LPCTSTR lpRootPathName,
_Out_opt_ LPTSTR lpVolumeNameBuffer,
_In_ DWORD nVolumeNameSize,
_Out_opt_ LPDWORD lpVolumeSerialNumber,
_Out_opt_ LPDWORD lpMaximumComponentLength,
_Out_opt_ LPDWORD lpFileSystemFlags,
_Out_opt_ LPTSTR lpFileSystemNameBuffer,
_In_ DWORD nFileSystemNameSize
);
Use code:
// test
package main
import (
"fmt"
"syscall"
"unsafe"
)
func main() {
var lpRootPathName = "C:\\"
var lpVolumeNameBuffer string
var nVolumeNameSize uint64
var lpVolumeSerialNumber uint64
var lpMaximumComponentLength uint64
var lpFileSystemFlags uint64
var lpFileSystemNameBuffer string
var nFileSystemNameSize uint32
kernel32, _ := syscall.LoadLibrary("kernel32.dll")
getVolume, _ := syscall.GetProcAddress(kernel32, "GetVolumeInformationW")
var nargs uintptr = 8
ret, _, callErr := syscall.Syscall9(uintptr(getVolume),
nargs,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(lpRootPathName))),
uintptr(unsafe.Pointer(&lpVolumeNameBuffer)),
uintptr(unsafe.Pointer(&nVolumeNameSize)),
uintptr(unsafe.Pointer(&lpVolumeSerialNumber)),
uintptr(unsafe.Pointer(&lpMaximumComponentLength)),
uintptr(unsafe.Pointer(&lpFileSystemFlags)),
uintptr(unsafe.Pointer(&lpFileSystemNameBuffer)),
uintptr(unsafe.Pointer(&nFileSystemNameSize)),
0)
fmt.Println(ret, callErr, lpVolumeNameBuffer)
}
... and finally have error :(
unexpected fault address 0xffffffffffffffff
fatal error: fault
[signal 0xc0000005 code=0x0 addr=0xffffffffffffffff pc=0x456b11]
Don't understand and google cant'd help with calling winapi functions and returng string as result.
Thank's.
Package unsafe
Package unsafe contains operations that step around the type safety of
Go programs.
type Pointer
type Pointer *ArbitraryType
Pointer represents a pointer to an arbitrary type. There are four
special operations available for type Pointer that are not available
for other types.
1) A pointer value of any type can be converted to a Pointer.
2) A Pointer can be converted to a pointer value of any type.
3) A uintptr can be converted to a Pointer.
4) A Pointer can be converted to a uintptr.
Pointer therefore allows a program to defeat the type system and read
and write arbitrary memory. It should be used with extreme care.
You failed to heed the warning that unsafe.Pointer "should be used with extreme care."
Try this:
package main
import (
"fmt"
"syscall"
"unsafe"
)
func main() {
var RootPathName = `C:\`
var VolumeNameBuffer = make([]uint16, syscall.MAX_PATH+1)
var nVolumeNameSize = uint32(len(VolumeNameBuffer))
var VolumeSerialNumber uint32
var MaximumComponentLength uint32
var FileSystemFlags uint32
var FileSystemNameBuffer = make([]uint16, 255)
var nFileSystemNameSize uint32 = syscall.MAX_PATH + 1
kernel32, _ := syscall.LoadLibrary("kernel32.dll")
getVolume, _ := syscall.GetProcAddress(kernel32, "GetVolumeInformationW")
var nargs uintptr = 8
ret, _, callErr := syscall.Syscall9(uintptr(getVolume),
nargs,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(RootPathName))),
uintptr(unsafe.Pointer(&VolumeNameBuffer[0])),
uintptr(nVolumeNameSize),
uintptr(unsafe.Pointer(&VolumeSerialNumber)),
uintptr(unsafe.Pointer(&MaximumComponentLength)),
uintptr(unsafe.Pointer(&FileSystemFlags)),
uintptr(unsafe.Pointer(&FileSystemNameBuffer[0])),
uintptr(nFileSystemNameSize),
0)
fmt.Println(ret, callErr, syscall.UTF16ToString(VolumeNameBuffer))
}
I don't know the exact problem you are having but I think it is likely because you are not using the functions in https://github.com/golang/go/blob/master/src/syscall/syscall_windows.go related to converting from the format that comes out of the kernel to what Go needs. Look at other callers to UTF16ToString, like in env_windows.go, to see how they are used.