C# — Преобразование IntPtr, указывающего на структуру sockaddr, в IPAddress

Из нативной функции P/Invoked я получаю IntPtr, который указывает на структуру sockaddr. Как преобразовать его в IPAddress?

Спасибо!


person kol    schedule 02.07.2013    source источник
comment
возможный дубликат функции p/invoke C, которая возвращает указатель на структура   -  person spender    schedule 02.07.2013
comment
@spender Во-первых, IPAddress не является структурой. Во-вторых, sockaddr может содержать адрес IPv4 или IPv6, поэтому преобразование не так просто, как в случае упомянутого вами вопроса. Так что это точно не дубликат.   -  person kol    schedule 02.07.2013


Ответы (3)


Поскольку вы не можете напрямую преобразовать Marshal тип sockaddr в тип IPAddress, вам придется сделать управляемая структура из него сначала для маршалинга в:

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

Затем вы можете использовать Marshal.PtrToStructure для получения типа sockaddr_in из вашего IntPtr следующим образом:

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

После этого будет достаточно просто создать новый объект IPAddress из только что полученных данных.

Вы можете найти перечисление ADDRESS_FAMILIES, которое содержится в EnumLib в приведенном выше примере, здесь: http://pastie.org/8103489

Изначально на pinvoke.net больше членов структуры, просто взгляните сюда если вы заинтересованы в них, но я думаю, что они вам вообще не понадобятся в этой конкретной ситуации.

person aevitas    schedule 02.07.2013
comment
Не могли бы вы немного почистить свой ответ? Например, нет определения EnumLib, и нет необходимости вызывать WSAAddressToString для выполнения преобразования. Я был бы рад принять ваш ответ, если бы он содержал код, необходимый для преобразования IntPtr в IPAddress как для случая IPv4, так и для IPv6. Спасибо за помощь! - person kol; 02.07.2013
comment
@kol Отредактировал мой ответ для краткости и удалил два лишних свойства из структуры. Я также добавил ссылку на исходный фрагмент на pinvoke, если вы хотите просмотреть их целиком. Вы также можете найти msdn.microsoft.com/en-us/library/windows/desktop/ на члене in_addr, специально для совместимости IPv4 и IPv6. - person aevitas; 02.07.2013
comment
Спасибо за помощь, я добавил окончательный код конвертера в качестве отдельного ответа. - person kol; 02.07.2013

Следуя ответу @aevitas, я нашел следующее решение:

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

Я успешно протестировал его с адресами IPv4 и IPv6.

person kol    schedule 02.07.2013

Вы должны создать соответствующую структуру в C#. Используя следующий код (p это вы IntPtr):

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

Вы можете "прочитать" его на С#. Остальной код прост, потому что у вас будет IP-адрес в sa_data

person Piotr Stapp    schedule 02.07.2013
comment
Извините, это не ответ на мой вопрос. IPAddress не является структурой, а sockaddr может содержать адрес IPv4 или IPv6, поэтому преобразование не так просто, как вы предлагаете. - person kol; 02.07.2013
comment
Что в вашем проекте определяет его структуру? IntPtr - это указатель на память, но вы должны знать, какой размер памяти вы пытаетесь прочитать. - person Piotr Stapp; 02.07.2013
comment
Ссылка на определение sockaddr: msdn.microsoft.com/en -us/library/windows/desktop/ms740496.aspx - person kol; 02.07.2013
comment
Вы должны проверить, какая система у вас есть. В этом случае вы можете узнать, какая прагма используется (_WIN32_WINNT). Полезные ссылки msdn.microsoft.com /en-us/library/windows/desktop/ и msdn.microsoft.com/en-us/library/ - person Piotr Stapp; 02.07.2013