What are the sign extension rules for calling Windows API functions (stdcall)? This is needed to call WInAPI from Go, which is strict about int types

huangapple go评论73阅读模式
英文:

What are the sign extension rules for calling Windows API functions (stdcall)? This is needed to call WInAPI from Go, which is strict about int types

问题

哦,当我回答这个答案时,有一件事我忘了,这是我自己也不太确定的事情,而且我在MSDN、Google和Stack Overflow搜索中找不到相关信息。

在Windows API中有许多地方使用了负数或者超过有符号整数范围的数值,例如CW_USEDEFAULTINVALID_HANDLE_VALUEGWLP_USERDATA等等。在C语言中,一切都很好:语言的整数提升规则会解决这个问题。

但是在Go语言中,我必须将所有参数都作为uintptr(相当于C的uintptr_t)传递给函数。函数的返回值也是以这种方式返回的,然后我需要进行比较。Go语言不允许整数提升,并且不允许在编译时将有符号常量表达式转换为无符号常量表达式。

目前,我在我的UI库中设置了一个临时解决方案来处理这些常量。(这里有一个示例,展示了这个解决方案的效果。)然而,我对这个解决方案还不太满意;我觉得它对ABI(应用二进制接口)做了一些假设,我想要对我所做的事情有绝对的把握。

所以我的问题是:在将有符号值传递给Windows API函数时,它们是如何处理的?在返回时又是如何处理的?

我的所有常量都是自动生成的示例输出)。自动生成器使用了C ffi,但我不想在主项目中使用它,因为我可以直接调用DLL(这也使得跨平台编译在今年剩下的时间里更容易)。如果我可以通过将所有内容转换为C端的变量来利用它,例如:

uintptr_t x_CONST_NAME = (uintptr_t) (CONST_NAME);

那将会很有帮助。但是没有这个答案,我无法做到这一点。

谢谢!

更新

IRC上的某人用不同的方式表达了这个问题(重新格式化以避免水平滚动):

[19:13] <FraGag> 基本上,你正在询问一个值为-1的int,在一个int为4字节、uintptr为8字节的情况下,如果在参数传递中返回的是0x00000000FFFFFFFF还是0xFFFFFFFFFFFFFFFF

基本上就是这样,但是特别针对Windows API的互操作性,无论参数传递的uintptr大小如何。

英文:

Oops, there was one thing I forgot when I made this answer, and it's something that I'm both not quite sure on myself and that I can't seem to find information for on MSDN and Google and the Stack Overflow search.

There are a number of places in the Windows API where you use a negative number, or a number too large to fit in a signed integer; for instance, CW_USEDEFAULT, INVALID_HANDLE_VALUE, GWLP_USERDATA, and so on. In the world of C, everything is all fine and dandy: the language's integer promotion rules come to the rescue.

