プログラミングの最近のブログ記事

概要: むずかしいです。でもうつくしいです。

OpenCVでカメラからのキャプチャを行うような事をC++/CLIでやろうとしています。で、まあリソース管理の為のコードを書くのは避けて通れない訳で、Disposeパターンに従って作ろうとしたら、public: virtual void Dispose(bool);が通らない......予約されていると? と思って見てみたら、こんなページがMSDN Libraryにありました。

URIが日本語用(ja-jp)なのに翻訳されていないというのはとりあえず置いておいて、C#に慣れた頭で読んでみると驚きの連続でした。ついでに眠いせいか難しい。一通り読んだだけでは収拾がつかないので、デストラクタ(Wikipedia)なども見つつ重要そうな所を僕なりにちょっとまとめてみます。

C++/CLIにおけるデストラクタとファイナライザ

デストラクタ

リソース(特にアンマネージドリソース)を積極的に解放する為にあるメソッド。リソースが要らなくなったらすぐ呼ばれるべきもので、スコープを抜ける時に自動的に呼ばれたり、プログラマが明示的に呼んだりする。

C++/CLIではデストラクタ構文(~classname())で表される。.NET FrameworkではIDisposable.Dispose()に相当する。C#はこいつを実装する事でデストラクタの機能を提供する。

ファイナライザ

リソースの解放し忘れによる被害を最小限に抑える為にあるメソッド。ガベージコレクタが使う最後の砦。インスタンスが占有する領域を解放したくなった時、リークが無いかどうか調べて、もしあれば解放する(デストラクトする)為のもの。ガベージコレクタが解放したくならなければ(=メモリがいっぱいあれば)呼ばれない事もあり得る。

C++/CLIではファイナライザ構文(!classname())で表される。.NET FrameworkではObject.Finalize()に相当する。C#におけるファイナライザは、C#ではデストラクタと呼ばれているらしく(初めて知った気がするが気のせいか)、デストラクタ構文(~classname())で表される。

C#のファイナライザがデストラクタと呼ばれているのが混乱を招きそうですが、まあC#の~classname() <=> Finalize()と最初から覚えてしまえば良さそう。

マネージドリソースとアンマネージドリソース

色んな所で出てきますが、おさらい。

マネージドリソース
ガベージコレクタによって管理されるリソース。ガベージコレクタの管轄であるマネージドヒープに作られたオブジェクトの事。C++/CLIのref classオブジェクトとかC#のclassオブジェクトなんかもこれ。幸運にもガベージコレクタが管理できるという色が強い、と思う。
アンマネージドリソース
ガベージコレクタの管轄外にあるリソース。例えばCのmallocや標準C++のnewなどによって、ガベージコレクタが監視できないヒープに確保されたオブジェクトなどが含まれる。ファイルハンドルも代表例の一つ。ガベージコレクタは残念ながらこいつらを自動的に解放する事はできない。

ガベージコレクションにおけるファイナライゼーションの順番

これは、保証されません。マネージドリソースAが別のマネージドリソースBへの参照を持っていた場合などはAからファイナライズすればいいように思えますが、更にBがAへの参照を持つ事もあり得るので、そうなるとどちらからやればいいかは自明ではありません。勿論規則を明確にすればどのような場合でも一意に決められるでしょうが、想像してみると判るように、それを実現する為のコストは割と高い(外部環境の影響を受けないように追加情報(例えばインスタンス化時刻とか)を載せなければならない)ですし、第一人間がそんなもの把握しきれません。

となると、です。オブジェクトAのファイナライザが呼ばれた時には、オブジェクトBのファイナライザは既に呼ばれているかも知れません。ファイナライゼーションが済んでしまえばガベージコレクタはそのオブジェクトが占有していたメモリ領域を解放しますから、今までBへの正しい参照だったものは危険な(使ってはいけない)参照になります。そしてBのファイナライゼーションが既に行われたかどうかを知る術はAにはありません。ファイナライザでは、マネージドリソースへの参照を使ってはいけないのです。

もしBがアンマネージドリソースだった場合は逆で、むしろ責任を持って解放してやる必要があります。ガベージコレクタがアンマネージドリソースをファイナライズする事はありませんし、BがAによってのみ所有されていれば、AはBのファイナライゼーションが済んだかどうかは正確に把握できるはずです。

Disposeパターン

こうなってくると、デストラクタでは場合分けが必要になる事が判ります。明示的に(ファイナライゼーションよりも早い段階で)デストラクトされる場合と、ファイナライゼーションによってデストラクトされる場合です。この為に、C#などではboolをパラメータとするDisposeメソッドを用意する事が推奨されています。Disposeパターンと呼ばれるようです?

public class MyClass {
  // disposingがtrueであればマネージドリソースのデストラクトも行う
  // アンマネージドリソースのデストラクトはdisposingの値に依らず行う
  // 派生クラスでやる内容変わるだろうからvirtualに
  protected virtual void Dispose( bool disposing ) {
    try {
      if ( disposing ) {
        // (マネージドリソースのデストラクト)
      }
      // (アンマネージドリソースのデストラクト)
    }
    finally {
      // もし親がいれば
      //base.Dispose( disposing );
    }
  }
  // デストラクタ
  // マネージド、アンマネージド両方とも解放
  public void Dispose() {
    // 明示的なデストラクション
    this.Dispose( true );
    System.GC.SuppressFinalize( this );
  }
  // ファイナライザ(デストラクタ構文)
  // アンマネージドリソースのみ解放
  ~MyClass() {
    // ガベージコレクタによるデストラクション
    this.Dispose( false );
  }
}

C++/CLIにおけるDisposeパターン等価コード

先程のコードを、C++/CLIで書くと次のようになります。

public ref class MyClass {
  // デストラクタ
  // マネージド、アンマネージド両方とも解放
  ~MyClass() {
    // (マネージドリソースのデストラクト)
    this->!MyClass();
  }
  // ファイナライザ
  // アンマネージドリソースのみ解放
  !MyClass() {
    // (アンマネージドリソースのデストラクト)
  }
};

美しい。これで等価だそうです。SuppressFinalize(Object)の所まで含めて。

