3

From a P/Invoked native function, I get an IntPtr which points to a sockaddr structure. How can I convert it to an IPAddress?

Thanks!

kol
  • 27,881
  • 12
  • 83
  • 120
  • possible duplicate of [p/invoke C function that returns pointer to a struct](http://stackoverflow.com/questions/779444/p-invoke-c-function-that-returns-pointer-to-a-struct) – spender Jul 02 '13 at 13:34
  • 1
    @spender First, IPAddress is not a structure. Second, sockaddr may contain an IPv4 or an IPv6 address, so conversion is not that straightforward as in case of the question you mentioned. So this is definitely not a duplicate of it. – kol Jul 02 '13 at 13:37

3 Answers3

5

Following @aevitas' answer, I came up with the following solution:

public enum SockAddrFamily
{
  Inet = 2,
  Inet6 = 23
}

[StructLayout(LayoutKind.Sequential)]
public struct SockAddr
{
  public ushort Family;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 14)]
  public byte[] Data;
};

[StructLayout(LayoutKind.Sequential)]
public struct SockAddrIn 
{
  public ushort Family;
  public ushort Port;
  public uint Addr;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
  public byte[] Zero;
}

[StructLayout(LayoutKind.Sequential)]
public struct SockAddrIn6 
{
  public ushort Family;
  public ushort Port;
  public uint FlowInfo;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] 
  public byte[] Addr;
  public uint ScopeId;
};

public IPAddress ConvertSockAddrPtrToIPAddress(IntPtr sockAddrPtr)
{
  SockAddr sockAddr = (SockAddr)Marshal.PtrToStructure(sockAddrPtr, typeof(SockAddr));
  switch ((SockAddrFamily)sockAddr.Family)
  {
    case SockAddrFamily.Inet:
    {
      SockAddrIn sockAddrIn = (SockAddrIn)Marshal.PtrToStructure(sockAddrPtr, typeof(SockAddrIn));
      return new IPAddress(sockAddrIn.Addr);
    }
    case SockAddrFamily.Inet6:
    {
      SockAddrIn6 sockAddrIn6 = (SockAddrIn6)Marshal.PtrToStructure(sockAddrPtr, typeof(SockAddrIn6));
      return new IPAddress(sockAddrIn6.Addr);
    }
    default:
      throw new Exception(string.Format("Non-IP address family: {0}", sockAddr.Family));
  }
}

I successfully tested it with both IPv4 and IPv6 addresses.

kol
  • 27,881
  • 12
  • 83
  • 120
2

Since you can't directly Marshal a sockaddr type into an IPAddress type, you'd have to make a managed struct out of it first to marshal it into:

[StructLayout(LayoutKind.Sequential)]
internal struct sockaddr_in
{
    internal EnumLib.ADDRESS_FAMILIES sin_family;
    internal ushort sin_port;
    internal in_addr sin_addr;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)] internal byte[] sin_zero;

    internal static sockaddr_in FromString(string host, int port)
    {
    var sockaddr = new sockaddr_in();
    var lpAddressLength = Marshal.SizeOf(sockaddr);
    WSAStringToAddress(host + ":" + port, EnumLib.ADDRESS_FAMILIES.AF_INET, IntPtr.Zero,
                      ref sockaddr, ref lpAddressLength);
    return sockaddr;
    }
}

You could then use Marshal.PtrToStructure to obtain a sockaddr_in type from your IntPtr like so:

(sockaddr_in) Marshal.PtrToStructure(address, typeof(sockaddr_in));

After that it should be fairly simple to create a new IPAddress object from the data you've just retrieved.

You can find the ADDRESS_FAMILIES enum that is contained in EnumLib in the above example here: http://pastie.org/8103489

There are originally more members of the struct over at pinvoke.net, just take a look here if you are interested in them but I reckon you won't need them at all in this specific situation.

aevitas
  • 3,753
  • 2
  • 28
  • 39
  • Would you please clean up your answer a bit? For example, there is no definition of EnumLib, and there is no need to call WSAAddressToString to do the conversion. I would be happy to accept your answer if it contained the code needed to convert the IntPtr to the IPAddress, for both the IPv4 and the IPv6 case. Thank you for your help! – kol Jul 02 '13 at 14:03
  • @kol Edited my answer for brevity and removed the two superfluous properties from the struct. I've also added a link to the original snippet over at pinvoke if you do want to view them entirely. You may also want to look up http://msdn.microsoft.com/en-us/library/windows/desktop/ms738571(v=vs.85).aspx on the `in_addr` member, specifically for IPv4 and IPv6 interoperability. – aevitas Jul 02 '13 at 14:23
  • 1
    Thanks for the help, I added the "final" converter code as a separate answer. – kol Jul 02 '13 at 16:27
0

You have to create corresponding structure in C#. The using following code (p is you IntPtr):

(MyStruct)System.Runtime.InteropServices.Marshal.PtrToStructure(p, typeof(MyStruct));

You can "read" it to C#. Rest of code is easy, because you will have IP adress in sa_data

Piotr Stapp
  • 19,392
  • 11
  • 68
  • 116
  • Sorry, this is not an answer to my question. IPAddress is not a struct, and sockaddr may contain an IPv4 or an IPv6 address, so conversion is not as simple as you suggest. – kol Jul 02 '13 at 13:39
  • What in your project defines which structure it is? IntPtr is pointer to memory, but You have to know how size of the memory you are tryng to read. – Piotr Stapp Jul 02 '13 at 13:45
  • Link to the definition of sockaddr: http://msdn.microsoft.com/en-us/library/windows/desktop/ms740496.aspx – kol Jul 02 '13 at 13:48
  • You have to check which system you have got. In this case you can find out which pragma is used (_WIN32_WINNT). Helpful links http://msdn.microsoft.com/en-us/library/windows/desktop/aa383745(v=vs.85).aspx and http://msdn.microsoft.com/en-us/library/system.environment.osversion%28VS.80%29.aspx – Piotr Stapp Jul 02 '13 at 14:01