N/Direct - The .NET Interoperability Resource Center 

FAQ

 

 

 

Most Valuable Professional

How do I call a function that returns a pointer to an array of structures?
Written: 6/19/2002 Updated: 6/20/2002

The general procedure is to retrieve the pointer, dereference it to retrieve the first structure member, increment the pointer by the structure size, retrieve the next member, and so on until you reach the end of the array. Finally, if the array memory needs to be freed by the caller, make sure you do that using the appropriate API.

Lets look at NetConnectionEnum as en example.

C#
[DllImport("netapi32.dll", CharSet=CharSet.Unicode)]
static extern uint NetConnectionEnum(string servername, string qualifier, uint level,
  out IntPtr bufptr, int prefmaxlen, out uint entriesread, out uint totalentries,
  ref IntPtr resume_handle);
[DllImport("netapi32.dll")]
static extern uint NetApiBufferFree(IntPtr Buffer);
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
struct CONNECTION_INFO_1
{
  public static readonly int SIZEOF_CI1 = Marshal.SizeOf( typeof(CONNECTION_INFO_1) );
  public uint coni1_id;
  public uint coni1_type;
  public uint coni1_num_opens;
  public uint coni1_num_users;
  public uint coni1_time;
  public string coni1_username;
  public string coni1_netname;
}
const int MAX_PREFERRED_LENGTH = -1;
// ...
uint n, total;
IntPtr pArray, resume = IntPtr.Zero;
uint r = NetConnectionEnum( null, "MyShare", 1, out pArray, MAX_PREFERRED_LENGTH,
                            out n, out total, ref resume );
if ( r != 0 ) {
  Console.WriteLine( "NetConnectionEnum failed: " + r );
  return;
}
Console.WriteLine( n + " connections" );
try {
  IntPtr pCurrent = pArray;
  CONNECTION_INFO_1 ci1;
  for ( uint i = 0; i < n; i++ ) {
    ci1 = (CONNECTION_INFO_1)Marshal.PtrToStructure( pCurrent, typeof(CONNECTION_INFO_1) );
    Console.WriteLine(
      "Id: {0}, Type: {1}, Files open: {2}, Users: {3}, Time: {4}, User: {5}, Netname: {6}",
      ci1.coni1_id, ci1.coni1_type, ci1.coni1_num_opens, ci1.coni1_num_users,
      ci1.coni1_time, ci1.coni1_username, ci1.coni1_netname );
    pCurrent = (IntPtr)((int)pCurrent + CONNECTION_INFO_1.SIZEOF_CI1);
  }
}
finally {
  if ( pArray != IntPtr.Zero ) NetApiBufferFree( pArray );
}  
    

VB.NET
Declare Unicode Function NetConnectionEnum Lib "netapi32.dll" (ByVal servername As String, _
  ByVal qualifier As String, ByVal level As Integer, ByRef bufptr As IntPtr, _
  ByVal prefmaxlen As Integer, ByRef entriesread As Integer, ByRef totalentries As Integer, _
  ByRef resume_handle As IntPtr) As Integer
Declare Function NetApiBufferFree Lib "netapi32.dll" (ByVal Buffer As IntPtr) As Integer
<StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)> _
Structure CONNECTION_INFO_1
  Public Shared ReadOnly SIZEOF_CI1 As Integer
  Shared Sub New()
    SIZEOF_CI1 = Marshal.SizeOf(GetType(CONNECTION_INFO_1))
  End Sub
  Public coni1_id As Integer
  Public coni1_type As Integer
  Public coni1_num_opens As Integer
  Public coni1_num_users As Integer
  Public coni1_time As Integer
  Public coni1_username As String
  Public coni1_netname As String
End Structure
Const MAX_PREFERRED_LENGTH As Integer = -1
' ...
Dim n, total As Integer
Dim pArray, hresume As IntPtr
Dim r As Integer = NetConnectionEnum(Nothing, "MyShare", 1, pArray, _
                                     MAX_PREFERRED_LENGTH, n, total, hresume)
If r <> 0 Then
  Console.WriteLine("NetConnectionEnum failed: " & r)
  Return
End If
Console.WriteLine(n & " connections")
Try
  Dim pCurrent As IntPtr = pArray
  Dim ci1 As CONNECTION_INFO_1
  Dim i As Integer
  For i = 0 To n - 1
    ci1 = CType(Marshal.PtrToStructure(pCurrent, GetType(CONNECTION_INFO_1)), CONNECTION_INFO_1)
    With ci1
      Console.WriteLine( _
        "Id: {0}, Type: {1}, Files open: {2}, Users: {3}, Time: {4}, User: {5}, Netname: {6}", _
        .coni1_id, .coni1_type, .coni1_num_opens, .coni1_num_users, .coni1_time,
        .coni1_username, .coni1_netname)
    End With
    pCurrent = New IntPtr(pCurrent.ToInt32() + CONNECTION_INFO_1.SIZEOF_CI1)
  Next
