Marshaling Structures






3.29/5 (14 votes)
Dec 4, 2004
3 min read

148182
The article shows marshling of simple and complex structures and how to pass them in function parameters.
Introduction
This tutorial will show all about structures that are:
- Marshaling simple and complex structures
- Passing Structures as Input/Output Parameters in functions
Background
You can combine data items of different types to create a structure. A structure associates one or more members with each other and with the structure itself. When you declare a structure, it becomes a composite data type, and you can declare variables of that type. A structure is a generalization of the user-defined type (UDT) supported by previous versions of Visual Basic. In addition to fields, structures can expose properties, methods, and events. A structure can implement one or more interfaces, and you can declare individual accessibility for each field.
Structures are useful when you want a single variable to hold several related pieces of information. For example, you might want to keep an employee's name, telephone extension, and salary together. You could use several variables for this information, or you could define a structure and use it for a single employee variable. The advantage of the structure becomes apparent when you have many employees and therefore many instances of the variable. The members can be of any data type, but you must include at least one nonshared variable or an event. You can also define Function procedures, properties, and events in a structure. You can define one property as the default property, provided it takes at least one argument. You can handle an event with a Shared Sub
procedure.
You can specify the accessibility of a structure using the Public
, Protected
, Friend
, or Private
keyword, or you can let it default to Public
. You must declare every member and specify accessibility for it. If you use the Dim
statement without any keywords, the accessibility defaults to Public
.
Furthermore, if using them in unmanaged code then you must specify the Layout the structure will use. I will use System.Runtime.InteropServices.Marshal
class.
For information about Marshal
class, see my article ‘Marshal Class Explained’ on this web site.
-
Marshaling Structures
Simple Structure
Let us consider a simple structure. We will copy its contents to a byte array as follow:
Imports System.Runtime.InteropServices Imports System.Text
From Structure to byte array:
Private Structure Test Dim Var1 As Short Dim Var2 As Short End Structure ‘Start here Dim Tst As Test ' Stuctuer variable Dim ByteArray() As Byte ' initialize Structure (Dummmy Values) Tst.Var1 = 911 Tst.Var2 = 7 Dim Ptr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Tst)) ReDim ByteArray(Marshal.SizeOf(Tst) - 1) 'now copy strcutre to Ptr pointer Marshal.StructureToPtr(Tst, Ptr, False) Marshal.Copy(Ptr, ByteArray, 0, Marshal.SizeOf(Tst)) ‘now use ByteArray ready for use Marshal.FreeHGlobal(Ptr)
Now reverse of above is as simple as coded below:
From Byte array to Structure
Let us consider a byte array. We will copy its content to a simple Structure Test as coded below:
Private Structure Test Dim Var1 As Short Dim Var2 As Short End Structure ' Function to Create structure must be called first Private Function BulidStr(ByVal Buff() As Byte, _ ByVal MyType As System.Type) As Object Dim MyGC As GCHandle = GCHandle.Alloc(Buff, GCHandleType.Pinned) ‘Marshals data from an unmanaged block of memory 'to a newly allocated managed object of the specified type. Dim Obj As Object = _ Marshal.PtrToStructure(MyGC.AddrOfPinnedObject, MyType) Return Obj ‘Free GChandle to avoid memory leaks MyGC.Free() End Function ‘Start here Dim Str As String = "Adnan Samuel" Dim Tst As Test ‘ Stuctuer variable Dim ByteArray () As Byte, i As Int16 ‘convert to bytes ByteArray = Encoding.Default.GetBytes(Str)’ Convert string to bytes ‘Call BulidStr method to create structure and 'copy byte array data to structure Tst = BulidStr(ByteArray, Tst.GetType) ‘Now use it and print it Debug.WriteLine(Tst.Var1.ToString) Debug.WriteLine(Tst.Var2.ToString)
Complex Structures
Let us consider following types of structures:
- Embedded structure.
- Structure that contain a pointer (which point another structure).
Let us consider a structure (embedded) that contains a reference to other structures. We will copy its content to a byte array as:
From Structure to byte array
Private Structure SAFEARRAYBOUND Dim cElements As Integer Dim lLbound As Integer End Structure Private Structure SAFEARRAY2D Dim cDims As Integer Dim fFeatures As Integer Dim cbElements As Integer Dim cLocks As Integer Dim pvData As Integer ‘reference to above structure Dim Bounds() As SAFEARRAYBOUND End Structure ' start code here Dim SA As SAFEARRAY2D Dim StrtoBy() As Byte 'check with integer array for better result 'Must initialize array to hold data ReDim SA.Bounds(1) 'first initalize your stucture here (I just put dummy values ) SA.cDims = 11 : SA.fFeatures = 12 : SA.cbElements = 9 : SA.cLocks = 12 SA.pvData = 6 : SA.Bounds(0).cElements = 15 : SA.Bounds(1).lLbound = 16 ' first get size of SA in pointer Dim ptr As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(SA)) 'Must initialize before use ReDim StrtoBy(Marshal.SizeOf(SA) - 1) Marshal.Copy(ptr, StrtoBy, 0, Marshal.SizeOf(SA)) Marshal.FreeCoTaskMem(ptr) 'now use byte array as you like
Now reverse of this (byte to structure) is easy however you have to manually put elements in Bound array as shown below:
From Byte array to Structure
Private Structure SAFEARRAYBOUND Dim cElements As Integer Dim lLbound As Integer End Structure Private Structure SAFEARRAY2D Dim cDims As Byte Dim fFeatures As Byte Dim cbElements As Byte Dim cLocks As Byte Dim pvData As Byte End Structure ' start code here Dim Str As String = "Adnan Samuel (adahmed911@hotmail.com)" Dim Tst As SAFEARRAY2D ' Structure variable Dim Bounds() As SAFEARRAYBOUND ‘define array manually Dim ByteArray() As Byte, i As Int16 'Convert to bytes ' Convert string to bytes ByteArray = Encoding.Default.GetBytes(Str) 'Get array address and copy it to structure Dim MyGC As GCHandle = _ GCHandle.Alloc(ByteArray, GCHandleType.Pinned) ‘This will copy byte array to SAFEARRAY2D elements only (0 to 4) Tst = CType(Marshal.PtrToStructure(MyGC.AddrOfPinnedObject, _ GetType(SAFEARRAY2D)), SAFEARRAY2D) MyGC.Free() ‘now time for SAFEARRAYBOUND Structure members (5 & 6). ReDim Bounds(1) ‘Show array values just to check For i = 0 To 6 Debug.WriteLine(ByteArray(i)) Next 'copy remaining bits manually in bound array Bounds(0).cElements = ByteArray(5) Bounds(1).lLbound = ByteArray(6) 'now use both structure ready fro use
Let us consider a structure that contains a pointer to a second structure as a member. I will show you only how to handle it.
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Ansi)> _ Public Structure Emp1 Public first As String Public last As String End Structure <StructLayout(LayoutKind.Sequential)> _ Public Structure Emp2 Public Employee As IntPtr Public age As Integer End Structure ‘Start code here Dim EmployeeName As Emp1 EmployeeName.first = "Adnan" EmployeeName.last = "Samuel" Dim EmpAll As Emp2 EmpAll.age = 26 Dim buffer As IntPtr = Marshal.AllocCoTaskMem( _ Marshal.SizeOf(EmployeeName)) Marshal.StructureToPtr(EmployeeName, buffer, False) EmpAll.Employee = buffer Debug.WriteLine(ControlChars.CrLf & "Employee before call:") Debug.WriteLine("first = " & EmployeeName.first & vbCrLf & "last = " _ & EmployeeName.last & vbCrLf & "age =" & EmpAll.age) 'Now pass the Emp2 structure into unmanaged code (e.g. PinvokeLib.dll) _ 'as byRef then see the result ‘------ Dim EmpRes As Emp1 = CType(Marshal.PtrToStructure(EmpAll.Employee, _ GetType(Emp1)), Emp1) Marshal.FreeCoTaskMem(buffer) Debug.WriteLine(ControlChars.CrLf & "Employee after call:") Debug.WriteLine("first = " & EmpRes.first & vbCrLf & "last = " & EmpRes.last & vbCrLf & "age =" & EmpAll.age)
-
Passing Structures as Parameters
- Input parameters
- Input/Output parameters
Let us consider a simple structure we pass it as ByVal in function:
<StructLayout(LayoutKind.Sequential)> _ Public Structure SECURITY_ATTRIBUTES Public nLength As Integer Public lpSecurityDescriptor As Integer Public bInheritHandle As Integer End Structure Public Declare Function CreateDirectory Lib "kernel32" _ Alias "CreateDirectoryA" (ByVal lpPathName As String, _ ByVal lpSecurityAttributes As SECURITY_ATTRIBUTES) As Boolean '' start code here Dim security As New SECURITY_ATTRIBUTES If CreateDirectory("c:\Adnan", security) Then MsgBox("Directory created.", MsgBoxStyle.Information) Else MsgBox("Directory not created.", MsgBoxStyle.Information) End If
Let us consider an example in which we can pass structure as Input/Output parameter or simply
ByRef
parameter in a function.- Start a new project a name it what you want then you have a form in a project.
- Now add a class name
OSVersionInfo
and add code below:Imports System.Runtime.InteropServices <StructLayout(LayoutKind.Sequential)> _ Public Class OSVersionInfo Public OSVersionInfoSize As Integer Public majorVersion As Integer Public minorVersion As Integer Public buildNumber As Integer Public platformId As Integer <MarshalAs(UnmanagedType.ByValTStr, SizeConst:=128)> _ Public versionString As String Public ServicePackMajor As Integer Public ServicePackMinor As Integer End Class
- Now add another class and name it API and add code below:
Imports System.Runtime.InteropServices Imports System.Runtime.InteropServices Public Class APIDeclare Ansi Function GetVersionEx _ Lib "kernel32" Alias "GetVersionExA" _ (<[In](), Out()> ByVal osvi As OSVersionInfo) As Boolean ‘Now you have option to use above function with I/O parameters or use 'below with ByRef but use one at a time ' Declare Ansi Function GetVersionEx2 Lib "kernel32" Alias _ ' "GetVersionExA" (ByRef osvi As OSVersionInfo) As Boolean End Class
- Now add a button to Form1 and add the code.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal _ e As System.EventArgs) Handles Button1.Click Dim versionInfo As New OSVersionInfo versionInfo.OSVersionInfoSize = Marshal.SizeOf(versionInfo) GetVersionEx(versionInfo) Debug.WriteLine("Major Version is: " & versionInfo.majorVersion) Debug.WriteLine("Minor Version is: " _ & versionInfo.minorVersion.ToString) Debug.WriteLine("Bulid Number is: " _ & versionInfo.buildNumber.ToString()) Debug.WriteLine("Service Pack: " _ & versionInfo.ServicePackMajor.ToString()) End Sub
I am always willing to help, so if you have any questions, suggestions about my article, feel free to email me. You can also reach me on msn messenger with screen name “Maxima”.