But in Go, I have to pass all my arguments to functions as uintptr (which is equivalent to C's uintptr_t). The return value from the function is also returned this way, and then I will need to compare. Go doesn't allow integer promotion, and it doesn't allow you to convert a signed constant expression into an unsigned one at compile-time.

Right now, I have a bit of a jerry-rig set up for handling these constants in my UI library. (Here's an example of what this solution looks like in action.) However, I'm not quite satisfied with this solution; it feels to me like it's assuming things about the ABI, and I want to be absolutely sure of what I'm doing.

So my question is: how are signed values handled when passing them to Windows API functions and how are they handled when returning?

All my constants are autogenerated (example output). The autogenerator uses a C ffi, which I'd rather not use for the main project since I can call the DLLs directly (this also makes cross-compilation easier at least for the rest of the year). If I could somehow leverage that, for instance by making everything into a C-side variable of the form

uintptr_t x_CONST_NAME = (uintptr_t) (CONST_NAME);

that would be helpful. But I can't do that without this answer.

Thanks!

Update

Someone on IRC put it differently (reformatted to avoid horizontal scrolling):

[19:13] <FraGag> basically, you're asking whether an int with a value of -1
                 will be returned as 0x00000000FFFFFFFF or as 0xFFFFFFFFFFFFFFFF
                 if an int is 4 bytes and an uintptr is 8 bytes

Basically this, but specifically for Windows API interop, for parameters passed in, and regardless of uintptr size.

答案1

得分: 0

@twotwotwo对我的问题的评论指引我朝着正确的方向。如果Stack Overflow允许将评论标记为答案并且可以标记多个答案,我会这样做。

简短版本:我现在的代码是正确的。

我编写了一个程序(如下所示),它简单地从syscall包中转储了所有的常量,并查找那些为负数但不等于-1的常量(因为那只是^0)。标准文件句柄(STD_ERROR_HANDLESTD_INPUT_HANDLESTD_OUTPUT_HANDLE)分别是(-12,-10和-11)。在syscall包中的代码将这些常量作为getStdHandle(h int)的唯一参数传递,该函数为包os生成所需的文件句柄。getStdHandle()将此整数传递给自动生成的函数GetStdHandle(stdhandle int),该函数包装了对GetStdHandle()系统调用的调用。GetStdHandle()接受整数并将其仅转换为uintptr以便传递给syscall.Syscall()。尽管在自动生成器的源代码(mksyscall_windows.go)中没有给出解释,但如果这个过程不起作用,那么fmt.Println()也不会起作用。

在windows/386和windows/amd64上的所有内容都是相同的;唯一与处理器相关的文件中的内容是GetStdHandle(),但相关代码是相同的。

我的negConst()函数已经在做同样的事情,只是更直接。因此,我可以安全地假设它是正确的。

谢谢!

// 2014年6月4日
// 基于2014年5月24日的代码
package main

import (
	"fmt"
	"os"
	"strings"
	"go/token"
	"go/ast"
	"go/parser"
	"go/types"
	_ "code.google.com/p/go.tools/go/gcimporter"
)

var arch string

func getPackage(path string) (typespkg *types.Package, pkginfo types.Info) {
	var pkg *ast.Package

	fileset := token.NewFileSet()		// parser.ParseDir()实际上会写入这个;不确定为什么它不返回一个
	filter := func(i os.FileInfo) bool {
		if strings.Contains(i.Name(), "_windows") &&
			strings.Contains(i.Name(), "_" + arch) &&
			strings.HasSuffix(i.Name(), ".go") {
			return true
		}
		if i.Name() == "race.go" ||		// 跳过这些
			i.Name() == "flock.go" {
			return false
		}
		return strings.HasSuffix(i.Name(), "_windows.go") ||
			(!strings.Contains(i.Name(), "_"))
	}
	pkgs, err := parser.ParseDir(fileset, path, filter, parser.AllErrors)
	if err != nil {
		panic(err)
	}
	for k, _ := range pkgs {		// 获取唯一的键
		if pkgs[k].Name == "syscall" {
			pkg = pkgs[k]
			break
		}
	}
	if pkg == nil {
		panic("package syscall not found")
	}
	// 我们不能直接将pkg.Files传递给types.Check(),因为前者是一个映射,而后者是一个切片
	ff := make([]*ast.File, 0, len(pkg.Files))
	for _, v := range pkg.Files {
		ff = append(ff, v)
	}
	// 如果我们不使用make()创建每个映射,package types将不会填充结构
	pkginfo.Defs = make(map[*ast.Ident]types.Object)
	pkginfo.Scopes = make(map[ast.Node]*types.Scope)
	typespkg, err = new(types.Config).Check(path, fileset, ff, &pkginfo)
	if err != nil {
		panic(err)
	}
	return typespkg, pkginfo
}

func main() {
	pkgpath := "/home/pietro/go/src/pkg/syscall"
	arch = os.Args[1]

	pkg, _ := getPackage(pkgpath)
	scope := pkg.Scope()
	for _, name := range scope.Names() {
		obj := scope.Lookup(name)
		if obj == nil {
			panic(fmt.Errorf("nil object %q from scope %v", name, scope))
		}
		if !obj.Exported() {		// 仅导出的名称
			continue
		}
		if _, ok := obj.(*types.Const); ok {
			fmt.Printf("egrep -rh '#define[ 	]+%s' ~/winshare/Include/ 2>/dev/null\n", obj.Name())
		}
		// 否则跳过
	}
}
英文:

@twotwotwo's comments to my question pointed me in the right direction. If Stack Overflow allowed marking comments as answers and having multiple answers marked, I'd do that.

tl;dr version: what I have now is correct after all.

I wrote a program (below) that simply dumped all the constants from package syscall and looked for constants that were negative, but not == -1 (as that would just be ^0). The standard file handles (STD_ERROR_HANDLE, STD_INPUT_HANDLE, and STD_OUTPUT_HANDLE) are (-12, -10, and -11, respectively). The code in package syscall passes these constants as the sole argument of getStdHandle(h int), which produces the required file handle for package os. getStdHandle() passes this int to an autogenerated function GetStdHandle(stdhandle int) that wraps a call to the GetStdHandle() system call. GetStdHandle() takes the int and merely converts it to uintptr for passing into syscall.Syscall(). Though no explanation is given in the autogenerator's source (mksyscall_windows.go), if this didn't work, neither would fmt.Println() =P

All of the above is identical on both windows/386 and windows/amd64; the only thing in a processor-specific file is GetStdHandle(), but the relevant code is identical.

My negConst() function is already doing the same thing, just more directly. As such, I can safely assume that it is correct.

Thanks!

// 4 june 2014
// based on code from 24 may 2014
package main
import (
"fmt"
"os"
"strings"
"go/token"
"go/ast"
"go/parser"
"code.google.com/p/go.tools/go/types"
_ "code.google.com/p/go.tools/go/gcimporter"
)
var arch string
func getPackage(path string) (typespkg *types.Package, pkginfo types.Info) {
var pkg *ast.Package
fileset := token.NewFileSet()		// parser.ParseDir() actually writes to this; not sure why it doesn't return one instead
filter := func(i os.FileInfo) bool {
if strings.Contains(i.Name(), "_windows") &&
strings.Contains(i.Name(), "_" + arch) &&
strings.HasSuffix(i.Name(), ".go") {
return true
}
if i.Name() == "race.go" ||		// skip these
i.Name() == "flock.go" {
return false
}
return strings.HasSuffix(i.Name(), "_windows.go") ||
(!strings.Contains(i.Name(), "_"))
}
pkgs, err := parser.ParseDir(fileset, path, filter, parser.AllErrors)
if err != nil {
panic(err)
}
for k, _ := range pkgs {		// get the sole key
if pkgs[k].Name == "syscall" {
pkg = pkgs[k]
break
}
}
if pkg == nil {
panic("package syscall not found")
}
// we can't pass pkg.Files directly to types.Check() because the former is a map and the latter is a slice
ff := make([]*ast.File, 0, len(pkg.Files))
for _, v := range pkg.Files {
ff = append(ff, v)
}
// if we don't make() each map, package types won't fill the structure
pkginfo.Defs = make(map[*ast.Ident]types.Object)
pkginfo.Scopes = make(map[ast.Node]*types.Scope)
typespkg, err = new(types.Config).Check(path, fileset, ff, &pkginfo)
if err != nil {
panic(err)
}
return typespkg, pkginfo
}
func main() {
pkgpath := "/home/pietro/go/src/pkg/syscall"
arch = os.Args[1]
pkg, _ := getPackage(pkgpath)
scope := pkg.Scope()
for _, name := range scope.Names() {
obj := scope.Lookup(name)
if obj == nil {
panic(fmt.Errorf("nil object %q from scope %v", name, scope))
}
if !obj.Exported() {		// exported names only
continue
}
if _, ok := obj.(*types.Const); ok {
fmt.Printf("egrep -rh '#define[ 	]+%s' ~/winshare/Include/ 2>/dev/null\n", obj.Name())
}
// otherwise skip
}
}

huangapple
  • 本文由 发表于 2014年6月4日 02:19:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/24022225.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定