SSブログ
前の10件 | -

日付け [雑談]

日付けの書式って、何種類くらいあると思います?ここで言う書式には、和暦や June 15, 2009 のようなものは含まず、年、月、日を区切り記号で区切っただけのようなものを指します。

単純に並べ方だけを見ると、年月日、月日年、日月年の 3 通りしかないので、区切り記号に色々使われたとしても、両手で収まる程度だと思っていたのだけど、、、。

.NET Framework で利用可能な CultureInfo を使って 2017 年 1 月 7 日の書式を出力させてみたら、こんなにあった。


01/07/2017
ロケールに依存しない言語 (ロケールに依存しない国)[]
07.01.17
ベラルーシ語 (ベラルーシ)[be-BY], バシュキール語 (ロシア)[ba-RU], ルクセングルグ語 (ルクセンブルグ)[lb-LU]
07.01.17 ý.
トルクメン語 (トルクメニスタン)[tk-TM]
07.01.2017
ドイツ語 (ドイツ)[de-DE], ノルウェー語、ブークモール (ノルウェー)[nb-NO], ルーマニア語 (ルーマニア)[ro-RO], ロシア語 (ロシア)[ru-RU], ウクライナ語 (ウクライナ)[uk-UA], タジク語 (キリル、タジキスタン)[tg-Cyrl-TJ], アルメニア語 (アルメニア)[hy-AM], アゼルバイジャン語 (ラテン、アゼルバイジャン)[az-Latn-AZ], マケドニア語 (マケドニア旧ユーゴスラビア共和国)[mk-MK], グルジア語 (ジョージア)[ka-GE], 北サーミ語 (ノルウェー)[se-NO], ウズベク語 (ラテン、ウズベキスタン)[uz-Latn-UZ], タタール語 (ロシア)[tt-RU], サハ語 (ロシア)[sah-RU], ドイツ語 (スイス)[de-CH], イタリア語 (スイス)[it-CH], ノルウェー語 ニーノシク (ノルウェー)[nn-NO], アゼルバイジャン語 (キリル、アゼルバイジャン)[az-Cyrl-AZ], ウズベク語 (キリル、ウズベキスタン)[uz-Cyrl-UZ], ドイツ語 (オーストリア)[de-AT], ドイツ語 (ルクセンブルグ)[de-LU], フランス語 (スイス)[fr-CH], ルレ サーミ語 (ノルウェー)[smj-NO], ドイツ語 (リヒテンシュタイン)[de-LI], 南サーミ語 (ノルウェー)[sma-NO]
07.01.2017.
ラトビア語 (ラトビア)[lv-LV]
07/01/17
セツワナ語 (南アフリカ)[tn-ZA], ウェールズ語 (英国)[cy-GB], ディヘビ語 (モルディブ)[dv-MV], セソト サ レボア語 (南アフリカ)[nso-ZA], バレンシア語 (スペイン)[ca-ES-valencia], セツワナ語 (ボツワナ)[tn-BW]
07/01/2017
カタルニア語 (カタルニア)[ca-ES], フランス語 (フランス)[fr-FR], ヘブライ語 (イスラエル)[he-IL], イタリア語 (イタリア)[it-IT], ポルトガル語 (ブラジル)[pt-BR], ウルドゥー語 (パキスタン・イスラム共和国)[ur-PK], インドネシア語 (インドネシア)[id-ID], ペルシャ語 (イラン)[fa-IR], ベトナム語 (ベトナム)[vi-VN], マルタ語 (マルタ)[mt-MT], マレー語 (マレーシア)[ms-MY], ラオス語 (ラオス人民民主共和国)[lo-LA], ガリシア語 (ガリシア)[gl-ES], シリア語 (シリア)[syr-SY], ケチュア語 (ボリビア)[quz-BO], ブルトン語 (フランス)[br-FR], マオリ語 (ニュージーランド)[mi-NZ], オクシタン語 (フランス)[oc-FR], コルシカ語 (フランス)[co-FR], アルザス語 (フランス)[gsw-FR], キチェ語 (グアテマラ)[qut-GT], ウォロフ語 (セネガル)[wo-SN], スコットランド ゲール語 (英国)[gd-GB], アラビア語 (イラク)[ar-IQ], 英語 (英国)[en-GB], スペイン語 (メキシコ)[es-MX], ポルトガル語 (ポルトガル)[pt-PT], アイルランド語 (アイルランド)[ga-IE], マレー語 (ブルネイ・ダルサラーム国)[ms-BN], シンド語 (パキスタン・イスラム共和国)[sd-Arab-PK], フラニ語 (ラテン、セネガル)[ff-Latn-SN], ケチュア語 (エクアドル)[quz-EC], アラビア語 (エジプト)[ar-EG], スペイン語 (スペイン)[es-ES], ケチュア語 (ペルー)[quz-PE], アラビア語 (リビア)[ar-LY], スペイン語 (グアテマラ)[es-GT], スペイン語 (コスタリカ)[es-CR], フランス語 (ルクセンブルグ)[fr-LU], 英語 (アイルランド)[en-IE], フランス語 (モナコ)[fr-MC], アラビア語 (オマーン)[ar-OM], 英語 (ジャマイカ)[en-JM], アラビア語 (イエメン)[ar-YE], 英語 (カリブ)[en-029], スペイン語 (コロンビア)[es-CO], アラビア語 (シリア)[ar-SY], 英語 (ベリーズ)[en-BZ], スペイン語 (ペルー)[es-PE], アラビア語 (ヨルダン)[ar-JO], 英語 (トリニダード・トバゴ)[en-TT], スペイン語 (アルゼンチン)[es-AR], アラビア語 (レバノン)[ar-LB], 英語 (ジンバブエ)[en-ZW], スペイン語 (エクアドル)[es-EC], アラビア語 (クウェート)[ar-KW], アラビア語 (アラブ首長国連邦)[ar-AE], スペイン語 (ウルグアイ)[es-UY], アラビア語 (バーレーン)[ar-BH], スペイン語 (パラグアイ)[es-PY], アラビア語 (カタール)[ar-QA], スペイン語 (ボリビア)[es-BO], スペイン語 (エルサルバドル)[es-SV], スペイン語 (ホンジュラス)[es-HN], スペイン語 (ニカラグア)[es-NI], スペイン語 (プエルトリコ)[es-PR]
07-01-17
バングラ語 (インド)[bn-IN], パンジャーブ語 (インド)[pa-IN], グジャラート語 (インド)[gu-IN], オディア語 (インド)[or-IN], テルグ語 (インド)[te-IN], カナラ語 (インド)[kn-IN], マラヤラム語 (インド)[ml-IN], フランス語 (ベルギー)[fr-BE], バングラ語 (バングラデシュ)[bn-BD], パンジャブ語 (パキスタン・イスラム共和国)[pa-Arab-PK]
07-01-2017
デンマーク語 (デンマーク)[da-DK], ロマンシュ語 (スイス)[rm-CH], ズールー語 (南アフリカ)[zu-ZA], フェロー語 (フェロー諸島)[fo-FO], ヒンディー語 (インド)[hi-IN], タミール語 (インド)[ta-IN], アッサム語 (インド)[as-IN], マラーティー語 (インド)[mr-IN], サンスクリット語 (インド)[sa-IN], コンカニ語 (インド)[kok-IN], グリーンランド語 (グリーンランド)[kl-GL], マプドゥングン語 (チリ)[arn-CL], タミール語 (スリランカ)[ta-LK], タマジット語 (ラテン、アルジェリア)[tzm-Latn-DZ], 中央アトラス タマジット語 (ティフィナグ、モロッコ)[tzm-Tfng-MA], アラビア語 (アルジェリア)[ar-DZ], アラビア語 (モロッコ)[ar-MA], アラビア語 (チュニジア)[ar-TN], スペイン語 (ベネズエラ ボリバル共和国)[es-VE], スペイン語 (チリ)[es-CL], 英語 (インド)[en-IN]
09/04/38
アラビア語 (サウジアラビア)[ar-SA]
1/7/2017
英語 (米国)[en-US], スワヒリ語 (ケニア)[sw-KE], チェロキー語 (チェロキー)[chr-Cher-US], ネパール語 (ネパール)[ne-NP], フィリピノ語 (フィリピン)[fil-PH], ハワイ語 (米国)[haw-US], モホーク語 (モホーク)[moh-CA], 英語 (フィリピン共和国)[en-PH], スペイン語 (米国)[es-US]
1438/4/9
パシュトゥー語 (アフガニスタン)[ps-AF], ダリー語 (アフガニスタン)[prs-AF]
2017.01.07.
ハンガリー語 (ハンガリー)[hu-HU]
2017/01/07
日本語 (日本)[ja-JP], バスク語 (バスク)[eu-ES], コサ語 (南アフリカ)[xh-ZA], アフリカーンス語 (南アフリカ)[af-ZA], 中央クルド語 (イラク)[ku-Arab-IQ]
2017/1/7
中国語 (繁体字、台湾)[zh-TW], チベット語 (中国)[bo-CN], イ語 (中国)[ii-CN], 中国語 (簡体字、中国)[zh-CN], モンゴル語 (伝統的モンゴル文字、中国)[mn-Mong-CN]
2017-01-07
韓国語 (韓国)[ko-KR], ポーランド語 (ポーランド)[pl-PL], スウェーデン語 (スウェーデン)[sv-SE], リトアニア語 (リトアニア)[lt-LT], モンゴル語 (キリル、モンゴル)[mn-MN], クメール語 (カンボジア)[km-KH], シンハラ語 (スリランカ)[si-LK], 北サーミ語 (スウェーデン)[se-SE], フランス語 (カナダ)[fr-CA], 英語 (カナダ)[en-CA], ルレ サーミ語 (スウェーデン)[smj-SE], 英語 (南アフリカ)[en-ZA], 南サーミ語 (スウェーデン)[sma-SE]
2017-1-7
ウイグル語 (中国)[ug-CN]
7. 1. 2017
チェコ語 (チェコ共和国)[cs-CZ], 上ソルブ語 (ドイツ)[hsb-DE], 下ソルブ語 (ドイツ)[dsb-DE]
7.01.2017
エストニア語 (エストニア)[et-EE]
7.1.2017
フィンランド語 (フィンランド)[fi-FI], アイスランド語 (アイスランド)[is-IS], スロバキア語 (スロバキア)[sk-SK], アルバニア語 (アルバニア)[sq-AL], トルコ語 (トルコ)[tr-TR], スロベニア語 (スロベニア)[sl-SI], スウェーデン語 (フィンランド)[sv-FI], 北サーミ語 (フィンランド)[se-FI], ボスニア語 (ラテン、ボスニア ヘルツェゴビナ)[bs-Latn-BA], ボスニア語 (キリル、ボスニア ヘルツェゴビナ)[bs-Cyrl-BA], スコルト サーミ語 (フィンランド)[sms-FI], イナリ サーミ語 (フィンランド)[smn-FI]
7.1.2017 г.
ブルガリア語 (ブルガリア)[bg-BG]
7.1.2017.
クロアチア語 (クロアチア)[hr-HR], セルビア語 (ラテン、セルビアおよびモンテネグロ (旧))[sr-Latn-CS], セルビア語 (キリル、セルビアおよびモンテネグロ (旧))[sr-Cyrl-CS], クロアチア語 (ラテン、ボスニア ヘルツェゴビナ)[hr-BA], セルビア語 (ラテン、ボスニア ヘルツェゴビナ)[sr-Latn-BA], セルビア語 (キリル、ボスニア ヘルツェゴビナ)[sr-Cyrl-BA], セルビア語 (ラテン、セルビア)[sr-Latn-RS], セルビア語 (キリル、セルビア)[sr-Cyrl-RS], セルビア語 (ラテン、モンテネグロ)[sr-Latn-ME], セルビア語 (キリル、モンテネグロ)[sr-Cyrl-ME]
7/01/2017
キニヤルワンダ語 (ルワンダ)[rw-RW], オランダ語 (ベルギー)[nl-BE], イヌクティトット語 (ラテン、カナダ)[iu-Latn-CA], 英語 (オーストラリア)[en-AU], 英語 (ニュージーランド)[en-NZ]
7/1/17
スペイン語 (パナマ)[es-PA], スペイン語 (ドミニカ共和国)[es-DO]
7/1/2017
ギリシャ語 (ギリシャ)[el-GR], イヌクティトット語 (カナダ音節文字、カナダ)[iu-Cans-CA], アムハラ語 (エチオピア)[am-ET], ハウサ語 (ラテン、ナイジェリア)[ha-Latn-NG], イボ語 (ナイジェリア)[ig-NG], ティグリニア語 (エチオピア)[ti-ET], ティグリニア語 (エリトリア)[ti-ER], 中国語 (繁体字、香港)[zh-HK], 中国語 (簡体字、シンガポール)[zh-SG], 中国語 (繁体字、マカオ)[zh-MO], 英語 (マレーシア)[en-MY], 英語 (シンガポール)[en-SG]
7/1/2560
タイ語 (タイ)[th-TH]
7-1-2017
オランダ語 (オランダ)[nl-NL], フリジア語 (オランダ)[fy-NL]
7-қаң-17
カザーフ語 (カザフスタン)[kk-KZ]
7-янв 17
キルギス語 (キルギス)[ky-KG]
9/4/1438
ヨルバ語 (ナイジェリア)[yo-NG]


なかには原型をとどめていないようなのもあり、ちょっとびっくり。


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 に対応することができない。


前の10件 | -

この広告は前回の更新から一定期間経過したブログに表示されています。更新すると自動で解除されます。