sysctlbyname でデバイスの種類を取得する

iOSiPad mini 2 とか、そういう識別子がとりたかったんですが、 UIDevice.CurrentDevice だと、 iPad だとか適当な種類しか取れず、どうやって調べるんだろう?ってググったら、

developer.apple.com

を使えばいいことは割とすぐに出てきます。
で、今をときめく Swift のサンプルなんかは比較的たくさん見つかるんですが、 Xamarin のものについては、あんまり出てきません。出てきたのは、これぐらい。

forums.xamarin.com

ところがですね。こいつ、資料としては古くて、いろいろ問題があるんです。

  • Unified API じゃない (MonoTouch.Constants.SystemLibraryってもはやない!)
  • 64-bit のことがちゃんと考慮されてない (最後の引数が uint)
  • 確保したメモリを開放してない(最低)
  • というか、p/invoke の使い方がダサい

という、主に最後の理由で、自分で書き直そうって思いました。
SysCtl.HwMachine で、内部のモデル識別子が得られます。
これを人間が読める奴に変換したい人は、下のページでググればいいと思います。

Models - The iPhone Wiki

おまけで、SysCtl.HwMemSize っていうメモリサイズが取得できる奴も搭載してみました。サンプルですね。

public static class SysCtl
{
  /// <summary>
  /// Internal model description.
  /// For the full list of the values, see the following URL:
  /// https://www.theiphonewiki.com/wiki/Models
  /// </summary>
  public static string HwMachine => GetSystemProperty("hw.machine");
  /// <summary>
  /// Memory equiped on the machine.
  /// </summary>
  public static long HwMemSize => GetSystemProperty64("hw.memsize");

  public static string GetSystemProperty(string name)
  {
    IntPtr len64 = IntPtr.Zero;
    if (sysctlbyname(name, null, ref len64, IntPtr.Zero, IntPtr.Zero) < 0)
      return null;
    var len = len64.ToInt32();
    if (len == 1)
      return "";
    var buf = new byte[len];
    if (sysctlbyname(name, buf, ref len64, IntPtr.Zero, IntPtr.Zero) < 0)
      return null;
    return Encoding.ASCII.GetString(buf, 0, len - 1); // removing the terminating \0.
  }

  public static long GetSystemProperty64(string name)
  {
    long val;
    var len = new IntPtr(Marshal.SizeOf<long>());
    if (sysctlbyname(name, out val, ref len, IntPtr.Zero, IntPtr.Zero) < 0)
      return -1;
    return val;
  }

  /// <summary>
  /// https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man3/sysctlbyname.3.html
  /// </summary>
  /// <param name="property"></param>
  /// <param name="buffer"></param>
  /// <param name="oldlenp"></param>
  /// <param name="_null1"></param>
  /// <param name="newlen"></param>
  /// <returns></returns>
  [DllImport(ObjCRuntime.Constants.SystemLibrary)]
  static extern int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string property, byte[] buffer, ref IntPtr oldlenp, IntPtr _null1, IntPtr newlen);

  [DllImport(ObjCRuntime.Constants.SystemLibrary)]
  static extern int sysctlbyname([MarshalAs(UnmanagedType.LPStr)] string property, out long value, ref IntPtr oldlenp, IntPtr _null1, IntPtr newlen);
}