Каковы правила расширения знака для вызова функций Windows API (stdcall)? Это необходимо для вызова WInAPI из Go, который строго относится к типам int

К сожалению, было одно, что я забыл, когда сделал этот ответ, и это то, в чем я не совсем уверен в себе, и что я не могу найти информацию для MSDN и Google и поиска переполнения стека.

В API Windows есть несколько мест, где вы используете отрицательное число или число, слишком большое, чтобы поместиться в целое число со знаком; например, CW_USEDEFAULT, INVALID_HANDLE_VALUE, GWLP_USERDATA, и так далее. В мире Си все хорошо и прекрасно: на помощь приходят целочисленные правила продвижения языка.

Но в Go я должен передать все свои аргументы функциям как uintptr (что эквивалентно С uintptr_t). Возвращаемое значение из функции также возвращается таким образом, и тогда мне нужно будет сравнить. Go не допускает целочисленное продвижение и не позволяет вам преобразовывать константное выражение со знаком в беззнаковое во время компиляции.

Прямо сейчас, у меня есть немного Jerry-Rig для настройки этих констант в моей библиотеке пользовательского интерфейса. ( Вот пример того, как это решение выглядит в действии.) Однако я не совсем удовлетворен этим решением; мне кажется, что он принимает на себя все, что касается ABI, и я хочу быть абсолютно уверенным в том, что я делаю.

Поэтому мой вопрос: как обрабатываются подписанные значения при передаче их функциям Windows API и как они обрабатываются при возврате?

Все мои константы генерируются автоматически ( пример вывода). Автогенератор использует Cffi, который я бы предпочел не использовать для основного проекта, поскольку я могу напрямую вызывать библиотеки 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.

1 ответ

Решение

Комментарии @twotwotwo к моему вопросу указали мне правильное направление. Если переполнение стека позволяет пометить комментарии как ответы и пометить несколько ответов, я бы это сделал.

tl; dr версия: то, что я имею сейчас, в конце концов правильно.

Я написал программу (ниже), которая просто выгружала все константы из пакета syscall и искала константы, которые были бы отрицательными, но не == -1 (как это было бы просто ^0). Стандартные файловые дескрипторы (STD_ERROR_HANDLE, STD_INPUT_HANDLE, а также STD_OUTPUT_HANDLE) (-12, -10 и -11 соответственно). Код в пакете syscall передает эти константы как единственный аргумент getStdHandle(h int), который создает требуемый дескриптор файла для пакета os. getStdHandle() передает это int автоматически сгенерированной функции GetStdHandle(stdhandle int) который оборачивает вызов GetStdHandle() системный вызов. GetStdHandle() принимает int и просто конвертирует его в uintptr для перехода в syscall.Syscall(), Хотя в источнике автогенератора (mksyscall_windows.go) нет объяснения, если бы это не сработало, то и не получилось бы fmt.Println() = Р

Все вышеперечисленное идентично как для windows/386, так и для windows/amd64; единственная вещь в специфичном для процессора файле GetStdHandle(), но соответствующий код идентичен.

мой negConst() функция уже делает то же самое, просто более прямо. Таким образом, я могу с уверенностью предположить, что это правильно.

Спасибо!

// 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
    }
}
Другие вопросы по тегам