WPF 4 ベータ2のマルチタッチ対応の1つである、タッチイベントを取り上げます。ドキュメントを読むと、タッチイベントとして、以下のようなものがUIElement クラスに追加されています。
- TouchDown:タッチされた場合に発生する。
- TouchMove:タッチした指を移動した場合に発生する。
- TouchUp:タッチした指を離す場合に発生する。
これ以外にも、PreviewTouchDown,Move,Upというイベントが定義されています。このイベントを使ったXAMLを以下のように定義します。
<Window x:Class="TouchEventSample.MainWindow"
xmlns="http://... /2006/xaml/presentation"
xmlns:x="http://.../winfx/2006/xaml"
Title="タッチイベント サンプル"
Width="500" Height="300">
<Canvas x:Name="canvas" Background="LightYellow"
TouchDown="Canvas_TouchDown"
TouchMove="Canvas_TouchMove"
TouchUp="Canvas_TouchUp" />
</Window>
Canvas 定義に、TouchDown、TouchMove、TouchUpイベントを定義しています。これらに対応するコードを以下のように記述します。
public partial class MainWindow : Window
{
// ブラシのリスト
static List _brushes = new List<Brush>()
{ Brushes.Red, Brushes.Blue,Brushes.Green,
Brushes.Purple, Brushes.Pink, Brushes.Orange };
// 次に使用するブラシのインデックス
static int _nextBrush = 0;
// タッチした場所(TouchDevice)ごとの図形の辞書
Dictionary<TouchDevice, Shape> _trails =
new Dictionary<TouchDevice, Shape>();
public MainWindow()
{ InitializeComponent(); }
private void Canvas_TouchDown(
object sender, TouchEventArgs e)
{
// 新しいシェイプの作成
var shape = CreateShape();
// Canvas上のタッチした場所を取得
var origin = e.GetTouchPoint(canvas);
// 図形をタッチした場所で表示するようにします
shape.RenderTransform =
new TranslateTransform(
origin.Position.X - shape.RenderSize.Width / 2,
origin.Position.Y - shape.RenderSize.Height / 2);
// 作成した図形を辞書へ登録します(移動するためです)
_trails[e.TouchDevice] = shape;
// 図形をCanvasへ追加します
canvas.Children.Add(shape);
canvas.InvalidateVisual();
// タッチデバイスをキャプチャします
canvas.CaptureTouch(e.TouchDevice);
}
private void Canvas_TouchMove(
object sender, TouchEventArgs e)
{
// タッチ移動がCanvas上で行われている場合
if (e.TouchDevice.Captured == canvas)
{
// タッチした場所を記録した図形を辞書から取得します
var shape = _trails[e.TouchDevice];
// 現在位置を取得します
var origin = e.GetTouchPoint(canvas);
// 図形を新しい位置へ移動します
shape.RenderTransform =
new TranslateTransform(
origin.Position.X - shape.RenderSize.Width / 2,
origin.Position.Y - shape.RenderSize.Height / 2);
}
}
private void Canvas_TouchUp(
object sender, TouchEventArgs e)
{
// タッチデバイスを解放します
canvas.ReleaseTouchCapture(e.TouchDevice);
// 図形を削除します
canvas.Children.Remove(_trails[e.TouchDevice]);
_trails.Remove(e.TouchDevice);
}
/// <SUMMARY>
/// 図形を作成するヘルパーメソッド
/// </SUMMARY>
/// <RETURNS>Shape(Ellipse)</RETURNS>
private Shape CreateShape()
{
Ellipse ellipse = new Ellipse();
ellipse.Width = 100;
ellipse.Height = 100;
ellipse.Fill = _brushes[_nextBrush];
_nextBrush = (_nextBrush + 1) % _brushes.Count;
return ellipse;
}
}
このコードのポイントは、シェイプを作成してからRenderTransformを設定していることです。TouchMoveイベントで図形を移動させるために、TranslateTransformのインスタンスを使用しています。この出来上がったコードを使用して実行すると、以下のようになります。
タッチした場所に図形が描画されて、移動すると図形が動き、離すと図形が消去されます。このサンプルで学習できることは、タッチイベントの発生順序になります。つまり、「Down->Move->Up」となるわけです。もちろんMoveが存在しない場合もあります。描画する図形を線などに置き換えれば、簡単なお絵かきノートのようなものも実現することができます。
Tech・Days 2010では、「.NET Framework 4時代の言語」というセッションを担当します。概要にも記載していますが、扱うテーマは以下の3つになります。
- Dynamic (動的)
- Declarative (宣言型)
- Concurrent (同時実行)
最初のテーマであるDynamicについては、DLRが提供されることで何ができるかというのがメインテーマになります。ここでお見せする予定のデモとしては、C#<->IronPython、C#<->IronRubyなどの静的言語と動的言語間の相互利用になります。両方を組み合わせることで、どういうことが実現できるかについて説明ができればと考えています。
2番目のテーマとしては、宣言型プログラミングの定義に従って XAML と Visual F#を取り上げます。XAMLについては、.NET Framework 4で提供される System.Xamlを説明する予定です。 F#は、関数型言語としての基本的な部分をご説明することを考えています。
3番目のテーマに関しては、これが一番難しいと感じています。Concurrentという単語を「同時実行」と訳していますが、「並行」と訳す場合もあります。Parallelという単語では、並列と訳するわけです。一般的に並列プログラミングと呼んでも、並行プログラミングと呼ばれることは少ないわけです。並列か並行かというのは、言葉の定義が大切なのですが、並列プログラミングに関するトピックはMSDN ライブラリの中では、Advanced Topicになっているため、一般の人には理解しにくいのではないかと考えています。従って、どのように説明するかという点に関しては、未だに検討しています。
すべてのテーマに関して考えていますのは、
- 今迄がどうだったのか
- .NET Framework 4 でどうなるのか
ということです。たとえば、Dynamicですが今迄は提供されていませんでしが、これからは提供されるわけです。そうすると、モックオブジェクトと本番オブジェクトを入れ替える場合に「Dynamic」を使うことで依存性を気にしないで入れ替えることが可能になります。もちろんプログラミング的には、インターフェース継承などを使って入れ替える方が、記述ミスの少ないプログラムを記述できますし、実行速度も速くなります。ですが、オブジェクトのインスタンスを作成するFactoryクラスなどを用意する必要があるわけです。これをDynamicにすることで、FactoryクラスがDynamicオブジェクトを返すことで、どのようなオブジェクトでも扱えるようになるわけです。もちろん、知らないメンバーを呼び出すことはコストがかかりますので、呼び出すメンバーの名前に関しては知っている必要がありますが、インターフェースを使うことなく実行時に解決できるのが、DLRのメリットになります。また、2度目以降の呼出しは、サイトバインダーによってキャッシュされていますから、速度の向上も望めるわけです。Dynamicを使用しなかった場合との違いは、「コンパイル時にメンバーを解決するか」「実行時に文字列でメンバーを解決するか」ということです。
この速度面の課題を許容するのなら、オブジェクトを作成するコンテナとしてDLRを活用する価値があるのではないかと、私は考えています。スクリプト系の言語が、色々なところで使われている状況を考えると、使っても問題ないと考えることができます。もちろん、絶対的に速度を重視する場合では、目的を達成するために別の手段をつかうべきだと思います。
PS.考えることが多くて、資料を読み込むのも大変な状況にいます。
Silverlight 4ベータSDKには、Managed Extensibility Framework(MEF)が含まれています。MEFを簡単に説明するとすれば、Composite Application を作成するためのフレームワークで、最新のモノはCodePlexで公開されています。このMEFとSilverlight Toolkitに含まれるSystem.ComponentModel.Coposition.Packaging.Toolkit.dllを組み合わせると、別のXAPをダウンロードして複合化(Composition)アプリケーションに仕上げることができます。このPackaging.Toolkitに含まれるPackageクラスを使って、自分で外部のXAPを読み込む方法をご紹介します。外部のパッケージを読み込むには、以下のようなコードを記述します。
Package package;
// イベントハンドラ
private void btnDownload_Click(object sender,
RoutedEventArgs e)
{
Uri uri = new Uri("ExternalXap1.xap", UriKind.Relative);
Package.DownloadPackageAsync(uri,
(args, p) => package = p );
}
PackageクラスのDownloadPackageAsyncメソッド呼び出せば、ラムダ式に記述したようにpackage変数にダウンロードしたXAPに関する情報が格納されます。格納されたPackageクラスのインスタンスには、以下のようなプロパティがあります。
- Uriプロパティ:指定したXAPのUriです。
- Assemblies:XAPに含まれるAppManifest.xamlのAssemblyPartで定義されているアセンブリをインスタンス化したコレクションです。
Packageクラスのインスタンスが手に入れば、クラスのインスタンスを作成するには以下のようにします。
foreach (var asm in package.Assemblies)
{
// 該当するアセンブリを見つけた場合
object obj = asm.CreateInstance("クラス名");
// 作成したインスタンスを使って何かの処理を行います
}
このPackageクラスのDownloadPackageAsyncメソッドを調べると以下のようなことを行っているのを理解することができます。
- WebClientクラスを使ってXAPの読み込み。
- XAPのStreamからStreamResourceInfoを作成。
- StreamResourceInfoからAppManifest.xamlを読み込み。
- マニフェストのAssemblyPartのSourceを使ってAssemblyPartインスタンスの作成。
- Application.GetResourceStreamを使ってアセンブリの読み込み(AssemblyPart.Load)。
- アセンブリのコレクションをPackageクラスのインスタンスに格納して、コールバック。
この動作から理解できることは、Silverlight 2で提供されたAPIだけで動的なXAPの読み込みというのが実現できるということです。また、Silverlight 4 ベータにおいて XAPの動的な読み込みに対応したAPIが提供されるのではなく、現状ではSilverlight Toolsの一部として提供されるということです。
追記:Silverlight2で動的にXAPを読み込むというチュートリアルがSilverlight.netで公開されていました。Loading Dynaic XAPs and Assemblies というものです。ビデオとサンプルコードが公開されています。
昨日は、わんくま同盟の勉強会に参加していました。ここであった質問に、Silverlight で WCFサービスのエラーを処理するにはどうしたら良いかというものがありました。基本的には、SDKドキュメントに記述があるのですが、なかなか知られていないのかなと思いました。
Silverlight 3で WCFのエラーを処理するには、WCFサービス側でビヘイビアを実装する必要があります。このビヘイビアでは、以下の2つの機能を実装する必要があります。
- HTTPステータスを「200OK」に変更する。
SDKでは、SilverlightFaultMessageInspector.BeforeSendReplyメソッドで行っています。
- ApplyDispatchBehaviorメソッドで、MessageInspectorsにSilverlightFaultMessageInspectorを追加する。
カスタムビヘイビアの準備ができたら、サービス側のエンドポイントビヘイビアに組み込みます。
サービスが出来上がれば、Silverlight側ではCompletedメソッドにエラー処理を組み込めば完了です。この場合は、try catchでエラーを補足するのではなく、イベント引数のErrorプロパティを使用します。「FaultExeption<ExceptionDetail> falut = e.Error as FaultExeption<ExceptionDetail>」のようにFaultExceptionへキャストしてから、Detailプロパティを使ってエラー内容にアクセスすることができるようになります。
WCFサービス側でエラー用のカスタムベヘイビアを作成していない場合は、HTTPステータスが500で返ってきます。この場合は、try catchでエラーを補足できますが、CommunicationExceptionとしか認識することができません。この理由から、HTTPステータスを200OKにする必要があります。Silverlight2の場合も考え方は同じなのですが、 FaultExeptionクラスなどがSilverlight3ほど用意されていませんので、自分で実装する必要がありました。
これらの理由は、Silverlight向けのWCF実装が.NET Framework向けのサブセットであり機能を縮小しているからです。WCFのフルセットであれば、try catchでエラーを補足してSOAP例外を取り出すことができますが、Silverlight向けのサブセットには含まれていないためです。
久し振りにDLRの状況を記述したいと思います。2010.1時点では、以下のようなものがリリースされています。
Visual Studio 2010 ベータ2と合わせて試すのなら、IronRuby 1.0 CTP for .NET 4.0 Beta2 がお勧めです。その理由は、.NET Framework 4.0 ベータ2に同期した IronRuby と IronPython のバイナリが含まれているからです。
また、結構前からになります DLR を使った言語サンプルとして SymPL が以前に提供されていたToyLanguage に替わって提供されています。SymPLには、2種類の実装が提供されています。
- charp:ExpandoObjectなどを使った言語実装です。
- charp-cponly:DLR対応のホスティングサンプルです。
SymPLの実装方法自体は同じなのですが、ホスティング方法の違いで2種類が提供されています。DLR 対応として DLRホスティング可能なサンプルとしては、charp-cponlyをお勧めします。DLRホスティングの方法としては、以前に私が公開した MyCalc と基本的な考え方は同じです。.NET Framework 4.0 ベータ2 対応の環境で異なるのは、以下の点です。
- ネームスペースの変更:System.Dynamicなどへの対応
- ScriptCode クラスが抽象クラスになった
特にScriptCode クラスが抽象クラスに変更されたことは重要で、LanguageContext クラスの CompileSourceCode メソッドの実装に影響します。CompileSourceCode メソッドの戻り値は、 ScriptCode のインスタンスとなります。よって、ScriptCode クラスを継承した実装クラスを記述する必要があります。実装する上で注意点は、以下のような点だと考えます。
- コンストラクタ:Ast などのインスタンスを保持するようにする。
- Run メソッド:オーバーライドするメソッドですが、このメソッドがスクリプトの実行時に呼び出されます。したがって、コンストラクタで保持した Astなどを 実行して、実行結果を戻すように実装する必要があります。
上記の変更を加えれば、以前に私が公開した MyCalcを .NET Framework 4.0 ベータ2で動作させられるようになります。
PS. IronRuby プロジェクトをリードしていた John Lamですが、リードを Jimmy Schementiと交代しました。彼は、このエントリの中で IronRuby チームを離れるのは簡単じゃなかったと述べると同時に、IronRuby プロジェクトは 1.0 リリースに向けて前進していると述べています。私自身も何回か John に会って話をしたり質問をしたりしていましたので、彼が離れることは残念でなりません。が、新しい役割で Johnが頑張ってくれることを期待もしています。Congratulations , John. Thank you very much.
エントリを書くのも久し振りなんですが、Silverlight 4ベータが公開されてから .NET Framework 4 とのアセンブリの共有ができるという情報が公開されています。このSilverlightとの共有に関して、情報を整理しようと考えています。詳細は、CLRチームのエントリに記載されていますが、そこに記載されている4つのアセンブリが以下になります。
- mscorlib.dll(System 名前空間)
- System.dll(System.Net 名前空間など)
- System.Core.dll(System 名前空間で、LINQやDLRなど)
- Microsoft.VisualBasic.dll(Microsoft.VisualBasic 名前空間で VBで使用するMyや関数などが含まれます)
上記のアセンブリに含まれているライブラリを使用しているプログラムであれば、CoreCLR とデスクトップ CLR がバイナリレベルで実行が可能になることをアセンブリの共有と呼んでいます。アセンブリの共有で実現できることは、以下のポータビリティです。
先にご紹介した CLR チームのエントリでは、.NET Framework 4対応の WPF アプリとSilverlight 用のライブラリプロジェクトで動作するという具体例を紹介しています。が、裏側で何が行われているかを推論してみたいと考えています。私は、この動作は以下のようなことを行っていると考えています。
- CoreCLR のアセンブリローダーのバインディング ポリシーの設定
- デスクトップ CLR のアセンブリローダーのバインディング ポリシーの設定
たとえばデスクトップ CLR 用の mscorlib.dll は約4.83MBあります。これに対して Silverlight のランタイム サイズの目標は2MBになっています(Silverlight 3では約3MBで、mscorlibは約1.4MBです)。このサイズが最終的にデスクトップと同じになるとは、考えられません。というか、MacOS に .NET Framework 4 が提供されませんので、Silverlight 用とデスクトップ CLR 用に別々のライブラリが用意されるのが当たり前と考えることができるからです。このように考えていくと、一部のクラスなどでデスクトップ CLR と CoreCLR 上で振る舞いが異なることが考えられます。具体的には、Silverlight では System.IO 名前空間の大半には SecurityCritical 属性が付与されています。 つまりブラウザ上のサンドボックスで動作する関係で、危険な行為を行えるメソッドに対しては安全とマークされているライブラリ(ランタイムライブラリ)からしか呼び出すことができないようになっているのです。このことは、Silverlight 4 と .NET Framework 4 でアセンブリを共有する場合に、以下のことに注意する必要があることを意味しています。
- CoreCLR で SecurityCritical 属性が付与されているメンバーを使用しない
また Silverlight 3までは System.Uri クラスで「file://....」という記述はできませんでした。Silverlight 1.1 アルファと呼んでいたプレビューの頃は使用できましたが、製品版では削除されたプロトコルになっているのです。したがって、ライブラリの互換性自体は向上しますが、機能的に Silverlight では制限されているメンバーが存在すると考えるのが妥当だと考えられます。
とは言っても、プログラムで使用する機能をライブラリ化(DLL)して Silverlight や ASP.NET、または WPF アプリケーションで共通して利用するというシナリオに置いて、保守するソースコードの種類が減りますので、使い方さえ間違わなければ効率の良いプログラムが記述できることでしょう。
PS. .NET Framework 4 と同様に DLR は、System.Core に含まれていますが、 dynamic キーワードなどが含まれるかまでは調べきれていません。
Visual Studio 2010 ベータ2に含まれているWPF 4でマルチタッチのアプリを作ろうとして、ベータ1との違いを調べていました。ローカルドキュメントには含まれていませんが、MSDNライブラリにウォークスルーが公開されています。
一番大きく変わったのが、FrameworkElementに記述していた「ManipulationMode="All"」が無くなって、「IsManipulationEnabled="True"」に変更になったことです。この変更に伴って、「ManipulationStartingイベント」でManipulationContainerを設定する必要があります。ManipulationModeは、ManipulationStartingEventArgs.Modeプロパティで設定するようになっています。この値のデフォルトが「All」になっています。
そしてウォークスルーでは、InertiaStartingのイベントハンドラでTranslationBehavior、ExpansionBehavior、RotationBehaiviorを設定しています。このBehaiviorに、移動量を設定しています。また、ManipulationDeltaイベントハンドラの最後にあるif文である「if (e.IsInertial && !containingRect.Contains(shapeBounds))」で境界を越えた時点で動作を終了するようになっています。この境界における制御なのですが、InertiaProcessorの場合はボーダーとエラスティック・マージンという考え方があるのですが、この考え方の適用方法はまだ調べきれていません。
これ以外には、WM_Touchイベントも処理できるようになっています。
MSDN会員向けに Visual Stduio 2010 ベータ2 日本語版が公開されました。英語版のベータ2から入れ替える場合の注意点を以下に記載します。
- Visual Studio 2010 Beta2 English のアンインストール
- Visual Studio 2010 Tools for Office Runtime Beta2 のアンインストール
- Microsoft .NET Framework 4 Extended Beta2のアンインストール
- Microsoft .NET Framework 4 Client Profile Beta2のアンインストール
- Microsoft Visual C++ Redistributable - x86 9.0.30729.4148のアンインストール
をしてからインストールします。気がついた注意点としては、Visual Studio 2010 Beta2のアンインストールが終了してからも、しばらくはsetup.exeが動き続けている点です。しばらく待てば、setuo.exeが終了しますので、それ以降のアンインストールを行うことができます。
また、日本語版ベータ2には、機械翻訳のドキュメントが付属しています。
Visual Studio 2010ベータ1のアンインストールは、ダウンロードページにある Instruction欄にあるHereからインストールに関するReadmeをダウンロードできますので、この手順に従った方が良いようです。簡単に手順を記載すると以下のようになります。
- Microsoft Team Foundation Server 2010 Object Modelのアンインストール
- Microsoft Visual Studio 2010 Team Suite Beta1のアンインストール
私の場合は、残っているVS2010関係をアンインストールしました
- Microsoft .NET Framework 4.0 Beta1のアンインストール
Language Pack、Extended、Client Profile
- Microsoft Visual C++ 2010関係のアンインストール
- Beta2のインストール
追記:ベータ2英語版のアンインストールの最後にsetup.exeがなかなか終了しないのは、環境に左右される模様です。もしかすると、インストールのフィードバック送信か何かで時間がかかる場合(未確認です)があるのかもしれません。
追記:ベータ2をアンインストールされる場合は、最初にVisual Studio ToolsからHelp Setting を使ってコンテンツを削除しないとヘルプコンテンツはHDD内に残りますのでご注意ください。
TechEd 2009のMVPラウンジでお見せしたSilverlight 3のマルチタッチ対応のピアノのデモですが、その後の調査で作成したデモのバグのためにSilverlight3ランタイムがハングアップしたようになっていたことが判明しました。この件は、多くの方が陥りそうな気がするので自戒の意味も込めて、状況を以下に記載します。
- Silverlight3でマルチタッチを使用するには、System.Windows.Input.Touchクラスを使用します。
具体的には、「Touch.FrameReported += Touch_FrameReported」のようにイベントハンドラを登録します。イベントハンドラは、「void Touch_FrameReported(object sebder, TouchFrameEventArgs e)」というシグネチャを持ちます。
- イベントハンドラ内では、タッチポイントコレクションを取得します。
「TouchPointCollection touchPoints e.GetTouchPoints(FrameworkElement)」メソッドでタッチポイントコレクションを取得します。タッチポイントコレクションには、複数のタッチされたポイントで構成されています。
- タッチポイントコレクションからタッチポイントを取り出して処理します。
「foreach (TouchPoint tp in touchPoints)」のようにタッチポイントを列挙します。タッチポイントには、ActionプロパティとTouchDeviceプロパティなどがあります。
- Actionプロパティによってタッチ動作を識別します。
TouchAction.Down、Move、Upという列挙値とActionプロパティを比較することでタッチ動作を記述します。タッチの動作の基本は、ダウン->移動->アップ(移動が無い場合もあります)になります。
- TouchDeviceプロパティによって、タッチされた場所を識別します。
TouchDevice.Idにタッチした場所の識別子が格納されています。つまり、ダウン->移動->アップが一回のタッチ動作であれば同じ識別子になります。複数(マルチ)のタッチであれば、TouchDevice.Idが異なるので一致するId毎にActionプロパティと組み合わせてタッチ動作を記述します。言い換えると、タッチ動作がダウン->移動->アップですからダウン時にTouchDevice.Idと座標を記録して、移動やアップ時に記録されたIdを取り出してタッチ動作にするのです。
- 私のサンプルで問題があった個所は、MouseLeftButtonDownイベントとタッチイベントの両方をハンドリングしていたことでした。これで何が問題になったかというと、軽いタッチの場合はOSというかブラウザがタッチダウンをMouseLeftButtonDownイベントに変換してしまうので、タッチダウンイベントを処理できないというものでした。
if tp.Action == TouchAction.Down)
{
Path hitKey = null;
// タッチされた場所の最上位のFrameworkElementを取得
FrameworkElement hitElement =
tp.TouchDevice.DirectOver as FrameworkElement;
// これが対処したコード
while (hitElement != null)
{
if (hitElement is Path)
{ hitKey = hitElement as Path;
Breake;
}
// Visual Tree を上へたどる
hitElement = hitElement.Parent as FrameworkElement;
}
if (hitKey != null) タッチダウンの動作を記述
}
上記のwhile文が対処コードです。このサンプルでは、Pathオブジェクトにタッチした場合に動作を作り込んでいます。while文の中で「hitElement = hitElement.Parent as FrameworkElement」とすることでVisual Treeを最上位までたどりnullでwhile文を抜けます。このwhile文を入れたことで、タッチダウンがMouseLeftButtonDownイベントに変換されなくなりました。
この動作はSilverlight3のマルチタッチ対応において仕様ともとれるものです。タッチデバイスでのタッチダウンイベントは、マウスのボタンダウンイベントと同様に扱うこともできるからです。
Windows 7もRTMしまして、マルチタッチに対応しているPCが幾つかあります。具体的には、以下のようなPCです。
- HP Touch Smart IQ800 や IQ500
- HP Touch Smart tx2 ノートブック
- DELL Latitude XT2 や XT
Microsoftで行うデモで良く見かけるのが、IQ800です。個人で所有するとなると、ノートブックになると思います。このように記述している私もHP Touch Smart tx2を購入しました。現在は、Windows 7 RTMをインストールして、マルチタッチ対応のプログラムを開発するのに使っています。マルチタッチ対応のプログラムの実行環境としては、以下のような方法があります。
- Silverlight 3
- .NET Framework 3.5
- WPF4(.NET Framework 4.0ベータ1)
- Win32 APIを使ったネイティブアプリケーション
Silverlight 3で開発するには、System.Windows.Input.Touchクラスを使用します。.NET Framework 3.5(WPFかWindows Forms)の場合は、Windows 7 Multitouch .NET Interop Sample Libraryを使用するのが簡単です。このライブラリの場合は、Win32 APIをラップしたヘルパークラスが提供されています。WPF4では、FrameworkElementにTouchイベントハンドラを記述できるようになっています。が、IDEで自動的には記述できませんのでXAMLに記述する必要があります。
Windows Touchを理解するには、ManipulationとInertiaを理解するのが良いでしょう。WM_GESTUREやWM_TOUCHメッセージは低レベルなものなので、自分で動作を作りこむ必要があります。これに対して、ManipulationやInertiaでは、複数のタッチによる振る舞いを処理するための仕組みが用意されているからです。デモなどで見かける、写真の移動や回転、ズームなどがManipulationで実現されているのです。
Silverlight3のWindows Touch対応は、WM_TOUCHメッセージのみの対応となります。従って、タッチによる振る舞いは、コードで作りこむ必要があります。
HP Touch Smart tx2を使っていて気がついた点は、マルチタッチのドライバーの動作が不安定だということです。色々と調べていくと、ACアダプタを抜いてバッテリモードで試すと安定していることに気がつきました。私が使用しているマルチタッチのドライバーでは、静電式パネルのタッチポイントの認識時に電源周りの磁界が影響するようです。この点を気をつけるようになってから、安定してマルチタッチを試すことができるようになりました。
TechEd 2009 横浜で「Deep Dive to .NET Framework CLR」というセッションを担当します。この資料を作成しています。このセッションは、私の著書である「The Root of .NET Framework」という書籍をモチーフにして、資料を構成しています。どのような内容が良いか、悩みながら作成を行っています。その途中経過を以下に引用します。
PEヘッダーから CLIヘッダーのアドレスを見つけて
CLI ヘッダーを読み解くのが、上記のスライドです。ここまで来るとメタデータが、どのように格納されているかを知りたくなることでしょう。それらも作成しているのですが、どこまで作るかが難しいところです。
これ以外にも、GCやCLRのホストインターフェース、RCWなどとアイディアがあるのですが、如何とも時間の制約がありますので作成したスライドを全部、説明しきれないかもしれません。現時点で30スライド程度が出来上がっていて、ここから SOSデバッガ拡張や.NET Framework 4.0の話を入れようと思っていますので。まだまだ、悩みは尽きません。公開されているセッションレベルは400番台なので、最低でも16進数ダンプの見方を知っている方が対象になると思います。
DLRを使ったExcelプログラミングというエントリーで、興味深いご指摘をいただきました。それは、COMオブジェクトのリリースを誰が面倒を見てくれるのかというものです。この問題を考える上で意識しないといけないのが、オブジェクトのライフサイクルの管理という側面です。具体的には、以下のようなものです。
- マネージ オブジェクトは、GCによって回収される。
- COM オブジェクトは、COMサーバーが参照カウンタが0になった時点で消滅させる。
オブジェクトの生存の可否そのものが、異なる観点で管理されているのです。つまり、
- マネージ オブジェクトは、マネージヒープ上で参照されないものがGCによって回収される。つまり、ルート オブジェクトから辿っていけないオブジェクトが、回収の対象になる。
- COMは参照カウンタで管理していおり、マネージコードからはRCWが内部で参照カウンタに対する操作を行う。従って、RCWが回収されない限り参照カウンタがデクリメントされない。
ということです。COM参照をカウンタを適切なタイミングでデクリメントするには、Marshal.ReleaseComObjectメソッドを呼ぶ必要があります。
(注)GCはCLRホスト内に存在しますので、アプリケーションがアンロードされれば解放されます。アンロード前にCOMサーバーを解放するために、Marchal.ReleaseComObjectを呼ぶかGC.Collectを呼び出す必要があるだけです。
この考えをベースに.NET Framework 4.0で導入されるDLRについて考えていきます。C#言語では、dynamicというキーワードによってレイトバインディングが実現されます。dynamicというキーワードを付与した変数は、コンパイルされたILを見るとSystem.Object型がSystem.Runtime.CompilerServices.DynamicAttributeによって振る舞いを変えるようになっています。この振る舞いを変えるという言葉の意味は、以下のようなことを意味しています。
- 通常のメソッド呼び出し:IL上は、直接メソッドを呼び出す(MethodInfoを示すマネージポインタである)。
- dynamic属性を持つオブジェクトのメソッド呼び出し:名前によってMethodInfoを取得してから、Invokeで呼び出す(この意味で、リフレクションを使ってMethodInfoを取得してからInvokeするのと同じです。厳密には、DLRのバインダーによってこれらの処理が行われます)。
つまりdynamicというキーワードは、リフレクションを使ってメソッドを呼び出す代わりにDLRが実行時にメソッド名の文字列からメソッドを呼び出してくれることになります。この時のパラメータの型に応じた呼び出しを最適化するために、CallSiteキャッシングという仕組みをDLRは用意しているのです。CallSiteキャッシングによって、同じパラメータを使ったメソッド呼び出しが高速化されるというメリットがあります。つまり実行時に文字列でメンバーを解決するが、繰り返し呼び出す場合の高速化メカニズムが用意されているのがDLRというレイトバインディングになります。今までのメソッド呼び出し比較した場合に、コンパイラが解決するか、実行時に解決するかという違いから速度的にdynamicの方が不利になるケースもあることでしょう。ですが、それは初回のメソッド呼び出し時のオーバーヘッドの違いで、2回目以降は同じとは言いませんが遜色ない程度に早くなると言えるでしょう。
これらの動的なメンバー呼び出しをCOMオブジェクトに適用するのが、DLRのCOMバインダーの役割になります。COMバインダーの設計思想は、VB6.0と同じようにCOMのAutomationインタフェースを使えるようにすることにあります。このため名前を使ってIDispatch::GetIDsOfNameメソッドでDispIDを取得してから、IDispatch::Invokeを呼び出すメカニズムを提供します。このことは、CLRにのCOMインタロップの仕組みとは異なっています。COMインタロップでは、インタロップ・アセンブリ(TLBから生成-tlbimp.exe-)を生成してアーリーバインディングを実現します。もちろん、自分でコードを記述するかVBコンパイラを使うことで、レイトバインディングを実現することもできました。これらのレイトバインディングが、コードを記述することなくCOMのAutomationインターフェース経由でCOMを扱えるようになるのが、DLRのCOMバインダーです。
ここまででdynamicキーワードでCOMを扱う時の特徴が理解できたのではないでしょうか。具体的には、以下のようなことです。
- COMオブジェクトのインスタンスは、RCWでラップされている。
参照カウンタは、RCWが管理している。
- メンバー呼び出しは、COMバインダーがGetIDsOfName、Invokeを使って呼び出している。
そうするとCOMオブジェクトのインスタンスを早期に回収するには、プログラマがコードを記述する必要があるということです。この意味において、今までと何も変わらないということができます。異なるのは、メンバーを呼び出す内部の仕組みだけです。
但し、.NET Framework 4.0ベータ1では実装されていませんが、DLR-0.91のソースコードに含まれるMicrosoft.Scripting.ComRuntimeHelpers.IUnknownReleaseDelegate(Microsoft.Dynamicの中にあります)のコメントなど参照してみてください。この実装などが、DLRの最終形に入ってくればDLRのCOMバインダーでCOMオブジェクトのリリースまで管理してくれる可能性があります。まだ開発途中ですので、DLRの開発方向を調べたい場合はcodeplexのDLRを参照するようにしてください。
PS.DLRのCOMバインダーは、System.Dynamic.dllアセンブリに実装されています。
毎日コミュニケーション 山口様より、献本していただきました。
タイトル:C# .NET アプリケーション 徹底攻略
作者:伊藤 真二
発売日:2009/4/23
メディア:単行本(ソフトカバー)
経験値をあげて高品質な業務アプリを作ろう
内容は、以下のような構成になっています。
- 導入
- .NET Frameworkアプリケーション設計
- チューニング
- リリース管理/セキュリティ
- COMアプリケーション連携
- 新しい.NET Framework/Silverlight
いわゆるTips系やリファレンス系とは異なる書籍で、.NET Frameworkを使ったプログラミングの定石というか王道を語っています。たとえば、Windows Formsではメインスレッドでしかコントロールを操作することができないため、デリゲートを使ってマルチスレッド プログラミングをするとか、FormのLoadイベントは、初期化処理には向かない。なぜなら、何度も呼び出される場合があるからなどです。
この意味で実際に開発して経験したノウハウを形式知化することで、ハマらないアプリケーション開発=高品質な業務アプリを作れるようにすることを目指した書籍です。中でもChapter4「チューニング」は約40ページあり、著者の論理的思考を裏付けるためのノウハウで満載です。チューニングは、先入観よりプロファイリングした結果に基づいてボトルネックに対処すべしというものです。試行錯誤するよりも、プロファイラで事実を把握することが、チューニングの王道であり、早道だということを丁寧に書かれています。
この意味で業務アプリケーションの実装方法を検討される方には、メリットがあるかと思います。もっとも、経験を積まれた方には、周知のことが多すぎるかも知れません。が、この経験した知識を形式知化したところにこそ本書の意義があると言えるでしょう。
PS.かなり前に読み終わったのですが、書評をまとめる時間がとれませんでした。お陰で、もう一度、査読することができました。
前回にご紹介した Railsカンファレンスの IronRuby on Railsセッションで、Rails以外のWebフレームワークとしてrackが紹介されていました。rackは、Ruby向けの Webインターフェースを提供するフレームワークで、Ruby on Rails 2.3.x系も内部でrackと統合されています。このrackをIronRubyを使って、IISで動かしてみました。その動かし方を以下に記載します。
1.必要なもの
2.rackのインストール
3.HTTPハンドラをビルドします
- githubからダウンロードしたIronRuby.Rackをビルドします。
- 私の場合は、以下のような手順で作業を行いました。
git clone git://github.com/jschementi/ironruby.git
コピーしたソースコードの中から、C:\Users\shozoa\wk\ironruby\Merlin\Main\Hosts\IronRuby.Rackフォルダを作業用フォルダへコピー。
IronRuby.Rack.slnファイルをVisual Studioで開いて、IronRuby.RackプロジェクトとIronRuby.Rack.Exampleプロジェクト以外のプロジェクトを削除(環境によって、IronRuby.Rack.Exampleプロジェクトは無効になっているかも知れません)
IronRuby 0.5.0の7つのアセンブリに対して参照をIronRuby.Rackプロジェクトへ追加しました。
これでIronRuby.Rackプロジェクトをビルドできるようになりました。
4.IISの環境を作成します
- 管理ツールのIISマネージャを開きます。
- Default Web Sitesの下へ新規のアプリケーションを作成します。
エイリアスを「IronRuby.Rack.Example」にします。
物理パスにコピーしたソースコードの「IronRuby.Rack\IronRuby.Rack.Example」を指定します。
- IronRuby.Rack.Example\web.config の configセクションを編集します。
<section name='microsoft.scripting'
type='Microsoft.Scripting.Hosting.Configuration.Section,
Microsoft.Scripting, Version=0.9.6.10, Culture=neutral,
PublicKeyToken=31bf3856ad364e35'
requirePermission='false' />
この変更は、アセンブリのバージョン番号とパブリックキートークンが異なるために行っています。
- Visual Studio で IISのWebプロジェクトを編集するには、IISにいくつかのオプションが必要になります。このオプションとは、Windowsの機能のことです。追加されるには、アプリケーションの追加で以下の機能を追加します。
セキュリティ-要求フィルタリング、Windows認証
管理ツール-IIS6と互換性のある管理-IISメタベースとIIS6構成との互換性
管理ツール-IIS管理コンソール、IIS管理サービス、IIS管理スクリプトとツール
(注)Visual Studio 2008で編集しないのであれば、この作業は必要はありません。私は、IIS7で動作確認を行っています。
- IronRuby.Rack.Example\Binフォルダへ必要なアセンブリを配置します。
IronRuby.Rackプロジェクトでビルドして出来たアセンブリ(私の場合Debug\Binフォルダにある8つ)をコピーしました。
- RubyとIronRubyのライブラリに対して、IISを起動するアカウント(Network Service)に対してアクセス権限を付与します。
「C:Ruby」と「C:\IronRuby-0.5.0」へ読み取り権限を私は付与しました。
5.ブラウザでテストします
6.IronRuby.Rackの制限事項
- GEM_PATH環境変数が「C:\ruby\lib\ruby\gems\1.8」固定になっている(Application.cs)ため、ruby 1.8系のインストールフォルダが「c:\ruby」で無ければならないということです。
- rackの構成ファイルが、Webアプリケーションのルート直下に無ければならない
- 制限ではないですが、HTTPハンドラは内部でRackupを行っています。
(注)この個所を応用すれば、別のフレームワークもサポートできるかも知れません。
- Visual Studioの開発用Webサーバーには対応していません。
昨年のRailsカンファレンスに続いて今年のRailsカンファレンスでは、「IronRuby on Rails」というセッションが行われました。このセッションでは、公開されたIronRuby 0.5.0とRuby on Rails 2.3.2を使って実際にRailsをIronRubyで動かしています。少し(大分かも)前から、IronRubyでRailsを動かすためのドキュメントが公開されています。このカンファレンスで何を紹介したかというサマリーが、Jimmyさんのブログで紹介されています。Railsを動かすドキュメントを使って、実際にRailsを動かしてみましたので、その手順を以下に記載していきます。
1.環境構築
IronRuby 0.5.0をダウンロードしてC:\IronRuby-0.5.0フォルダへ展開し、OneClick Installerで使ってC:\RubyへRubyをインストールしました。
2.Rails 2.3.2 の導入
- RubyGems のアップデート
- Ruby on Rails 2.3.2をRubyGemsを使ってインストール
#コマンドプロンプトで作業
gem update --system
gem install rails --v2.3.2 --include-dependencies
(注)RubyGemsを利用するために、Ruby 1.8系が必要になります。
3.環境変数の設定
- PATH環境変数へ IronRubyのBinフォルダへのパスを追加します。
- GEM_PATH環境変数へRubyGemsのパスを設定します。
SET PATH=%PATH%;C:\IronRuby-0.5.0\bin
SET GEM_PATH=C:\ruby\lib\ruby\gems\1.8
4.IronRubyの構成ファイルである「ir.exe.config」のLibralyPathsの値にRuby1.8のライブラリパスを設定します。
<set language="Ruby" value="..\lib\IronRuby;
c:\ruby\lib\ruby\site_ruby\1.8\;
c:\ruby\lib\ruby\1.8\" option="LibraryPaths" />
(注)valueの値は読みやすいように改行していますが、実際に設定する場合は改行を含めないで下さい。またir.exe.configは、IronRuby 0.5.0を展開した中のBinサブフォルダに存在します。
4.SQL Server のActiveRecord Adapter の導入
- MS SQL Server Adapter
- ダウンロードした「mssql_adapter.rb」を「C:\Ruby\lib\ruby\gems\1.8\gems\activerecord-2.3.2\lib\active_record\connection_adapters」フォルダーにコピーします。
(注)もちろん、SQL Serverをインストールしておく必要があります。私の場合は、SQL Server 2008 Express Editionで確認を行いました。このアダプターでは、以下のようなデータ型の対応付けが行われています。
:primary_key => "int not null identity(1,1) primary key ",
:string => { :name => "varchar", :limit => 255 },
:text => { :name => "text" },
:integer => { :name => "int" },
:float => { :name => "float" },
:decimal => { :name => "numeric" },
:datetime => { :name => "datetime" },
:timestamp => { :name => "datetime" },
:time => { :name => "datetime" },
:date => { :name => "datetime" },
:binary => { :name => "varbinary", :limit => 'MAX' },
:binary => { :name => "image" },
:boolean => { :name => "bit" }
5.WEBrickに対するパッチを投入します
- 「C\Ruby\lib\ruby\1.8\webrick\httprequest.rb」へパッチを投入します。
- パッチは、githubから入手します。
パッチの内容は、2か所あります。最初に、163行目のHttpRequestクラスのto_sメソッドです。
# ret = @request_line.dup
# @raw_header.each{|line| ret << line }
ret = @request_line ? @request_line.dup : ""
@raw_header.each{|line| ret << line } if @raw_header
次は、8行目(require)の直後に追加します。
# IronRuby bug: IO#read seems to chop off the first char
class TCPSocket
def read size
recv size
end
end
6.Railsからエンコード指定を取り除きます。2.3.2のRailsはデフォルトがUTF-8のエンコーディングです。が、IronRubyではUTF-8エンコーディングの文字列が組み合わさるとバグが発生するためです
- 「C:\Ruby\lib\ruby\gems\1.8\gems\rails-2.3.2\lib\initializer.rb」の「initializ_encoding」メソッドの実装をコメントアウトします。
407行目の「$KCODE='u' if RUBY_VERSION < '1.9'」をコメントにします。
ここまでで、準備が完了です。ここからは、Railsアプリケーションを作成していく作業となります。
7.新しいRailsアプリケーションを作成します
- 「irails IronRubyOnRails」コマンドを使用します。
「3.環境変数」を設定したコマンドプロンプトで作業を行います。
(注)成功すれば、コマンドプロンプトに「create ....」というログが表示されます。IronRubyでは、各種のコマンドの先頭に「i」というプレフィックスが付きます。インタラクティブRuby(irb)なら、「iirb」となります。これらのコマンドは、Windows向けにはBATファイルがあり、Linux向けには拡張子無しのシェルスクリプトファイルが提供されています。
8.データベースの設定を行います
- IronRubyOnRails\config\database.ymlファイルを編集します。
development:
adapter: mssql
host: (local)\SQLEXPRESS
database: ironruby_dev
integrated_security: true
上記のように設定するか、host、database、integrated_securityパラメータを「connection_string」パラメータで置き換えます。
続いて「ironruby_dev」という名前のデータベースをSQL Serverで作成します。SQL Server 2008 Management Studio Expressを使用するのが簡単です。
(注)このパラメータ形式は、SQL Serverをご存じな方には馴染み易いことでしょう。ADO.NETなどで、指定する接続文字列をパラメータとして指定しているのです。
9.Scaffolding を使ってサンプルのアプリケーションを作成します
- 「ir script/generate scaffold post title:string body:text published:boolean」コマンドをIronRubyOnRailsフォルダで実行します。
(注)成功すればコマンドプロンプトに「create....」というログが表示されます。
10.アプリケーションコントローラーのバグ対応を行います。
- 「app\controllers\posts_controller.rb」で「default_url_options」メソッドをpublicに定義します。
class PostsController < ApplicationController
...
...
# 私の場合は、最後に追加しました
public :default_url_options
end
(注)この変更は、IronRubyのバグ1354に対する対応です。rails 2.3.2では、default_url_optionsメソッドがprotectedとして定義されています。IronRubyでは、publicである必要があります。publicに変更しないでWEBrickを起動すると、http リクエストに対するレスポンスが帰ってきません。default_url_optionsメソッドのエラーがコンソールに出力される場合もあります。
11.データベースのテーブルを作成します
- 「irake db:migrate」コマンドを実行してテーブルを作成します。
C:\IronRubyOnRails>irake db:migrate
(in C:/IronRubyOnRails)
== CreatePosts: migrating =================
-- create_table(:posts)
-> 0.0550s
-> -1 rows
== CreatePosts: migrated (0.0990s) ========
成功すれば上記のように「create table...」などのログがコンソールに出力されます。失敗すると以下のように「System::Text::EncoderFallbackException」が出力されます。
C:\IronRubyOnRails>irake db:migrate
(in C:/IronRubyOnRails)
rake aborted!
rake aborted!
mscorlib:0:in `GetBytes': 値が有効な範囲にありません。
(System::Text::EncoderFallbackException)
from :0:in `write'
from :0:in `puts'
.....失敗した場合は、「config\database.yml」の設定内容を見直してください。host: (local)\SQLEXPRESSなどを書き間違えると、このエラーが出力されます(私も書き間違いをしていて、このエラー原因を探すのに苦労しました)。
11.WEBrickを起動してテストを行います
- 「ir script/server」コマンドを実行します。
C:\IronRubyOnRails>ir script/server
=> Booting WEBrick
=> Rails 2.3.2 application starting on http://0.0.0.0:3000
=> Call with -d to detach
=> Ctrl-C to shutdown server
[2009-06-09 17:14:17] INFO WEBrick 1.3.1
[2009-06-09 17:14:17] INFO ruby 1.8.6 (2008-05-28) [i386-mswin32]
[2009-06-09 17:14:17] INFO WEBrick::HTTPServer#start:
pid=4568 port=3000
正常に起動すれば、上記のようなログが出力されます。しかしながら、私の環境(Windows Vista with SP2)では少し時間がかかります。
- ブラウザで「http://localhost:3000/」へアクセスすれば、railsのページが表示されることでしょう。
- 「about your application's environment」をクリックします。
正常に実行されれば実行環境が上記のように表示されます。が、この情報はRailsのInfoControllerが処理する関係で、IronRubyとは表示されることはありません。あくまでも、Ruby 1.8系として表示されるだけです。
- 次に「http://localhost:3000/posts」へブラウザでアクセスします。
上記のようにpostsテーブルの一覧が表示されます。new postsのリンクをクリックしてデータを追加していけば、問題なく動作することを確認していくことができます。
この時点で、コンソールを確認すると以下のようなログが出力されていました。
Processing Rails::InfoController#properties
(for 127.0.0.1 at 2009-06-09 17:21:5) [GET]
Completed in 584ms (View: 189, DB: 0) | 200 OK
[http://localhost/rails/info/proerties]
Processing PostsController#index
(for 127.0.0.1 at 2009-06-09 17:25:21) [GET]
Rendering template within layouts/posts
Rendering posts/index
Completed in 735ms (View: 540, DB: 0) | 200 OK
[http://localhost/posts]
これらのログが表示されるのが、私の環境では少し遅くなっています。ブラウザでは問題なく動作しているのですが、WEBrickのログ出力が遅くなるという問題があるようです。もしかすると、環境依存かもしれません。
ここまでの動作で、IronRubyを使ってRuby on Rails 2.3.2が動作していることを確認することができました。データベースとしては、SQL Serverです。まだまだパフォーマンス的な問題や、バグが残っているにしてもIronRubyによる Ruby on Railsの実現が近くなってきたのを感じ取ることができます。是非、皆さんも試してみてください。そしてバグを見つけたら、フィードバックをしてください。
PS.昨年のRailsカンファレンスでもIronRubyでrailsを動かしていますが、日本では情報が少ないように感じています。是非、皆さんも動かしてみてください。
追記:WEBrickへのパッチを投入するファイル名を追加しました。