回 帖 发 新 帖 刷新版面

主题:[讨论]CopyMemory 从一个字节数组中提取各种类型的数据的问题

[code=c]
Option Explicit

Private Declare Sub CopyMemoryAny Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSrc As Any, ByVal ByteLen As Long)
Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Long, pSrc As Long, ByVal ByteLen As Long)

Private Type Rec_Data2
'自定义结构体,36字节
    rd_time(2) As Byte
    rd_yali As Integer
    rd_maxYali As Integer
    rd_liu As Integer
    rd_midu As Integer
    rd_tai As Integer
    rd_zhuJiang As Single
    rd_shui As Integer
    rd_sha As Integer
    rd_state As Byte
    rd_qksm As Byte
    rd_stuff(12) As Byte
End Type

Private Sub Command1_Click()
'创建测试文件
Dim fNo As Integer
Dim i As Integer
Dim j As Integer
Dim tmp As Rec_Data2

fNo = FreeFile
Open "e:\test1.txt" For Binary As fNo
    For i = 0 To 1
        For j = 0 To 2
            tmp.rd_time(j) = Rnd() * 60
        Next
        tmp.rd_yali = Rnd() * 5
        tmp.rd_maxYali = Rnd() * 5
        tmp.rd_liu = Rnd() * 50
        tmp.rd_midu = Rnd() * 3
        tmp.rd_tai = Rnd() * 200
        tmp.rd_zhuJiang = Rnd() * 500
        tmp.rd_shui = Rnd() * 10
        tmp.rd_sha = Rnd() * 10
        tmp.rd_state = Rnd() * 255
        tmp.rd_qksm = Rnd() * 255
        For j = 0 To 10
            tmp.rd_stuff(j) = 32
        Next
        tmp.rd_stuff(11) = 13
        tmp.rd_stuff(12) = 10
        Debug.Print "-----------------------------------"
        PrintType tmp
        Put #fNo, , tmp
    Next
Close fNo
End Sub

Private Sub Command2_Click()
'读取测试文件
Dim fNo As Integer
Dim i As Integer
Dim tmp As Rec_Data2
Dim bytArr() As Byte
Dim p2 As Integer   '2字节
Dim p4 As Single    '4字节
ReDim bytArr(36) As Byte   '不知为何这里的下标要定义成36。为35时后会多读出一个全为0的bytArr
fNo = FreeFile
Open "e:\test1.txt" For Binary As fNo
    Do While Not EOF(fNo)
        Get fNo, i * 36 + 1, bytArr
        
        [color=red]'单独地一个一个地取出,正确
        CopyMemory ByVal VarPtr(p2), ByVal VarPtr(bytArr(3)), 2
        Debug.Print p2
        CopyMemoryAny p4, bytArr(13), 4
        Debug.Print p4
        
        '作为一个整体(自定义结构体),数值不正确
        CopyMemoryAny tmp, bytArr(0), 36
        Debug.Print "======================================="
        PrintType tmp
        [/color]
        i = i + 1
    Loop
Close fNo
End Sub

Private Function PrintType(ByRef typRec As Rec_Data2) As Long
'打印出结构体的所有成员
'要是有实现了IEnumerator接口的Type就好了,能用for each
Dim i As Integer
For i = 0 To 2
    Debug.Print "rd_time("; i; ")="; typRec.rd_time(i)
Next
Debug.Print "rd_yali="; typRec.rd_yali
Debug.Print "rd_maxYali="; typRec.rd_maxYali
Debug.Print "rd_liu="; typRec.rd_liu
Debug.Print "rd_midu="; typRec.rd_midu
Debug.Print "rd_tai="; typRec.rd_tai
Debug.Print "rd_zhuJiang="; typRec.rd_zhuJiang
Debug.Print "rd_shui="; typRec.rd_shui
Debug.Print "rd_sha="; typRec.rd_sha
Debug.Print "rd_state="; typRec.rd_state
Debug.Print "rd_qksm="; typRec.rd_qksm
End Function

[/code]

回复列表 (共3个回复)

沙发

多用Long,是32位CPU的原生数据,速度比16位的Integer快,而且范围更大,我看不出有什么理由用Integer.

CopyMemory的参数原型本来就是两个Any一个Long,为什么要新声明一个CopyMemoryAny呢?

Do While Not EOF(fNo) -> Do Until EOF(fNo), ^_^ 简短点 ^_^