このように書くと、デストラクタ構文の方は強いデストラクションをやっていて、ファイナライザ構文の方は弱いデストラクションをやっているようにも見えてきます。

まとめ

ま、上に書いた事がすべてではありますが、一応。

  • C++/CLIでは、DisposeやFinalizeなどのメソッドは文字通り書く事は無い。デストラクタ構文、ファイナライザ構文を使う。
  • ファイナライザでは他のマネージドリソースをデストラクトするべきではない
  • 明示的にデストラクタが呼ばれればマネージドリソースも含めてデストラクトするべきで(強いデストラクション)、
  • ガベージコレクタのファイナライゼーションで初めてデストラクタが呼ばれるようなら、マネージドリソースはデストラクトすべきではない(弱いデストラクション)。
  • C++/CLIでは、強いデストラクションをデストラクタ構文で、弱いデストラクションをファイナライザ構文で書く
  • つまり、デストラクタではマネージドリソースを解放した上でファイナライザを呼び出し
  • ファイナライザでアンマネージドリソースを解放する

うん。読みながら理解しようとしていた時には混乱しかけていたけれど、まとめてみれば何て事無い、とても素直で綺麗な設計だと思います。さすがはリソース管理にうるさい(?)C++が前身の言語と言うべき? 気をつける事と言えば、C#ではファイナライザ(デストラクタ構文)がDispose(bool)を呼び出すのが普通なので、一見逆の事をやっているように見える事ですね。実際やっている事は同じだと。ふむ。

概要: OpenMPを利用すると、さほど労力をかけずプログラムを並列化できるようです。自分ではまだ試していませんがかなり面白そう。

自分で頑張ってスレッド作って、複数スレッド間で頑張って同期したりしなくても、コード中にヒントを書いておけば勝手に並列化してくれる(と思う)OpenMPなんてのがあるそうです。概要掴むにはWikipediaにあるOpenMPの記事が役に立ちます。

当然ながらアプリケーションの複数スレッド間で資源が競合しないようにする為には元のソースコードに対する深い知識が必要ですが、OpenMPはコンパイラが使うミドルウェアといった感じのようです。コンパイラによるサポートが必要ですが、gccのC++コンパイラやVisual C++のclといった(独断と偏見によれば)メジャーなコンパイラではサポートされているようです。コンパイラは当然元のソースコードをよく知っていますから、うまく動くって訳ですね。ちなみにVisual C++の場合はVisual C++ の OpenMPを読むと良さそう。

こいつを知ったのは、最近OpenCVを入れたのですが、そこでOpenMPが使われていたからです。なるほど画像処理でも局所性が高いものが多いでしょう。スレッドを分ける要請は無いものの、CPU余ってるなら使いたいと。で、OpenMPという訳か。

ミドルウェアで思い出しましたが、Globusってのもあったなあ。こちらは複数台のコンピュータを使って一つのタスクをする(つまり、グリッドコンピューティングをする)為のミドルウェアだそうですが、前から入れよう入れよう思っているものの全然調べてない......。まあうちのとこのサーバ群も遊んでるマシンは少なくなってきてはいますが。

概要: とりあえず、SounDecoder Soundsのプログラムコード例を、簡単なものですが書きました。

先月の中旬まではまあいいとして、その後完全にほったらかしにしていたSounDecoder Soundsのプログラム例。まあ忙しかったというのはありますが、それにしてもこれ以上ほったらかすとまた以前と同じなので(もう同じかも知れませんが)、とりあえず基本的な所だけ、えいやと書いてみました。こんなの言われなくても判るレベルとか、SounDecoder Playerの方がまだ参考になりそうとか、思う所は多々あるにせよ、今後充実させていく為の前置きとして。

それにしても、以前些細な事でもこまめに書こうとか思ったはずなのに、相変わらずひどい放置ぶり。一回当たりのワークロードを軽くするように、ほんと心掛けねば......。

概要: SounDecoderとSounDecoder Sounds MDXの最新版を公開しました。これで.NET環境から簡単にサウンドの再生ができるライブラリは一通り揃いましたが、まだプログラム例はできていません。

何とかかんとか、公開にこぎ着けました......。最終版をビルドしようとしたら直前になってバグが出てくるわ(結局DirectSoundのバグだったみたいで(違うかも知れませんが)、今は直してあります)、周辺の文書整備してたら予想外に時間を食うわで結局一日中はおろか八月中に出すのも無理でしたが......(最初はこれ今年の春休み中に完成させようと思っていたのは内緒だ)。

SounDecoderは、コアモジュールだけ0.2.0.0にバージョンアップです。バージョン番号が1上がってるのは一般にはマイナーバージョンの桁ですが、割と重要なアップデートです。というか、SounDecoder 0.2.0.0じゃないとSounDecoder Sounds MDXが使えません。あ、そう言えば依存バージョン書くの忘れた......。

SounDecoder SoundsはSounDecoderを使って手軽にサウンドを再生する為のもので、今回公開したのはManaged DirectXのDirectSoundをラップするMDX 0.1.0.0です。SounDecoder 0.2.0.0以降と一緒に使います。

リファレンスは一応doxygenで作ったものがありますが、プログラム例が一つも無いので、ぼちぼち整備していこうと思います。今月上旬から中旬にかけてはあまり捗りそうにないですが、一日一例だけでも書き続けていきたい所。でないとまた億劫になりそうで。

SounDecoderやその周りのライブラリ群のメジャーバージョンはまだ0のままですが、XAudio2とかOpenALとかの実装を書き始めて、今の仕様のままで確定できそうになった頃に1に上げたいと思います。それまでは多少の仕様変更は大目に見て下さると幸いです。

概要: Managed DirectXのDirectSoundによる実装を作り始めようか、って所で浮気するのも何なんですが、XAudio2ってどうなんでしょう。 XAudioの評判は、Googleで調べてみて...

Managed DirectXのDirectSoundによる実装を作り始めようか、って所で浮気するのも何なんですが、XAudio2ってどうなんでしょう。

