调用Syscall函数时获取异常

时间:2019-06-25 14:07:46

标签: c++ go dll ffi

我正在使用Go的syscall软件包来调用用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 个答案:

答案 0 :(得分:0)

设置

所以,基本上,您所拥有的映射是

  1. int* bufferbuffer uintptr

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

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

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

    const char* keyconst char* prefixconst char* version-与上面相同。

问题

现在这是怎么回事。

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

  2. 您将argsCount函数参数的地址传递给argc。如果将其解释为计数,那么在典型的商品平台/操作系统(例如amd64 / Windows)上的地址是一个巨大的价值。

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

  3. 这里有两个问题:

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

      这是因为slice值(当前,在您正在使用的“引用” Go实现中)是3个参数的结构:基础数据数组的地址,该数组中元素的数量为在切片值上调用时,append函数可以使用而无需重新分配的数组中此类元素的总数。

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

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

    2. 在Go中,一个字符串(通常,假设是这种情况)包含编码为UTF-8的字符。通常,这不是Windows本地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具有垃圾回收功能,因此,运行时必须知道指向所有当前活动对象的所有指针。只要存在一个包含指向程序运行时分配的内存块的指针的变量,该内存块就不会被垃圾回收。

unsafe.Pointer值被视为对存储块的正确引用,而uintptr不是。 这意味着代码< / p>

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

正在运行,一旦分配了p的地址,GC便可以随意回收p分配的对象,这是因为p是对该对象的唯一引用,并且u不是。

现在考虑参考Go实现中的GC与程序代码同时运行。 这意味着foo运行时,GC也会运行,它可能会从someType的脚下扫掠foo的实例。

作为该通用规则的例外,Go编译器保证在同一语言表达式中发生的所有uintptr(unsafe.Pointer)类型转换均不受GC的影响。因此,以我们之前的示例为例,

foo(uintptr(unsafe.Pointer(new(someType)))
return

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

因为向uintptr的类型转换发生在单个表达式中,这是一个函数调用。

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