ようこそ。睡眠不足なプログラマのチラ裏です。

ARPテーブルの取得(F#) おまけもあるよ。


元ネタ:ARPテーブルの取得 (C#)(F#) - SIN@SAPPOROWORKSの覚書
http://d.hatena.ne.jp/spw0022/20111108/1320700838


SINさんがF#を書きまくっている今日この頃。F#の街札幌のF#りょくの高まりを感じざるを得ない。
F#らしい書き方かどうかはわかりませんが、SINさんのコードをベースにあまり深く考えずに。
コメント欄にお邪魔するには長いのでこちらで。


ARP(Address Resolution Protocol)テーブルの取得

#nowarn "9" "51"

open System
open System.Runtime.InteropServices
open System.Linq

[<DllImport("iphlpapi.dll")>]
extern int GetIpNetTable(IntPtr pTcpTable, int *pdwSize, bool bOrder);

[<Struct; StructLayout(LayoutKind.Sequential)>]
type MIB_IPNETROW =
    val Index:int
    val PhysAddrLen:int
    [<MarshalAs(UnmanagedType.ByValArray, SizeConst = 6)>]
    val PhysAddr:byte []
    val Addr:int
    val Type:int

let ipstr(addr:int)=
    let b = BitConverter.GetBytes(addr)
    sprintf "%d.%d.%d.%d" b.[0] b.[1] b.[2] b.[3] 

let macstr(m:byte []) = 
    sprintf "%02x-%02x-%02x-%02x-%02x-%02x"  m.[0] m.[1] m.[2] m.[3] m.[4] m.[5]

let typeStr = ["";"その他";"無効";"動的";"静的"]

//取得部分
let ar = 
  let mutable size = 0
  GetIpNetTable(IntPtr.Zero, &&size, true) |> ignore
  let p = Marshal.AllocHGlobal(size)
  if GetIpNetTable(p, &&size, true) = 0 then 
    let end' = Marshal.ReadInt32(p) - 1
    let result = 
      let step = Marshal.SizeOf(typeof<MIB_IPNETROW>)
      let getPtr = 
        let ptr' = ref (IntPtr.Add(p, 4)) 
        (fun num -> if num=0 then !ptr'  else ptr':=IntPtr.Add(!ptr',step); !ptr') 
      [0..end'] |> List.map (fun x -> getPtr x) 
                |> List.map (fun ptr -> Marshal.PtrToStructure(ptr, typeof<MIB_IPNETROW>) :?> MIB_IPNETROW) 
    Marshal.FreeHGlobal(p) 
    result
  else []

//出力部分
printfn "インデックス\tインターネット アドレス\t物理アドレス\t種類"
ar |> List.toSeq 
   |> Seq.groupBy (fun n -> n.Index)
   |> Seq.iter (fun (i,ms) ->
                  printfn "\nインターフェース:0x%x\n  インターネット アドレス\t物理アドレス\t種類" i
                  ms |> Seq.iter (fun m -> printfn "  %-15s\t%s\t%s" <| ipstr(m.Addr) <| macstr(m.PhysAddr) <| typeStr.[m.Type]))
    
printfn "何かのキーを押してください。"
Console.ReadKey() |> ignore



■主な変更点とかモロモロ

  • ワーニングの波線が残り続けるのは、精神衛生上アレなので #nowarn で非表示に。
  • 取得部分をひとまとめの関数に。
  • なるべく、みゅーたぶり(mutableを使い)たくはないので、そのあたりをいじる。
  • 「for 〜 in 〜 do 〜」を利用することは、決してわるいことではありません。が、F#ではSeqモジュールを利用してLINQのノリで書ける。
  • 元ネタ「ar.Where(fun (x:MIB_IPNETROW) -> x.Index=i.Key)」で再度絞り込みする必要はなく、arのGroupByした結果をそのまま利用して結果を出力する。
  • 前方パイプライン演算子には負けますが、後方パイプライン演算子もなかなかかわいいです。
  • ふつうのF#erなので、奇をてらったへんたいコードは書きません。


おまけ

趣旨からはだいぶズレますが、コマンドラインからコマンドを実行して取得した値をそのまま書き出すズルしてみたり:p

open System

let filename = Environment.GetEnvironmentVariable("ComSpec")
let arguments = @"/c arp -a"
let psi = new Diagnostics.ProcessStartInfo(filename, arguments, CreateNoWindow = true, UseShellExecute = false, RedirectStandardInput = false, RedirectStandardOutput = true) 
let p = Diagnostics.Process.Start(psi)
p.WaitForExit()
let results = p.StandardOutput.ReadToEnd()
printfn "%s" results
Console.ReadKey () |> ignore


これはひどいw