XAudioの評判は、Googleで調べてみてもあまり見当たりません。SlimDX と C# で学ぶ DirectXさんとこのDirectSound と XAudio の違いにはXAudio では、PCM, ADPCM, XMA, xWMA のみ扱うことができます。その他のサウンドフォーマットは使用できません。(DirectSound と同様にアプリ開発者が自分で PCM に変換すれば使うことができますが、その場合は XAudio の提供するストリーム再生機能の恩恵を受けることができず、再生可能なサウンドサイズに厳しい制限が発生します。)とあり、それだけでがっかりして興味を無くしていました。

ところが、電波の缶詰さんとこのウマイハナシの一節を見ると、何だかストリーミング再生できそうな感じです。MSDNのIXAudio2SourceVoice::SubmitSourceBufferとかXAUDIO2_BUFFERとか見ても、複数回に分けてデータを渡せそうな感じ......真面目に調べてみる価値はあるかも知れません。

ただ困った事に、XAudio2ってC#ユーザを考慮していないのか、C++向けのソースコードしか載っていないんですね。当然.NETのアセンブリも無いようだし、COM参照可能でもないみたいで。これは......C++/CLIを使えというMSさんの啓示? 一度、.NET FrameworkとUnmanaged C++を知ってる人向けのC++/CLIへの移行ガイド(C++: .NET Framework プログラミング最良の言語)を読んだ事はあるのですが、......ちゃんとやるべきかなあ......C#がとても綺麗なので使う事も無いか、と思っていたのですが、どうもこれは。

DirectMusicがManagedなインターフェイスを持っていない事に気付いたのもついさっきです、困った。

概要: ようやく、前回言っていたライブラリの、暫定ですが公開に漕ぎ着けました。SounDecoderです。libogg、libFLAC、libmpg123などは、クロスプラットフォームとはいえ.NET Fra...

ようやく、前回言っていたライブラリの、暫定ですが公開に漕ぎ着けました。SounDecoderです。libogg、libFLAC、libmpg123などは、クロスプラットフォームとはいえ.NET Frameworkからはそう簡単にアクセスできず、またフォーマット間でデコーダの利用方法に差異がある為、これを解消しよう、という魂胆のものです(こういうの既にあるのかな、ってあるんだったら思いっきり無駄足踏んだ訳ですが)。初期化時以外は(場合によっては初期化も含めて)、SounDecoder.SoundDecoderという抽象クラスがSounDecoderユーザの知るべき唯一のインターフェイスで、内部でどのようにそれぞれのデコーダ(libvorbisfileとかlibFLACとか)が使われているかは知る必要がありません。

現時点で公開できているのは、コアとなるSounDecoder、Ogg VorbisをデコードするヘルパーSounDecoder Helper Ogg Vorbis、Native/Ogg FLACをデコードするSounDecoder Helper FLAC、その他諸々のドキュメントです。MP3を含むMPEG 1.0/2.0/2.5 Audio Layer I/II/IIIをデコードするヘルパーSounDecoder Helper MPEG Audioは、既にできあがってはいますがそのプロジェクトが入ったディレクトリをミラーしてくるのを忘れて(今は帰省中です)、帰るまで手に入りません。また、すぐに音を再生できるようにする為のインターフェイス、SounDecoder.Sounds.Soundは、インターフェイスは作りましたがまだ実装がありません(近日中にDirectSoundのものを公開予定)。プログラム例は、まだ書き足りませんが公開予定日を大幅に回っている為、今回の所は三つまでです。

これ以上ずるずる引き延ばすとまた大変な事になりそうなのでとりあえずできている所までを公開しましたが、実際の所これだけではまだ使い易くありませんね......少なくともSounDecoder Soundsの実装を一つは作らないと。

それにしても、自分用のプログラムって、気楽だ。

概要: 前書き(言い訳) 年末年始は、ぼちぼち実装を始めようと思っていた企画中のプロジェクトも思うように進める事ができず、生活リズムばかり崩れていました。明けてしまって残念です。このままの状態を続けると更に状...

前書き(言い訳)

年末年始は、ぼちぼち実装を始めようと思っていた企画中のプロジェクトも思うように進める事ができず、生活リズムばかり崩れていました。明けてしまって残念です。このままの状態を続けると更に状況が悪くなる事は(「長い時間」という高い代償を払って)判り切っていますので、何かしら別の生産的な活動をしようと思い直しました。

それ程労力と時間がかからず、かつある程度の達成感があるものが良さそうです。という訳で白羽の矢が立ったのが、ライセンス関連の問題を調べるのが面倒で公開していなかった、各種のサウンドデコーダ。加えて、Managed DirectX用のライブラリです。前々から作ろうとしている(そしてその度に頓挫する)ゲームで使おうと思って作ったものの、未だろくに役に立っていないのですが、ウェブ上でざっと(かなりいーかげんに)調べてみると、それ程需要が無さそうでもない為、単独公開してみよう、というちょっとせこい理由からです。ついでに、オープンソースにしようと思っている企画中のプロジェクトでも使う予定がある為、これもオープンソースで公開してみてもいいんじゃないかな、と。

公開する予定のもの

まず、実際にデコードをするコードが含まれる、Win32のネイティブDLL群です。これらは、.NET Frameworkのプラットフォーム呼び出し(Platform Invoke)で扱えるように修正を加えたものです。そのままでも.NET言語から使えますが、扱いをより簡単にする為に、後述するデコーダのインターフェイスが用意されます。

次のリストに示す各フォーマットには、それぞれ専用のDLLが用意されます。これらのDLLとそのソースコードはライブラリの改変物であり、元のライブラリと同じライセンスで提供する予定です。

  • Vorbis、特にOgg VorbisXiph.Org Foundationのリファレンスデコーダ(修正BSDライセンス)を利用します。
  • FLACNative FLACOgg FLAC。同様にXiph.Org Foundationの管理するリファレンスデコーダ(修正BSDライセンス)を利用します。
  • MP3mpg123の一部であるlibmpg123(LGPL 2.1)を利用します。
  • 勿論、RIFFコンテナに入った生のPCMデータを持つWaveファイルもサポートします(これには特別なDLLは要りません)。

