C# で マジックパケット(Wake On Lan、WOL)の送信

遠隔からコンピュータを起動する機能です。
起動されるコンピュータ側の設定も必要。

オーバーロードしてるから、長く見えるけど、要は一番下のメソッドが重要。

using System.Net;
using System.Net.Sockets;
using System.IO;
/// <summary>
/// マジックパケットを送信します。
/// </summary>
/// <param name="address">IPアドレス</param>
/// <param name="subnetmask">サブネットマスク</param>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(string address, string subnetmask, byte[] physicalAddress)
{
    SendMagicPacket(IPAddress.Parse(address), IPAddress.Parse(subnetmask), physicalAddress);
}

/// <summary>
/// マジックパケットを送信します。
/// </summary>
/// <param name="address">IPアドレス</param>
/// <param name="subnetmask">サブネットマスク</param>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(IPAddress address, IPAddress subnetmask, byte[] physicalAddress)
{
    uint uip = BitConverter.ToUInt32(address.GetAddressBytes(), 0);
    uint usub = BitConverter.ToUInt32(subnetmask.GetAddressBytes(), 0);

    uint result = uip | (usub ^ 0xFFFFFFFF);

    SendMagicPacket(new IPAddress(result), physicalAddress);
}

/// <summary>
/// ブロードキャストアドレス(255.255.255.255)に対してマジックパケットを送信します。
/// </summary>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(byte[] physicalAddress)
{
    SendMagicPacket(IPAddress.Broadcast, physicalAddress);
}

/// <summary>
/// 指定されたアドレスに対してマジックパケットを送信します。
/// 送信先のアドレスはブロードキャストアドレスである必要があります。
/// </summary>
/// <param name="broad">ブロードキャストアドレス</param>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(string broad, byte[] physicalAddress)
{
    SendMagicPacket(IPAddress.Parse(broad), physicalAddress);
}

/// <summary>
/// 指定されたアドレスに対してマジックパケットを送信します。
/// 送信先のアドレスはブロードキャストアドレスである必要があります。
/// </summary>
/// <param name="broad">ブロードキャストアドレス</param>
/// <param name="physicalAddress">起動するマシンのMACアドレス</param>
private void SendMagicPacket(IPAddress broad, byte[] physicalAddress)
{
    MemoryStream stream = new MemoryStream();
    BinaryWriter writer = new BinaryWriter(stream);
    for (int i = 0; i < 6; i++)
    {
        writer.Write((byte)0xff);
    }
    for (int i = 0; i < 16; i++)
    {
        writer.Write(physicalAddress);
    }

    UdpClient client = new UdpClient();
    client.EnableBroadcast = true;
    client.Send(stream.ToArray(), (int)stream.Position, new IPEndPoint(broad, 0));
}

C# でコンピュータのネットワークカードの名前の一覧を列挙

まぁ、2.0 ならこれで一覧がとれるかな?と。

using System.Net.NetworkInformation;
NetworkInterface[] nic = NetworkInterface.GetAllNetworkInterfaces();
foreach (NetworkInterface var in nic)
{
    Console.WriteLine(var.Description);
}

「MS TCP Loopback interface」 ってやつは、リストアップする際に除いたほうがイイ。
調べたら出てくると思うけど、localhost 接続用の仮想 NIC だったと思う。

WMI を使って、NIC一覧を取得するなら、こんな感じ。

using System.Management; //参照設定に System.Management を追加
ManagementClass mc = new ManagementClass("Win32_PerfRawData_Tcpip_NetworkInterface");
ManagementObjectCollection moc = mc.GetInstances();

foreach (ManagementObject mo in moc)
{
    // 情報を表示
    Console.WriteLine("名前     = {0}", mo["Name"]);
    Console.WriteLine("接続速度 = {0} Mbps", Convert.ToInt32(mo["CurrentBandwidth"]) / 1000 / 1000);

    Console.WriteLine("------");
}

無効になってる NIC は拾えないので(今は未検証だけど、たしかそうだった)、ご注意。

C# の TcpClient をハブだけの環境で使うと遅い気がする

