So-net無料ブログ作成
検索選択
前の10件 | -

Entity Framework - 3 [Programming]

私の場合、Entity Framework に興味があって手を出したわけではなく、単に LINQ to SQL に代わりに使える環境が欲しかったので勉強を始めてみたというだけのものだった。LINQ to SQL が SQL Server に特化したアプローチだったのに対して、Entity Framework が他のデータベースにも対応可能になるということで、乗り換えが促されている感じがする。ただし、現時点では OLE DB や ODBC には対応していないために、コストをかけずに(これが大事) LINQ を体験するには SQL Server を導入する以外に選択肢がない。なので、なんのための Entity Framework なのか今一スッキリしないところもある。既存のデータベースにアクセスしたいならコードファーストなんてアプローチはとりあえず「暇があったら見てみてもいい」程度の進化でしかないしねぇ。

Entity Framework に対応した LINQ to Entities も利用可能ではあるけれど、LINQ to SQL に対し削除されてしまった機能があるのがつらい。今の段階(とりあえず、利用可能な環境を広めたい?)では SQL Server 固有の機能を盛り込むのは難しいのかもしれないけど、完全な互換性を保っていないというのは、かなりの減点対象になると思う。

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

SQL Server (LINQ to SQL) を利用している限りは(DataContext の構築は LINQ to SQL 固有のアプローチですが)普通に利用可能な構文だけど、このまま LINQ to Entities で利用しようとすると、実行時にエラーが発生してしまう。ワイルドカードがの構文が異なるとかいう理由ではなく、Like 演算子が利用できないという理由らしいので、ビルド時にエラーしてくれれば多少は許せるのだけれど、、、。


Entity Framework - 2 [Programming]

プログラムでデータベースを扱うようになると、ほぼ確実にお目にかかるのが Join。

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

型付けされていない Data Set を使用しているので若干ながくなっているけど、動作は雰囲気で読み取れると思う。これを LINQ を使って書き直すとこうなる。


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

LINQ を利用すると Join 句を消し去ることができることがある。代わりに登場するのがコレクションを介して参照先のテーブルアクセスするコード。完全に同じ動作になるわけではないけど、SQL が生理的に馴染まないというプログラマにはお勧めの構文になる。

ソースコードからは読み取れないのだけど、データベースから ADO.NET Entity Data Model を作成することでこのようなカラクリが埋め込まれる。

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 [雑談]

最近のプログラミング言語では論理値(あるいは真理値)を表す型がサポートされている。以前は整数型で代用されることがあったけれど、つまらないバグを生むからという理由だろうか、整数型とは互換性のない(相互にキャストすることができない)型として定義されるようになってきている。

Windows の API は C 言語をベースに作られているために、そもそも論理値という考え方がない。API の呼び出し結果が成功 or 失敗を返す際、失敗なら FALSE、成功なら FALSE 以外を返すというパターンが殆どで、思いつく限り成功時に TRUE を返すというものはない。

なので

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 のようなシンボルが割り当てられているのだけれど、 一部の仕様が変わった影響なのかシンボルが割り当てられていないコードを使用することもある。

以下のようなイメージがあり、これを合成することを考える。左から順番にブラシに割り当てるパターン(背景と同化してしまって解りづらいけど、左半分が黒、右半分が白のパターンです)、転送元ビットマップに割り当てる花の絵、転送先ビットマップに割り当てるボタンの絵となっている。

pattern.pngsource.pngdestination.png 


