gzipを圧縮するだけのソフトを作った

大量のテキストをgzipに圧縮する必要があったんで作った。解凍機能?なにそれおいしいの?

DLhttps://www.dropbox.com/s/cnuelxk5glhd2a0/SimpleGzipComressor.zip?dl=0

使い方ドラッグアンドドロップするだけ!
20160512.png

ディレクトリをD&Dでも直下のファイルのみファイル別に圧縮されます

ソースhttps://github.com/koshian2/SimpleGzipCompressor
スポンサーサイト

Control.Invokeを使わない別スレッドからのコントロールのアクセス

フォームコントロールは別スレッドからのおさわり厳禁なんで、アクセスするときはcontrol.Invokeメソッドを使わなければなりません。詳しくは:
Windowsフォームで別スレッドからコントロールを操作するには?
http://www.atmarkit.co.jp/ait/articles/0506/17/news111.html

例えば、フォームコントロールのアップデートをスレッドセーフにするには

private void TextUpdate(Control control, string text)
{
Action act = () =>
{
control.Text = text;
};
if(control.InvokeRequired)
{
control.Invoke(act);
}
else
{
act();
}
}

のように書くのが一般的かと思います(あるいはデリゲートを定義してコールバックを使う)。ただ、これ結構コードが冗長になります

実はこれタイマーを使うとうまく解決できるのではないかと思います。タイマーと言ってもいくつかあるのですが、使うのは2つ。

(1) System.Windows.Forms.Timer
(2) System.Timers.Timer


