Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations derfloh on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

CopyMemory - Strange behavior 1

Status
Not open for further replies.

Unscruffed

Programmer
Apr 2, 2002
102
AU

VB6 on XP

Hi everyone. After trying for an hour to figure out why I couldn't create a bitmap from my byte data, I finally worked out that it was the CopyMemory API that was the problem.

Rather than go into lengthy code and descriptions, I wrote a little test code that you can try yourselves.

To test my theory, I created a new VB6 program with the following code in Form1:
Code:
Option Explicit

Private Type BITMAPFILEHEADER
    bfType As Integer
    bfSize As Long
    bfReserved1 As Integer
    bfReserved2 As Integer
    bfOffBits As Long
End Type

Private Type BITMAPINFOHEADER
    biSize As Long
    biWidth As Long
    biHeight As Long
    biPlanes As Integer
    biBitCount As Integer
    biCompression As Long
    biSizeImage As Long
    biXPelsPerMeter As Long
    biYPelsPerMeter As Long
    biClrUsed As Long
    biClrImportant As Long
End Type

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

Private mBmpFileHeader As BITMAPFILEHEADER
Private mBmpInfoHeader As BITMAPINFOHEADER

Private Sub Command1_Click()
    Dim bTmp3() As Byte
    Dim bTmp4() As Byte
    [green]' Fill mBmpFileHeader with easy to read bytes from h00 to h13...[/green]
    With mBmpFileHeader
        .bfType = &H100&
        .bfSize = &H5040302
        .bfReserved1 = &H706&
        .bfReserved2 = &H908&
        .bfOffBits = &H13121110
    End With
    [green]' Fill mBmpInfoHeader with easy to read bytes from h00 to h39...[/green]
    With mBmpInfoHeader
        .biSize = &H3020100
        .biWidth = &H7060504
        .biHeight = &H11100908
        .biPlanes = &H1312
        .biBitCount = &H1514
        .biCompression = &H19181716
        .biSizeImage = &H23222120
        .biXPelsPerMeter = &H27262524
        .biYPelsPerMeter = &H31302928
        .biClrUsed = &H35343332
        .biClrImportant = &H39383736
    End With
    [green]' Delete any existing test files...[/green]
    If Len(Dir$(App.Path & "\*.tmp")) Then
        Kill App.Path & "\*.tmp"
    End If
    [green]' Write the mBmpInfoHeader UDT directly to a file...[/green]
    Open App.Path & "\1_mBmpInfoHeader_UDT.tmp" For Binary As #1
        Put #1, , mBmpInfoHeader
    Close #1
    [green]' Write the mBmpFileHeader UDT directly to a file...[/green]
    Open App.Path & "\2_mBmpFileHeader_UDT.tmp" For Binary As #2
        Put #2, , mBmpFileHeader
    Close #2
    [green]' Copy the mBmpInfoHeader UDT to a byte array,
    ' and write the byte array to a file...[/green]
    ReDim bTmp3(Len(mBmpInfoHeader) - 1)
    CopyMemory bTmp3(0), ByVal mBmpInfoHeader, Len(mBmpInfoHeader)
    Open App.Path & "\3_mBmpInfoHeader_Bytes.tmp" For Binary As #3
        Put #3, , bTmp3
    Close #3
    [green]' Copy the mBmpFileHeader UDT to a byte array,
    ' and write the byte array to a file...[/green]
    ReDim bTmp4(Len(mBmpFileHeader) - 1)
    CopyMemory bTmp4(0), ByVal mBmpFileHeader, Len(mBmpFileHeader)
    Open App.Path & "\4_mBmpFileHeader_Bytes.tmp" For Binary As #4
        Put #4, , bTmp4
    Close #4
    [green]' Quit.[/green]
    Unload Me
End Sub

Run the program, click the command button, then open the 4 files in a Hex editor.

I'll use the following key to illustrate the obvious:
Code:
[green]00 00[/green] = Correct bytes
[red]00 00[/red] = Unknown bytes - otherwise known as WTF bytes ;)
[blue]00 00[/blue] = Bytes at wrong offset

1_mBmpInfoHeader_UDT.tmp
Code:
[green]00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39[/green]

2_mBmpFileHeader_UDT.tmp
Code:
[green]00 01 02 03 04 05 06 07 08 09 10 11 12 13[/green]

3_mBmpInfoHeader_Bytes.tmp
Code:
[green]00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39[/green]

4_mBmpFileHeader_Bytes.tmp
Code:
[green]00 01[/green] [red]00 00[/red] [blue]02 03 04 05 06 07 08 09 10 11[/blue]

One UDT copies fine, and the other doesn't. Anyone have any idea in the world about what's going on here?

