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
Dim ByteArray() As Byte
Tst.Var1 = 911
Tst.Var2 = 7
Dim Ptr As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(Tst))
ReDim ByteArray(Marshal.SizeOf(Tst) - 1)
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
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
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
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
Dim SA As SAFEARRAY2D
Dim StrtoBy() As Byte
ReDim SA.Bounds(1)
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
Dim ptr As IntPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(SA))
ReDim StrtoBy(Marshal.SizeOf(SA) - 1)
Marshal.Copy(ptr, StrtoBy, 0, Marshal.SizeOf(SA))
Marshal.FreeCoTaskMem(ptr)
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
Dim Str As String = "Adnan Samuel (adahmed911@hotmail.com)"
Dim Tst As SAFEARRAY2D
Dim Bounds() As SAFEARRAYBOUND ‘define array manually
Dim ByteArray() As Byte, i As Int16
ByteArray = Encoding.Default.GetBytes(Str)
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
Bounds(0).cElements = ByteArray(5)
Bounds(1).lLbound = ByteArray(6)
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)
‘------
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
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
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”.
I am software engineer and working from last four years in the information technology field. I love to do intresting tasks. I love to see diffrent places and listening muzik.