republican 发表于 2011-2-12 06:55:06

DllStruct如何使用数组存储二进制数据?

最近在做进程间通讯,要处理混合数据,所以想建立良好的数据处理方法,遂选用DllStruct.

测试代码如下:;-----测试一;
        $tBuffer = DllStructCreate( 'Long Pointer;Long Length;Byte Data')
        If @error Then MsgBox(48,"错误",@error)

        _Struct_SetData($tBuffer,101392,456,StringToBinary("测试一下"))
        _Struct_GetData($tBuffer)
        $tBuffer = 0
;---测试二;
        $tBuffer = DllStructCreate( 'Long Pointer;Long Length;Byte Data')
        If @error Then MsgBox(48,"错误",@error)

        _Struct_SetData($tBuffer,101392,456,StringToBinary("测试一下"),1)
        _Struct_SetData($tBuffer,222222,368,StringToBinary("测试一下"),2)
        _Struct_GetData($tBuffer)
        _Struct_GetData($tBuffer,1)
        _Struct_GetData($tBuffer,2)
        $tBuffer = 0

       
Func _Struct_SetData($Handle,$Pointer,$Length,$Data,$Index = Default)
        DllStructSetData($Handle, 1, $Pointer,$Index)
        DllStructSetData($Handle, 2, $Length,$Index)
        DllStructSetData($Handle, 3, $Data,$Index)
EndFunc

Func _Struct_GetData($Handle,$Index = Default)
        Local $Text = ''
        For $i = 1 To 3
                $Text &=DllStructGetData($tBuffer, $i,$Index)
                If $i = 3 Then $Text &= "解码:" & BinaryToString(DllStructGetData($tBuffer, $i),$Index)
                $Text &= @CRLF
        Next
        MsgBox(0,$Index, $Text)
EndFunc我想做的是用数组来存储不同的数据(每个数据包括三部分,起始点,长度,实际数据),因为对Dllstrct不是很了解,所以出现了上述代码的情况。
即,Long可以正确使用数组区分,但Byte却不可以,他一次只写入一个字节。
这是Byte的限制还是我使用不当?可否提供相应的资料?

另外,数据长度是对每一个数组而言,还是整个Struct而言?

netegg 发表于 2011-2-12 08:10:07

如果把数组改成结构,然后把指针套回去用呢?

republican 发表于 2011-2-12 08:18:35

回复 2# netegg

我不是很理解,具体而言是?

netegg 发表于 2011-2-12 12:46:51

本帖最后由 netegg 于 2011-2-12 12:49 编辑

$ptr=dllstructgetptr(dllstructcreate('type value;type value;')) ;这个里面可以分为数组了
dllstructcreate('type;' &$ptr &';' )

不知道行不行

uni_wl 发表于 2011-2-12 14:33:34

同求答案同求答案

republican 发表于 2011-2-12 20:28:06

回复 4# netegg

思路不错,不过粗略测试了一下,创建失败,@Error = 传递的字符串中有一个未知的数据类型

如果是这么麻烦的话,干脆用数组算了....

netegg 发表于 2011-2-12 21:29:19

看看_WinAPI_GetCDType函数原型

pusofalse 发表于 2011-2-12 22:57:39

long Size0;ubyte Data0[?];long Size1;ubyte Data1[?];long Size2;ubyte Data2[?];...

接收数据的那一方,如果只需要 遍历数据,那么使用这个结构是高效并且节省资源的。
Size0表示 Data0的长度,Data0中的问号?表示数据长度是不定长的,也因此节省了占用空间。

起始地址+Size0+4就会得到Size1的地址,Size1的地址+Size1+4就得到了Size2的地址,+4是因为long型数值Size 占4字节。有多少个数据,就连续定义多少个long Size和ubyte Data[?]。

这种结构对于“遍历”来说是高效的,但是,如果要获取Data1的数据,就必须从Size0开始计算,得出Size1的地址,Size1+4才是Data1的地址。最坏的情况,需要循环到结构最后,才能得到需要的数据,如果须考虑这种情况,必须再想一个结构,使得函数可以在O(1)内完成检索:

long Offset0;long Offset1;long Offset2;...
全是long型,表示数据n相对于起始地址的偏移地址,Offset0表示Size0的偏移地址,Offset1表示Size1的偏移地址。这里必须要设为偏移地址,而不能是绝对地址,因为进程间通信,数据发送到另一方时,起始地址的指针就会改变。如果要获取第n个数据,起始地址+[起始地址+(n-1)*4]就是数据n的地址。

综合起来:如果有3个数据需要发送,结构就是:
long Offset0;long Offset1;long Offset2;long Size0;ubyte Data0[?];long Size1;ubyte Data1[?];long Size2;ubyte Data2[?]
在使用时,问号?必须要改成一个合法的数值表示数据长度。

