日付け [雑談]
Entity Framework - 3 [Programming]
Sub Main() Console.OutputEncoding = System.Text.Encoding.GetEncoding(932) Using ex = New 駅データDataContext() Dim query = From s In ex.Stations Where s.Line.line_name Like "*京浜東北線*" Order By s.station_cd Select New With {s.station_name} For Each item In query Console.WriteLine(String.Format("{0}", item.station_name)) Next End Using End Sub
Entity Framework - 2 [Programming]
Sub Main() Console.OutputEncoding = System.Text.Encoding.GetEncoding(932) Console.WriteLine("vb01") Using table As New DataTable() Using con As New SqlClient.SqlConnection(My.Settings.駅データ) Using cmd As New SqlClient.SqlCommand( "SELECT s.station_name " & "FROM station AS s " & "JOIN line AS l ON s.line_cd = l.line_cd " & "WHERE l.line_name = @name " & "ORDER BY s.station_cd", con ) cmd.Parameters.Add(New SqlClient.SqlParameter("@name", "JR京浜東北線")) Using adp As New SqlClient.SqlDataAdapter(cmd) adp.Fill(table) End Using End Using End Using For Each item In table.Rows Console.WriteLine(String.Format("{0}", item("station_name"))) Next End Using End Sub
Sub Main() Console.OutputEncoding = System.Text.Encoding.GetEncoding(932) Console.WriteLine("vb03") Using ex = New 駅データEntities() Dim query = From s In ex.Stations Join l In ex.Lines On s.line_cd Equals l.line_cd Where l.line_name = "JR京浜東北線" Order By s.station_cd Select New With {s.station_name} For Each item In query Console.WriteLine(String.Format("{0}", item.station_name)) Next End Using End Sub
Sub Main()
Console.OutputEncoding = System.Text.Encoding.GetEncoding(932)
Console.WriteLine("vb03")
Using ex = New 駅データEntities()
Dim query =
From s In ex.Stations
Where s.Line.line_name = "JR京浜東北線"
Order By s.station_cd
Select New With {s.station_name}
For Each item In query
Console.WriteLine(String.Format("{0}", item.station_name))
Next
End Using
End Sub
Entity Framework - 1 [Programming]
.NET Framework 上で動作させるアプリケーションでデータベースを扱うなら、大抵は ADO.NET を利用することになる。ただ、ADO.NET にもバリエーションがあり、微妙に異なる構文を覚えていかないとならない。
ADO.NET の古典的な利用方法はコレ。
Sub Main() Console.OutputEncoding = Text.Encoding.GetEncoding(932) Using table As New 駅データ.StationDataTable() Using adp As New 駅データTableAdapters.StationTableAdapter() adp.FillByLineCode(table, 11103) End Using For Each item In table Console.WriteLine(String.Format("{0}:{1}", item.station_cd, item.station_name)) Next End Using End Sub
アダプタ(StationTableAdapter)やらデータセット(StationDataTable)を別に定義することになるので、ソースコード自体は結構すっきりしている。
型付けされていない Data Set を使用するならこうなる。
Sub Main() Console.OutputEncoding = System.Text.Encoding.GetEncoding(932) Using table As New DataTable() Using con As New SqlClient.SqlConnection(My.Settings.駅データ) Using cmd As New SqlClient.SqlCommand("SELECT * FROM station WHERE line_cd=@code ORDER BY station_cd", con) cmd.Parameters.Add(New SqlClient.SqlParameter("@code", 11103)) Using adp As New SqlClient.SqlDataAdapter(cmd) adp.Fill(table) End Using End Using End Using For Each item In table.Rows Console.WriteLine(String.Format("{0}:{1}", item("station_cd"), item("station_name"))) Next End Using End Sub
Visual Basic では Using を使用するたびにネストが増えてしまうので、異様に深いネストが気になることがある。状況によっては SqlConnection や SqlCommand を使用しないことも可能だけれど、パラメータクエリを利用しようとするとどうしても長くなってしまう。
最後は Entity Framework。LINQ to Entity が利用できるのが最大の特徴。
Sub Main() Console.OutputEncoding = System.Text.Encoding.GetEncoding(932) Using ex = New 駅データEntities() Dim query = From x In ex.Stations Where x.line_cd = 11103 Order By x.station_cd For Each item In query Console.WriteLine(String.Format("{0}:{1}", item.station_cd, item.station_name)) Next End Using End Sub
SQL が完全に姿を消すので個人的に一番気に入ってはいるのだけれど、既存のシステムのバージョンアップのような場合にはなかなか適用するのが難しい。
Boolean [雑談]
if (AnyFunction() == TRUE) { // 成功した場合の処理 }
なんて記述してしまうと面倒なことになりかねない。論理値を扱えるならこんなことはない(false でなければ true しかない)のだけれど、昔のイヤな経験がトラウマになっているのか、今ひとつしっくりこない感じがある。
※ FALSE を「ファルス」と発音するエンジニアは、それだけで信用したくない。
Windows 6 - Raster Operation [Programming]
Windows API にはイメージのブロック転送の機能が備わっている。ビットマップとブラシを使用する PatBlt、ビットマップ 2 対とブラシを使用する BitBlt、ビットマップ 2 対とブラシ、マスク用のビットマップを使用する MaskBlt 等が存在している。また、イメージの拡縮や変形をサポートする亜種もある。
ブロック転送の際にラスターオペレーションと呼ばれる演算を施すことが可能で、簡単なイメージの合成等ができるようになっている。上述の PatBlt に使用されるラスターオペレーションは 16 種の操作が利用できる 2 次ラスターオペレーション(Binary Raster Operation)、これ以外のものは 256 種類の操作が利用できる 3 次ラスターオペレーション(Ternary Raster Operation)と呼ばれる。
MaskBlt のラスターオペレーションは 4 次ではなく、3 次ラスターオペレーションを 2 種類使用する。
3 次ラスターオペレーションでは 256 種類の操作が可能とは書いたけど、すべてが実用的な操作かと言うと、そんなこともない。多く使われるのは単純なコピーを表す SRCCOPY とかベタ塗りの WHITENESS や BLACKNESS といった単純なものだろう。実用的なものの多くは SRCCOPY のようなシンボルが割り当てられているのだけれど、 一部の仕様が変わった影響なのかシンボルが割り当てられていないコードを使用することもある。
以下のようなイメージがあり、これを合成することを考える。左から順番にブラシに割り当てるパターン(背景と同化してしまって解りづらいけど、左半分が黒、右半分が白のパターンです)、転送元ビットマップに割り当てる花の絵、転送先ビットマップに割り当てるボタンの絵となっている。
ラスターオペレーションを使用するには真理値表を読み解く必要がある、、、というか、その場で書けるようにする事が肝心。以下、3 次ラスターオペレーション コード(ROP Code)を求める方法。
- 表
とりあえず表から作る。手書きでもいいし、Excel を使ってもいい。タイトル行には左から P(Pattern の P) , S(Source の S) , D(Destination の D) , ROP と書き込んでいく。2 行目以降は P , S , D の下に 000 , 001 , 010 というように 2 進数の各桁を書き込んでいく。3 桁しかないので 8 通りの行データが出来上がれば正解。 - パターン 黒
パターンが黒(P が 0)の部分(表の上半分)は転送先ビットマップの値を残すものとする。そこで、ROP 列には D 列の値を書き込んでやる。 - パターン 白
パターンが白(P が 1)の部分(表の下半分)は転送元ビットマップの値を転送するようにする。ROP 列には S 列の値を書き込むようにする。 - 出来上がり
出来上がった ROP 列の値を下から読み上げたものはオペレーションインデックスと呼ばれる。今回の場合は 1 1 0 0 1 0 1 0 となっているはず。
オペレーションインデックスをそのまま BitBlt 等の ROP Code として与えてやれればいいのだけど、実はひと手間が必要。オペレーションインデックスから ROP Code を求めてやらないといけない。一覧はココにあるので必要なら参照してください。
真理値表の値だけで結果が想像できる人も多くはないと思うので、結果のイメージを張っておきます。
Windows - 5 SetWindowsHookEx [Programming]
Win32 API のなかで、怪しそうな仕様の筆頭って言ったらやっぱりコレ。知らなければ知らないで済むだろうけど、一度知ってしまうと全てコレで賄えちゃうんじゃないかって思うほど。Windows Hook と呼ばれるこの機能を要約すると、「スレッドに投げられたメッセージを、そっと抜き取る」盗聴器のようなものということになる。
Windows Hook の形態は 2 つあり、自分自身に仕掛けるもの(スレッドフック)と、システム全体に仕掛けるもの(グローバルフック)に分類される。 特定のスレッドに仕掛けるものがあったら便利そうだとも思うのだけど、残念ながらその選択肢は認められていない。
Windows Hook を利用するためには、知らなければならないものがいくつかある。 ひとつはコールバック関数。Windows のプログラミンをしていると結構出てくるのでご存知の方も多いとは思うけど、Windows Hook でも利用することになる。このコールバック関数で抜き取ったメッセージに対する処理を記述する。もうひとつは、DLL の作成方法。前述のスレッドフックの場合には不要なのだけど、グローバルフックを利用したいなら必須のスキルになる。できれば共有セクションのことも知っておくと便利に使えるようになる。DLL に上述のコールバック関数を仕込んで、稼働中のスレッドのメッセージキューに仕掛けるという事を行う。
今回は、簡単なスレッドフックの使い方。メッセージボックスの出現位置を変更してみる。元ネタはこちら。Visual Basic のコードを C++ で書き換えてみた。
static HHOOK Hook = ::SetWindowsHookEx(
WH_CBT,
[](int nCode, WPARAM wParam, LPARAM lParam)->LRESULT {
if (nCode==HCBT_ACTIVATE) {
CRect desktop, rect;
::SystemParametersInfo(SPI_GETWORKAREA, 0, &desktop, 0);
::GetWindowRect((HWND)wParam, &rect);
::SetWindowPos(
(HWND)wParam,
NULL,
(desktop.Width()-rect.Width())/2,
(desktop.Height()-rect.Height())/2,
0,
0,
SWP_NOZORDER|SWP_NOSIZE
);
::UnhookWindowsHookEx(Hook);
}
return ::CallNextHookEx(Hook, nCode, wParam, lParam);
},
NULL,
::GetCurrentThreadId()
);
::MessageBox(NULL, _T("Information"), _T("テスト"), MB_OK|MB_ICONINFORMATION);
最近の C++ ではラムダが使えるようになっているので、コールバック関数の部分に使ってみた。このコード片をコマンドハンドラにでも仕込んでやれば、メッセージボックスの出現位置を変更することができる。ココの表示エリアの制限があるので縦長になってしまっているけど、実際の処理は見た目ほど長くはない。
Windows - 4 Double Buffering [Programming]
どんなプラットフォームであっても、凝った絵を描こうと思ったらプラットフォーム特有のお作法を覚える必要がある。直感的にわかりやすいのはフレームバッファに直接イメージを書き込んでいくような初期の PC にあったような方式だろうけど、これだとテキストを描くとか背景を抜くといった簡単な機能でさえ自分でロジックを記述しなければならないので意外にスキルを求められる。その対極にあるのが 3D のレンダリング。フレームバッファ自体を操作することはほぼ不可能で、与えられた描画機能だけを用いて画面を作り上げていく。画面上に現れる建物や人といったオブジェクトを作成するのにもそれなりのツールやテクニックが必要となるので上記とは別の意味で敷居が高かったりする。
Windows の場合、マルチタスク OS の環境下で動作するアプリケーションは、デバイス(ハードウェア)を抽象化する GDI(Graphics Device Interface) と呼ばれるサブシステムの提供する機能を用いて描画を実行することになる。直接デバイスを扱わないというのは、利点であるとともに欠点となってしまうこともある。ゲームのようにどうしてもデバイスの機能を扱いたいという要望に対しては、Direct X と呼ばれるものを利用していくというのが、当時の Windows の流儀のひとつだった。
今回の話は、フォームやダイアログに既成のコントロールに描画を任せてしまう場合には殆ど関係のない話。
GDI を操作するための最初の関門は、いくつかの用語の意味を覚えるところにあった。その筆頭はデバイスコンテキスト、、、公式の文書はこれ。機械翻訳すると「デバイスコンテキストは、グラフィック・オブジェクトとそれらに関連する属性、出力に影響するグラフィックモードを定義する構造です。」な感じになるのだけれど、、、訳わからん。
意訳というか、ほぼ誤訳に近い感じになってしまうのだけど、「デバイスに依存しない描画機能の集合体」というのがその正体。「直線を引く」とか「図形を塗りつぶす」とか「文字列を書く」といった、描画機能を提供してくれているのがデバイスコンテキストになる。
次が GDI オブジェクト。GDI オブジェクトは、デバイスコンテキストに与えて、描画機能の特性を変更するためのオブジェクト。具体的には、「ペンの太さや色(Pen)」 とか「塗りつぶすパターン(Brush)」とか「文字フォント(Font)」といったものを表すオブジェクトのこと。ビットマップ(Bitmap) も GDI オブジェクトのひとつで、扱いとしてはフレームバッファに近いものだけど、実際は似非な存在。
2つめの関門は、自分の認識と GDI の実装とのギャップの大きさにある。GDI を使用した描画には「自分のタイミングで描画する事ができない」 という特性がある。具体的には、WM_PAINT メッセージに応答する形で描画を実行するという手続きを踏む。そして、ただ待っているだけでは WM_PAINT メッセージはやってこないので、再描画が必要となった領域を無効化することでメッセージを誘発するという手続きも必要になる。
実際の描画はこれらの要件(制約?)の下で実行して行くことになるのだけれど、これだけの情報で実践しても殆どの場合ガッカリするのがオチだったりする。Web でも「描画はできるようになったけど、チラツキが酷い。」とか「チラツキを抑えるためのテクニックを教えて」といった書き込みを結構見かける。
解ってしまえば別に難しいことでもないのだけど、教えてくれるひとが近くにいないと、なかなかゴールにたどり着けないかもしれない。
GDI を使った描画をする際、ダブルバッファリングの手法を用いる事がほぼ必須になる。オフスクリーンレンダリングなどとも呼ばれるこの手法、もともとはモニタの表示期間に描画を実行することを避けることで画面のチラツキを回避しようとするためのもので、直接 V-Sync 信号を扱う初期のパソコンや安価なゲーム機のプログラミングでは常套手段だった。
ハードウェアを直接操作することができない GDI を用いた描画では、ダブルバッファリングによって完全に問題が解決するわけではない。まあ、程度の問題ということになってしまうのだけど、動きのあるゲームのようなものでない限り、問題ないレベルに仕上がる手法ということになる。
GDI がうまいこと処理していてくれていればアプリケーションレベルで対処する必要もないのだけれど、 やってくれないなら自分でやるしかない。そして、定型的な手順になるので何度か実践しているうちに慣れるのも早いだろうし、採用したことによる効果もほぼ問題ないレベルになる。
ダブルバッファリングを用いた描画の手順は以下の通りになる。
- WM_PAINT をメッセージを受けたら描画を開始する。Win32 API を利用する場合は BeginPaint の発行、MFC を利用する場合は CPaintDC の構築を行い デバイスコンテキストを取得する。
- 1. で作成したデバイスコンテキストのコピーを作成する。同様にビットマップを作成して、コピーに選択する。
- 2. で作成したデバイスコンテキストで描画を実行する。
- コピーに作った描画イメージを、1. で取得したデバイスコンテキストに転送する。多くの場合、BitBlt によるイメージの転送になる。
- 描画の終了。Win32 API の場合は、EndPaint を呼び出す。MFC なら、スコープの消滅とともに適当に処理してくれるので、なにも考えなくていい。
ダブルバッファリングを使用しない場合と比べると、2. および 4. の手順が追加になる。チラツキの根本的な回避というわけではなく、要は 3. の処理時間よりも 4. の処理時間の方が圧倒的に短くなるため、結果としてチラツキが目立ちにくくなるだけというものだったりする。
Windows - 3 Agenda [Programming]
巨大なライブラリやフレームワークに依存しないプログラミングというのも結構楽しいかもしれない。ひと昔以上も前の記憶の断片と、殆ど更新されることのない古い公式ドキュメントを頼りに、効率を無視した作業を進めている。
そんな中、Windows のネイティブアプリケーション特有のノウハウが必要となることを想定している。具体的に挙げると「Double Buffering」、「Layered Window」、「Dynamic Subclassing/Owner Draw」、「Windows Hook」、「Raster Operation」、「Multi Threading」といったものが該当する。
一応、記憶の断片が残っているのでなんとかなるような気もするけど、細かいところは完全に忘却の彼方だったりもするので、英文ドキュメントと戦わなければならないかもしれない。以前参照していたドキュメントが姿を消していることも多く、意外に苦労しそうな予感もある。
とりあえず見た目から整えようということで始めた作業は、 ダイアログベースのアプリケーションを基に、非矩形のウィンドウを表示すること。Layered Window を適用することで背景を抜くことはできるのだけど、透明な背景越しに下のウィンドウに触れられるように小細工をすることにした。秒間 2 ~ 3 コマ程度の簡単なアニメーションをするイメージを使用するつもりなので、常時ウィンドウの形状を変化させることにも対応したい。さらに言えば、GPU のスペックに依存せず、CPU 負荷を極力抑えることを考慮する。
目標としてはリモートデスクトップ越しの操作(私の使用する環境がほぼコレ)であってもちらつかず、動作がスムーズになるようにしたい。また、クライアント PC、サーバー PC (ともに Windows マシン)に関わらず動作できることが条件。
Windows - 2 Shell Lightweight Utility [Programming]
久しぶりに Windows 向けのプログラミングをして感じたのは、「結構変わったなぁ」ということ。Windows 7 以降のプラットフォーム向けに作ったのは殆どすべてが .NET ramework 上のものだったので、Win32 API を直接弄ることがなく、情報を集めていなかったのが原因のひとつだと思う。
Windows の環境には Shell Lightweight Utility という名の、ユーティリティ関数群が提供されている。これはパスやレジストリのような Windows 環境独自のリソースを扱うためのもの。
Windows の仕様に合わせた操作を提供してくれているので、適当に手書きしたコードを書くよりは絶対に安全だ、、、と思って現役時代は多用していた。
久しぶりにこのページ(https://msdn.microsoft.com/en-us/library/windows/desktop/bb773559(v=vs.85).aspx)を見たら、とんでもないことになっていた。
全てではないのだけれど、「誤って使用すると、バッファオーバーランが発生することがあります。」 という注意書きがある。
Visual Studio 2015 に含まれているヘッダーでは、以前の関数たちには deprecated とマークされている。 使用頻度の高そうなものはほぼ全滅しているようなので、必要なら差し替えた方がいいのかもしれない。
ただ、リプレイスされたバージョンのものは Windows 8/Windows Server 2012 以降のプラットフォームにしか対応していない模様。Windows Vista や Windows 7 に対応することができない。