Introduction
This tutorial will show you how to use the WIndows API function CopyMemory
to populate Class data using a string as input.
In my line of work, I have to deal with several file formats that contain fixed length records. This is probably the fastest, most straight-forward way to read those files because the data is a 'known size'. CopyMemory
is ideal for this because parsing data is virtually eliminated.
I'm going to walk you through the entire process and explain as I go, starting with a new ConsoleApplication
. You should follow the article using .NET on your own machine. I try to avoid straying from the topic at hand, so when you get to the bolded sections of code, just copy and paste them into your program.
Background
CopyMemory
is a function that will copy the contents of one block of memory to a different block of memory without regard to the data type that is stored there. This results in an ultra fast copy of data, especially for objects like Structures, Classes and Strings.
Initializing Classes using CopyMemory
Create the Project
- Open up Visual Studio.NET and click the 'New Project' button.
- Click Visual Basic Projects, from the Project Types list.
- Click Console Application, from the Templates list.
- Type
CopyMemorySample
in the Name box. - Click the OK button.
- The project template will open and you will see the module 'Module1' in the Code Viewer. It is normally good to give descriptive names to all code components, but for this tutorial we will leave Module1 as is.
Here is what your module should have in it right now:
Module Module1
Sub Main()
End Sub
End Module
Add Windows API declarations
NOTE: We put the declarations into a class because it makes our code much easier to understand. It is not necessarily required, but you will see that it works nicely.
- In the Solution Explorer, right click on the CopyMemorySample Project. (Just under the Solution in bold.)
- Select the 'Add' menu.
- Select 'Add Class' from the Add menu.
- Type
WINAPI
in the Name box. - Click the Open button.
- The class
WINAPI
will be added and you will see the class in the Code Viewer as follows:
Public Class WINAPI
End Class
- Copy or type in the following code so your WINAPI Class looks like this:
Public Class WINAPI
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal pDst As IntPtr, _
ByVal pSrc As String, _
ByVal ByteLen As Long)
End Class
Add the CClassTest Class
- In the Solution Explorer, right click on the
CopyMemorySample
Project. (Just under the Solution in bold.) - Select the 'Add' menu.
- Select 'Add Class' from the Add menu.
- Type
CClassTest
in the Name box. - Click the Open button.
- The class
CClassTest
will be added and you will see the class in the Code Viewer as follows:
Public Class CClassTest
End Class
- Now, type or copy in the following Class declaration:
Imports System.Runtime.InteropServices
<StructLayout(LayoutKind.Sequential)> _
Public Class CClassTest
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=10)> Public ID As Char()
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=20)> Public Name As Char()
<MarshalAs(UnmanagedType.ByValArray, SizeConst:=30)> Private m_chAddress As Char()
Public Property Address() As Char()
Get
Return m_chAddress
End Get
Set(ByVal Value As Char())
m_chAddress = Value
End Set
End Property
End Class
- Switch the Code View back to Module1 by clicking the Module1 tab at the top of the Code View window.
- Add the function definition below to Module1:
Private Function CopyStringToClass(ByVal Source As String, ByVal Target As Object) As Object
If Target Is Nothing Then Return Nothing
Dim API As WINAPI = New WINAPI()
Dim p_objTarget As IntPtr
Try
p_objTarget = Marshal.AllocHGlobal(Marshal.SizeOf(Target))
API.CopyMemory(p_objTarget, Source, Marshal.SizeOf(Target))
Marshal.PtrToStructure(p_objTarget, Target)
Marshal.FreeHGlobal(p_objTarget)
Catch ex As System.OutOfMemoryException
CoughUpCookies(ex)
Catch e As Exception
CoughUpCookies(e)
End Try
API = Nothing
Return Target
End Function
What the Code Does
- The Function
CopyStringToClass
takes a String and an Object instance as parameters. - Declare a variable of type '
WINAPI
' and initialize it using the New keyword. - Declare a variable of type '
IntPtr
' to hold a memory address.
- Using a
Try..Catch
block, trap any exceptions (specifically OutOfMemory
exceptions). - Using the
System.Runtime.InteropServices.Marshal
class, allocate a 'Target' sized block of memory on the Global Heap
and assign the returned pointer to that memory to p_objTarget (which is an IntPtr object). - Use the member function
CopyMemory
of the WINAPI
object to copy the string into the memory
that we set aside for clsTest
(the 'Target' sized block of heap memory). - Use the
System.Runtime.InteropServices.Marshal.PtrToStructure
method to copy the data from the heap memory that is pointed to
by p_objTarget
into the Target object. - Call the
System.Runtime.InteropServices.Marshal.FreeHGlobal
method to free the block of
memory that we allocated on the heap for the copy. If this is not done, it will result in a memory leak. - Set the API object (instance of
WINAPI
) to Nothing (null). - Return the populated class to the caller.
Finishing Up:
Now add the following code listed between the 'ADD THIS CODE' comments to Module1. Module1 is complete (for now):
Imports System.Runtime.InteropServices
Module Module1
Sub Main()
Dim strSource As String = "10785236ABJohn F. Kennedy Jr. 1234 Pennsylvania Blvd. "
Dim clsTemp As CClassTest = New CClassTest()
clsTemp = CopyStringToClass(strSource, clsTemp)
PrintResultsOfCopy(strSource, clsTemp)
End Sub
Private Function CopyStringToClass(ByVal Source As String, ByVal Target As Object) As Object
If Target Is Nothing Then Return Nothing
Dim API As WINAPI = New WINAPI()
Dim p_objTarget As IntPtr
Try
p_objTarget = Marshal.AllocHGlobal(Marshal.SizeOf(Target))
API.CopyMemory(p_objTarget, Source, Marshal.SizeOf(Target))
Marshal.PtrToStructure(p_objTarget, Target)
Marshal.FreeHGlobal(p_objTarget)
Catch ex As System.OutOfMemoryException
CoughUpCookies(ex)
Catch e As Exception
CoughUpCookies(e)
End Try
API = Nothing
Return Target
End Function
Private Sub PromptForEnter()
Console.WriteLine()
Console.Write("Press <Enter> to continue...")
Console.ReadLine()
End Sub
Private Sub PrintResultsOfCopy(ByVal Source As String, ByVal Value As CClassTest)
Console.WriteLine(ControlChars.CrLf & "Results of CopyMemory for CCLassTest:")
Console.WriteLine("Source String = " & Source)
Console.WriteLine("CClassTest.ID = " & CType(Value.ID, String))
Console.WriteLine("CClassTest.Name = " & CType(Value.Name, String))
Console.WriteLine("CClassTest.Address = " & CType(Value.Address, String))
Call PromptForEnter()
End Sub
Private Sub CoughUpCookies(ByVal e As System.Exception)
Console.WriteLine()
Console.WriteLine("Exception Caught: " & e.Message)
Console.WriteLine()
Call PromptForEnter()
End Sub
Private Sub CoughUpCookies(ByVal e As System.OutOfMemoryException)
Console.WriteLine()
Console.WriteLine("Exception Caught: " & e.Message)
Console.WriteLine()
Call PromptForEnter()
End Sub
End Module
Running the Sample
- In the Visual Studio.NET IDE, press the <F5> key.
- A Console window will appear and show you that our code works if you entered it exactly as shown above.
- When you are done, press <Enter> to close the program.
Points of Interest
ISSUE: You should note that, although the MSDN documentation for MarshalAs(UnManagedType.ByValTStr)
shows that this can be used to marshal strings for Classes and Structures, it does not work for Classes and Structures. One character is lost for every string in your class or structure (Standard Strings work fine). This is why I use Char Arrays in my Class and Structure examples
History
- Initial Article Date: 02/26/2003
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.