登录 用户中心() [退出] 后台管理 注册
   
您的位置: 首页 >> CLQ工作室开源代码 >> 主题: [go/golang]go语言中调用 dll 时传递字符串的大坑及解决方法     [回主站]     [分站链接]
标题
[go/golang]go语言中调用 dll 时传递字符串的大坑及解决方法
clq
浏览(0) + 2019-04-18 15:51:20 发表 编辑

关键字:

[2020-11-21 02:43:16 最后更新]

[go/golang]go语言中调用 dll 时传递字符串的大坑及解决方法


go1.7.3中的dll字符串参数与垃圾回收机制

这次使用 golang 调用标准 windows stdcall 的 dll 函数时发现,golang 中的垃圾回收机制是非常晦涩的,一定要注意.
在非常密集的大压力调用下,传递给 dll 的字符串参数使用不当的话会在传递出去前被垃圾回收导致程序访问了不存在的内存而崩溃!


1.和 C++ 的类析构造函数不同,函数体内定义的变量也有可能在还没出函数体时就被回收了!
解决的办法是:在函数体最后再使用一下这个变量,让其保持引用.但这样的代码不小心的话很有可能会被编译器优化掉.


2.和 C/C++ 不同, golang 中的指针也能让其所在的内存块一直保持引用. 但调用 unsafe.Pointer() 得到的指针则不会保持这种引用.


3.再强调一次! unsafe.Pointer 操作后的指针尽量在最后需要时再转换出来,特别不要作为函数的参数来使用.那样会引起编码的整体流畅度,思想会在到底垃圾回收,引用
等有没有效上纽结,干脆就不要用它传递参数,就当所有变量都是可以强引用的好了.

4.以上都是猜测和测试后的结论,最安全保守的做法是: 1 - 分配内存块,并在函数体最后 fmt.println 一下某个字节; 2 - 引出非 unsafe.Pointer() 的指针给子函数调用; 3 - unsafe.Pointer() 的指针不作为参数,在最后要使用时再临时转换出来.
这种思想应该可以很好的对付所有的垃圾回收机制,理解上也更容易.

2020 补充

5.这些在 dll 中调用的注意点在 go 源码中的 unsafe.go 中有非常详尽的说明。
而我们后面提到的那个示例使用了最安全的保守做法性能又非常好,我觉得尽量用这种方式,毕竟 golang 自己调用 windows api dll 的那种方式太隐晦,太容易误导大家写出错误的代码了。

它的处理方法也比较简单,就是用 cgo 来分配内存,然后又用 cgo 自己释放。这样就完全是使用的 c 语言的内存处理方式。就解决了这个很容易混淆的东西。

//参考 https://github.com/mattn/go-oci8/blob/master/oci8.go

//unsafe.Pointer 指针保存

//字符串内容保存//最好的当然还是这种在调用 dll 前明确先分配好内存
// connectString := cString(dsn.Connect)
// defer C.free(unsafe.Pointer(connectString))
// username := cString(dsn.Username)
// defer C.free(unsafe.Pointer(username))
// password := cString(dsn.Password)
// defer C.free(unsafe.Pointer(password))

--------------------------------------------------
测试时的代码如下
package main;

import (
"fmt"
// "fmt"
//"io/ioutil"
// "database/sql"
//"fmt"
// "reflect"
// "strconv"
//"strings"
"syscall"
"unsafe"
"net/http"
// "runtime"
//"runtime/internal/atomic" //这个是内部包,不能这样引用
//从 Go\src\syscall\os_windows.go 来看,一个 stdcall 是开了一个线程来调用一个 dll 函数的
//不对,好象是 tstart_stdcall, newosproc 才开

"runtime/debug"
"runtime"

)

//据说是高手的代码
//https://github.com/liudch/goci/blob/master/oci.go

//其实只要导出一个函数就可以了

var (
http_func * syscall.LazyProc = nil; //syscall.NewLazyDLL("C:\\Program Files\\Oracle\\instantclient_11_2\\oci.dll")
http_func_sql * syscall.LazyProc = nil;
)

