|
|
|
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 );
}
|
|