次に、これらの扱いを容易にする為のC#によるインターフェイスの実装です。インターフェイスISoundDecoderには、PCMデータを読み取る為のint ISoundDecoder.Read( Array data, int offset, int count )メソッドだとか、(デコード後の)PCMデータのサンプル長を得る為のlong ISoundDecoder.Length { get; }プロパティなどが用意されます(される予定です)。例えばOgg Vorbisの為のISoundDecoderの実装は、前述のプラットフォーム呼び出し用のWin32 DLLの内、Ogg Vorbis用のDLLを利用しながら、このインターフェイスの実装に要求される機能を実現します。利用方法は(仕様が大きく変わらなければ)大体こんな感じになる予定です。

// Ogg Vorbisファイルsample.oggをデコードする場合
using ( ISoundDecoder decoder = new OggVorbisDecoder(
  File.OpenRead( "sample.ogg" ) ) ) {
  SoundFormat format = decoder.Format;
  byte[] data = new byte[ 1 * format.BytesPerSecond ];

  // 先頭から1秒間のPCMデータを読み出す
  decoder.Read( data, 0, 1 * format.BytesPerSecond );

  // ......
  // dataを使った何らかの処理
  // ......
}

最後に、ISoundDecoderとManaged DirectXのDirectSoundを使ってすぐに音を再生できるようにする為のISoundインターフェイスと、その実装であるStaticSoundStreamingSoundです。実際の所、MDXでDirectSoundを使うのはあまり良い選択肢ではないようですが、とりあえず殆どできあがっているものがあるのでひとまずこれを利用可能にする予定です。興味が移ればSlimDX経由でDirectSoundとか、いっそASIOやOpenAL用の実装を書くかも知れません。はい、予定は未定です。

ISoundDecoderとその実装や、ISoundとその実装などは、C#で書かれ、元のライブラリからはソースコードとしてもバイナリ(あるいはアセンブリ)としても独立しており、恐らくLGPL的にも「ライブラリを利用するプログラム」扱いになると思うので(もしここをライセンスに詳しい方が読んでおられて、これが誤りだと気付いたら是非お教え願います)、条文が簡潔な修正BSDライセンスで提供するつもりです。LGPLはひとまずLGPL 2.1の参考訳から読んでみようかと思ったのですが、あまりの条文の長さの前に打ちのめされ、精読する気になりません......

公開予定時期

以前自分用に作ったものがあるので、殆ど同等の機能を有するライブラリは既にありますが、改めてちゃんとライセンスを読んだり、元になるライブラリの最新版を持ってきたり、汚い所をある程度書き直したりするつもりなので、若干時間を取って作業したいと思います。とは言えあまりこれに時間をかけすぎると目的の一つ(やる気回復)が失われるので、近い内にするつもりです。一月末には例のドタバタイベントがあるので、それより前に終わらなければ二月上旬を目処に公開できるよう努力します。

さあ、これで後には退けなくなった(のかどうかは、こんな僻地での宣言では知る由も無い)。

概要: 先日、ちょっとしたツールを作る必要があって、折角だからと思ってWPFで作ってみました。WPFの概要については、++C++; // 未確認飛行物体 C - Windows Presentation Fo...

先日、ちょっとしたツールを作る必要があって、折角だからと思ってWPFで作ってみました。WPFの概要については、++C++; // 未確認飛行物体 C - Windows Presentation Foundation 概要(WPF)辺りを見ると簡潔で判りやすいのではないでしょうか。使ってみた感じでは、Direct Xを汎用化したというよりも、GDIを拡張・汎用化・高速化した、といった感じのライブラリです。Visual Studio 2008以降でGUIアプリケーションを作るなら、今までのSystem.Windows.Formsを使う前に検討する価値はあると思います。

ま、前置きはさておき、WPFでメニューを作る時に戸惑って手間取ったので、ちょっと覚え書き。半ば自分用なので、前提知識の細々とした説明はしません。++C++; // 未確認飛行物体 C - .NET Framework 3.0で全体像を掴んだら、MSDNライブラリのWPFの項、Windows Presentation Foundationを必要に応じて参照すると良いでしょう。今回の話は、主にここのコマンド実行の概要に書いてある話の要約です。

メニュー

作ったツールにもメニューを付けようと思いました。メニューを作る方法自体はすぐ見つかります(何しろツールボックスにも入ってますから)。例えば、まっさらなウィンドウにドックパネルを作って、その一番上にメニューを付けるには、次のようなXAML文書を作ります。

<!-- MainWindow.xaml -->
<Window x:Class="CommandSample.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="コマンドのサンプル" WindowStartupLocation="CenterScreen">
  <DockPanel>
    <Menu DockPanel.Dock="Top">
      <MenuItem Header="ファイル(_F)">
        <MenuItem Header="新規作成(_N)" />
        <Separator />
        <MenuItem Header="他の形式で形式で入力(_I)" />
        <Separator />
        <MenuItem Header="終了(_X)" />
      </MenuItem>
    </Menu>
  </DockPanel>
</Window>

ファイルポップアップメニューの中に、三つのアイテムがあるだけの単純なメニューです。なお、このXAMLソースは、CommandSample名前空間のMainWindowウィンドウに適用する事が想定されています(1行目のx:Class属性指定)。

単純なメニューコマンド処理

これだけではメニューは表示されるだけで何ら役に立ちません。このメニューを機能させる一番簡単で単純な(しかし使われない?)方法は、メニューアイテムのClickイベントを処理する事です。

<!-- MainWindow.xaml -->
<Window x:Class="CommandSample.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="コマンドのサンプル" WindowStartupLocation="CenterScreen">
  <DockPanel>
    <Menu DockPanel.Dock="Top">
      <MenuItem Header="ファイル(_F)">
        <MenuItem Header="新規作成(_N)" Click="FileNew" />
        <Separator />
        <MenuItem Header="他の形式で入力(_I)" Click="FileImport" />
        <Separator />
        <MenuItem Header="終了(_X)" Click="FileExit" />
      </MenuItem>
    </Menu>
  </DockPanel>
</Window>

上のように、Click属性指定でイベントハンドラの名前を値にすれば、クリックされた時にそのイベントハンドラが呼び出されるようになります。

// MainWindow.xaml.cs
using System.Windows;
// ……