func LoadHttpDlls(){

//mod_http := syscall.NewLazyDLL("http.dll");
mod_http := syscall.NewLazyDLL("http_dll.dll")

http_func = mod_http.NewProc("http_func"); // Clear all attribute-value information in a namespace of an application context
////http_func_sql = mod_http.NewProc("http_func_sql");
http_func_sql = mod_http.NewProc("http_func_sql_err1");

}//

func ExecuteHttp(w http.ResponseWriter, r *http.Request) (string, error) {

defer PrintError("ExecuteHttp");

//这个在高速时仍然有问题(异常退出),后期再用 golang 自己的锁定试试

//Ajax跨域问题的两种解决方法之一,据说 html5 后的才支持
w.Header().Set("Access-Control-Allow-Origin", "*");

//if (r.URL.Path == "/s/new.php")||(r.URL.Path == "/s/old.php")

if (r.URL.Path == "/sql/get")||(r.URL.Path == "/sql/get.php"){

sql := r.FormValue("sql");
sql = Trim(" " + sql + " "); //test 测试变量生存期
sql = " " + sql + " "; //test 测试变量生存期

//------------------
//调试 delphi 的 dll 时意外发现 syscall.StringToUTF16Ptr 已经是弃用的了 go 1.7.3 的源码注释中说了用 UTF16PtrFromString 来代替
sqlp, err := syscall.UTF16PtrFromString(sql);
if err !=nil { panic("dll 调用参数严重错误!!!");}

a, err2 := syscall.UTF16FromString(sql); //来自 UTF16PtrFromString 的源码
if err2 !=nil { panic("dll 调用参数严重错误!!! err2");}

sqlp = &a[0];

//------------------
//dll_errtest1(sql);
dll_errtest2(sql);
return "", nil;

//------------------
p1 := uintptr(unsafe.Pointer(sqlp));

runtime.GC(); //不调用这两个应该也是很容易重现的//还是加上容易重现,并且是一大堆一起失效,而没的的话则是久不久失效一个指针
debug.FreeOSMemory();

r0, _, e1 := http_func_sql.Call(
//utf82utf16(r.URL.Path),//,
p1, //uintptr(unsafe.Pointer(sqlp)), // utf82utf16(sql),//, !!! 这个会出错,内存混乱
//uintptr(unsafe.Pointer(http_func)), // OCIEnv **envhpp,
//uintptr(0), // ub4 mode,
//0, // CONST dvoid *ctxp,

);

//if r0 != 0 {
// return "", error(e1)
//}//if

if e1 != nil {
// return "", error(e1)
}//if

////s := prttostr(r0, 4*1024*1024); //这个确实是有问题的
fmt.Println(r0);

//fmt.Println(a); //奇怪这里不调用的话, a 的内存在多线程中会发生变化,估计是调用时间拖动得长的话被垃圾回收了,所以一定要在 dll 调用后再使用一下其中的变量,
//特别是字符串变量一定要注意,不能是只使用它的指针,要整个字节缓冲区一起//过会我写个可以一定重现的 dll 函数调用

//参考 zsyscall_windows.go 的用法,也是一直引用一个内存区的

//return s, nil;
return "", nil;
}//if

//--------------------------------------------------

r0, _, e1 := http_func.Call(
utf82utf16(r.URL.Path),//,
//uintptr(unsafe.Pointer(http_func)), // OCIEnv **envhpp,
//uintptr(0), // ub4 mode,
//0, // CONST dvoid *ctxp,

);

if r0 != 0 {
return "", error(e1)
}//if

return "", nil
}//


