Introduction
The Marshal
class has many members, but most of them are utilities that aid in interoperation with COM code. I will explain only those that are mostly used with managed code.
With .NET, Visual Basic has a lot of power to deal with Windows systems at a low level, and work with unmanaged code from external libraries. That power is found in three new tools: IntPtr
, .NET's platform-dependent representation of a memory address; GCHandle
, which helps you pin and retrieve the address of data in the managed memory heap; the Marshal
class, the one-stop shop for all your memory allocation, cleanup, and manipulation needs.
If you decide to work with direct memory manipulation in VB.NET, the first thing you need to understand is the IntPtr
type. IntPtr
is a structure that represents both addresses and handles (most handles are pointers to Windows pointers). IntPtr
instances are also platform-dependent (or independent, depending on your point of view). On a 32-bit system, IntPtr
is 32-bit, while on a 64-bit system, IntPtr
is 64-bit. The benefit is that you don't need to change or recompile your code for both platforms. Any function within the .NET Framework that exposes a way to work with addresses and handles, uses the IntPtr
type. Such functions are marshaled to unmanaged code simply as the internal address number, meaning you can pass a variable of IntPtr
type to any unmanaged code that expects a pointer. So the good news is that, although you can't use this in VB.NET:
Dim MyPointer As Void *
this works brilliantly in VB.NET as:
Dim MyPointer As IntPtr
Note that there was no IntPtr
type in beta 1 � instead, functions that worked with pointers and handles used the Integer
type. The IntPtr
type has a ToInt32
method that converts the address to an Integer
, but that can and will cause an Overflow
exception on a 64-bit system. IntPtr
also has a ToInt64
method, but you'll have to keep track of the platform if you want to do these conversions.
StrPtr() and VarPtr() in VB.NET
Both undocumented VB 6 functions return address of a variable. Same functionality can obtained in VB.NET by GCHANDLE
class.
Let's examine how you place the address of a variable into an IntPtr
. You use the GCHandle
class which has an AddrOfPinnedObject
method that returns an IntPtr
for a variable. You must "pin" the data before you can get this address. This prevents the garbage collector from moving the data inadvertently while you're referring to the original address. This code pins a variable, displays its address on the console window, and frees the handle:
Dim MyString As String = �Adnan Samuel�
Dim gh As GCHandle = GCHandle.Alloc (MyString, GCHandleType.Pinned)
Dim AddrOfMyString As IntPtr = gh.AddrOfPinnedObject()
Console.WriteLine (AddrOfMyString.ToString())
gh.Free()
Explanation
AllocHGlobal and AllocCoTaskMem
AllocHGlobal
, allocates memory on the native heap by using the GlobalAlloc
function internally; and AllocCoTaskMem
, which is similar but instead uses the COM memory manager (CoTaskMemAlloc
). Both functions have one parameter, the number of bytes to allocate. Both functions return an IntPtr
, the base address of the newly allocated buffer. To free the memory, you use the FreeHGlobal
or FreeCoTaskMem
methods, depending on which allocation method you used. Each of these functions has one parameter, the address to the newly allocated buffer returned by the allocation functions.
Use the WriteByte
, WriteInt16
, WriteInt32
, or WriteInt64
methods to write simple numeric data to an unmanaged buffer. Each of the functions takes as parameters the destination address of the write operation and the numeric value you want to write. These functions are also overloaded to allow an optional third parameter indicating an offset from the address provided, which can be useful if you're attempting to fill memory with arrays of elements or fields of a structure, for instance:
Dim MyPointer As IntPtr = Marshal.AllocHGlobal(4)
Marshal.WriteInt32(MyPointer, 255)
Marshal.FreeHGlobal(MyPointer)
You can also use the address provided by unmanaged code instead of allocating your own:
Dim MyPointer As IntPtr = New IntPtr([insert integer address _
from unmanaged code here])
Marshal.WriteInt32(MyPointer, 255)
The reverse is also possible. You can read simple numeric data from an IntPtr
address using the ReadByte
, ReadInt16
, ReadInt32
, and ReadInt64
methods:
Dim MyInteger As Integer = Marshal.ReadInt32(MyPointer)
String Functions
Reading and writing strings is similar to reading and writing simple numeric data, with one minor exception: you don't allocate memory first, then write a string to it. Instead, the act of creating a string in unmanaged memory allocates the space and returns the string's address. There are seven write methods: StringToBSTR
, StringToCoTaskMemAnsi
, StringToCoTaskMemUni
, StringToCoTaskMemAuto
, StringToHGlobalAnsi
, StringToHGlobalUni
, and StringToHGlobalAuto
. The StringToCoTaskMemxxx
functions write string data to COM allocated memory, while StringToHGlobalxxx
functions write to the native unmanaged heap. Functions ending in Ansi
write single-byte ANSI strings. Functions ending in Uni
write double-byte Unicode strings. The functions ending in Auto
write ANSI or Unicode strings, depending on the operating system: ANSI strings on Windows 98 and ME, Unicode strings on NT-based platforms (Windows NT 4.0, 2000, and XP). StringToBSTR
writes an Automation BSTR
, which is analogous to using the SysAllocString
function. Each of these functions accepts a string as an input argument and returns a pointer to the resulting string:
Dim MyStrPointer As IntPtr = Marshal.StringToHGlobalAuto("Hello World")
Four read methods � PtrToStringAnsi
, PtrToStringUni
, PtrToStringAuto
, and PtrToStringBSTR
� read the data at a given address and create a managed String
object containing a copy of the characters. Use PtrToStringAnsi
if the unmanaged string is ANSI, PtrToStringUni
if the unmanaged string is Unicode, or PtrToStringBSTR
if the unmanaged string is of BSTR
type. PtrToStringAuto
assumes the unmanaged string is ANSI on a Windows 98 or ME system, and Unicode on a Windows NT 4.0, 2000, or XP platform. PtrToStringAuto
actually calls PtrToStringAnsi
or PtrToStringUni
, depending on the operating system. Each function is also overloaded to accept an optional number of characters to copy. If you don't provide the number of characters, the function looks for a terminating null character:
Dim MyString As String = Marshal.PtrToStringAuto(MyPointer)
Dim MyString As String = Marshal.PtrToStringAuto (MyPointer, 5)
To free the unmanaged memory buffer holding a string, you call either the FreeHGlobal
or FreeCoTaskMem
member. To free a BSTR
created with StringToBSTR
, you call FreeBSTR
, which in turn calls the FreeSysString
function.
StructureToPtr and PtrToStructure
You write a Structure (user-defined type) to unmanaged memory by using the StructureToPtr
method. This method requires you to have a memory buffer allocated ahead of time. It takes three parameters: the Structure you wish to write, the address (IntPtr
) of the memory buffer, and a delete flag. Setting the delete flag to True
wipes and frees any existing data from the buffer. This is important, because you can cause memory leaks by failing to delete the existing buffer space. For example, if one of the fields of the structure is a reference to another structure or string, the data being referenced by the field won't be freed if the parameter is set to False
. Also note that Structures are copied to unmanaged memory using specific formatting (which you can control optionally), and the unmanaged copy might not look exactly like the managed representation. Use the SizeOf
method to determine the number of bytes required for the buffer:
Dim MyVariable As Point
MyVariable.X = 100
MyVariable.Y = 250
Dim MyPointer As IntPtr = Marshal.AllocHGlobal (Marshal.SizeOf(MyVariable))
Marshal.StructureToPtr(MyVariable, MyPointer, False)
Use the PtrToStructure
method to reverse the process and read a structure from unmanaged memory. You can use this method either as a Function
that returns a copy of the structure based on Type
, or as a Sub
that fills a structure parameter. Note that PtrToStructure
returns an Object
type reference, and with Option Strict On
, you must cast to the structure type (by using CType
, for example):
Dim MyPoint As Point
MyPoint = CType(Marshal.PtrToStructure (MyPointer, GetType(Point)), Point)
Copy Method
Reading and writing array data is especially valuable when you need to stream binary data. The Copy
method does both reads and writes, depending on the parameters you pass to it. If you want to write the data, you need to allocate some buffer space first, just as you would with strings. Next, you call the Copy
method, and pass the array itself, the index in the array of the element you want to start the copy with, the destination address (IntPtr
resulting from the allocation), and the size of the buffer:
Dim MyData(255) As Byte
Dim BufferAddress As IntPtr = Marshal.AllocHGlobal(256)
Marshal.Copy(MyData, 0, BufferAddress, 256)
To read an array from unmanaged memory, call the Copy
method, and pass the address of the buffer (IntPtr
), the array you want to fill with the data from the buffer, the index of the array you want to start copying into, and the size of the data you want to copy:
Dim MyData(255) As Byte
Marshal.Copy(BufferAddress, MyData, 0, 256)
The Marshal
class has many more methods, but most of them are utilities that aid in interoperation with COM code.
Conclusion
These functions are quite helpful in marshaling managed data as well as a good replacement for unmanaged memory functions such as CopyMemory
also known as RtlMoveMomry
.
I am always willing to help, so if you have any questions, or suggestions about my article, feel free to email me. You can also reach me on MSN Messenger with screen name �Maxima�.