namespace CommandSample {
  public partial class MainWindow : Window {
    // ……
    private void FileNew( object sender, RoutedEventArgs e ) {
      // 新規作成の処理
    }
    private void FileImport( object sender, RoutedEventArgs e ) {
      // 別形式入力の処理
    }
    private void FileExit( object sender, RoutedEventArgs e ) {
      // 終了の処理
    }
    // ……
  }
}

イベントハンドラはRoutedEventHandler型です。EventHandlerではEventArgsを第二引数に取っていた所を、RoutedEventArgsを第二引数に取る点が違います。でも、殆ど同じですね。

これでもまあ動くのですが、どうやらWPFで推奨されるメニューコマンド処理手法はこれではないようです。WPFでは、メニューから実行されるコマンドというのはメニューに固有の概念ではなく、メニューというものは、もっと一般化された「コマンド」を呼び出す一つの手段であると解釈されるようです。

コマンド実行の概念

コマンド実行の概要に拠れば、コマンド実行には四つの主要な概念があるんだとか。

  • コマンドは、実行されるアクションです。
  • コマンド ソースは、コマンドを呼び出すオブジェクトです。
  • コマンドの対象は、コマンドが実行されているオブジェクトです。
  • コマンド バインディングは、コマンド ロジックをコマンドに割り当てるオブジェクトです。

コマンドは、先程挙げた例だと、「新規作成する」「他の形式で入力する」「終了する」といった、抽象化された操作です。このコマンドは必ずしもメニューから実行されるものではありません。メニューや、キージェスチャ(キーボードショートカット……例えばCtrl+Nとか)、それにボタンから呼び出されたりします。こういった、コマンドを呼び出すものをコマンドソースと言うようです。コマンドの対象というのは、まあ文字通りですが、そのコマンドを何に対して行うのかを示します。「終了する」なんてのは必ずアプリケーション全体対象ですから気にする必要はありませんが、例えば「コピー」操作なんかは対象を決めないとやりようがありませんね。そんな訳で、コマンドの対象はXAMLの中で明示的に示す事もできます。特に指定しなければ(今回は指定しません)、フォーカスのあるコントロールになります。コマンドバインディングは、コマンドとイベントハンドラとの結びつきの事です。後述します。

コマンド

コマンドは、前述の通り抽象化された操作です。特定のアプリケーションや、実際に行われる処理が何であるかなどに依存しない概念です。この為、一般的なコマンド群というのは、WPFで既に定義されています。その一つがSystem.Windows.Input.ApplicationCommandsです。ここには、NewとかOpenとかSaveとか、何かのデータオブジェクトを扱うようなアプリケーションなら、その大半に存在するような操作のコマンドが定義されています。さっきの例では「新規作成する」というコマンドがありました。これにはApplicationCommands.Newがぴったりです。とりあえずこれを「新規作成」メニューアイテムのコマンドとします。

<!-- MainWindow.xaml -->
<Window x:Class="CommandSample.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="コマンドのサンプル" WindowStartupLocation="CenterScreen">
  <DockPanel>
    <Menu DockPanel.Dock="Top">
      <MenuItem Header="ファイル(_F)">
        <MenuItem Header="新規作成(_N)" Command="New" />
        <Separator />
        <MenuItem Header="他の形式で入力(_I)" />
        <Separator />
        <MenuItem Header="終了(_X)" />
      </MenuItem>
    </Menu>
  </DockPanel>
</Window>

ApplicationCommandsなどのWPFクラスのメンバは、文字列で書いても勝手に変換してくれるので、すっきり書く事ができます。

コマンドバインディング

繰り返しますがコマンドは最初から特定の処理と結びついている訳ではありません。メニューアイテムが選択されてコマンドが呼ばれても、そのままでは何も実行されません。コマンドバインディングは、コマンドと特定のイベントハンドラとの結びつきを記述します。

<!-- MainWindow.xaml -->
<Window x:Class="CommandSample.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="コマンドのサンプル" WindowStartupLocation="CenterScreen">
  <Window.CommandBindings>
    <CommandBinding Command="New" Executed="FileNewCommandExecuted"
      CanExecute="FileCommandsCanExecute" />
  </Window.CommandBindings>
  <DockPanel>
    <Menu DockPanel.Dock="Top">
      <MenuItem Header="ファイル(_F)">
        <MenuItem Header="新規作成(_N)" Command="New" />
        <Separator />
        <MenuItem Header="他の形式で入力(_I)" />
        <Separator />
        <MenuItem Header="終了(_X)" />
      </MenuItem>
    </Menu>
  </DockPanel>
</Window>

Commandにはこのバインディングで結びつけるコマンドを、Executedにはコマンドが実行された時のイベントハンドラを、CanExecuteにはコマンドの実行可否を判断するイベントハンドラを指定します。CanExecuteは必要なければ無くてもいいようです。

// MainWindow.xaml.cs
using System.Windows;
using System.Windows.Input;
// ……

namespace CommandSample {
  public partial class MainWindow : Window {
    // ……
    private void FileNewCommandExecuted( object sender, ExecutedRoutedEventArgs e ) {
      // 新規作成の処理
    }
    private void FileCommandsCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
      e.CanExecute = true;
      return;
    }
    // ……
  }
}

イベントハンドラは上のように記述します。FileCommandsCanExecuteはファイルコマンドの実行可否を判断するものですが、ファイルコマンドはすべていつでも実行可能なので、e.CanExecuteにはtrueを入れて返します。

ここまですると、きちんとメニューから新規作成コマンドを実行できるようになります。

カスタムコマンド

当然ながら、ApplicationCommandsには無い(更に、EditCommandsなどにも無い)コマンドが考えられます。これまでの例では、「他の形式で入力」などがそうです。「終了する」コマンドも、「(ファイルを)閉じる」コマンド(ApplicationCommands.Close)とは微妙にニュアンスが異なる気がします。

自分でコマンドを作るのも、実は簡単です。適当なクラスにRoutedCommandのスタティックインスタンスを作って、それをApplicationCommandsなどのクラスのコマンドの代わりに使えばいいのです。

// MainWindow.xaml.cs
using System.Windows;
using System.Windows.Input;
// ……