//重要!!! 调用 dll 传递字符串参数因 gc 导致数据失效的示例(实际上会因此内存访问错误崩溃)
func dll_errtest1(sql string) (string, error) {

defer PrintError("dll_errtest1");


//------------------
//调试 delphi 的 dll 时意外发现 syscall.StringToUTF16Ptr 已经是弃用的了 go 1.7.3 的源码注释中说了用 UTF16PtrFromString 来代替
sqlp, err := syscall.UTF16PtrFromString(sql);
if err !=nil { panic("dll 调用参数严重错误!!!");}

a, err2 := syscall.UTF16FromString(sql); //来自 UTF16PtrFromString 的源码
if err2 !=nil { panic("dll 调用参数严重错误!!! err2");}

sqlp = &a[0];

//------------------
p1 := uintptr(unsafe.Pointer(sqlp));

runtime.GC(); //不调用这两个应该也是很容易重现的//还是加上容易重现,并且是一大堆一起失效,而没的的话则是久不久失效一个指针
debug.FreeOSMemory();

//放到一个单独的函数时,这里调用 gc 后面会立即崩溃! 不 gc 的时候还能运行一下

r0, _, e1 := http_func_sql.Call(
//utf82utf16(r.URL.Path),//,
p1, //uintptr(unsafe.Pointer(sqlp)), // utf82utf16(sql),//, !!! 这个会出错,内存混乱
//uintptr(unsafe.Pointer(http_func)), // OCIEnv **envhpp,
//uintptr(0), // ub4 mode,
//0, // CONST dvoid *ctxp,

);

//if r0 != 0 {
// return "", error(e1)
//}//if

if e1 != nil {
// return "", error(e1)
}//if

////s := prttostr(r0, 4*1024*1024); //这个确实是有问题的
fmt.Println(r0);

//fmt.Println(p1); //强制再引用一会,不让垃圾回收//这个变量没用
//fmt.Println(a); //强制再引用一会,不让垃圾回收//这个变量有用
////fmt.Println(sqlp); //强制再引用一会,不让垃圾回收//这个变量有用,这个 *uint16 变量居然也可以让其所在的 []uint16 数组保持引用!!! 也就是说即使是引用了指针,其数组也是会保持的,那么会解引用的原因应该只是那个 unsafe.Pointer 后的变量
//也就是说 unsafe.Pointer 后的变量就随时可能被释放掉!


//fmt.Println(a); //奇怪这里不调用的话, a 的内存在多线程中会发生变化,估计是调用时间拖动得长的话被垃圾回收了,所以一定要在 dll 调用后再使用一下其中的变量,
//特别是字符串变量一定要注意,不能是只使用它的指针,要整个字节缓冲区一起//过会我写个可以一定重现的 dll 函数调用

//参考 zsyscall_windows.go 的用法,也是一直引用一个内存区的

//return s, nil;
return "", nil;



}//

//同 dll_errtest1 ,只是为了单独测试 UTF16PtrFromString 后的指针是否可以保持引用
func dll_errtest2(sql string) (string, error) {

defer PrintError("dll_errtest2");


//------------------
//调试 delphi 的 dll 时意外发现 syscall.StringToUTF16Ptr 已经是弃用的了 go 1.7.3 的源码注释中说了用 UTF16PtrFromString 来代替
//从 go 1.7.3 的源码来看,这是因为 StringToUTF16Ptr 会在调用 StringToUTF16 时,无法转换时
//调用 panic 引发程序立即退出
//ps.这种从 go 源码中直接退出的机制也太可怕了,一旦发生甚至不知道是怎样退出的,还以为是自己代码的问题...

sqlp, err := syscall.UTF16PtrFromString(sql);
if err !=nil { panic("dll 调用参数严重错误!!!");}

//a, err2 := syscall.UTF16FromString(sql); //来自 UTF16PtrFromString 的源码
//if err2 !=nil { panic("dll 调用参数严重错误!!! err2");}

//sqlp = &a[0];

//------------------
p1 := uintptr(unsafe.Pointer(sqlp));

runtime.GC(); //不调用这两个应该也是很容易重现的//还是加上容易重现,并且是一大堆一起失效,而没的的话则是久不久失效一个指针
debug.FreeOSMemory();

//放到一个单独的函数时,这里调用 gc 后面会立即崩溃! 不 gc 的时候还能运行一下

r0, _, e1 := http_func_sql.Call(
//utf82utf16(r.URL.Path),//,
p1, //uintptr(unsafe.Pointer(sqlp)), // utf82utf16(sql),//, !!! 这个会出错,内存混乱

);


if e1 != nil {
// return "", error(e1)
}//if

fmt.Println(r0);

fmt.Println(sqlp); //参考 dll_errtest1 处的说明//经测试,至少在 go 1.7.3 UTF16PtrFromString 后的指针变量是可以保持其所在原内存块的引用的,会让其内存块不被垃圾回收

//这也说明 golang 这样的垃圾回收语言,它的回收时机有时候还是很晦涩的

//golang 自己调用 dll 使用字符串时的处理可以参考 zsyscall_windows.go 见下一个示例

return "", nil;
}//