主な違いは、
(1) : System.Windows.Forms.Timer
・コントロールのあるスレッドで必ず実行される(スレッドセーフ
・同期的に処理されるので、実行中の入力はロックされる
・Intervalが極端に低い場合は精度が低く、高頻度なアップデートには適さない
(2) : System.Timers.Timer
・別スレッドで実行される(スレッドセーフではない)。ただし、Timer.SynchronizingObjectにForm等のコントロールを与えることで、BeginInvokeと同等の処理が行われる(既存のコードの改修なしでスレッドセーフにできる)
・非同期的に処理されるので、実行中の入力はロックされない
・Forms.Timerより精度は高い

他にもSystem.Threading.Timerがありますが、これをフォームコントロールで使う理由はありません。素直にSystem.Timers.Timerを使いましょう。

実装は次のようにします。

(1) Queue<Action>のように、コントロールの更新用のキューを用意する(キューのforeach等の列挙操作はスレッドセーフではないものの、Dequeue, Enqueueはスレッドセーフが保証されているので)
(2) コントロールにアクセスして値を書き換える際に、処理を(1)のキューに放り込む
(3) タイマーから定期的に(1)から読み込んで実行

System.Timers.Timerを使った例。ボタンを押すと100msごとにラベルの値が変わります。Forms.Timerはほとんど変わらないので読み替えてください。Forms.Timerの場合は、どんな違うスレッドからキューを追加しても、処理はコントロールのあるスレッドで行われるのでInvokeは発生しない(はず)。UIのロックに目を瞑れば結構速いです。

public partial class Form1 : Form
{
System.Timers.Timer timer;
Random rnd;
Queue ctlQueue;

public Form1()
{
InitializeComponent();

ctlQueue = new Queue();

rnd = new Random(Environment.TickCount);

timer = new System.Timers.Timer();
timer.Interval = 100;
timer.SynchronizingObject = this;
timer.Elapsed += timer_Elapsed;
timer.Start();
}

void timer_Elapsed(object sender, ElapsedEventArgs e)
{
if(ctlQueue.Count > 0)
{
ctlQueue.Dequeue()();
}
}

//キューに処理を放り込む
private void button1_Click(object sender, EventArgs e)
{
foreach(var i in Enumerable.Range(0, 100))
{
Action act = () =>
{
label1.Text = rnd.Next(0, 100).ToString();
};
ctlQueue.Enqueue(act);
}
}
}


(多分本来のタイマーの使い方じゃない)

ビットフラグで画像を比較する

恋愛SLG: プログラミングで彼女をつくる|paizaオンラインハッカソン7
https://paiza.jp/poh/ando

ちょっとやってみて全解禁してきた。初心者向けの問題が多かったんで余裕でした^q^
https://paiza.jp/poh/ando/share/0b8eed07

ひとつ面白い問題があったのでそれで記事を。レアアイテムのメガネの解禁問題で中級らしいんだけど、これが上級で水着が中級かなってぐらいすこし面倒な問題でした。まあ正攻法でやらなかったからなんだけどね。こんな問題↓

問題
20151209_1.png
20151209_2.png

部分一致ではなく、パターンの完全一致なんで、何のひねりもなく普通に考えると、

(1) N×Nの画像を、M×Mにトリミング(N≧M)
(2)トリミングした(1)の画像がパターンに一致するかを、ループか何かでぶん回して探索
(3)一致してればトリミングした座標を返す

というプロセスになるはず。ただ、ループで回して探索するのは芸がない!ということで、0と1の白黒画像であることを利用して2進数変換してビットフラグから一致検索する方法を考えてみます。ビットフラグにしちゃえば数字同士の計算だから場合によっては計算速くなるし、完全一致じゃなくて類似度の検索とか拡張も簡単なんで、より実戦的かもしれない(?)

注意:多倍長整数(BigInteger)を使うので、このまま問題に入力してもSystem.Numericsが足りませんとか出て正解にはなりません。オフラインで楽しむぶんがしかできないのでオナニーしましょう。

流れ
(1)画像、パターンを行単位のビットフラグに変換
例:
1 0 1
0 1 0
0 0 0
という画像なら、
[5, 2, 0]
という値になればOK

(2)画像からM×Mにトリミングする際は全てビット演算で行う。
例えば、N=3で、0行目~1行目&1列目~2列目の値を取り出したいときは、
画像:
1 0 1
0 1 0
0 0 0
マスク:
* 0 1
* 1 0
* * *
とすればいいので、マスクに使う値は
0 1 1
0 1 1
0 0 0
マスク値と画像の論理積を行単位で取ればいい(ここがミソ)。マスクした後、末尾に余計な0がくっついてる場合は逆方向に右方向にビットシフトしてM×Mのスケールに落とすことをお忘れなく。(先頭の0は無視してOK)

(3)トリミングした画像とパターンとの一致は、数値==数値ですればよい


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;

namespace Megane
{
class Program
{
static void Main(string[] args)
{
InputValue val = new InputValue();
int input_progress = 0;
int input_line = 0;
StringBuilder sb_buffer = new StringBuilder();

int inputstatus = 0;

//読み込み
while (inputstatus == 0)
{
switch (input_progress)
{
//Nの値
case 0:
int n;
if (int.TryParse(Console.ReadLine().Trim(), out n))
{
val.N = n;
input_progress++;
continue;
}
else
{
input_progress = -1;
break;
}
//画像の値
case 1:
if (input_line == val.N - 1)
{
//最終行の場合
sb_buffer.AppendLine(Console.ReadLine().Trim());

if (val.SetPicture(sb_buffer.ToString()))
{
sb_buffer = new StringBuilder();
input_progress++;
input_line = 0;
continue;
}
else
{
input_progress = -1;
break;
}
}
else if (input_line < val.N)
{
//途中行の場合
sb_buffer.AppendLine(Console.ReadLine().Trim());
input_line++;
continue;
}
else
{
//なんかおかしい場合
input_progress = -1;
break;
}
//Mの値
case 2:
int m;
if (int.TryParse(Console.ReadLine().Trim(), out m))
{
val.M = m;
input_progress++;
continue;
}
else
{
input_progress = -1;
break;
}
//パターンの値
case 3:
if (input_line == val.M - 1)
{
//最終行の場合
sb_buffer.AppendLine(Console.ReadLine().Trim());

if (val.SetPattern(sb_buffer.ToString()))
{
sb_buffer = new StringBuilder();
input_progress++;
input_line = 0;
continue;
}
else
{
input_progress = -1;
break;
}
}
else if (input_line < val.M)
{
//途中行の場合
sb_buffer.AppendLine(Console.ReadLine().Trim());
input_line++;
continue;
}
else
{
//なんかおかしい場合
input_line = -1;
break;
}
//正常に終了した場合
case 4:
inputstatus = 1;

input_line = 0;
input_progress = 0;
sb_buffer = new StringBuilder();
break;
//異常な値が入った場合
case -1:
inputstatus = -1;

input_line = 0;
input_progress = 0;
sb_buffer = new StringBuilder();
break;
}
}

//処理
if (inputstatus == 1)
{
var result = val.Compare();
if (result != null) result.Display();
}
}
}

public class InputValue
{
public int N { get; set; }
public string Picture { get; set; }
public List PictureBitFlags { get; private set; }

public int M { get; set; }
public string Pattern { get; set; }
public List PatternBitFlags { get; private set; }

public class MatchResult
{
public int X { get; set; }
public int Y { get; set; }

public void Display()
{
Console.WriteLine(Y + " " + X);
}
}

//ビットフラグにコンバート
private IEnumerable ConvertToBitFlags(string lines)
{
string[] separator = new string[] { Environment.NewLine };

foreach (var row in lines.Split(separator, StringSplitOptions.RemoveEmptyEntries))
{
BigInteger rowBitFlag = new BigInteger();

foreach (var cell in row.Split(' '))
{
if (cell == "1") rowBitFlag = (rowBitFlag << 1) | 1;
else rowBitFlag = rowBitFlag << 1;//1以外は0とみなす
}

yield return rowBitFlag;
}
}

//画像のセット
public bool SetPicture(string lines)
{
var picbit = ConvertToBitFlags(lines).ToList();

if (picbit.Count == N)
{
this.Picture = lines;
this.PictureBitFlags = picbit;
return true;
}
else
{
return false;
}
}

//パターンのセット
public bool SetPattern(string lines)
{
var patbit = ConvertToBitFlags(lines).ToList();

if(patbit.Count == M)
{
this.Pattern = lines;
this.PatternBitFlags = patbit;
return true;
}
else
{
return false;
}
}

//写真のトリミング
private IEnumerable TrimPicture(int startX, int startY, int length)
{
foreach(int row in Enumerable.Range(startY, length))
{
if (row >= N) yield return 0;

//行をマスクする値を求める
//N=5で2~3個目を抽出する場合は、0 1 1 0 0のようなマスクを作る
BigInteger rowmask = new BigInteger();
for (int i = 0; i < length; i++) rowmask = (rowmask << 1) | 1;//トリミングする部分
int nottrim = Math.Max(0, N - startX - length);//トリミングしないセルの個数
rowmask = rowmask << nottrim;

//AND演算でマスキング(気持ちいい)
BigInteger trim = PictureBitFlags[row] & rowmask;

//右側の0をビットシフトしてM×Mにスケーリングする
yield return trim >> nottrim;
}
}

//比較する
public MatchResult Compare()
{
if (this.PatternBitFlags == null || this.PictureBitFlags == null) return null;

//パターンのサイズにトリミングする
foreach (int startX in Enumerable.Range(0, N - M + 1))
{
foreach (int startY in Enumerable.Range(0, N - M + 1))
{
//トリミングされた画像
var trimmedpic = TrimPicture(startX, startY, M);

//行比較
int cnt = 0;
bool ismatch = true;
foreach (var row in trimmedpic)
{
//画像の完全一致は数値比較で良い
ismatch = ismatch && (row == PatternBitFlags[cnt]);
cnt++;
}
if (cnt == 0) ismatch = false;//トリミングデータがない場合

//完全一致した場所を発見した場合
if (ismatch)
{
var result = new MatchResult()
{
X = startX,
Y = startY,
};
return result;
}
}
}

return null;
}
}

}


ね、気持ちいいでしょ(簡単だとは言っていない)?

ちなみにこれ、数値の一致ではなく、一致した桁数を比較すれば擬似的な類似度が定義できます。抽出するピクセル数を限定すれば、とても速い画像検索アルゴリズムができそうですね。

WinFormsでホットキーを使う

アプリケーションが非アクティブのときにもショートカットキーを捕捉して実行したくなったケースがあったので。

ググってたら、SetWindowsHookEx(グローバルフック)を使い方法があるらしい。いいラッパーがあったんだけど、キーが1文字ずつしか取得できない(例えばCtrl+Cを押すと、Ctrlが押されたイベント→Cが押されたイベントと別々に出る)ので、押されたキーをキャッシュする等の実装を行わなきゃいけなくてめんどい。さらにグローバルフックの挙動がキーボードで押されたキー1つ1つに反応して効率が悪い。さらに挙動がキーロガーっぽくてあんまり正攻法じゃないなという感じがしたので、調べてたらホットキーというもっと直球なやり方があった(なんでこれが最初に出てこなかった)

SetWindowsHookExでやる場合はこちらを参考に
http://tomoemon.hateblo.jp/entry/20060706/p1
http://hongliang.seesaa.net/article/7539988.html

上のリンク下のSetWindowsHookExのラッパーをパクって参考に、ホットキーの場合のラッパーを書いてみた。ここらへんを参考に実装:
http://anis774.net/codevault/hotkey.html
http://inner2.hatenablog.com/entry/2013/10/27/215707


///
/// ホットキーが入力されたときに実行されるメソッドを表すイベントハンドラ
///

public delegate void HotKeyEventHandler(object sender, HotKeyEventArgs e);

///
/// HotKeyPushイベントのデータを提供するクラス
///

public class HotKeyEventArgs : KeyEventArgs
{
///
/// ホットキーの名前
///

public string HotKeyName { get; private set; }
///
/// ホットキーのID
///

public int HotKeyId { get; private set; }

public HotKeyEventArgs(HotKeyItem hotKeyItem)
: base(hotKeyItem.keydata)
{
this.HotKeyName = hotKeyItem.name;
this.HotKeyId = hotKeyItem.id;
}
}

///
/// 設定でホットキーの設定を保存するためのクラス
///

[DataContract]
public class HotKeyItem
{
///
/// ホットキーの名前
///

[DataMember]
public string name { get; set; }
///
/// ホットキーのID
///

[IgnoreDataMember]
public int id { get; set; }
///
/// キー(KeyEventArgs.KeyDataの値)
///

[DataMember]
public Keys keydata { get; set; }
///
/// ホットキーが有効かどうか
///

[DataMember]
public bool enable { get; set; }

}

///
/// ホットキーの操作を補足し、任意のメソッドを挿入する
///

public class HotKey : UserControl
{
const int MOD_ALT = 0x0001;
const int MOD_CONTROL = 0x0002;
const int MOD_SHIFT = 0x0004;
const int WM_HOTKEY = 0x0312;

[DllImport("user32.dll")]
extern static int RegisterHotKey(IntPtr HWnd, int ID, int MOD_KEY, Keys KEY);
[DllImport("user32.dll")]
extern static int UnregisterHotKey(IntPtr HWnd, int ID);


//イベントリストのキー
public static readonly object EventHotKeyPressed = new object();
///
/// 補足するイベントリストのコレクション
///

public List HotKeyList { get; private set; }
///
/// ホットキーが操作されたときに発生する
///

public event HotKeyEventHandler HotKeyPressed
{
add { base.Events.AddHandler(EventHotKeyPressed, value);}
remove{base.Events.RemoveHandler(EventHotKeyPressed, value);}
}

protected virtual void OnHotKeyPressed(HotKeyEventArgs e)
{
HotKeyEventHandler handler = base.Events[EventHotKeyPressed] as HotKeyEventHandler;
if (handler != null)
handler(this, e);
}

///
/// 新しいインスタンスを作成する
///

public HotKey()
{
this.HotKeyList = new List();
}

///
/// ホットキーを追加する
///

/// ホットキーの設定のインスタンス
public void AddHotKey(HotKeyItem hotKeyItem)
{
//修飾キー
int modflag = 0;
Keys modkeys = hotKeyItem.keydata & Keys.Modifiers;
if ((modkeys & Keys.Alt) == Keys.Alt) modflag = modflag | MOD_ALT;
if ((modkeys & Keys.Control) == Keys.Control) modflag = modflag | MOD_CONTROL;
if ((modkeys & Keys.Shift) == Keys.Shift) modflag = modflag | MOD_SHIFT;
//修飾キー以外のKeyCode
Keys keycode = hotKeyItem.keydata & Keys.KeyCode;
//ホットキーの登録
for(int i=HotKeyList.Count; i<=0xbfff; i++)
{
if(RegisterHotKey(this.Handle, i, modflag, keycode) != 0)
{
hotKeyItem.id = i;
break;
}
}
//コレクションに追加
HotKeyList.Add(hotKeyItem);
}

///
/// ホットキーを削除する
///

/// ホットキーの設定のインスタンス
public void RemoveHotKey(HotKeyItem hotKeyItem)
{
UnregisterHotKey(this.Handle, hotKeyItem.id);
//コレクションから削除
HotKeyList.Remove(hotKeyItem);
//設定のIDを0に戻す
hotKeyItem.id = 0;
}

protected override void WndProc(ref Message m)
{
base.WndProc(ref m);

if(m.Msg == WM_HOTKEY)
{
//Windowsメッセージで送られてくるhotkeyid
int hotkeyid = (int)m.WParam;
//該当のホットキーを探す
foreach(var h in HotKeyList)
{
if (h.id == hotkeyid) OnHotKeyPressed(new HotKeyEventArgs(h));
}
}
}

///
/// 使用されているアンマネージリソースを解放し、オプションでマネージリソースも解放する。
///

/// マネージリソースも解放する場合はtrue。
protected override void Dispose(bool disposing)
{
if(!disposed)
{
disposed = true;
while (HotKeyList.Count > 0) RemoveHotKey(HotKeyList[0]);
base.Dispose(disposing);
}
}

///
/// ファイナライザ
///

~HotKey()
{
Dispose(false);
}
private bool disposed = false;
}


HotKeyEventArgsとHotKeyItemの別々のクラスがあるのは、ホットキーの設定をJSONに出力して保存したかったから。EventArgsをそのままシリアル化するのもねえ…って感じしたので。終了時に設定したホットキーの設定を必ず削除するようにしたいので、Disposeメソッドを実装。HotKeyのメインのクラスはなんでもいいけどControl.Handleを取得できると楽なので、デザイナーで扱いやすいようにUserControlでやってみた。

これの使い方は簡単で、

private void Form1_Load(object sender, EventArgs e)
{
//ホットキー
hotKey1.AddHotKey(new HotKeyItem()
{
enable = true,
keydata = Keys.Control | Keys.C,
name = "コピー",
});
}
//ホットキーのイベントハンドラ
private void hotKey1_HotKeyPressed(object sender, HotKeyEventArgs e)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("ホットキー");
sb.AppendLine(" ID:" + e.HotKeyId);
sb.AppendLine(" 名前:" + e.HotKeyName);
sb.AppendLine(" KeyData:" + e.KeyData);
sb.AppendLine(" KeyCode:" + e.KeyCode);
sb.AppendLine(" Shift:" + e.Shift);
sb.AppendLine(" Alt:" + e.Alt);
sb.AppendLine(" Ctrl:" + e.Control);
sb.AppendLine("が押されました");

MessageBox.Show(sb.ToString(), "ホットキー");
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
hotKey1.Dispose();
}