namespace CommandSample {
  public partial class MainWindow : Window {
    public static RoutedCommand FileImportCommand = new RoutedCommand();
    public static RoutedCommand FileExitCommand = new RoutedCommand();
    // ……
    private void FileNewCommandExecuted( object sender, ExecutedRoutedEventArgs e ) {
      // 新規作成の処理
    }
    private void FileImportCommandExecuted( object sender, ExecutedRoutedEventArgs e ) {
      // 別形式入力の処理
    }
    private void FileImportCommandCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
      // 状況に応じてe.CanExecuteにtrue/falseを入れる
    }
    private void FileExitCommandExecuted( object sender, ExecutedRoutedEventArgs e ) {
      // 終了の処理
    }
    private void FileCommandsCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
      e.CanExecute = true;
      return;
    }
    // ……
  }
}

XAMLソースの記述も殆ど同じように変えるだけです。ただ、デフォルトの名前空間はWPFのものなので、今回コマンドを作ったクラスの名前空間を参照できるようにする必要があります。

<!-- MainWindow.xaml -->
<Window x:Class="CommandSample.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:cs="clr-namespace:CommandSample"
  Title="コマンドのサンプル" WindowStartupLocation="CenterScreen">
  <Window.CommandBindings>
    <CommandBinding Command="New" Executed="FileNewCommandExecuted"
      CanExecute="FileCommandsCanExecute" />
    <CommandBinding Command="{x:Static cs:MainWindow.FileImportCommand}"
      Executed="FileImportCommandExecuted" CanExecute="FileImportCommandCanExecute" />
    <CommandBinding Command="{x:Static cs:MainWindow.FileExitCommand}"
      Executed="FileExitCommandExecuted" CanExecute="FileCommandsCanExecute" />
  </Window.CommandBindings>
  <DockPanel>
    <Menu DockPanel.Dock="Top">
      <MenuItem Header="ファイル(_F)">
        <MenuItem Header="新規作成(_N)" Command="New" />
        <Separator />
        <MenuItem Header="他の形式で入力(_I)"
          Command="{x:Static cs:MainWindow.FileImportCommand}" />
        <Separator />
        <MenuItem Header="終了(_X)" Command="{x:Static cs:MainWindow.FileExitCommand}" />
      </MenuItem>
    </Menu>
  </DockPanel>
</Window>

これで他のコマンドも動くようになります。

キージェスチャでコマンド呼び出し

コマンドを呼び出せるのは、前述の通りメニューアイテムだけではありません。ここでは一般的によく使われるであろうキージェスチャにも対応したいと思います。

尤も、ApplicationCommandsのコマンドには、既定のインプットジェスチャがあったりして、ここまでの記述だけで、新規作成コマンドはCtrl+Nで呼び出せるはずです。しかし、カスタムコマンドである「他の形式で入力」コマンドもキージェスチャだけで呼び出せるようにしたいと思うかも知れません(思わないとは思いますが、まあ「ファイルを開く」コマンドはもう既定のキージェスチャがついてしまっているので、説明用です)。これは入力バインディングの一つとしてキーバインディングを定義する事で実現されます。

<!-- MainWindow.xaml -->
<Window x:Class="CommandSample.MainWindow"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:cs="clr-namespace:CommandSample"
  Title="コマンドのサンプル" WindowStartupLocation="CenterScreen">
  <Window.CommandBindings>
    <CommandBinding Command="New" Executed="FileNewCommandExecuted"
      CanExecute="FileCommandsCanExecute" />
    <CommandBinding Command="{x:Static cs:MainWindow.FileImportCommand}"
      Executed="FileImportCommandExecuted" CanExecute="FileImportCommandCanExecute" />
    <CommandBinding Command="{x:Static cs:MainWindow.FileExitCommand}"
      Executed="FileExitCommandExecuted" CanExecute="FileCommandsCanExecute" />
  </Window.CommandBindings>
  <Window.InputBindings>
    <KeyBinding Key="I" Modifiers="Control"
      Command="{x:Static cs:MainWindow.FileImportCommand}" />
  </Window.InputBindings>
  <DockPanel>
    <Menu DockPanel.Dock="Top">
      <MenuItem Header="ファイル(_F)">
        <MenuItem Header="新規作成(_N)" Command="New" />
        <Separator />
        <MenuItem Header="他の形式で入力(_I)" InputGestureText="Ctrl+I"
          Command="{x:Static cs:MainWindow.FileImportCommand}" />
        <Separator />
        <MenuItem Header="終了(_X)" Command="{x:Static cs:MainWindow.FileExitCommand}" />
      </MenuItem>
    </Menu>
  </DockPanel>
</Window>

プログラムコード中からコマンド呼び出し

プログラムコードの中からも、コマンドを呼び出したい事があります。例えば、ファイルを開いたり、他の形式で入力を受け付ける場合には、その前にまず状態を初期化しておきたいでしょう。この時FileNewCommandExecutedと全く同じ内容を書くのはやりたくありません。

コマンドは、メニューやボタンが無くても、プログラム中から直に呼び出せます。コマンドのインスタンスのExecuteメソッドを呼べばいいだけです。

// MainWindow.xaml.cs
using System.Windows;
using System.Windows.Input;
// ……

namespace CommandSample {
  public partial class MainWindow : Window {
    public static RoutedCommand FileImportCommand = new RoutedCommand();
    public static RoutedCommand FileExitCommand = new RoutedCommand();
    // ……
    private void FileNewCommandExecuted( object sender, ExecutedRoutedEventArgs e ) {
      // 新規作成の処理
    }
    private void FileImportCommandExecuted( object sender, ExecutedRoutedEventArgs e ) {
      ApplicationCommands.New.Execute( null, this );
      // 別形式入力の処理
    }
    private void FileImportCommandCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
      // 状況に応じてe.CanExecuteにtrue/falseを入れる
    }
    private void FileExitCommandExecuted( object sender, ExecutedRoutedEventArgs e ) {
      // 終了の処理
    }
    private void FileCommandsCanExecute( object sender, CanExecuteRoutedEventArgs e ) {
      e.CanExecute = true;
      return;
    }
    // ……
  }
}