-----------------------------------------
对应 delphi 函数源码为

var
id:Integer = 0;

function http_func_sql_err1(sql:PWideChar):PAnsiChar;stdcall;
var
i,j:Integer;
_sql:string;
tmp:string;
begin
//indll
tmp := sql;
Writeln(tmp);

CoInitialize(nil);
try
//ShowMessage(sql); //线程里面这个不安全的

if gLock = nil then gLock := TThreadLock.Create(Application);

try
gLock.Lock();
id := id + 1;

while True do
begin
//Writeln(id, ' - ', 'test:' + tmp);

//--------------------------------------------------
_sql := sql;
//_sql := tmp;//sql; //调试发现线程并没有重入,但传入的 sql 已经被改变了,所以要尽快接收参数

Writeln(id, ' - ', '_sql:' + _sql);
Writeln(id, ' - ', 'tmp:' + tmp); //可以看到,出错的时候,这两者确实不一样了! golang 调用的参数居然发生了变化! 这是为什么,实际上还是在一个调用中啊

if _sql <> tmp then Break;
//--------------------------------------------------

Sleep(1000);

end;


finally

gLock.UnLock(); //一定要保证解锁
end; // try

Writeln('end.');

except on E: Exception do

//MessageBox(0, PChar( e.message), '', 0);
Writeln('Http 调用组件时,错误异常! ' + e.message);

end;

CoUninitialize();

end;







clq
2019-04-18 15:55:21 发表 编辑

golang 中自己调用 windows dll 时使用的方法则是非常隐晦和利用了非 unsafe 指针在传参时可以一直保持引用,直到函数返回的特性(也有可能是 golang 方便这样调用故意如此实现的).按这种方法得出的代码如下


//测试不保留引用的情况下,看看只传递非 unsafe.Pointer() 的指针,是否能保持内存的存在//感觉 go1.7.3 就是这样传递字符串参数的
//如果成立说明对非 unsafe.Pointer 指针的引用可以保持到传递出去的函数调用返回时
func dll_errtest3(sql string) (string, error) {

defer PrintError("dll_errtest3");

sqlp, err := syscall.UTF16PtrFromString(sql);
if err !=nil { panic("dll 调用参数严重错误!!!");}

//------------------
//p1 := uintptr(unsafe.Pointer(sqlp));

runtime.GC(); //不调用这两个应该也是很容易重现的//还是加上容易重现,并且是一大堆一起失效,而没的的话则是久不久失效一个指针
debug.FreeOSMemory();

//放到一个单独的函数时,这里调用 gc 后面会立即崩溃! 不 gc 的时候还能运行一下
dll_errtest3_sub(sqlp);

//fmt.Println(sqlp); //重要! 可以看到,将非 unsafe.Pointer 指针传递出去后就可以不用再人为的保持内存块引用了

return "", nil;
}//

func dll_errtest3_sub(sqlp *uint16) {

p1 := uintptr(unsafe.Pointer(sqlp));

r0, _, e1 := http_func_sql.Call(
//utf82utf16(r.URL.Path),//,
p1, //uintptr(unsafe.Pointer(sqlp)), // utf82utf16(sql),//, !!! 这个会出错,内存混乱

);


if e1 != nil {
// return "", error(e1)
}//if

fmt.Println(r0);
}//


clq
2019-04-18 16:03:54 发表 编辑

这是个挺严重的问题,我看了下网上的一些代码都是有这个问题的. 这些原因显然都是没搞清楚 go 本身调用 dll 的代码为什么不用保留引用造成的.


例如

