英文:
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_USEDEFAULT
、INVALID_HANDLE_VALUE
、GWLP_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_HANDLE
,STD_INPUT_HANDLE
和STD_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
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论