Каковы правила расширения знака для вызова функций 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
}
}