例えばCtrl+Cをホットキーに登録。非アクティブの状態でCtrl+Cを入力すると、
20151018_1.png
このようにダイアログが出力される。このときついでにアクティブ状態にしてやるといいかもしれない。ただし、起動中はどのアプリに対しても通常のCtrl+C=コピーが無効化されるので注意。(ホットキーを使えばコピペできないようにするマシンが作れるんじゃ

DataContractJsonSerializerでnew DateTime()をパースできない

関連
stackoverflow:http://stackoverflow.com/questions/4025851/why-can-datetime-minvalue-not-be-serialized-in-timezones-ahead-of-utc

次のようなコードはもちろんJSONにシリアル化もできるし、逆シリアル化もできます。

//using System.Runtime.Serialization;
//using System.Runtime.Serialization.Json;

class Program
{
static void Main()
{
Hoeee h = new Hoeee();
h.Date = DateTime.Today;

//JSONへパース
string json;
using(var ms = new MemoryStream())
{
var ser = new DataContractJsonSerializer(typeof(Hoeee));
ser.WriteObject(ms, h);
ms.Position = 0;
using(var sr = new StreamReader(ms))
{
json = sr.ReadToEnd();
}
Console.WriteLine("オブジェクト→JSONのパース");
Console.WriteLine(json);
//オブジェクト→JSONのパース
//{"Date":"\/Date(1444230000000+0900)\/"}
}
//逆シリアル化
Hoeee h2;
var serializer = new DataContractJsonSerializer(typeof(Hoeee));
using(var ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
h2 = (Hoeee)serializer.ReadObject(ms);
}
Console.WriteLine("JSON→オブジェクトの逆シリアル化");
Console.WriteLine(h2.Date);
//JSON→オブジェクトの逆シリアル化
//2015/10/08 0:00:00
//続行するには何かキーを押してください . . . }
}
}

[DataContract]
public class Hoeee
{
[DataMember]
public DateTime Date { get; set; }
}



ところが、「h.Date = DateTime.Today;」をコメントアウト、実質的にはh.Dateをnew DateTime()にするとエラーを吐いてしまいます。

ハンドルされていない例外: System.Runtime.Serialization.SerializationException: U
TC への変換時、DateTime.MaxValue より大きい DateTime 値、または DateTime.MinValu
e より小さい DateTime 値は、JSON にシリアル化することはできません。 ---> System.
ArgumentOutOfRangeException: 指定された引数は、有効な値の範囲内にありません。
パラメーター名:value
--- 内部例外スタック トレースの終わり ---
場所 System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTimeInDef
aultFormat(DateTime value)
場所 System.Runtime.Serialization.Json.JsonWriterDelegator.WriteDateTime(Date
Time value)
場所 WriteHoeeeToJson(XmlWriterDelegator , Object , XmlObjectSerializerWriteC
ontextComplexJson , ClassDataContract , XmlDictionaryString[] )
場所 System.Runtime.Serialization.Json.JsonClassDataContract.WriteJsonValueCo
re(XmlWriterDelegator jsonWriter, Object obj, XmlObjectSerializerWriteContextCom
plexJson context, RuntimeTypeHandle declaredTypeHandle)
場所 System.Runtime.Serialization.Json.XmlObjectSerializerWriteContextComplex
Json.WriteDataContractValue(DataContract dataContract, XmlWriterDelegator xmlWri
ter, Object obj, RuntimeTypeHandle declaredTypeHandle)
場所 System.Runtime.Serialization.XmlObjectSerializerWriteContext.SerializeWi
thoutXsiType(DataContract dataContract, XmlWriterDelegator xmlWriter, Object obj
, RuntimeTypeHandle declaredTypeHandle)
場所 System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWri
teObjectContent(XmlWriterDelegator writer, Object graph)
場所 System.Runtime.Serialization.Json.DataContractJsonSerializer.InternalWri
teObject(XmlWriterDelegator writer, Object graph)
場所 System.Runtime.Serialization.XmlObjectSerializer.InternalWriteObject(Xml
WriterDelegator writer, Object graph, DataContractResolver dataContractResolver)

場所 System.Runtime.Serialization.XmlObjectSerializer.WriteObjectHandleExcept
ions(XmlWriterDelegator writer, Object graph, DataContractResolver dataContractR
esolver)
場所 System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject
(XmlDictionaryWriter writer, Object graph)
場所 System.Runtime.Serialization.Json.DataContractJsonSerializer.WriteObject
(Stream stream, Object graph)
場所 ConsoleApplication4.Program.Main() 場所 d:\…\ConsoleApplication4\Program.cs:行 28


詳しい解説は最初に張ったstackoverflowに載っていますが、要約すると、
・new DateTime()はDateTime.MinValueと等しい(DateTimeはstructであり、==で比較するとTrueを返す)
・DataContractJsonSerializerでシリアル化するにおいて、DateTimeのKindがDateTimeKind.Utcでなければ、現在のタイムゾーンからUTCに変換する計算をする
・日本の時差はUTC+9:00なので、new DateTime() == DateTime.MinValueから-9hをしてUTC時間に変換すると、DateTime.MinValueをマイナス方向のオーバーフローしてしまう。このときハンドルされない例外がスローされてしまう。


というものです。この解決策はいろいろ考えられますが、とりあえずDateTimeをDateTimeOffsetに変えて定義すると発生しなくなるそうです。(CoreTweetもDateTimeOffsetで定義していたしなるほどなと)。
        static void Main()
{
Hoeee h = new Hoeee();

//JSONへパース
string json;
using (var ms = new MemoryStream())
{
var ser = new DataContractJsonSerializer(typeof(Hoeee));
ser.WriteObject(ms, h);
ms.Position = 0;
using (var sr = new StreamReader(ms))
{
json = sr.ReadToEnd();
}
Console.WriteLine("オブジェクト→JSONのパース");
Console.WriteLine(json);
//オブジェクト→JSONのパース
//{"Date":{"DateTime":"\/Date(-62135596800000)\/","OffsetMinutes":0}}

}
//逆シリアル化
Hoeee h2;
var serializer = new DataContractJsonSerializer(typeof(Hoeee));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
h2 = (Hoeee)serializer.ReadObject(ms);
}
Console.WriteLine("JSON→オブジェクトの逆シリアル化");
Console.WriteLine(h2.Date);
//JSON→オブジェクトの逆シリアル化
//0001/01/01 0:00:00 +00:00
//続行するには何かキーを押してください . . .
}
}