RoutedCommand.Executeの第一引数は、任意のパラメータ(object型)です。このパラメータはイベントハンドラの引数eParameterメンバとしてイベントハンドラに渡されるので、必要なら適当なクラスのインスタンスを渡す事で呼び出し元と呼び出し先との間でデータのやりとりができます。

概要: mpg123はコンソールで走るMPEGオーディオデコーダですが、その構成要素にlibmpg123というのがあります。今回はこいつを.NETからプラットフォーム呼び出しして使えるように、Win32 DL...

mpg123はコンソールで走るMPEGオーディオデコーダですが、その構成要素にlibmpg123というのがあります。今回はこいつを.NETからプラットフォーム呼び出しして使えるように、Win32 DLLを作ってみます。と言うよりは、自分で作ってみた時に困ったり躓いた点のメモです。


実は既に用意されている

ダウンロードページにlibmpg123 Win32 DLLっていうのが親切にも置いてある。が、MinGWでの使用を想定して作られたものなので、今回はこれでは不十分。面倒だけど自分で作る。

コンパイラ

結論から言うと、VC++では無理っぽい。やろうとしても、環境特有のヘッダとか定数とか型とか設定とか嫌と言うほど出てきて、やる気にならない、と言うか、無理。お勧め通りMinGWのgccを使う事にする。

MinGWのインストール

MinGWとMSYSをインストールする。MinGW + MSYS による Win32 な OS での開発が参考になる。gccが動くかどうかテストする時、Vistaだとcc1.exeが見つからないって言われるけど、適当にパス通せば良い。例えば、/etc/fstabに

C:/MinGW/libexec/gcc/mingw32/3.4.5 /mingwlib

を追加しておいて、/etc/profileに

export PATH="$PATH:/mingwlib"

を書いておくとか。ここまでしたら、MSYSのシェルでgccがちゃんと動くようになる。

libmpg123のダウンロード

SourceForgeから最新版を落としてくる。ここではそれを解凍して、できたディレクトリの内容を~/mpg123の中にコピーしたとして話を進める。

呼び出し規約の変更

なんだかやっぱりデフォルトの呼び出し規約はstdcallじゃないっぽい(cdeclでもないらしい? 未確認)ので、プラットフォーム呼び出しする関数と、少なくともアンマネージドコードから呼び出されるコールバック関数については__stdcallを付けて回る。具体的には、~/mpg123/src/libmpg123/mpg123.h.inのプロトタイプ宣言と、同ディレクトリのmpg123.cの関数本体の定義を探し回って__stdcallを書き加えていく。mpg123_replace_readerを使う場合は、それの引数(勿論r_readとr_lseekだけ)に加えて、reader.hのreader_data構造体のメンバ、r_read、r_lseek、read、lseek、それとreaders.cのposix_readとposix_lseekもstdcallを使うようにしておく。

隠れた曲者CCALIGN

詳細は後述するが、(MinGW上のgccでのみDLLを利用するのでない限り)CCALIGNを1にしてはいけない。~/mpg123/src/config.hはconfigureさんが作ってしまうので、彼自身を編集する。~/mpg123/configureの、14767行目辺りのccalign="yes"ccalign="no"に変更する。その下のresult: yesもresult: noにしておいた方が気持ちいいかも。修正後はその近辺は次のようになる。

ccalign="unknown"
if test $ccalign = "unknown"; then
  { echo "$as_me:$LINENO: checking __attribute__((aligned(16)))" >&5
echo $ECHO_N "checking __attribute__((aligned(16)))... $ECHO_C" >&6; }
  ccalign="no"
  echo '__attribute__((aligned(16))) float var;' > conftest.c
  if $CC -c -o conftest.o conftest.c; then
    ccalign="no"
    { echo "$as_me:$LINENO: result: no" >&5
echo "${ECHO_T}yes" >&6; }
  else
    { echo "$as_me:$LINENO: result: no" >&5
echo "${ECHO_T}no" >&6; }
  fi
  rm -f conftest.o conftest.c
fi
if test $ccalign = "yes"; then

cat >>confdefs.h <<\_ACEOF
#define CCALIGN 1
_ACEOF

fi

これでconfig.hに#define CCALIGN 1は現れなくなる。

make libmpg123.dll

ここまで来たら、あとはmakeするだけ。~/mpg123/makedll.shを実行すれば、時間はかなり(冗談じゃない程)かかるものの一発でDLLを作成してくれる。デバッグ版を作りたい場合は--enablge-debug=yesをオプションとして付けるといい。makeが終わったら、Hintsにあるようにstrip --strip-unneeded libmpg123-0.dllしておくと軽量化できる。そういう訳で、DLLは~/mpg123/libmpg123-0.dllとして得られるのだけれど……0って何だ。


おまけ : デバッグ版

gccでデバッグ版DLLを作っても、VC++やVisual Studioじゃデバッグできない、それじゃ--enable-debug=yesしても意味無いじゃん。それはそうなんだけど、DEBUGが定義されていると、libmpg123は標準エラー出力に色々と進捗状況を吐き出してくれる。何も無いよりは随分ましだろう。尤もプラットフォーム呼び出しをする場合標準エラー出力はどっかに飛んでいってしまう(のか、どうだか?)ので、そのままではこれも意味が無い。更に、.NETのSystem.Console.SetErrorでリダイレクトっぽい事をしようとしてみても、してくれない。

それじゃあlibmpg123側でリダイレクトしてしまえ、と言う事で、libmpg123.cのmpg123_initの頭でこうやっておく。

#ifdef DEBUG
  freopen( "./stderr.log", "w", stderr );
#endif

律儀にfreopenの戻り値をグローバル変数か何かに取っておいて、mpg123_exitでfcloseしてもいい。ちなみに、--enable-debug=yesオプション付きでmakeしたDLLは、stripするとかなり痩せる。どうせgdbは使えないんだし、痩せさせてしまえ。

そのおまけ : stderrオートフラッシュ

