1
votes

I'm trying to marshal the MIB_TCPTABLE_OWNER_MODULE struct from a P/Invoked' call into GetExtendedTcpTable, defined in iphlpapi.dll.

My P/Invoke signature is defined as this:

[DllImport("iphlpapi.dll", SetLastError = true)]
private static extern uint GetExtendedTcpTable(IntPtr pTcpTable, ref int dwSize, bool sort, int ipVersion, int tableClass, int reserved);

According to the documentation on MSDN (and looking through the header files), this should set the pTcpTable parameter to the address of a MIB_TCPTABLE_OWNER_MODULE structure, who has a member which is an array of MIB_TCPROW_OWNER_MODULE structures. From tcpmib.h:

typedef struct _MIB_TCPTABLE_OWNER_MODULE
{
    DWORD                   dwNumEntries;
    MIB_TCPROW_OWNER_MODULE table[ANY_SIZE];
} MIB_TCPTABLE_OWNER_MODULE, *PMIB_TCPTABLE_OWNER_MODULE;

ANY_SIZE is defined to be 1.

Here is my problem; I've defined the MIB_TCPTABLE_OWNER_MODULE and MIB_TCPROW_OWNER_MODULE structs in C# like so:

[StructLayoutAttribute(LayoutKind.Sequential)]
public struct MIB_TCPTABLE_OWNER_MODULE
{
    public uint dwNumEntries;
    MIB_TCPROW_OWNER_MODULE table;
}

[StructLayoutAttribute(LayoutKind.Sequential)]
public struct MIB_TCPROW_OWNER_MODULE
{
    public uint dwState;
    public uint dwLocalAddr;
    public uint dwLocalPort;
    public uint dwRemoteAddr;
    public uint dwRemotePort;
    public uint dwOwningPid;
    public ulong liCreateTimestamp;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = TCPIP_OWNING_MODULE_SIZE)]
    public ulong[] OwningModuleInfo;
}

Since I won't know the size of the returned MIB_TCPTABLE_OWNER_MODULE's table member at declaration, my plan was to increment the IntPtr and use Marshal.PtrToStructure to extract each array member from the table member.

The call to Marshal.PtrToStructure returns (no memory violation exceptions), but I wind up with garbage values in the struct members. Here is my complete code:

[DllImport("iphlpapi.dll", SetLastError = true)]
private static extern uint GetExtendedTcpTable(IntPtr pTcpTable, ref int dwSize, bool sort, int ipVersion, int tableClass, int reserved);

[StructLayoutAttribute(LayoutKind.Sequential)]
public struct MIB_TCPROW_OWNER_MODULE
{
    public uint dwState;
    public uint dwLocalAddr;
    public uint dwLocalPort;
    public uint dwRemoteAddr;
    public uint dwRemotePort;
    public uint dwOwningPid;
    public ulong liCreateTimestamp;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = TCPIP_OWNING_MODULE_SIZE)]
    public ulong[] OwningModuleInfo;
}

[StructLayoutAttribute(LayoutKind.Sequential)]
public struct MIB_TCPTABLE_OWNER_MODULE
{
    public uint dwNumEntries;
    MIB_TCPROW_OWNER_MODULE table;
}

private const int TCPIP_OWNING_MODULE_SIZE = 16;
private const int AF_INET = 2;
private const int TCP_TABLE_OWNER_MODULE_ALL = 8;

public static void GetConnectionDetails()
{
    var bufferSize = 0;
    var ret = GetExtendedTcpTable(IntPtr.Zero, ref bufferSize, true, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0);
    var tableBuffer = Marshal.AllocHGlobal(bufferSize);

    try
    {
        ret = GetExtendedTcpTable(tableBuffer, ref bufferSize, true, AF_INET, TCP_TABLE_OWNER_MODULE_ALL, 0);

        if (ret != 0)
            throw new Exception("Oh noes!");

        var convertedTable = (MIB_TCPTABLE_OWNER_MODULE)Marshal.PtrToStructure(tableBuffer, typeof (MIB_TCPTABLE_OWNER_MODULE));

        var finalTable = new MIB_TCPROW_OWNER_MODULE[convertedTable.dwNumEntries];

        var rowPtr = (IntPtr) ((long) tableBuffer + Marshal.SizeOf(convertedTable.dwNumEntries));
        for (int i = 0; i < convertedTable.dwNumEntries; i++)
        {
            var row = (MIB_TCPROW_OWNER_MODULE)Marshal.PtrToStructure(rowPtr, typeof (MIB_TCPROW_OWNER_MODULE));

            finalTable[i] = row;

            rowPtr = (IntPtr) ((long) rowPtr + Marshal.SizeOf(row)); // Move to the next entry
        }

        foreach (var entry in finalTable)
        {
            // do something with each entry
            Console.WriteLine(entry.dwState);
            Console.WriteLine(entry.dwRemoteAddr);
        }
    }
    finally
    {
        Marshal.FreeHGlobal(tableBuffer);
    }
}

Comparing memory between this and an unmanaged version (that works properly), I do see some differences in the memory of the marshaled struct that I can't account for; there are a few bytes different.

Any assistance is most appreciated!

1
It is already wrapped by .NET. Use IPGlobalProperties.GetActiveTcpConnections() and GetActiveTcpListeners(). Use the Reference Source to see how they did it.Hans Passant
Thanks Hans! I wasn't aware that it was already wrapped; I'll check that out. Still curious why my marshalling code isn't working they way I expected it to, though.Bryan Porter

1 Answers

3
votes

Consider this struct:

[StructLayoutAttribute(LayoutKind.Sequential)]
public struct MIB_TCPTABLE_OWNER_MODULE
{
    public uint dwNumEntries;
    MIB_TCPROW_OWNER_MODULE table;
}

You are assuming that the offset of table is equal to the size of dwNumEntries. But you are forgetting about alignment. Since the largest type in MIB_TCPROW_OWNER_MODULE is 8 bytes wide, that type has alignment of 8. Which means that in order for it to be aligned it must be placed at an offset that is a multiple of 8. And hence there is padding between dwNumEntries and table. You need to allow for that padding.

So, at this point:

var rowPtr = (IntPtr) ((long) tableBuffer + 
    Marshal.SizeOf(convertedTable.dwNumEntries));

you add 4 to the address held in tableBuffer. You actually need to add 8. You should use Marshal.OffsetOf to calculate the offset:

var rowPtr = (IntPtr)((long)tableBuffer + 
    (long)Marshal.OffsetOf(typeof(MIB_TCPTABLE_OWNER_MODULE), "table"));