[DataContract]
public class Hoeee
{
[DataMember]
public DateTimeOffset Date { get; set; }
}


C#の入門書や入門サイトを見ると、日付けはとりあえずDateTimeと書いてあるのでここは要注意ですね。

C#WinFormsでオーディオを再生する

いくつか試したところの違いのメモ

●SmallBasicLibararyを使う
C#で音楽を再生する一番簡単な方法
http://d.hatena.ne.jp/chi-bd/20140216/1392534158

【雑感】
ものすごい簡単。Sound.Play(fileName)またはSound.PlayAndWait(fileName)でwaveだろうがmp3だろうが簡単に再生できる。
【問題点】
・マルチスレッド環境で複数の音楽ファイルを同時に再生する問題に弱い。PlayAndWaitすると再生が終了するまでスレッドがロックされてしまう(UIだと致命的)。PlayAndWaitをasyncで非同期ラップしてやると、再生が完了していない段階で再生ボタンを押すと、InvalidOperationExceptionが飛んで死ぬ。AutoResetEventやMutexを使って再生部分を並列化させないようにしても、同じファイルを再生しようとしたときに、再生が終了しても前のスレッドからオーディオファイルが解放されないバグ?があって死ぬ(内部実装がわからないけどMCIで再生したときにcloseしなかった場合の挙動に似ていた)。要はハマるとめちゃくちゃ沼る
・どうしても多重再生したいなら、プロセスを多重起動してコマンド引数でファイルを割り当ててやれば多重再生できる。ただし、再生完了時にプロセスを閉じてやらないとゴミプロセスがタスクマネージャーを圧迫するし、すぐプロセスが消えてしまうのでCoreAudio等を使っても事実上音量の調整ができない(そもそもSmallBasicのSoundに音量調整の機能がない)。