以上结构还有一个不足之处,就是 还没有一个字段来表示数据的数量。如果通信的另一方根本不知道数据量有多少,错误地想获取第4个数据,那么根据这个公式“起始地址+[起始地址+(4-1)*4]”就会计算出错误的地址而引发越界访问。为了确保程序的安全,最终的结构应该是:

long UBound;long Offset0;long Offset1;long Offset2;long Size0;ubyte Data0[?];long Size1;ubyte Data1[?];long Size2;ubyte Data2[?]

只在开头加了一个long UBound,表示数据的数量,这个结构能容纳3个,所以UBound的值就必须设为3。获取第n个数据的地址应该改用“起始地址+[起始地址+n*4]”。

示例代码:
#include <Memory.au3>
#include <Array.au3>

$aData = StringSplit("a,b,c,d,abc,abcd", ",", 3)

$pSendBuffer = _CreateSendBuffer($aData)

For $i = 0 To UBound($aData) - 1
        MsgBox(0, $i, _ParseBuffer($pSendBuffer, $i))
Next
$aParsed = _ParseBuffer($pSendBuffer, -1)
_ArrayDisplay($aParsed)

; 解析结构
Func _ParseBuffer($pBuffer, $iIndex = -1)
        Local $tData, $pData, $iSize, $tSize, $tOffset, $tUBound, $iUBound

        $tUBound = DllStructCreate("long UBound", $pBuffer)
        $iUBound = DllStructGetData($tUBound, "UBound") ; 数据量

        If $iIndex < 0 Then ; 遍历
                Local $aData[$iUBound]

                For $i = 0 To $iUBound - 1
                        $aData[$i] = _ParseBuffer($pBuffer, $i)
                Next
                Return $aData

        ElseIf $iIndex < $iUBound Then ; 获取其中第$iIndex个数据

                ; 获取偏移地址。
                $tOffset = DllStructCreate("long EntryOffset", $pBuffer + $iIndex * 4 + 4)
                $pData = $pBuffer + DllStructGetData($tOffset, "EntryOffset") ; 绝对地址

                $tSize = DllStructCreate("long Size", $pData) ; 数据长度
                $tData = DllStructCreate("wchar Data[" & DllStructGetData($tSize, "Size") / 2 & "]", $pData + 4)

                Return DllStructGetData($tData, "Data") ; 返回实际数据
        EndIf
EndFunc        ;==>_ParseBuffer

; 创建结构
Func _CreateSendBuffer($aData)
        Local $tBuffer, $pBuffer, $iBuffer, $tData, $iUBound, $aOffset

        $iUBound = UBound($aData) ; 数据量
        Redim $aOffset[$iUBound]

        For $i = 0 To $iUBound - 1
                ; 第$i个数据在结构中的偏移地址
                $aOffset[$i] = $iBuffer + $iUBound * 4 + 4

                ; 数据所需长度
                $iBuffer += StringLen($aData[$i]) * 2 + 6
        Next

        ; 结构所需长度
        $iBuffer += $iUBound * 4 + 4

        ; 分配内存,+4是为了避免引起越界访问。
        $pBuffer = _MemGlobalAlloc($iBuffer + 4)
        $tBuffer = DllStructCreate("long UBound", $pBuffer)

        ; 设置 数据量
        DllStructSetData($tBuffer, "UBound", $iUBound)

        ; 填充结构
        For $i = 0 To $iUBound - 1
                $tBuffer = DllStructCreate("long EntryOffset", $pBuffer + $i * 4 + 4)
                DllStructSetData($tBuffer, "EntryOffset", $aOffset[$i]) ; 偏移

                $tData = DllStructCreate("long Size;wchar Data[" & StringLen($aData[$i]) + 1 & "]", $pBuffer + $aOffset[$i])
                DllStructSetData($tData, "Size", DllStructGetSize($tData) - 4) ; 数据长度,-4即减去long Size所占长度。
                DllStructSetData($tData, "Data", $aData[$i]) ; 实际数据
        Next

        Return $pBuffer
EndFunc        ;==>_CreateSendBuffer

水木子 发表于 2011-2-13 10:43:45

回复 8# pusofalse

非常专业且详细的解说,虽然很多地方还是看不太懂,学习啦!

gto250 发表于 2011-2-13 11:07:13

看的是一头雾水,还是再学习学习

republican 发表于 2011-2-13 22:04:57

回复 8# pusofalse

思维很严密,_ParseBuffer中出现了递归的情况,真的很佩服P版的处理能力!

看到P版这么详细的回复,我感到很惭愧,占用了P版那么多时间...

怪我没说清楚,进程通讯几乎完成,参考的是命名管道。

而本贴想问的是,如何缓存大量零散的二进制数据。因为我想做的是多进程下载,想在父进程建立一个结构,缓存各个子进程下载好的数据,等满足条件时(2M-128M),一次写入。

我不确定能否用的上P版的函数,但相信不少看过的人都会有所启发。And 非常感谢P版~
页: [1]
查看完整版本: DllStruct如何使用数组存储二进制数据?