.Net Framework には、TcpClient という Socket のラッパっぽい、便利クラスがある。

だが、このクラスを使って、ハブだけのクローズドな環境で利用すると、接続開始してから、何かのタイムアウト待ちで、通信できるまで15秒かかってしまう。
デフォルトゲートウェイがある環境(ルーターがある環境)ではすぐ通信できるが、無い環境では15秒後に通信が始まる。

Socket クラスを使用すると、すぐに通信できるのを見ると TcpClient は恐らく、何らかの処理(名前解決とか?)を行おうとし、そのタイムアウトが15秒に設定してあるためと思われる。

なので、TcpClient と同じようなメソッドを実装したクラスで、既存の TcpClient の実装部分を置き換えることでコレが解決する。
具体的には、以下のようなクラスを作成した。

using System;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace Util
{
    public class TcpClient2
    {
        private IPEndPoint end;
        private Socket socket;
        private NetworkStream stream;
        
        public TcpClient2(string host, int port) : this(IPAddress.Parse(host), port)
        {
        }
        public TcpClient2(IPAddress addr, int port)
        {
            end = new IPEndPoint(addr, port);
        }
        public NetworkStream GetStream()
        {
            if(socket == null)
            {
                socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SendTimeout);
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, ReceiveTimeout);
                socket.Connect(end);
                stream = new NetworkStream(socket);
            }
            return stream;
        }
        public void Close()
        {
            socket.Close();
        }
        public int ReceiveTimeout = 0;
        public int SendTimeout    = 0;
    }
}

C# で数字を object 型にキャストした値型の扱いについて

次のようなコードを実行すると、どうなるだろうか。

double d = 1.23456;
int i = (int)d;
Console.WriteLine(i);

double 型を int 型にキャストしているだけだ。
これは問題なく動作する。

では、次のコードはどうだろうか。

double d = 1.23456;
object o = d;
int i = (int)o;
Console.WriteLine(i);

このコードは動作しない。

object 型から int へキャストした時点で
ハンドルされていない例外: System.InvalidCastException: 指定されたキャストは有効ではありません。
といった Exception がスローされる。

じゃー、一旦 object 型になってしまった、何らかの値の型を別な値の型に変換したいときはどうするかというと、Convert クラスを使う。
上の例でいうなら

int i = Convert.ToInt32(o);

とすることで変換できる。
なお、 object 型にする値は double 型でなくても、全ての値型で起る。

C# で 2ギガを超えるファイルを StreamWriter で書き込むと例外が投げられる。

以下のようなコードを実行すると、ファイルサイズが 2GB を超えた時点で例外が投げられる。

Encoding enc = Encoding.GetEncoding(932);
string str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\r\n";