Don't worry, your not crazy. If you think that what's happening here is impossible you're not alone. Try it yourself and see if you get the same results.

Cheers.


Heaven doesn't want me, and Hell's afraid I'll take over!
 
Did you try calling it like;

CopyMemory ByVal VarPtr(bTmp4(0)), ByVal VarPtr(mBmpFileHeader), Len(mBmpFileHeader)




 
However I think what you are seeing is byte padding in the udt as it is stored in memory. When you copy it to the byte array from memory you include the padding. When you write the udt direct to disk however vb automatically strips out the padding.
For some discussion see thread222-693110
 

Thanks Hugh, you are correct about the alignment. The thread you pointed out explains the problem.

In relation to ByRef, ByVal and VarPtr, I tested each of the following and all had the same effect when copying from a UDT to a byte array:
Code:
CopyMemory Dest, UDT, Length
CopyMemory Dest, [blue]ByVal[/blue] UDT, Length
CopyMemory Dest, [blue]VarPtr([/blue]UDT[blue])[/blue], Length
CopyMemory Dest, [blue]ByVal VarPtr([/blue]UDT[blue])[/blue], Length

The solution is therefore to copy UDT items which are not memory aligned seperately from items which are memory aligned.
For example, with the BITMAPFILEHEADER structure, I only need to worry about the first 2 values:
Code:
bfType As Integer   <= 16bit aligned in both UDT and memory.

bfSize As Long      <= 16bit aligned in UDT, 32bit aligned in memory.
                       This is why we get the two [red]00 00[/red] padding
                       bytes before it.
In memory, the remaining items are all correctly aligned because of the alignment of bfSize.
This means bfType can be copied in one call, and the remaining items copied seperately as a "chunk" as follows:
Code:
Option Explicit

Private Type BITMAPFILEHEADER
    bfType As Integer
    bfSize As Long
    bfReserved1 As Integer
    bfReserved2 As Integer
    bfOffBits As Long
End Type

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

Private Sub Command1_Click()
	Dim bTmp() As Byte

    [green]' Fill mBmpFileHeader with easy to read bytes from h00 to h13...[/green]
    With mBmpFileHeader
        .bfType = &H100&
        .bfSize = &H5040302
        .bfReserved1 = &H706&
        .bfReserved2 = &H908&
        .bfOffBits = &H13121110
    End With

    [green]' Delete any existing test files...[/green]
    If Len(Dir$(App.Path & "\*.tmp")) Then
        Kill App.Path & "\*.tmp"
    End If

	ReDim bTmp(Len(mBmpFileHeader) - 1)
	[green]' Copy just the first item.
	' This is an Integer, so only copy 2 bytes...[/green]
	CopyMemory bTmp(0), mBmpFileHeader, 2
	[green]' Copy the rest of the UDT.
	' The source location is the pointer of the second item (bfSize).
	' The number of bytes to copy is Len(mBmpFileHeader) minus the
	' length of bfSize which is 2...[/green]
	CopyMemory bTmp(2), mBmpFileHeader.bfSize, Len(mBmpFileHeader) - 2
	[green]' Write the byte array to a file...[/green]
	Open App.Path & "\mBmpFileHeader_Corrected.tmp" For Binary As #1
		Put #1, , bTmp
	Close #1

    [green]' Quit...[/green]
    Unload Me
End Sub

This gives the following result in the file:
Code:
[green]00 01 02 03 04 05 06 07 08 09 10 11 12 13[/green]
...which means that the UDT copied correctly to the byte array.

This process will become much more complex with larger UDT's where items drop in and out of "memory matched alignment", but you should be able to avoid copying each UDT item individually and break it up into "chunks".

Thanks again to Hugh.


Heaven doesn't want me, and Hell's afraid I'll take over!
 
Good. Be aware of the difference between Len(udt) and LenB(udt), the latter includes the padding chars; because that should affect the DIM used to setup the byte array.
 

Good tip Hugh. I didn't fully understand the difference between Len and LenB until I came across this UDT problem. After reading your 2nd post above and doing some testing, the difference became clear.


Heaven doesn't want me, and Hell's afraid I'll take over!
 
Turns out that Hugh was correct on all counts. While sending the UDT ByRef worked in the IDE, it fails in the compiled executable. CopyMemory needs a pointer so the correct syntax is:
Code:
CopyMemory Dest, [blue]ByVal VarPtr([/blue]UDT[blue])[/blue], Length

Same applies for individual items or chuncks - provide the pointer of the first item to copy:
Code:
CopyMemory Dest, [blue]ByVal VarPtr([/blue]UDT.Item2[blue])[/blue], Length

Cheers.


Heaven doesn't want me, and Hell's afraid I'll take over!
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top