调用 Windows API 函数(stdcall)的符号扩展规则是什么?这是从 Go 调用 WInAPI 所必需的,Go 对 int 类型有严格要求
题
哎呀,我做的时候忘了一件事 这个答案, ,这是我自己不太确定的事情,而且我似乎无法在 MSDN、Google 和 Stack Overflow 搜索上找到相关信息。
Windows API 中有很多地方需要使用负数,或者是太大而无法放入有符号整数的数字;例如, CW_USEDEFAULT
, INVALID_HANDLE_VALUE
, GWLP_USERDATA
, , 等等。在 C 的世界里,一切都那么美好:该语言的整数提升规则可以解决这个问题。
但在 Go 中,我必须将所有参数传递给函数 uintptr
(相当于C的 uintptr_t
)。函数的返回值也是这样返回的,然后我就需要进行比较。Go 不允许整数提升,并且不允许您在编译时将有符号常量表达式转换为无符号常量表达式。
现在,我有一个 有点偷工减料 设置为在我的中处理这些常量 用户界面库. (以下是此解决方案实际效果的示例。)但是,我对这个解决方案不太满意;在我看来,它就像是在假设有关 ABI 的事情,而且我想绝对确定我在做什么。
所以我的问题是:将带符号的值传递给 Windows API 函数时如何处理以及返回时如何处理它们?
我所有的常数都是 自动生成的 (示例输出)。自动生成器使用 一个 C 菲, ,我不想在主项目中使用它,因为我可以直接调用 DLL(这也使得交叉编译至少在今年剩余的时间里变得更容易)。如果我能以某种方式利用它,例如将所有内容都变成以下形式的 C 端变量
uintptr_t x_CONST_NAME = (uintptr_t) (CONST_NAME);
那会有帮助的。但如果没有这个答案我就无法做到这一点。
谢谢!
更新
IRC 上有人提出了不同的说法(重新格式化以避免水平滚动):
[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
基本上是这样,但专门用于 Windows API 互操作、传入的参数,并且无论 uintptr 大小如何。
解决方案
@twotwotwo 对我的问题的评论为我指明了正确的方向。如果 Stack Overflow 允许将评论标记为答案并标记多个答案,我会这样做。
TL;博士版本:我现在所拥有的毕竟是正确的。
我编写了一个程序(如下),它简单地转储了系统调用包中的所有常量,并查找负数常量,但不是 == -1 (因为这只是 ^0
)。标准文件句柄 (STD_ERROR_HANDLE
, STD_INPUT_HANDLE
, , 和 STD_OUTPUT_HANDLE
) 分别为 (-12、-10 和 -11)。系统调用包中的代码将这些常量作为唯一参数传递 getStdHandle(h int)
, ,它生成 os 包所需的文件句柄。 getStdHandle()
将此 int 传递给自动生成的函数 GetStdHandle(stdhandle int)
包装了对 GetStdHandle()
系统调用。 GetStdHandle()
获取 int 并将其转换为 uintptr
用于传递到 syscall.Syscall()
. 。尽管自动生成器的源代码(mksyscall_windows.go)中没有给出任何解释,但如果这 没有 工作,也不会 fmt.Println()
=P
以上所有内容在 windows/386 和 windows/amd64 上都是相同的;处理器特定文件中唯一的内容是 GetStdHandle()
, ,但相关代码是相同的。
我的 negConst()
function 已经在做同样的事情,只是更直接。因此,我可以放心地假设它是正确的。
谢谢!
// 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
}
}