●MCIを使う
MIDI、MP3などの音楽ファイルを再生する
http://dobon.net/vb/dotnet/programing/playmidifile.html
C# - Play mp3 File using winmm.dll
https://www.youtube.com/watch?v=STNAOk_IiqE

【雑感】
レスポンスが早い。多重再生をしても特に問題がない。
【問題点】
waveファイルだと完璧に開けるけど、mp3ファイルは開けない場合がある:mciSendStringでopenしたときにエラーコード266が飛んでくる。原因はよくわからないけど、コーデックあたりが闇そう。配布用のアプリだとおま環連発であんまよくない。waveファイルに統一しちゃうなら最強なんだけどね。
※シングルスレッドの場合、open→playが終わったあと同一ファイルをplayすると再生されないみたい。Threadを新たに立てて、open→playを連発すれば多重再生できるけど、再生完了した部分がGCに回収されずに残る部分がある(再生ボタンを押しまくってメモリ使用量を見てると徐々に増えていく)。open→play→closeの過程をちゃんと踏めばこの問題は発生しない。IDisposableインターフェイスを実装させるのが賢いかもしれない。

●Windows Media Player Controlを使う
MIDI、MP3などの音楽ファイルを再生する
http://dobon.net/vb/dotnet/programing/playmidifile.html

【雑感】
メディアプレイヤーそのものを使ってるからダブルクリックで再生できるやつはなんでも開ける。音量調整も簡単。
【問題点】
初期化で若干立ち上がりが遅い場合がある(それはそう)

WMコントロールを追加すると、Interop.WMPLib.dllとAxInterop.WMPLib.dllが実行ファイルフォルダに追加されるけど、これがうざいなと思ったらdynamic型でCOMコンポーネントを初期化すればいいようです。詳しくはこれ↓

.NET FrameworkでWindows Media Player Controlを利用するもう一つの方法
http://qiita.com/fujieda/items/d8642eae891d096d4028


この3つを試した結果、やっぱりWindowsMediaPlayerが最強だなということになりました。デスクトップアプリのSEなんで遅延はそこまで気にしないので。シューティングゲームみたいにレイテンシーの小ささが要求される場合は、全てのSEをwavファイルで統一してMCIで読み込ませるのがベストかと。
プロフィール

こしあん

Author:こしあん
(:3[____]
【TwitterID : koshian2】
【ほしい物リスト】http://goo.gl/bDtvG2

Twitter
カウンター
天気予報

-天気予報コム- -FC2-
カテゴリ
月別アーカイブ
最新記事
最新トラックバック
検索フォーム
リンク