string filepath = Path.GetTempFileName();
long count = 0;
try
{
    // ファイルを新規作成
    using (StreamWriter writer = new StreamWriter(filepath, false, enc))
    {
        while (true)
        {
            writer.Write(str);

            if (count++ > 100000)
            {
                count = 0;
                Console.Write("{0:#,0} KB\r", writer.BaseStream.Position / 1024);
            }
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine();
    Console.WriteLine(ex);
}
finally
{
    try
    {
        File.Delete(filepath);
    }
    catch { }
}

例外は、こんな感じ。

System.ArgumentException: GetBytes() または GetByteCount() を呼び出す前に、Convert() 操作を完了するか、または Encoder.Reset() を呼び出さなければなりません。エンコーダ '日本語 (シフト JIS)' フォールバック 'System.Text.InternalEncoderBestFitFallback'。
 場所 System.Text.DBCSCodePageEncoding.GetBytes(Char* chars, Int32 charCount, Byte* bytes, Int32 byteCount, EncoderNLS encoder)
 場所 System.Text.EncoderNLS.GetBytes(Char* chars, Int32 charCount, Byte* bytes, Int32 byteCount, Boolean flush)
 場所 System.Text.EncoderNLS.GetBytes(Char[] chars, Int32 charIndex, Int32 charCount, Byte[] bytes, Int32 byteIndex, Boolean flush)
 場所 System.IO.StreamWriter.Flush(Boolean flushStream, Boolean flushEncoder)
 場所 System.IO.StreamWriter.Dispose(Boolean disposing)
 場所 System.IO.TextWriter.Dispose()

この2ギガ問題を解決するには・・・ファイルサイズが2ギガを越える前に、一度ストリームを閉じて、その後、ファイルに追記する形でストリームを開きなおすと良い。
具体的なコードは以下の通り。

Encoding enc = Encoding.GetEncoding(932);
string str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\r\n";

string filepath = Path.GetTempFileName();
long count = 0;

long cycle = 1 * 1024 * 1024 * 1024; // 1G
long streamsize = cycle;

// ファイルを新規作成
StreamWriter writer = new StreamWriter(filepath, false, enc);
try
{
    while (true)
    {
        writer.Write(str);

        if (count++ > 100000)
        {
            count = 0;
            Console.Write("{0:#,0} KB\r", writer.BaseStream.Position / 1024);
        }

        if (writer.BaseStream.Position > streamsize)
        {
            streamsize += cycle;
            writer.Flush();
            writer.Close();
            // ファイルに追記する
            writer = new StreamWriter(filepath, true, enc);
        }
    }
}
catch (Exception ex)
{
    Console.WriteLine();
    Console.WriteLine(ex);
}
finally
{
    try
    {
        writer.Close();
    }
    catch { }
    try
    {
        File.Delete(filepath);
    }
    catch { }
}

こんな感じ。
ここでは、1ギガごとに、ストリームを作り直すようにした。
ちなみに、using を使うと、変数が読み取り専用になり StreamWriter を作り直せないので外した。
そのため、 finally で閉じ忘れないようにした。

C# で埋め込んだリソースからデータを読み取る

プロジェクトを右クリックし、追加、既存の項目
そして、ファイルの種類をすべてのファイル、にして、アセンブリファイルに埋め込みたいファイルを選択。
プロジェクトに追加されただけだと何もしないので、追加されたファイルを選択し、ビルドアクションプロパティを、埋め込まれたりソースにする。

これで、ビルドして出来た実行ファイル、DLL にファイルが埋め込まれる。
実行ファイル、DLLに埋め込んだファイルは、C# のコード上から読み込みが行える。

using System;
using System.IO;
using System.Reflection;

public static Stream GetAssemblyStream(string name)
{
    Stream stream;

    // アセンブリから読み込む努力。
    Assembly asm = Assembly.GetExecutingAssembly();
    //Assembly asm = Assembly.GetAssembly(typeof(Program)); // 自分のクラスを指定

    // 何もせずにアセンブリから取れたなら返す。
    stream = asm.GetManifestResourceStream(name);
    if (stream != null)
    {
        return stream;
    }

    // パスとかいじってアセンブリから取ろうとする。
    string fullname = asm.FullName;
    string[] asmname = fullname.Split(",".ToCharArray());

    if (asmname.Length >= 2)
    {
        name = name.Replace("/", ".").Replace("\\", ".");
        name = asmname[0] + "." + name;
        stream = asm.GetManifestResourceStream(name);

        return stream;
    }

    // どれもダメだったら null
    return null;
}

例えば、このメソッドを使って、画像のリソースを埋め込んだ場合は、次のようなコードで Bitmap クラスとして扱える。

Bitmap bmp = Bitmap.FromStream(GetAssemblyStream("umekomareta.jpg"))

C# で Win32 のエラーコードをメッセージに変換する

まんまです。
FormatMessageA とか、その辺を使えばいいのかな?と思ったのですが、
すげー面倒そうなので、他を調べたら出てきました。
動いたのでメモ。

/// <summary>
/// 指定されたエラーコードから、エラーを説明するメッセージと16進数表現を返します。
/// </summary>
/// <param name="code"></param>
/// <returns></returns>
public static string FormatMessage(int code)
{
    byte[] b = BitConverter.GetBytes(code);
    Array.Reverse(b);//リトルエンディアンなので
    string bs = "0x" + BitConverter.ToString(b, 0).Replace("-", "");

    return string.Format("{0}({1})", new System.ComponentModel.Win32Exception(code).Message, bs);
}