dsaxw4201 2014-06-03 18:19
浏览 104
已采纳

调用Windows API函数(stdcall)的符号扩展规则是什么? 从Go调用WInAPI是必需的,这对int类型非常严格

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条回答 默认 最新

  • dsbtwy1329 2014-06-04 22:13
    关注

    @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
    ", obj.Name())
            }
            // otherwise skip
        }
    }
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥50 树莓派安卓APK系统签名
  • ¥15 maple软件,用solve求反函数出现rootof,怎么办?
  • ¥65 汇编语言除法溢出问题
  • ¥15 Visual Studio问题
  • ¥15 state显示变量是字符串形式,但是仍然红色,无法引用,并显示类型不匹配
  • ¥20 求一个html代码,有偿
  • ¥100 关于使用MATLAB中copularnd函数的问题
  • ¥20 在虚拟机的pycharm上
  • ¥15 jupyterthemes 设置完毕后没有效果
  • ¥15 matlab图像高斯低通滤波