go实现数据处理(FFI实战之对接GOCGO)
原生类语言,相对来说,我个人最熟悉的是Delphi,而且当下一些三方的库,提供Delphi的SDK的也很少,为了使用Go的丰富的生态库,然后使用Delphi优秀的界面开发方案,写本篇章的主要目的也就是一个抛砖引玉吧,希望引出各大Delphier扩展三方框架的玉石,同时也算是提供给广大Delphier能灵活使用Go来对接Delphi的提供一个简要的方案吧,我来为大家科普一下关于go实现数据处理?以下内容希望对你有帮助!
go实现数据处理
Delphi中的一些数据类型到Go的数据类型的转换原生类语言,相对来说,我个人最熟悉的是Delphi,而且当下一些三方的库,提供Delphi的SDK的也很少,为了使用Go的丰富的生态库,然后使用Delphi优秀的界面开发方案,写本篇章的主要目的也就是一个抛砖引玉吧,希望引出各大Delphier扩展三方框架的玉石,同时也算是提供给广大Delphier能灵活使用Go来对接Delphi的提供一个简要的方案吧。
实际上无论是什么语言什么类型的语言,要跨语言通用 首要的就是搞清楚各自的数据类型的内存结构,以及对齐方式,只要将这些都搞清楚了,那其他的都是不变应万变,水到渠成的事情。
delphi字符串到Go的字符串的转换的几种方法- 自己构造符合Go语言结构的结构
这个方法,前面咱们讲过并且也用过了,也就是之前定义的gostring,如下:
typedef struct _goString{
char* UTF8Data;
int datalen;
}goString,*pgoString;
这样做有他的好处,也有他的不好之处,好处是其他语言只要定义一个这种格式的数据类型就能和Go的字符串进行交互,本方法任何语言都实用用,比较通用,不好之处,就是不能使用各自语言的原始数据类型,必须要声明一个对应的数据类型来做转化处理。
那么有没有法子来让各自的原生语言使用原生语言的数据类型呢,肯定是有的,请继续看。
- 使用标准C的char*或者wchar*
通常情况下,咱们需要跨语言,使用动态库,基本上也都是使用标准C的char*和wchar*作为参数来传递字符串相关信息的。这样大家都认识,并且各个语言也都有这种原始数据指针的类型,比如Delphi中就有对应的PAnsichar,和Pchar等就支持。所以这种方式也是比较通用的。
由于就是指针,所以咱们在Go中接收数据,直接使用uintptr类型来接收就好,然后就是要将这个数据转换到Go的string类型,如何转换呢,这就是需要分析的点了,首先在标准C中的char*, wchar*实际上就是一个以\0结尾的连续字符数组,如此一来,咱们的突破点就找到了,那就是通过查找\0就能知道这个串的长度,那么就容易了,当然这些都是需要指针操作的说,现在给出相关的转换代码如下:
func PcharLen(dstr uintptr) int {
if dstr == 0 {
return 0
}
ptr := unsafe.Pointer(dstr)
for i := 0; ; i {
if 0 == *(*uint16)(ptr) {
return int(i)
}
ptr = unsafe.Pointer(uintptr(ptr) 2)
}
return 0
}
func FastPchar2String(pcharStr uintptr) String {
if pcharStr == 0 {
return ""
}
s := new(reflect.SliceHeader)
s.Data = pcharStr
s.Len = PcharLen(pcharStr)
s.Cap = s.Len
return string(utf16.Decode(*(*[]uint16)(unsafe.Pointer(s))))
}
func Pchar2String(pcharstr uintptr) string {
if pcharstr == 0 {
return ""
}
ptr := unsafe.Pointer(pcharstr)
gbt := make([]uint16, 0, 255)
for i := 0; ; i {
if 0 == *(*uint16)(ptr) {
break
}
gbt = append(gbt, *(*uint16)(ptr))
ptr = unsafe.Pointer(uintptr(ptr) 2)
}
return string(utf16.Decode(gbt))
}
这里的代码转换主要使用的特性就是查找内存中以\0结尾的来区分字符串完毕,完了之后,直接获取数据块数据,然后使用相应的解码函数解码,我这里主要都是针对高版本的Delphi,所以给定的参数Pchar实际上都是String的类型,然后直接Pcharde,其编码格式就是UTF16的格式,所以最终使用utf16去解码,如果是使用的是AnsiString,或者低版本的,就需要使用相应的格式去做解码的,如果是D2007之下,应该使用GBK去解码了,还有可能是Utf8那么就需要使用Utf8去解码了。
除了使用这种方式,直接使用Delphi自己的更原始的公民string类型行不行呢。咱们继续往下分析。
- 使用Delphi的一等原始公民字符串类型直接作为参数传递
同理咱们先来分析一下Delphi的字符串的数据类型,到底是个什么东西,这一块,实际上相应的手册以及Delphi的自带源码中的一些字符串处理函数中都有一些蛛丝马迹,这里就不多说了,实际上他就是一个指针,咱们使用sizeof(string)也可以发现这个长度就是指针的大小。
Delphi的string在内存中的实际格式如下:
codePage偏移 |
字符元素长度 偏移 |
引用计数 偏移 |
字符串长度 偏移 |
数据区偏移 |
-12 |
-10 |
-8 |
-4 |
0 |
这里的偏移,指的是地址偏移,一个Delphi字符串的指针地址就是数据区偏移,所以咱们可以直接使用Pchar将一个字符串转成一个char*的
另外就是这是支持Unicode的Delphi,应该是从2010开始,基本上都是这个存储结构,如果是低版本的,则只有引用计数偏移以及字符串长度偏移
知道了这个结构,那么咱们就能来实现这个原始字符串类型的代码实现了,首先是接收类型,已经说了string就是一个指针,所以,接收类型咱们依然使用uintptr作为接收参数,然后要做的就是根据这个内存结构来将数据还原到Go的版本
package main
func DelphiStringLen(delphiString uintptr) (result int32) {
//Delphi字符串的地址的-4地址位置为长度
if delphiString == 0 {
return 0
}
result = *(*int32)(unsafe.Pointer(delphiString - 4))
return
}
func FastDelphiString2String(delphiString uintptr, unicodeDelphi bool) string {
if delphiString == 0 {
return ""
}
dataLen := *(*int32)(unsafe.Pointer(delphiString - 4))
if dataLen == 0 {
return ""
}
var codePage uint16
if unicodeDelphi {
codePage = *(*uint16)(unsafe.Pointer(delphiString - 12))
} else {
codePage = 936
}
switch codePage {
case 1200:
//UTF16编码页
s := new(reflect.SliceHeader)
s.Data = delphiString
s.Len = int(dataLen * 2)
s.Cap = s.Len
return string(utf16.Decode(*(*[]uint16)(unsafe.Pointer(s))))
case 65001:
//是UTF8的,那么直接开整
s := make([]byte, dataLen)
CopyMemory(unsafe.Pointer(&s[0]), unsafe.Pointer(delphiString), uintptr(dataLen))
return *(*string)(unsafe.Pointer(&s))
case 936, 20936:
//gbk的
s := new(reflect.SliceHeader)
s.Data = delphiString
s.Len = int(dataLen)
s.Cap = s.Len
if resultUtf8, err := GBK2Utf8(*(*[]byte)(unsafe.Pointer(s))); err == nil {
return *(*string)(unsafe.Pointer(&resultUtf8))
}
case 950:
//繁体中文
}
return ""
}
上面两个函数,就可以直接接收Delphi的原生string类型,并且,咱们还针对codepage来针对不同的编码页,做了不同的转换,比如如果此时Go写了一个动态库,导出函数
//export delphiString
func delphiString(delphiString uintptr, unicodeDelphi bool) {
codePage := *(*uint16)(unsafe.Pointer(delphiString - 12))
notify(fmt.Sprintf("字符串长度:%d,codePage:%d", DelphiStringLen(delphiString), codePage))
notify(FastDelphiString2String(delphiString, unicodeDelphi))
}
然后咱们可以在Delphi中直接声明一个函数类型
var delphiString: procedure(m: string;unicodeDelphi: Boolean);cdecl;
//调用
var
msg: string;
begin
str := '不得闲测试234123服大';
delphiString(str,True);
end;
//或者
var delphiString: procedure(m: UTF8String;unicodeDelphi: Boolean);cdecl;
//调用
var
msg: UTF8String;
begin
str := '不得闲测试234123服大';
delphiString(str,True);
end;
到这里,基本上,我就将我所知道的将Delphi的字符串转到Go的字符串的方式都讲了,有兴趣的可以自己试试。
Delphi的时间日期类型到Go的日期时间类型同理咱们先看一下两个日期时间类型的区别,Delphi中的TDateTime实际上就是一个Double类型,而Go中的Time类型是一个结构体,并且两者针对时间存储的规则也不相同,所以想用指针啥的来直接转换,肯定就不行了。
思考一下Double类型就是Go语言中的float64,咱们在Go中可以使用float64类型来接收Delphi的日期类型,而主要要做的,就是需要将Delphi的传递过来的double转换成一个有效的Go的时间类型,咱们可以先看一下规则,从Delphi自己的时间单元相关的代码可以发现,Delphi的TDateTime实际上表示的是 其表示的时间到1899-12-30号的天数 (表示的时间的毫秒数/一天的总共毫秒数集合),通过这个规则,咱们做好转换就OK了
package DelphiTime
type (
TDateTime float64
)
const (
MinsPerHour = 60
MinsPerDay = 24 * MinsPerHour
SecsPerDay = MinsPerDay * 60
MSecsPerDay = SecsPerDay * 1000
)
var delphiFirstTime time.Time
func init() {
delphiFirstTime = time.Date(1899, 12, 30, 0, 0, 0, 0, time.Local)
}
func (date TDateTime) ToTime() time.Time {
mDay := time.Duration(date)
ms := (date - TDateTime(mDay)) * TDateTime(MSecsPerDay)
return delphiFirstTime.Add(mDay*time.Hour*24 time.Duration(ms)*time.Millisecond)
}
func Time2DelphiTime(t time.Time) TDateTime {
if t.IsZero() {
return 0
}
days := t.Sub(delphiFirstTime) / (time.Hour * 24)
y, m, d := t.Date()
nowdate := time.Date(y, m, d, 0, 0, 0, 0, time.Local)
times := float64(t.Sub(nowdate)) / float64(time.Hour*24)
return TDateTime(float64(days) times)
}
通过上面的分解说明,咱们可以看到,要让双方互通,主要就是内存结构保持一致,其他的按照相关类型的规则去做相应的去转化,去处理就好,也就是知道其所以然,搞明白构造之后,就可以随便玩了,完全可以根据自己的想法去行走,去应用,而不需要死扣着CGO文档中的一些东西去生搬硬套。
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com