go - 如何“将 Go 指针传递给 Cgo”?
问题描述
我对将 Go 指针(据我了解,包括所有指针类型以及unsafe.Pointer
)传递给 cgo 感到困惑。当使用 cgo 调用 C 函数时,我只能提供 C 端已知类型的变量,或者unsafe.Pointer
如果它与void*
C 函数签名中的 -typed 参数匹配。因此,当“传递给 C 的 Go 指针在调用的生命周期内被固定”C.some_wide_enough_uint_type
时,如果我被迫将其强制转换为或C.some_c_pointer_type
预先转换,Go 怎么知道我传递的实际上是 Go 指针?在它被投射的那一刻,它是一个 Go 指针的信息不是丢失了,我冒着 GC 改变指针的风险吗?(我可以看到至少在 Go 端保留指针类型引用时如何防止释放)
我们有一个项目有相当多的工作 cgo 代码,但对其可靠性的信心为零。我想看一个“这里是如何正确地做到这一点”的例子,它不会通过使用C.malloc()
或诸如此类来规避 Go 的内存模型,不幸的是大多数例子都是这样做的。
因此,无论“固定调用生命周期的指针”实际上意味着什么,我都看到了一个问题:
- 如果这意味着 Go 将固定整个程序中的所有指针,我会在将 Go 指针转换为 C 类型和实际调用 cgo 调用之间的时间间隔中看到竞争条件。
- 如果这意味着 Go 将仅固定那些正在传递的 Go 指针,那么当调用时它们只能具有 C 类型时,它如何知道它们是 Go 指针?
我已经阅读了半天的 Go 问题,并且开始觉得我只是错过了一些简单的东西。任何指针表示赞赏。
编辑:我将尝试通过提供示例来澄清这个问题。
考虑一下:
/*
#include <stdio.h>
void myCFunc(void* ptr) {
printf((char*)ptr);
}
*/
import "C"
import "unsafe"
func callMyCFunc() {
goPointer := []byte("abc123\n\x00")
C.myCFunc(unsafe.Pointer(&goPointer[0]))
}
在这里,Go's unsafe.Pointer
-type 毫不费力地转换为 C's void*
-type,所以我们在 C 方面很高兴,我们也应该在 Go 方面:指针清楚地指向 Go 分配的内存,所以它应该是微不足道的让 Go 弄清楚它应该在调用期间固定这个指针,尽管它是一个不安全的指针。是这样吗?如果是这样,无需进一步研究,我会认为这是将 Go 指针传递给 cgo 的首选方式。是吗?
然后,考虑一下:
/*
#include <stdio.h>
void myCFunc(unsigned long long int stupidlyTypedPointerVariable) {
char* pointerToHopefullyStillTheSameMemory = (char*)stupidlyTypedPointerVariable;
printf(pointerToHopefullyStillTheSameMemory);
}
*/
import "C"
import "unsafe"
func callMyCFunc() {
goPointer := []byte("abc123\n\x00")
C.myCFunc(C.ulonglong(uintptr(unsafe.Pointer(&goPointer[0]))))
}
在这里,我希望 Go 不会对某个C.ulonglong
-typed 变量是否实际上意味着包含 Go 指针的地址做出任何猜测。但我是对的吗?
我的困惑很大程度上源于这样一个事实,即实际上不可能编写一些代码来可靠地测试它。
最后,这个呢:
/*
#include <stdio.h>
void cFuncOverWhichIHaveNoControl(char* ptr) {
printf(ptr);
}
*/
import "C"
import "unsafe"
func callMyCFunc() {
goPointer := []byte("abc123\n\x00")
C.cFuncOverWhichIHaveNoControl((*C.char)(unsafe.Pointer(&goPointer[0])))
}
如果我出于某种原因无法更改 C 函数的签名,我必须强制转换为*C.char
. 当值已经是 C 指针类型时,Go 还会检查值是否是 Go 指针吗?
解决方案
查看当前 cgo 文档中关于传递指针的部分,(感谢 peterSO)我们发现
Go 指针一词是指指向 Go 分配的内存的指针
以及该
指针类型可以保存 Go 指针或 C 指针
因此,使用uintptr
和其他整数(读取:非指针)类型将失去 Go 对固定指针的保证。
uintptr 是整数,而不是引用。将指针转换为 uintptr 会创建一个没有指针语义的整数值。即使 uintptr 拥有某个对象的地址,如果对象移动,垃圾收集器也不会更新该 uintptr 的值,该 uintptr 也不会阻止对象被回收。
来源:https ://golang.org/pkg/unsafe/#Pointer
关于诸如*char
/之类的 C 指针类型*C.char
,只有当指向的数据本身不包含指向 Go 分配的其他内存的指针时,它们才是安全的。这实际上可以通过尝试触发 Go 的 Cgo 调试机制来展示,该机制不允许将 Go 指针传递给(或传入)本身包含另一个 Go 指针的值:
package main
import (
"fmt"
"unsafe"
/*
#include <stdio.h>
void cFuncChar(char* ptr) {
printf("%s\n", ptr);
}
void cFuncVoid(void* ptr) {
printf("%s\n", (char*)ptr);
}
*/
"C"
)
type MyStruct struct {
Distraction [2]byte
Dangerous *MyStruct
}
func main() {
bypassDetection()
triggerDetection()
}
func bypassDetection() {
fmt.Println("=== Bypass Detection ===")
ms := &MyStruct{[2]byte{'A', 0}, &MyStruct{[2]byte{0, 0}, nil}}
C.cFuncChar((*C.char)(unsafe.Pointer(ms)))
}
func triggerDetection() {
fmt.Println("=== Trigger Detection ===")
ms := &MyStruct{[2]byte{'B', 0}, &MyStruct{[2]byte{0, 0}, nil}}
C.cFuncVoid(unsafe.Pointer(ms))
}
这将打印以下内容:
=== Bypass Detection ===
A
=== Trigger Detection ===
panic: runtime error: cgo argument has Go pointer to Go pointer
使用*C.char
绕过了检测。只有使用unsafe.Pointer
才会检测 Go 指针到 Go 指针的场景。不幸的是,这意味着我们将不得不void*
在 C 函数的签名中偶尔添加一个模糊的 - 参数。
为清楚起见添加:Go 可以很好地固定 a*C.char
或此类所指向的值,这是可以安全通过的;它只是(合理地)不会努力找出它是否可能包含其他指向 Go 分配的内存的指针。投射到unsafe.Pointer
实际上是安全的;从中铸造可能是危险的。
推荐阅读
- javascript - 如何添加到 VSC 智能感知 javascript 模块?
- sql - SQL:如何拉取执行过动作 A 但从未执行过动作 B 的客户 ID?
- typescript - 如何删除事件监听器
- haskell - 如何处理类成员函数中的类型类约束
- spring-mvc - 部分反序列化存储在 hazelcast 中的 spring session 对象
- c# - 如何在 dotnet core 中创建可重复使用的 POST 视图对象?
- ios - 有没有在标签栏开关上调用的方法?
- javascript - 如何为作为字符串存储在变量中的表中的特定单元格着色
- reactjs - 如何使用 Azure 应用服务设置服务器以接受 POST 请求
- python - TypeError:“函数”和“函数”的实例之间不支持“>”