Finally
  If Not pArray.Equals(IntPtr.Zero) Then NetApiBufferFree(pArray)
End Try
    

Instead of retrieving member by member, you can read the entire array from the buffer with a call to RtlMoveMemory (a.k.a. CopyMemory). This reduces the number of transitions between managed and unmanaged code, and thereby improves performance. The code above can be modified like this

C#
[DllImport("kernel32.dll")]
static extern void RtlMoveMemory([Out] CONNECTION_INFO_1[] dest, IntPtr src, uint cb);
// ...
try {
  if ( n > 0 ) {
    CONNECTION_INFO_1[] ci1 = new CONNECTION_INFO_1[n];
    RtlMoveMemory( ci1, pArray, (uint)(n * CONNECTION_INFO_1.SIZEOF_CI1) );
    for ( int i = 0; i < n; i++ )
      Console.WriteLine(
        "Id: {0}, Type: {1}, Files open: {2}, Users: {3}, Time: {4}, User: {5}, Netname: {6}",
        ci1[i].coni1_id, ci1[i].coni1_type, ci1[i].coni1_num_opens, ci1[i].coni1_num_users,
        ci1[i].coni1_time, ci1[i].coni1_username, ci1[i].coni1_netname );
  }
}
finally {
  if ( pArray != IntPtr.Zero ) NetApiBufferFree( pArray );
}  
    

VB.NET
Declare Sub RtlMoveMemory Lib "kernel32.dll" (<Out> ByVal dest() As CONNECTION_INFO_1, _
  ByVal src As IntPtr, ByVal cb As Integer)
' ...
Try
  If n > 0 Then
    Dim ci1(n-1) As CONNECTION_INFO_1
    RtlMoveMemory(ci1, pArray, n * CONNECTION_INFO_1.SIZEOF_CI1)
    Dim i As Integer
    For i = 0 To n - 1
      With ci1(i)
        Console.WriteLine( _
          "Id: {0}, Type: {1}, Files open: {2}, Users: {3}, Time: {4}, User: {5}, Netname: {6}", _
          .coni1_id, .coni1_type, .coni1_num_opens, .coni1_num_users, .coni1_time,
          .coni1_username, .coni1_netname)
      End With
    Next
  End If
Finally
  If Not pArray.Equals(IntPtr.Zero) Then NetApiBufferFree(pArray)
End Try
    

Unfortunately, an IntPtr must be cast to an int (Integer) and then back to be incremented, since neither C# nor VB.NET supports addition of IntPtrs. This is not ideal, since the size of an IntPtr is platform dependent. This is one reason you might want to use real pointers instead in C#. Another reason is that it often gives you cleaner code. The downside is that you don't get the nice marshaling support provided by the runtime, and are limited to fairly simple structures. Below is a rewrite of the C# code that uses unsafe code instead.

C#
[DllImport("netapi32.dll", CharSet=CharSet.Unicode)]
unsafe static extern uint NetConnectionEnum(string servername, string qualifier, uint level,
  out CONNECTION_INFO_1* bufptr, int prefmaxlen, out uint entriesread, out uint totalentries,
  ref IntPtr resume_handle);
[DllImport("netapi32.dll")]
unsafe static extern uint NetApiBufferFree(void* Buffer);
unsafe struct CONNECTION_INFO_1
{
  public uint coni1_id;
  public uint coni1_type;
  public uint coni1_num_opens;
  public uint coni1_num_users;
  public uint coni1_time;
  public char* coni1_username;
  public char* coni1_netname;
}
const int MAX_PREFERRED_LENGTH = -1;
// ...
uint n, total;
IntPtr resume = IntPtr.Zero;
CONNECTION_INFO_1* pArray;
uint r = NetConnectionEnum( null, "MyShare", 1, out pArray, MAX_PREFERRED_LENGTH,
                            out n, out total, ref resume );
if ( r != 0 ) {
  Console.WriteLine( "NetConnectionEnum failed: " + r );
  return;
}
Console.WriteLine( n + " connections" );
try {
  CONNECTION_INFO_1* pci = pArray;
  for ( uint i = 0; i < n; i++, pci++ )
    Console.WriteLine(
      "Id: {0}, Type: {1}, Files open: {2}, Users: {3}, Time: {4}, User: {5}, Netname: {6}",
      pci->coni1_id, pci->coni1_type, pci->coni1_num_opens, pci->coni1_num_users,
      pci->coni1_time, new String(pci->coni1_username), new String(pci->coni1_netname) );
}
finally {
  if ( pArray != null ) NetApiBufferFree( pArray );
}