//据说是高手的代码
//https://github.com/liudch/goci/blob/master/oci.go
//2019 更新,这份代码的字符串传递也是有问题的,例如
/*
func OCILogon(env *OCIHandle, username, password, database string) (svcctx *OCIHandle, err error) {
var s *OCIHandle = new(OCIHandle) // Service context
s.t = OCI_HTYPE_SVCCTX
var e *OCIHandle = new(OCIHandle) // Error handle
e.t = OCI_HTYPE_ERROR

r0, _, e1 := procOCILogon.Call( // sword OCILogon (
env.h, // OCIEnv *envhp,
uintptr(unsafe.Pointer(&e.h)), // OCIError *errhp,
uintptr(unsafe.Pointer(&s.h)), // OCISvcCtx **svchp,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(username))), // CONST OraText *username,
uintptr(len(username)), // ub4 uname_len,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(password))), // CONST OraText *password,
uintptr(len(password)), // ub4 passwd_len,
uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(database))), // CONST OraText *dbname,
uintptr(len(database))) // ub4 dbname_len );

if r0 != 0 {
err = error(e1)
return
}

return s, nil
}
这其中的用户名就是有可能在大压力环境下失效
*/


clq
2019-04-18 16:06:22 发表 编辑

不过我觉得这也是 go 本身的原因,函数体的变量还没出函数体就有可能被回收确实不合理.

我测试的是 go 1.7.3 ,最新版本的情况未知.


clq
2019-04-18 16:50:11 发表 编辑

这一份 oci 的代码,对字符串就做有一些手工释放的工作.

https://github.com/mattn/go-oci8/blob/master/oci8.go


clq
2020-04-30 14:28:14 发表 编辑

