调用 Syscall 函数时出现异常

Getting an exception when calling Syscall function

我正在使用 Go 的系统调用包来调用用 C++ 编写的 DLL。

C++ 方法签名如下所示。

init(int* buffer, int argc, char* argv[], const char* fileName, const char* key, const char* prefix, const char* version)

这是我用来在 Go 中调用上述方法的函数。

func init(
  buffer uintptr, 
  argsCount int, 
  args []string, 
  fileName string, 
  key string, 
  prefix string, 
  version string
) uintptr {
    // libHandle is handle to the loaded DLL
    methodAddress := getProcAddress(libHandle, "init")

    status, _, err := syscall.Syscall9(
      methodAddress,
      7,
      buffer,
      uintptr(unsafe.Pointer(&argsCount)),
      uintptr(unsafe.Pointer(&args)),
      uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(fileName))),
      uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(key))),
      uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(prefix))),
      uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(version))),
      0,
      0)

      fmt.Println(err.Error())

      return status
 }

我在调用此方法时收到此错误并且对此一无所知。

Exception 0xc0000005 0x0 0x0 0x7fffe30bdb33
PC=0x7fffe30bdb33

syscall.Syscall9(0x7fffe32db600, 0x7, 0x97db50, 0xc00007ff10, 
0xc00007ff70, 0xc000054180, 0xc0000541a0, 0xc0000541c0, 0xc0000541e0, 
0x0, ...)

c:/go/src/runtime/syscall_windows.go:210 +0xf3
main.main()

 E:/Path/test.go:157 +0x2be
 rax     0x81fbf0
 rbx     0x1
 rcx     0x7ff804ad1310
 rdi     0x7ff10
 rsi     0xc00007ff70
 rbp     0x0
 rsp     0x81fbc0
 r8      0x0
 r9      0x7ff804ad0000
 r10     0xc00007ff00
 r11     0x81fbf0
 r12     0x7ff10
 r13     0xc00007ff70
 r14     0xc000054180
 r15     0x97db50
 rip     0x7fffe30bdb33
 rflags  0x10257
 cs      0x33
 fs      0x53
 gs      0x2b

设置

所以,基本上,您得到的映射是

  1. int* bufferbuffer uintptr

  2. int argcunsafe.Pointer(&argsCount),其中 &argsCount 是指向 int

    的指针
  3. char* argv[]unsafe.Pointer(&args),其中 args[]string

  4. const char* fileNameunsafe.Pointer(syscall.StringToUTF16Ptr(fileName))

    const char* keyconst char* prefixconst char* version — 同上。

问题

现在这有什么问题。

  1. 禁止在函数之间传递包含实时 Go 对象地址的 uintptr 值。 (稍后会详细介绍。)

  2. 您将 argsCount 函数参数的地址传递给 argc。像 amd64/Windows 这样的典型商品 platform/OS 上的地址是一个非常巨大的值——如果解释为计数。

    我敢打赌,该函数在尝试从 argv 中读取那么多元素时会崩溃——导致它读取您的进程未映射的内存。

  3. 这里有两个问题:

    1. 将切片值的地址作为期望该切片的第一个元素的地址的参数传递是错误的。

      这是因为切片值(目前,在您应该使用的“参考”Go 实现中)是一个具有 3 个字段的 struct:底层数据数组的地址、元素的数量在允许使用的数组中,以及在调用切片值时 append 函数无需重新分配即可使用的数组中此类元素的总数。

      当您有 args []string 并执行 &args 时,您将获得该结构的地址,而不是该切片的第一个元素的地址。

      要执行后者,请使用 &args[0]

    2. 在 Go 中,一个字符串(通常,我们假设是这种情况)包含编码为 UTF-8 的字符。这通常不是 Windows-native C++ 代码在表示需要 char *.

      时期望处理的内容

      我猜,你需要先为argv构造一个合适的东西, 像

      argv := make([]unsafe.Pointer, 0, len(args))
      for i, s := range args {
          argv[i] = unsafe.Pointer(syscall.StringToUTF16Ptr(s))
      }
      

      然后将&argv[0]传递给被调用者。

      但请参阅下面的 syscall.StringToUTF16Ptr()

  4. 您为将字符串数据传递给具有 const char* 类型的其余参数所做的准备似乎是正确的,但前提是被调用者确实意味着其 char 是一个 16 位整数。

    换句话说,用于编译该库的源代码和工具链必须确保char确实是wchar_t or WCHAR

    如果是,那么你做的应该没问题;否则不是。你应该验证一下。

关于在表达式中传递 uintptr 的注意事项

Go 具有垃圾收集功能,因此它的 运行time 必须知道指向所有当前活动对象的所有指针。只要存在一个包含指向在程序 运行 期间分配的内存块的指针的变量,该块就不会是 garbage-collected.

unsafe.Pointer 值算作对内存块的正确引用,但 uintptr 不是。 这意味着当代码

p := new(someType)
u := uintptr(unsafe.Pointer(&p))
foo(u)
return

是运行ning,一旦p分配了它的地址,GC就可以自由回收p分配的对象——仅仅是因为p是对该对象的唯一引用,而 u 不是。

现在考虑参考 Go 实现中的 GC 运行 与您的程序代码同时发生。 这意味着当 foo 运行s 时,GC 可能也会执行,并且它可能会从 foo.

的脚下清除您的 someType 实例

作为此一般规则的一个例外,Go 编译器保证在同一 语言表达式 中发生的所有 uintptr(unsafe.Pointer) 类型转换都不受 GC 的影响。所以就拿我们之前的例子来说,做

就可以了
foo(uintptr(unsafe.Pointer(new(someType)))
return

p := new(someType)
v := unsafe.Pointer(&p)
foo(uintptr(v))
return

因为 type-conversion 到 uintptr 发生在单个表达式中——这是一个函数调用。

因此,您不能将指向 Go 对象的指针作为 uintptrs 传递,除非 它们包含从“C 端”获得的指针。