标题
golang 中如何访问 c 语言风格的字节流缓冲区
clq
浏览(399) +
2023-01-25 14:15:32 发表
编辑
关键字:
[2023-01-25 14:17:22 最后更新]
golang 中如何访问 c 语言风格的字节流缓冲区
在 c 语言或者是 delphi 这样的原生语言中字节缓冲区是很常见的。例如 buf = malloc(1024);
但在脚本语言中基本上不会有。所以 nodejs 中特意扩展了一个专用的类,而 java 中是用的流类。
在 golang 中是怎样呢? golang 中也有类似流的东西,但在与 C 语言的函数直接操作时会很困难,所以很多库直接用的 cgo 。
但如果是纯 golang 直接调用 dll 或者是读取二进制文件应该怎么处理呢? 我没仔细找官方推荐的方法,不知道有没有,不过通过对 golang 操作 dll 和读取二进制文件的方法可以知道以下的方法是可靠的。
即用 []byte 来“代替” char * buf 这样的 c 语言表达式。实际上这并不是“代替”而是它就等于 c 语言中的 char * ,只不过在访问时需要一些技巧.
读取二进制的代码如下
s := make([]byte, 4096);
off := 1024;
//f.Seek(off, 0); //SEEK_SET = 0
f.Seek(int64(off), os.SEEK_SET); //SEEK_SET = 0
//nr, err := f.Read(s[:]); //可怕的语法 //尽量用 function_filebuf.go
//slice[:] 应该表示的切片本身,所以不用应该是一样的效果
nr, err := f.Read(s);
看到这里时其实还有疑虑,这 []byte 里对应的物理内存是怎样的,怎样取得 char * 这样的指针地址呢?这个 []byte 里的内存块是连续的吗,还只是一个列表?
实际上这 []byte 就是一块连续的内存,通过 "&s[0]" 就可以取得它的内存块地址。这个语法其实更接近取 delphi 字符串的内存块地址。
首先 "s[0]" 表示的是内存块的第一个字符,而 "&" 在 golang 和 c 语言中是一样的,都是取内存地址,
所以取第一个字符(字节)的地址就是整个内存块的地址了,当然前提是 []byte 中不是元素列表而是整块原始的物理内存。
这其实很好证明,只要找一个 golang 调用字符串相关的 api dll 调用你就会知道了,golang 是先将字符串转换成 u16 的 []byte 然后再取的 "&s[0]" 传递给 dll 的函数的。
而相关的 unsafe 什么什么的一堆操作函数不过是在强制转换指针类型罢了。
以下是我直接调用 gtk 的 dll 的示例,如果传递的 appid 不对的话,gtk 是会报 appid 为 null 或者不符合规范的。
```
appid := "org.gtk.example\x00";
buf := []byte(appid);
p := &buf[0];
r := unsafe.Pointer(p);
r2 := uintptr(r);
app,_,_ := gtk_application_new2.Call(r2, IntPtr(0));
```
这里要注意的是 r2 的结果是会被 gc 回收的,所以不能在函数外先转换再在这里用。如果想用一个函数封装的话,它的返回值是不能为 r2 的,而应该是包含 r2 和原始 []byte 结果的结构体才能避免 gc 。
我思考了好多年后才发现这一方便的处理方式,之前一直是苦记 golang 的释放规则和语法规则。实现上可以轻松简单的封装成以下结构体和转换函数
```
//避免 gc 回收
type cstring struct {
s string;
buf []byte;
c_str uintptr; //最后的 c 语言 dll 导出函数中需要的 char * 指针
}//
var g_id = to_c_str("org.gtk.example\x00");
func to_c_str(s string) cstring {
//return uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(s)));
var r cstring;
r.buf = []byte(s);
p := &r.buf[0];
//p := &buf;
//&StringToUTF16(s)[0]
//return p;
_r := unsafe.Pointer(p);
r.c_str = uintptr(_r);
return r;
}//
```
这时候,调用代码就变成了
app,_,_ := gtk_application_new2.Call(g_id.c_str, IntPtr(0));
而网上流传的以下两个函数中
```
func IntPtr(n int) uintptr {
return uintptr(n);
}//
func StrPtr(s string) uintptr {
return uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(s)));
}//
```
StrPtr 的结果肯定是不安全的,会在某个意外的时间点被回收导致程序莫名的崩溃。这我在多年前将 golang 封装成 http 的服务器 dll 中碰到了,一直在某个访问量大的时候崩溃,后来才发现是这个原因。
其实这在有 gc 的语言中,比如 java c# 中都存在,我目前在其他语言中的办法是维护一个 map 将生成的指针(语言中一般叫引用)都保存一份,在不需要时才手工从 map 中删除这样就避免了 gc 。
NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.