func dll_errtest1(sql string) (string, error) {

defer PrintError("dll_errtest1");


sqlp, err := syscall.UTF16PtrFromString(sql);
if err !=nil { panic("dll 调用参数严重错误!!!");}


//------------------
p1 := uintptr(unsafe.Pointer(sqlp));

runtime.GC(); //不调用这两个应该也是很容易重现的//还是加上容易重现,并且是一大堆一起失效,而没的的话则是久不久失效一个指针
debug.FreeOSMemory();

//----
//http_func_sql.Call(p1); //这个会立即崩溃

http_func_sql.Call(uintptr(unsafe.Pointer(sqlp))); //这个可以,但是实际上也是不安全的,因为 gc 随时可能发生,不过 golang 内部调用 windows dll 都是这样在最后一步强制转换就行了,并没有用别的东西使引用的内存一直存在

实际上最关键的是后面4句话,其中最关键的又是 uintptr 临时变量,即 uintptr(unsafe.Pointer( 的转换结果,实际上这个的转换结果是不能用变量来保存的。
只能在 dll 的函数最后调用的函数体中做为参数的转换。如果在 dll 函数调用之前已经转换出来了,那么这个转换结果所指向的原变量是有可能被 gc 的。



runtime.GC();
debug.FreeOSMemory();

这两名话就是强制产生了 gc 。对于这种现象,golang 的语法是这样解释的,如果内存的指针一直被引用,那么内存也会一直被引用并不会被 gc ,所以 []byte

转换出来的 byte* p = &a[0] 这样的切片转换得到的 byte * 指针也是可以安全的传递的,它会使原来的整个切片内存都一直有效果。就相当于 delphi 中一个局部变量的
pchar 从动态数组中转换出来后可以一直让所转换来的动态数组数据有效!即使是这个局部变量所声明的函数已经执行完成了。这和我们传统语言中,数组出了作用域就会
无效的情况是很不相同的。原因的传统语言的作用域大多是用堆栈来实现内存回收的,而 gc 系统下却是不会的。

这相当于 golang 下的所有指针都是可以安全使用的,因为它们指向的内存永远存在!这也是 golang 为什么写的代码很安全的原因。

但 uintptr 唯独是一个例外,它的指针是不会产生这种引用的。实际上它本质上是一个整数,所以它根本没有多余的地方来记录自己原来所在的内存区是哪里。
这也从一个侧面说明 golang 中的 byte * 这样的本质上应该只是语法糖,实际上它产生的仍然是一个结构体(专门用来传递指针的结构体,实际上就是所谓的引用)。
这个是 golang 中最与众不同的,包括和 java 都不同,实际上就相当于它没有基础类型(不过这又与它在结构体中的表现冲突,暂时中以理解为它对结构体的的操作时
又进行了拆箱吧)。

这应该也是为什么不能直接将 byte * 这样的参数直接给 dll 的 call 函数的原因,因为那不是真正的指针,所以要全部都转换成 uintptr(unsafe.Pointer


----------------------------------------------------------------
最后只剩下一个问题 unsafe.Pointer 的结果据说也是可以一直让内存被引用着的,那么用什么类型来保存它的值呢?另外,除了强制转换为 uintptr ,还能转换为其他的类型吗?



----------------------------------------------------------------
最后,对于 dll 中不要用变量保存 uintptr(unsafe.Pointer ,要到最后调用时再转换的说法在 golang 的源码中就特意说明了。在源码

D:\go1.10.8\src\unsafe\unsafe.go

中写了非常多。


----------------------------------------------------------------
参考以下文章,不一定正确

https://blog.csdn.net/cbmljs/article/details/82983639


Go语言学习笔记--unsafe.Pointer 和 uintptr
cbmljs 2018-10-09 15:52:58 5573 收藏 3
展开

这里有一些关于unsafe.Pointer和uintptr的事实:

uintptr是一个整数类型。
即使uintptr变量仍然有效,由uintptr变量表示的地址处的数据也可能被GC回收。
unsafe.Pointer是一个指针类型。
但是unsafe.Pointer值不能被取消引用。
如果unsafe.Pointer变量仍然有效,则由unsafe.Pointer变量表示的地址处的数据不会被GC回收。
unsafe.Pointer是一个通用的指针类型,就像* int等。

由于uintptr是一个整数类型,uintptr值可以进行算术运算。 所以通过使用uintptr和unsafe.Pointer,我们可以绕过限制,* T值不能在Golang中计算偏移量:

package main

import (
"fmt"
"unsafe"
)

func main() {
a := [4]int{0, 1, 2, 3}
p1 := unsafe.Pointer(&a[1])
p3 := unsafe.Pointer(uintptr(p1) + 2 * unsafe.Sizeof(a[0]))
*(*int)(p3) = 6
fmt.Println("a =", a) // a = [0 1 2 6]

// ...

type Person struct {
name string
age int
gender bool
}

who := Person{"John", 30, true}
pp := unsafe.Pointer(&who)
pname := (*string)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.name)))
page := (*int)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.age)))
pgender := (*bool)(unsafe.Pointer(uintptr(pp) + unsafe.Offsetof(who.gender)))
*pname = "Alice"
*page = 28
*pgender = false
fmt.Println(who) // {Alice 28 false}
}








clq
2020-04-30 14:36:03 发表 编辑

connectString := cString(dsn.Connect)
defer C.free(unsafe.Pointer(connectString))
username := cString(dsn.Username)
defer C.free(unsafe.Pointer(username))
password := cString(dsn.Password)
defer C.free(unsafe.Pointer(password))

clq
2020-04-30 15:22:49 发表 编辑

这个例子写得最简单,而且触发机率非常高,强烈推荐。在 go 1.10 下都是有效的。

package main;

import (
"fmt"
// "fmt"
//"io/ioutil"
// "database/sql"
//"fmt"
// "reflect"
// "strconv"
//"strings"
"syscall"
"unsafe"
/// "net/http"
// "runtime"
//"runtime/internal/atomic" //这个是内部包,不能这样引用
//从 Go\src\syscall\os_windows.go 来看,一个 stdcall 是开了一个线程来调用一个 dll 函数的
//不对,好象是 tstart_stdcall, newosproc 才开

"runtime/debug"
"runtime"

)


//unsafe.Pointer

//参考 https://github.com/mattn/go-oci8/blob/master/oci8.go

//unsafe.Pointer 指针保存