至于LZ说的问题,这里面有个数据对齐的概念,[url]http://book.csdn.net/bookfiles/827/10082724877.shtml[/url]

Len(Rec_Data2) = 36, LenB(Rec_Data2) = 40
rd_time后面补1,rd_stuff后面补3

具体请看这里的讨论:[url]http://topic.csdn.net/t/20061107/08/5138172.html[/url]

板凳


看过LZ以往发的例子,感觉你总是习惯用一些很复杂的数据结构,这样很难抓住问题的关键点,也很难观察研究

来个简化的版本:

Option Explicit

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As Any, pSrc As Any, ByVal ByteLen As Long)

Private Type TestType
    a(2) As Byte
    b As Long
    c As Long
End Type

Private Const FILE_NAME As String = "E:\test.bin"


Private Sub Command1_Click()
    Dim tt As TestType
    tt.a(0) = 0
    tt.a(1) = 1
    tt.a(2) = 2
    tt.b = 5
    tt.c = 6
    PrintType "原值", tt
    
    Open FILE_NAME For Binary Access Write As #1
        Put #1, , tt
    Close #1
End Sub

Private Sub Command2_Click()
    Dim tt As TestType
    Dim byt() As Byte
    
    Open FILE_NAME For Binary Access Read As #1
        
        Get #1, , tt
        PrintType "直读结构", tt
        
        Seek #1, 1              '复位文件指针,重新读取
        ReDim byt(LOF(1) - 1)   '重新分配大小
        
        Get #1, , byt
        PrintBytes byt
        
        CopyMemory tt, byt(0), LOF(1)
        PrintType "用CopyMemory从数组复制", tt
                
    Close #1
End Sub

Private Sub PrintType(ByVal msg As String, tt As TestType)
    Debug.Print "-----" & msg & "-----"
    Debug.Print "a(0)", tt.a(0)
    Debug.Print "a(0)", tt.a(1)
    Debug.Print "a(0)", tt.a(2)
    Debug.Print "b", tt.b
    Debug.Print "c", tt.c
    Debug.Print ""
End Sub

Private Sub PrintBytes(byt() As Byte)
    Dim sBuffer As String
    sBuffer = "字节数组:"
    Dim i As Long
    For i = LBound(byt) To UBound(byt)
        sBuffer = sBuffer & " " & Format(Hex(byt(i)), "00")
    Next
    Debug.Print sBuffer
    Debug.Print ""
End Sub


结果:

-----原值-----
a(0)           0 
a(0)           1 
a(0)           2 
b              5 
c              6 

-----直读结构-----
a(0)           0 
a(0)           1 
a(0)           2 
b              5 
c              6 

字节数组: 00 01 02 05 00 00 00 06 00 00 00

-----用CopyMemory从数组复制-----
a(0)           0 
a(0)           1 
a(0)           2 
b              100663296 
c              0 

3 楼

我的分析:
vb put结构时,是会按顺序取出每一个成员分别写入的.get结构时就按顺序每一个成员分别赋值.
理由:文件大小为11,而不是内存中的12 ( Len(TestType) = 11, Len(TestType) = 12 ), put get结构前后值正确

00 01 02 05 00 00 00 06 00 00 00
00 / 01 / 02 / 05 00 00 00 / 06 00 00 00

LZ从这可以看出vb里的类型应该实现了Ixxx接口 ^_^
好像COM的东西都是可以枚举的,不然你写成DLL时,别的程序引用后,怎么能知道你的Type里的成员名字?

用CopyMemory从数组复制,实际上只复制了11个字节,而TestType内存中的长度为12.
byt(2)后应该空一个字节,然后再复制,但CopyMemory可不管你这些,这导致后面所有数据的"提前"了一个字节,而TestType最后一个字节没有被改动.

CopyMemory后TestType的内存值:

00 01 02 05 00 00 00 06 00 00 00 / 00

而VB在内存中读写成员时是要内存对齐的,因此每个值是这样划分的:

00 / 01 / 02 [05] / 00 00 00 06 / 00 00 00 00

[05]是不被访问的字节,现在明白了各个成员的值得原因了吧:
a(0)           0 
a(0)           1 
a(0)           2 
b              100663296 
c              0 

&H06000000 = 100663296 ^_^

OK,分析完毕.


我来回复

您尚未登录,请登录后再回复。点此登录或注册