stderrはバッファリングされなくても、再割り当てしたファイルストリームはバッファリングされてしまって、AccessViolationExceptionとかで正常にfcloseされなかった時にstderr.logに何も入ってなかった、じゃ悲しいので、面倒だけど毎回バッファをフラッシュしてやる事にする。幸いlibmpg123のエラー出力は~/mpg123/src/libmpg123/debug.hにまとまっているので、そのマクロ集を次のように変えてやればいい。

#define debug(s) do{ fprintf(stderr, "[" __FILE__ ":%i] debug: " s "\n", __LINE__);\
	fflush( stderr ); }while(0)
#define debug1(s, a) do{ fprintf(stderr, "[" __FILE__ ":%i] debug: " s "\n", __LINE__, a);\
	fflush( stderr ); }while(0)
/* ...... */

fflushを付けてブロックにしただけ。do-whileもくっついてるけど、これはereturnとの付き合いの結果。

CCALIGN?

CCALIGNを1にしておくと、適宜アライメントをしてくれる。gccはアライメントを指定する修飾子に__attribute__((aligned(x)))というのがあるようで、これを使えば変数をxバイト境界に乗っけてくれるようだ。libmpg123のmakeでは、CCALIGNが1と定義されていれば、この修飾子を使ってアライメントが必要な変数の位置を調整する。

そんなものが使われているという事は使わないといけないようなものがあるからで、例えばSSE命令がメモリにアクセスする場合、普通はメモリ上のデータは16バイト境界に乗っていないといけない。らしい。libmpg123も最適化デコーダの一つにSSEデコーダを持っていて、mpg123_newのdecoderに"sse"を渡せば使ってくれるし、NULLを渡した場合でも、利用可能な範囲内でSSEデコーダが一番効率がいいと思えば暗黙裏に使ってくれる。

そんな訳で、gccは__attribute__((aligned(x)))が使えるので、放っておけば良さそうなものだけれど、実は問題がある。どうも、gccで作ったDLLをgccから利用した場合はアライメントは保たれたままのようなのだが、gccで作ったDLLのアライメント情報は、他の環境では理解されないようなのだ。VC++然り、CLR然り。この辺って処理系依存なのか。VC++にも似たような__declspec(align(x))という修飾子があるけど、Microsoft Specificだ。で、運が悪いと、__attribute__((aligned(16)))で修飾されたデータのアドレスが16バイト境界からずれて(実際、AccessViolationExceptionが投げられていた時には4バイトずれていた)、movapsが駄々をこねる……。

いや、そう、実は駄々をこねるのはmovapsだけであって、libmpg123では、他のSSE命令はメモリに直接アクセスしないように書かれている。CCALIGNがオプショナルである事が何よりの証拠。CCALIGNが定義されない場合は、アライメントを行う代わりに、16バイト境界に乗っていなくても文句を言わないmovupsを使ってくれる。パフォーマンスは多少落ちるかも知れないけれど、_alligned_mallocを使って動的に領域をlibmpg123の外から与えたりするのはちょっと暴挙に過ぎるし、何より一番簡単だろう。

概要: 放置しすぎて、このまま放置を続けた方が本来あるべき姿のような気もするけど、折角書く気になったのだから捨て置くのももったいない。という訳で何か最近気づいた事を適当に書いてみる。 最近ようやくやる気が復活...

放置しすぎて、このまま放置を続けた方が本来あるべき姿のような気もするけど、折角書く気になったのだから捨て置くのももったいない。という訳で何か最近気づいた事を適当に書いてみる。

最近ようやくやる気が復活してきたので、例の(?)DirectXのラッパライブラリを見直している。Managed DirectXってなんかどうも捨てられてるっぽい(November 2007以降ではSDKからManaged用のドキュメントが削除されるとか......XNAってのが主流なのか?)し、既存のラッパライブラリをC++ .NETからいじった方が色々と楽そうな気もするけど、無下にするのも忍びないし、ま、一回くらい自力でラッパライブラリ作る経験があったって罰は当たらないだろうという事で継続する事にする。しかし、企画した時から2年以上経っているんだなあ。無為に過ごすと時って凄く速く過ぎ去るもので。少年老いやすく学成りがたしとはよく言ったものだ。


ま、それはともかく。Microsoft.DirectX.Direct3D.Textureのコンストラクタでストリームから画像を読み込むと、とんでもなく時間がかかりませんか?

Device device;
Stream source;

this.texture = new Texture( device, source, Format.Unknown, Pool.Managed );

みたいな事をやると。いや勿論、いつでも遅いという訳では無いのです。ただ、これやると、デバッガでデバッグしようとすると破壊的に遅くなる。物的には何も破壊しないけれど、デバッグ付きで実行しようとする気を決定的なまでに殺いでくれる。このコンストラクタ一回呼び出しただけで数秒かかるんじゃやってられない。

コード見直すに当たって、これも解消できたらいいなあと思って代替手段を探していた時、そう言えばTextureLoaderっていうのもあったなあと思って、それっぽく書き直してみた。

Device device;
Stream source;

// 実際にはFilter.Linearが使えるかどうかCaps見てやんないと駄目だろうけど
this.texture = TextureLoader.FromStream( device, source, 0, 0, 1,
  Usage.None, Format.Unknown, Pool.Managed, Filter.Linear, Filter.Linear, 0 );

あっさり改善されました。

......。

ひょっとしてTextureコンストラクタってMicrosoftの罠?

TextureLoaderは名前空間こそDirect3Dだけど、Direct3DXのアセンブリに入ってるのでどれか一つ参照に追加しないとならない。まあ一番古いのを使えばいいか。


今度はMP3に浮気中。MP3って、Ogg VorbisやFLACみたいにリファレンスエンコーダ/デコーダが無かったから敬遠していたけど、MP3ファイルを一々WAVEやOgg Vorbisに直してから使うの面倒くさいなあ、と思い始めて、いっその事対応する予定と相成りました。問題はどのデコーダを使うか......。エンコーダならLAMEが有名らしい事は知ってるんだけど、デコーダは何が有名なのかな。mpg123? メモリ上のデータをデコードするmpg123_decodeの仕様は気に入らないけど、代わりにread/seekを置き換えるmpg123_replace_reader使えば何とか綺麗にできそうだ。

このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のカテゴリはXMLです。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。