ラスターオペレーションを使用するには真理値表を読み解く必要がある、、、というか、その場で書けるようにする事が肝心。以下、3 次ラスターオペレーション コード(ROP Code)を求める方法。


  1. とりあえず表から作る。手書きでもいいし、Excel を使ってもいい。タイトル行には左から P(Pattern の P) , S(Source の S) , D(Destination の D) , ROP と書き込んでいく。2 行目以降は P , S , D の下に 000 , 001 , 010 というように 2 進数の各桁を書き込んでいく。3 桁しかないので 8 通りの行データが出来上がれば正解。
  2. パターン 黒
    パターンが黒(P が 0)の部分(表の上半分)は転送先ビットマップの値を残すものとする。そこで、ROP 列には D 列の値を書き込んでやる。
  3. パターン 白
    パターンが白(P が 1)の部分(表の下半分)は転送元ビットマップの値を転送するようにする。ROP 列には S 列の値を書き込むようにする。
  4. 出来上がり
    出来上がった ROP 列の値を下から読み上げたものはオペレーションインデックスと呼ばれる。今回の場合は 1 1 0 0 1 0 1 0 となっているはず。

オペレーションインデックスをそのまま BitBlt 等の ROP Code として与えてやれればいいのだけど、実はひと手間が必要。オペレーションインデックスから ROP Code を求めてやらないといけない。一覧はココにあるので必要なら参照してください。

真理値表の値だけで結果が想像できる人も多くはないと思うので、結果のイメージを張っておきます。 

ROP.png

 


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 がうまいこと処理していてくれていればアプリケーションレベルで対処する必要もないのだけれど、 やってくれないなら自分でやるしかない。そして、定型的な手順になるので何度か実践しているうちに慣れるのも早いだろうし、採用したことによる効果もほぼ問題ないレベルになる。

ダブルバッファリングを用いた描画の手順は以下の通りになる。

 

  1. WM_PAINT をメッセージを受けたら描画を開始する。Win32 API を利用する場合は BeginPaint の発行、MFC を利用する場合は CPaintDC の構築を行い デバイスコンテキストを取得する。
  2.  1. で作成したデバイスコンテキストのコピーを作成する。同様にビットマップを作成して、コピーに選択する。
  3. 2. で作成したデバイスコンテキストで描画を実行する。
  4. コピーに作った描画イメージを、1. で取得したデバイスコンテキストに転送する。多くの場合、BitBlt によるイメージの転送になる。
  5. 描画の終了。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 に対応することができない。


Windows - 1 [Programming]

以前は、Windows ネイティブの開発が主だったのだけれど、最近は .NET Framework や Web アプリケーションのような(直接)Windows に関わらないような業務が増えてきている。開発環境としては Eclipse を使うようにもなってきたんだけど、やっぱり Visual Studio が馴染む感じがある。最近の Visual Studio は .NET Framework 以外の環境にも対応するようになってきているので、提供される全ての機能を把握するのは難しい(無理?)というのが率直な意見だったりもする。

休日に何の気もなしに Visual Studio を弄っていたら、 MFC(Microsoft Foundation Classes) のアプリケーションのサポートが続いていることが確認できた。MFC を現役で扱っていたのが Windows XP 全盛の頃(西暦 2000 年前後)なので相当の年月が経過しているのを実感した。

実業務とはちょっと離れ、あくまでも趣味のプログラミングということでネイティブな Windows プログラムを作ってみようと思う。素材はデスクトップに置くマスコット。ただの癒しではなく、ちょっとしたユーティリティとしての機能を盛り込んだものにするつもり。

以前、Windows XP 環境で使用する個人用のユーティリティとして作ったアプリケーションのリメイク版だったりする。当時の実行ファイルは現環境(Windows 10)でも動作可能であることは確認できているのだけれど、多くの外部ライブラリに依存してしまっている関係で再ビルドができなくなってしまっていて機能追加ができない。そこで、外部の依存関係を排除し作り直してみようと思う。

久しぶりに MFC アプリケーションのスケルトンを見た感じは、「懐かしい」の一言。.NET Framework のようなライブラリに比べると設計が古い感じは否めないし、提供される機能は少ない。多くのことを自分で構築しなければならないので、作業効率を考えたら今更感が先行してしまう。

現役だった頃は把握していたつもりのノウハウや制限に関して、年月の経過とともに忘却の彼方となってしまったことも含め、新しいモノに触れたような感触が楽しかったりする。

仕事から帰った後の趣味のプログラミング、サンデープログラマをしばらく楽しんでみようと思う。 


前の10件 | -