//字符串内容保存//最好的当然还是这种在调用 dll 前明确先分配好内存
// connectString := cString(dsn.Connect)
// defer C.free(unsafe.Pointer(connectString))
// username := cString(dsn.Username)
// defer C.free(unsafe.Pointer(username))
// password := cString(dsn.Password)
// defer C.free(unsafe.Pointer(password))


func main(){
dll_errtest1("12311111111111111111aaaaaaaaaaaa");
//dll_errtest2("12311111111111111111aaaaaaaaaaaa");

}//


//重要!!! 调用 dll 传递字符串参数因 gc 导致数据失效的示例(实际上会因此内存访问错误崩溃)
func dll_errtest1(sql string) () {

//------------------
//调试 delphi 的 dll 时意外发现 syscall.StringToUTF16Ptr 已经是弃用的了 go 1.7.3 的源码注释中说了用 UTF16PtrFromString 来代替
sqlp, err := syscall.UTF16PtrFromString(sql);
if err !=nil { panic("dll 调用参数严重错误!!!");}

// a, err2 := syscall.UTF16FromString(sql); //来自 UTF16PtrFromString 的源码
// if err2 !=nil { panic("dll 调用参数严重错误!!! err2");}

// sqlp = &a[0];

//------------------
p1 := uintptr(unsafe.Pointer(sqlp));
buf_len := len(sql);

//------------------
runtime.GC(); //不调用这两个应该也是很容易重现的//还是加上容易重现,并且是一大堆一起失效,而没的的话则是久不久失效一个指针
debug.FreeOSMemory();

//----
//2020

print_buf_v1(p1, buf_len);//这个会立即崩溃,偶尔会成功
//print_buf_v1(uintptr(unsafe.Pointer(sqlp)), buf_len); //最后才 unsafe.Pointer 就会一直是成功的
//print_buf_v2(int(uintptr(unsafe.Pointer(sqlp))), buf_len); //最后才 unsafe.Pointer 就会一直是成功的

return;
fmt.Println(p1);

}//

//测试暂存的 unsafe.Pointer 是否会被 gc //结论确实不会被回收内存
func dll_errtest2(sql string) () {

sqlp, _ := syscall.UTF16PtrFromString(sql);


//------------------
p1 := uintptr(unsafe.Pointer(sqlp));
buf_len := len(sql);

var p2 unsafe.Pointer = unsafe.Pointer(sqlp);

//------------------
runtime.GC(); //不调用这两个应该也是很容易重现的//还是加上容易重现,并且是一大堆一起失效,而没的的话则是久不久失效一个指针
debug.FreeOSMemory();

//----
print_buf_v1(uintptr(p2), buf_len); //确实不会崩溃,那说明 unsafe.Pointer 确实可以让被其指向的内存一直保存不被 gc

return;


fmt.Println(p1, buf_len);


}//

func print_buf_v1(buf uintptr, buf_len int){
var p * byte;

for i:=0; i
p = (* byte)(unsafe.Pointer(buf));

var b byte = *p;
fmt.Print(b);

buf++;
}//

fmt.Println("print_buf() ok");
}//

//用 int 代替 uintptr 后确实是一样的,所以说 uintptr 确实就是整数,
//和 D:\go1.10.8\src\unsafe\unsafe.go 的说法是一致的
func print_buf_v2(buf int, buf_len int){
var p * byte;

for i:=0; i
p = (* byte)(unsafe.Pointer(uintptr(buf)));

var b byte = *p;
fmt.Print(b);

buf++;
}//

fmt.Println("print_buf() ok");
}//







总数:7 页次:1/1 首页 尾页  
总数:7 页次:1/1 首页 尾页  


所在合集/目录



发表评论:
文本/html模式切换 插入图片 文本/html模式切换


附件:



NEWBT官方QQ群1: 276678893
可求档连环画,漫画;询问文本处理大师等软件使用技巧;求档softhub软件下载及使用技巧.
但不可"开车",严禁国家敏感话题,不可求档涉及版权的文档软件.
验证问题说明申请入群原因即可.

Copyright © 2005-2020 clq, All Rights Reserved
版权所有
桂ICP备15002303号-1