(2013/03/01~)
2013/03/08

Lenovo Thinkpad Tablet2 で使えるセンサー一覧

 http://shopap.lenovo.com/jp/products/tablets/thinkpad2/

センサー名 カテゴリ 説明
Broadcom GNSS Geolocation Sensor Location GNSS (GPS)
簡易デバイス方向センサー Orientation
HID センサー コレクション: Custom Motion
HID センサー コレクション: Accelerometer Motion 加速度センサー
HID センサー コレクション: Gyrometer Motion ジャイロセンサー
HID センサー コレクション: Compass Orientation コンパスセンサー
HID センサー コレクション: Inclinometer Orientation 傾きセンサー
HID センサー コレクション: Orientation Orientation 方向センサー
HID センサー コレクション: Ambient Light Light 環境光センサー

 Delphi XE3 のセンサー機能を使って取得した生ログは次の通り。

Broadcom GNSS Geolocation Sensor(Category: Location)
簡易デバイス方向センサー(Category: Orientation)
HID センサー コレクション: Custom(Category: Motion)
HID センサー コレクション: Accelerometer(Category: Motion)
HID センサー コレクション: Gyrometer(Category: Motion)
HID センサー コレクション: Compass(Category: Orientation)
HID センサー コレクション: Inclinometer(Category: Orientation)
HID センサー コレクション: Orientation(Category: Orientation)
HID センサー コレクション: Ambient Light(Category: Light)

 利用可能なすべてのセンサーを列挙するコードは次の通り。

uses
  ..., System.TypInfo, System.Sensors;

procedure TForm1.Button1Click(Sender: TObject);
var
  Manager: TSensorManager;
  Sensor: TCustomSensor;
  i: Integer;
begin
  Memo1.Lines.Clear;
  Manager := TSensorManager.Current;
  Manager.Active := True;
  // すべてのセンサーを列挙する
  for i:=0 to Manager.Count-1 do
    begin
      Sensor := Manager.Sensors[i];
      Memo1.Lines.Add(Sensor.Name + 
       Format('(Category: %s)', [GetEnumName(System.TypeInfo(TSensorCategory), Ord(Sensor.Category))]));
    end;
  Manager.Active := False;
end;

 VCL でも FMX でもコードは同じ。これだけセンサーが揃っていればかなり遊べると思う。


2013/03/11

IBConsole 日本語版+α Windows Unicode Edition rel.28 / ANSI Edition rel.67 (但し人柱専用)

 できました。人柱バージョンの概略はこちらからどうぞ。

 今の所人柱機能に文句を言ってくるヒトが居ない (使われてないのかもしれませんが) ので、折をみてジャンクボックスに移動します。

 本バージョンはメンテナンスビルドです。UAC / WOW64 環境下で正常動作しない可能性があるのを潰してみたつもりです。様々な環境で試した訳ではないので、何とも微妙な表現になっている事をご容赦ください。


2013/03/14

iso-2022-jp を Unicode 変換すると機種依存文字が失われる

 元ネタは以下。

 iso-2022-jp で半角カナや機種依存文字が使えないのは認識していたけれど、CP932 (Shift_JIS) から Unicode へはちゃんと変換されるのだし、ANSI Delphi ではうまく行くのならどうにかすればどうにかなるハズだと思った。iso-2022-jp も Shift_JIS も 同じ JIS X 0208 を文字集合としているのだしね。

 free-ml では半角数字を詰めてくれちゃうのでテキストエディタにコピーして加工したりしてたら思いのほか誤字脱字を量産してしまった。動くのは動くのでサンプルとしてはいいのだろうけれど、気持ち悪いので最終的なコードを書き直してみた。

uses
  ..., EncdDecd, SkregularExpressions, uIso2022jpEncoding; // XE の RegularExpressions で名前付きグループを使用するとエラーになる事があります

procedure TForm1.Button1Click(Sender: TObject);
const
  Exp = '=\?(?<CHARSET>.+)\?(?<ENCODING>.+)\?(?<ENCTEXT>.+)\?=';
var
  Buf: TBytes;
  Match: TMatch;
  sHeader, sCharSet, sEncoding, sEncText: string;
begin
  // Edit1 にある MIME BASE64 文字列を変換し、Edit2 に返す
  sHeader := Edit1.Text;
  Match := TRegEx.Match(sHeader, Exp);
  if not Match.Success then
    Exit;
  sCharSet  := Match.Groups.Item['CHARSET' ].Value;
  sEncoding := Match.Groups.Item['ENCODING'].Value; // B(BASE64) or Q(Quotable-Printed)
  sEncText  := Match.Groups.Item['ENCTEXT' ].Value;
  Buf := DecodeBASE64(sEncText);
  with TIso2022jpEncoding.Create do
    begin
      Edit2.Text := GetString(Buf);
      Free;
    end;
end;

 このコードは、

  1. "=?iso-2022-jp?B?GyRCOzN5dTlieXU1XHl1GyhC?=" のような文字列から BASE64 化された文字列を正規表現で抜き出し
  2. ...たいのだけれど、XE の RegularExpressions では名前付きグループがうまく動かないことがある (QC#93333) ので、SkRegExp を使い、
    (上記コードではエラーにはならないと思います)
  3. 抜き出した BASE64 文字列を EncdDecd.DecodeBASE64() で TBytes に変換したもの (iso-2022-jp バイト列) を
  4. TIso2022jpEncoding で iso-2022-jp -> Shift_JIS -> Unicode (UTF-16LE) に変換

 という事を行っている。やっている処理の割にはスッキリとした記述になったと思う (やろうと思えばもっと短くできるけど)。ここで使われている TIso2022jpEncoding は iso-2022-jp <-> Shift_JIS <-> Unicode (UTF-16LE) するためのエンコーディングクラス。思いっきり totonica 氏作の CP51932Encoding のパクリである (w

 山本隆さんの "全角チルダ/波ダッシュ問題に対応したTEncodingクラス" とマージしてみても面白いと思う。

 余談だけれども、上に書いたように Delphi 標準の正規表現はバージョンによって挙動が異なるので、それぞれのバージョンで正規表現を試した方がいい (特に XE)。SkRegExp ならば問題があっても入れ替えればバージョンを揃えて挙動を一致させる事ができるため、僕自身は SkRegExp を使うことが多い。

 PerlRegExp Trainer には XE / XE2 / XE3 でコンパイルした 3 つのバイナリが含まれている。左が PerlRegExp Trainer (XE バイナリ) で、右が SKRegExp Trainer

Windows 7 以降の OS ではアプリケーションマニフェストを記述しないと Vista 互換モードで実行される

 元ネタはらいなタンさんの以下のツイート。

 正直知らんかった (w 丁度いい機会なので、Delphi VCL Tips にまとめてみた。

 幾つか手持ちのものでテストしてみたけれど、客観的にみて、体感できる程の効果が得られた気はしなかった。オーナードローしてるとか、FireMonkey アプリとかの方がネイティブモードのメリットを享受しやすいのではないかと思う...理屈的に。

 何が 「丁度いい機会」 なのかと言うと、BDE 関連の質問が続いたから。"BDE Administrator" や "Database Desktop" には、コレを参考にして requireAdministrator な外部マニフェストを置けばいいと思うの。内部マニフェストにするのは利用規約的にどうかと思うし。

 ただ、これをやったからといってアプリケーション側の BDE の問題がすべて解決するかと言えばそうではないのでご注意。

XN Resource Editor 3.0.0.1 [ja] rev.29

 関連して更新。

 変更点は以下の通り。

 アプリケーションマニフェスト絡みの軽微な変更です。

 XN Resource Editor を使えば、内部マニフェストを持たないアプリケーションにアプリケーションマニフェストを埋め込む事ができます ([リソース | リソースを追加 | XP Theme Manifest])。


2013/03/16

Manifest Ex

 アプリケーションマニフェスト の件を簡単に試すためのコンポーネント。64bit / FireMonkey (Windows) にも対応しています。

 パッケージが用意されているのは Delphi 2009 ~ XE3...Unicode 版 Delphi ですね。 Delphi 6 ~ XE3 です。これ以外の Delphi ではパッケージを自前で作るか、ユニットを既存のパッケージに追加してインストールする事になります。

 インストール手順に一部トリッキーな箇所がありますので、readme.txt はちゃんと読んでください

 インストールすると TVistaManifest / TVistaAdminManifest / TNativeManifest / TNativeAdminManifest が追加されます。TXPManifest / XPMan と同じ理屈なので、インストールせず VistaMan / VistaAdminMan / NativeMan / NativeAdminMan として使うこともできます。

コンポーネント ユニット レベル 説明
TXPManifest XPMan asInvoker (2007 以降の場合) Delphi 標準添付。FMX では使えない。
TVistaManifest VistaMan asInvoker 2007 以降では XPMan と同じ効果になる。
TVistaAdminManifest VistaAdminMan requireAdministrator 管理者権限への昇格プロンプトを出す。
TNativeManifest NativeMan asInvoker TVistaManifest の効果に加え、ネイティブモードで動作。
TNativeAdminManifest NativeAdminMan requireAdministrator TVistaAdminManifest の効果に加え、ネイティブモードで動作。

 コードが皆無のコンポーネントなのに、アイコンとパッケージ作成に手間を取られてしまいました。Delphi 2007 以前のパッケージがないのはそのためです (飽きましたw)。「パッケージ作ったよー」 という方がいらっしゃいましたらご連絡下さい...てか、作って下さいお願いします。

 現在アップされているものは twitter でツイートした時のものよりも新しく、小アイコン (16x16) の視認性が向上しています。

 上記コンポーネントを使う (または uses 節に追加する) 場合には、"ランタイムテーマを有効にする" をオフにしなくてはなりません。

 XPMan、VistaMan と来て、何故 SevenMan じゃないのかと思うかもしれませんが、そうなると次は "エイトマン" ですよ?どっかから訴えられたらどうしてくれるんですか!

SkregExp ver 2.3.0

 リリースされました。正規表現のメタ文字をエスケープする EscapeRegExChar() クラスメソッドが追加されています。

 使い方と存在理由は "文字列を正規表現形式にエスケープする - 正規表現の活用 (主に Delphi 2009 以降)" に書いてありますのでご一読下さい。

 従来からあった EncodeEscape() は、例えば () {} 等をエスケープしてくれませんが、これは元々そういった用途のために作られたものではないからだそうです。今回新しく追加された EscapeRegExChar() は Delphi XE 以降の TPerlRegEx.EscapeRegExChars() と互換性があります。


2013/03/17

「技の謎ときなど....余人にするべきものじゃないんじゃがの....」

 アプリケーションマニフェストはリソースとして実行ファイルに埋め込まれます (内部マニフェスト)。このリソースは下の画像のような構造になっています。

 リソーススクリプトファイルで書くと以下のようになります。

#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1
#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID 2
#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID 3
#define RT_MANIFEST 24
CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "マニフェストファイル名"

 RT_MANIFEST (24) がリソースタイプ "XP Theme Manifest" で、CREATEPROCESS_MANIFEST_RESOURCE_ID (1) が "1" に対応しています。リソースの種類と ID は固有でなくてはならず、重複するとコンパイラが "Duplicate Resource" というエラーを出すか、ワーニングにして重複リソースを自動で取り除いてくれます。

 ...さて、Delphi 2007 ~ XE で "ランタイムライブラリを有効にする" にチェックを入れて、XPMan を使ったらどうなるでしょうか?

 環境によっては "Duplicate Resource" になりません。これは言語の種類が異なるため、言語リソースとして複数登録されるからです。しかしながら、この状態だと意図しないリソースが使われてしまう事があり、誤動作の原因となってしまいます。個人的にはアプリケーションマニフェストは "ニュートラル言語" で統一すべき (言語別に持つ必要はない) だと思うのですが。

 Manifest Ex ではどうやっているのでしょう?すべて ID が 1 のマニフェストリソースを 4 つ用意しているのでしょうか?そのようにすると、言語を変更しない限りパッケージ内で "Duplicate Resource" になってしまいますし、言語を安易に設定してしまうと逆に "Duplicate Resource" にならずに困ったことになります (適当でない言語にすべきではない)。では、ID を変更しているのでしょうか?ですが、ID が 1 でないマニフェストリソースはアプリケーションマニフェストと解釈されません

 Manifest Ex の Source フォルダにある Install_Resource と Replace_Resource がミソです。初期状態で Source フォルダにある 4 つの *.res は Install_Resource 内の *.res と同じもので、ID が 2 / 3 / 4 / 5 と振られています (ID = 1 は XPMan)。これにより、"Duplicate Resource" にならずにパッケージをインストールできます。

 "インストール後に Replace_Resource の中身を Source にコピーしろ" とReadme.txt にある事でお解かりかとは思いますが、Replace_Resource 内の *.res はすべて ID が 1 となっています。これにより、T~Man を重複して貼りつけた場合には "Duplicate Resource" になるようにしてあるのです。

 逆に言えば、コピーの手順を忘れてしまっている場合、ID が 1 でない無効なアプリケーションマニフェストが埋め込まれてしまい、期待した動作となりません。

 アプリケーションマニフェストを埋め込んでも思ったようなような動作にならない場合には、XN Resource Editor 等のリソースエディタで実行形式ファイル (*.exe) を実際に確認してみる事をオススメします。


2013/03/20

Manifest Ex ver1.01

 アプリケーションマニフェスト を簡単に追加するためのコンポーネント。64bit / FireMonkey (Windows) にも対応しています。

 今回の変更は、インストール用リソースの ID 変更です。

Value ID Component
1 CREATEPROCESS_MANIFEST_RESOURCE_ID TXPManifest
2 ISOLATIONAWARE_MANIFEST_RESOURCE_ID -
3 ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID -
20 N/A TVistaManifest
21 N/A TVistaAdminManifest
22 N/A TNativeManifest
23 N/A TNativeAdminManifest

 winuser.h を眺めてみたら ID=16 まで予約されているようなので、ID=2~5 を ID=20~23 へ変更しました。もちろん、実行時用リソースの ID はすべて 1 (CREATEPROCESS_MANIFEST_RESOURCE_ID) になります。理屈上、必須の修正ではありません。

XN Resource Editor 3.0.0.1 [ja] rev.32

 Manifest Ex に関連して更新。

 rev.31~32 の変更点は以下の通り。

 最初のは...

 こういう事です...絶対に忘れちゃうと思ったので。イチイチ資料を漁るのは面倒ですからね。

 そういえば、いつからかは不明なのですが、オリジナルの XN Resource Editor 3.0.0.1 が DL できなくなっているようです。Internet Archive で探せばまだ DL できますけれど...。

アプリケーションマニフェストが含まれていないアプリケーションにアプリケーションマニフェストを埋め込んでみる

 例としてバイナリエディタ Stiring にアプリケーションマニフェストを埋め込んでみます。

  1. アーカイブを解凍したトコロです。
  2. XN Resource EditorStiring を喰わせます。
  3. [リソース | リソースを追加...]
  4. "Application Manifest" を選択し、[OK] ボタンを押下。
  5. アプリケーションマニフェストが追加されました。
  6. Stiring はネイティブモードで動作可能なので、ネイティブモードの記述を追加します。
  7. ツールボタンを押すとネイティブモードの記述が追加されました。
  8. 変更を保存して Stiring を起動すると、コントロールがテーマで描画されるようになりました。

    左がアプリケーションマニフェストを組み込んだ後、右がオリジナルです。
  9. リソースモニタで確認してみましたが、ちゃんとネイティブモードで動作しているようです。

IBConsole 日本語版+α Windows Unicode Edition rel.29 / ANSI Edition rel.68 (人柱外しました)

 できました。問題なさそうなのでジャンクボックスへ移動しました。

 個人的には使わない機能ですが、あればあったで便利だと思ったので追加しておきました。元ネタは "Interbase、DBgridの数値に位取り表示をするには (Embarcadero Discussion Forum)" です。

 自動的にクリップボードに入る訳ではないので、全選択 (〔Ctrl〕+A) してコピー (〔Ctrl〕+C) してからフォームデザイナで 〔Ctrl〕+V (貼り付け) してください。

 余談ですが、選択 / 挿入 / 更新 / 削除クエリボタンで生成される SQL のパラメータは WHERE 句のパラメータ (キー項目) が ":_(アンダーバー)+項目名" で、それ以外が ":項目名" となっています。これは Delphi でコードを書くときに誤りを見つけやすくするためです。また、TIBUpdateSQL の仕様でパラメータを生成すると、(仕様上仕方ないですけど) パラメータの命名規則に一貫性がなく都度コードを書き換える必要が出てきます。

 何を言ってるか解らないのであれば、実際にボタンを押して SQL を生成してみてください。そして WHERE 句のパラメータが TIBUpdateSQL と同じ仕様だった場合の事を考えてみましょう。

SQL 文 IBConsole TIBUpdateSQL
SELECT
(RefreshSQL)
:_項目名 :項目名
UPDATE
(ModifySQL)
:_項目名 :OLD_項目名
DELETE
(DeleteSQL)
:_項目名 :OLD_項目名

 そのままでは WHERE 句のコードの共用 (コピペを含む) ができない事が解ると思います。本当にちょっとした事なのですが、これだけの事で随分と効率が違うものです。「全部 ":項目名" で統一すればもっと効率がいい」?...確かにそうかもしれませんが、WHERE 句 (キー項目) はアンダーバー付きとしておく事で、ロジックのミスを見つけやすくできるのですよ。

 「パラメータなんぞ使わずに変数をそのまま + で連結すりゃいいじゃん。何言ってるかわかんねぇ!」...オーケーブラザー、SQL インジェクションに気を付けて達者で暮らすんだぞ。


2013/03/23

ペン色とブラシ色と前景色と背景色 (Delphi の TCanvas)

 元ネタは "[delphi-users:3063] FillRectとRectangleの差"

 ペン色=前景色、ブラシ色=背景色 という思い込みがあるとなかなか理解しにくいと思います。

 上段がソリッドブラシ (Brush.Style = bsSolid) で下段がハッチブラシ、左列が透過で、右列が不透過の例です。画像内の左側が FillRect()、右側が Rectangle() です。

 ここには 3 つの色が存在しています。ペンの色 (赤)、ブラシの色 (青)、そして背景色 (緑) です。VCL では背景色はブラシ色で設定され、背景モードには

 が設定されます。つまり、ハッチブラシを使った場合、背景色と背景モードを変更しないと、

 という現象になります。繰り返しになりますが、VCL ではブラシ色を変更すると、背景色がブラシ色に設定され、背景モードも変更されます。よって、背景色及び背景モードを変更したい場合には "描画コマンドの前" かつ "ブラシ変更の後" に行う必要があります。

 背景色または背景モードが影響を及ぼすのは、

 "ペンの背景色" というと違和感があるかもしれませんが、ハッチブラシと同じようなものです。破線の隙間の色は背景色または背景モードの影響を受けます。

 では以下のコードを実行するとどのように表示されるでしょうか?

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
  i: Integer;
begin
  with (Sender as TPaintBox), Canvas do
    begin
      Font.Color  := clYellow;    // フォントの色
      Font.Size   := 16;          // フォントサイズ

      Brush.Color := clRed;       // ブラシ色 (赤)
      Pen.Color   := clGreen;     // ペン色   (緑)
      Pen.Style   := psDot;       // 点線
      Pen.Width   := 1;           // お約束

      SetBkColor(Handle, clBlue); // 背景色   (青)
      SetBkMode(Handle, OPAQUE);  // 背景モード (不透過)

      // 左上にブラシ色の数値を描画
      TextOut(00, Format('%.6x', [Brush.Color]));

      // 右上から左下へ対角線を引く(/)
      for i:=1 to 10 do
        begin
          MoveTo(Width, i-1);
          LineTo(0, Height + i-1);
        end;

      // 右下に四角形を描画
      Rectangle(Width - 50, Height - 25, Width, Height);
    end;
end;

 少し下にスクロールすると答えの画像があります。どのように描画されるのかを、まずは想像してみてください。フォームの色はデフォルトの clBtnFace です。
















































 想像通りでしたか?

 これでも随分端折った話をしています。TCanvas のメソッドは Windows GDI をラッピングしていますので、詳細については MSDN の記述と、(Vcl.)Graphics.pas の実装を確認してみてください。

ペンと前景モード (Delphi の TCanvas)

 おっと、コレを書くのを忘れていました。

 つまりは、ペン色もブラシ色も前景色なのです。そして、ラスターオペレーションにおいては "前景モード" というのがあります。

procedure TForm1.PaintBox1Paint(Sender: TObject);
var
  PM: TPenMode;
begin
  with (Sender as TPaintBox), Canvas do
    begin
      Pen.Color   := clGreen;     // ペン色   (緑)
      Pen.Style   := psSolid;     // 実線
      Pen.Width   := 5;

      Brush.Color := clYellow;    // ブラシ色 (黄)
      Brush.Style := bsSolid;     // ソリッドブラシ (背景モードが OPAQUE に設定される)

      Font.Color  := clRed;       // フォント色
      Font.Size   := 16;          // フォントサイズ

      // 現在の前景モードを退避
      PM := Pen.Mode;

      // 文字列 (1)
      TextOut(00'Hello,');

      // 矩形 (1)
      Rectangle(Rect(030, Width div 250));
//    FillRect(Rect(0, 30, Width div 2, 50));

      // 横線 (1)
      MoveTo(0    , 70);
      LineTo(Width, 70);

      // 前景モードを変更
      Pen.Mode := pmMerge;
//    SetROP2(Handle, R2_MERGEPEN);

      // 背景モードを変更
      SetBkMode(Handle, TRANSPARENT); // 透過

      // 文字列 (2)
      TextOut(0100'world.');

      // 矩形 (2)
      Rectangle(Rect(0130, Width div 2150));
//    FillRect(Rect(0, 130, Width div 2, 150));

      // 横線 (2)
      MoveTo(0    , 170);
      LineTo(Width, 170);

      // 前景モードを元に戻す
      Pen.Mode := PM;
    end;
end;

 フォームの色は clBlue (青) を指定してあります。

 VCL において前景モードは Pen.Mode に割り当てられています。API だと SetROP2 / GetROP2 です。実行した結果は以下の通りです。

 予想通りでしたか?前景モードがペン色に対してだけ効果があるのであれば、下の長方形の内側の色はブラシ色で塗りつぶされているので黄色になるハズですが、そうはなっていません。前景モードの効果はペンまたはブラシの色に作用します。また、画像の通りフォントの色には作用しません

 Pen.Mode はブラシの色にも作用します。この仕様なのであれば、Brush.Mode というのに SetBkMode / GetBkMode を割り当ててくれてもよかった気はしますが、そうなると SetBkColor / GetBkColor はどうするんだ?という話になっちゃいますからね。

 Rectangle での描画を FillRect に変えると以下のようになります。

 予想通りでしたか?FillRect は背景モード (SetBkMode) だけでなく前景モード (SetROP2) も効きません。

 この辺の詳細は "Windows API バイブル 1 (ISBN-4881350382)" とか Windows 3.1 時代の本に記述があるのですが、引越しのドサクサで手元には "Windows API バイブル 2 (ISBN-4881351060)" しか残っていません...まぁ、あったとしてもモノクロ印刷なので直感的には理解できないかもしれませんが。

 VCL の TCanvas / GDI においては 前景色 (Font.Color / Pen.Color / Brush.Color)、前景モード (Pen.Mode / SetROP2)、背景色 (SetBkColor)、背景モード (SetBkMode) によって描画色が決定されます。


2013/03/24

前景モード (Delphi の TCanvas)

 折角なので関連ネタを。

 前景モードは現在画面に表示されている色と、描画しようとしている色をどういう風に混ぜ合わせるか?という指定を行います。RGB ですから、"光の三原色" ですね。混ぜれば混ぜるほど明るくなります。混ぜれば混ぜるほど (理論的には) 黒に近づく "色の三原色" とは反対です。

 要は RGB 値をビット演算するという事ですね。ですので、混ぜ合わせるパターンは 16 個あります。詳細は Wikipedia の 論理演算の項を参考にしてみてください。

 Pen.Mode の初期値は pmCopy です。これは画面の色に対して、描画しようとしている色...つまり前景色で上書きします。既に描画されている色には左右されないという事になります。

 pmCopy に対応する SetROP2 に指定できるモードの定数は R2_COPYPEN です。"PEN" とか名前に入れるから混乱するのですよ、まったく。ちなみに、pmCopy 等の TPenMode と R2_COPYPEN 等の定数には値に互換性がありませんので注意してください (キャストしちゃダメですよ)。

TPenMode fnDrawMode TForegroundMode ビット演算 論理結合子
定数 定数 定数
pmBlack 0 R2_BLACK 1 fgmBlack 1 False (黒) 偽 / 矛盾
pmWhite 1 R2_WHITE 16fgmWhite 16True (白) 真 / 恒真 / トートロジー
pmNop 2 R2_NOP 11fgmNop 11画面色(変更されない) 命題 [画面色] ⇔ pmCopy
pmNot 3 R2_NOT 6 fgmNot 6 not 画面色 否定 [画面色] ⇔ pmNotCopy
pmCopy 4 R2_COPYPEN 13fgmCopy 13前景色 命題 [前景色] ⇔ pmNop
pmNotCopy 5 R2_NOTCOPYPEN 4 fgmNotCopy 4 not 前景色 否定 [前景色] ⇔ pmNot
pmMergePenNot6 R2_MERGEPENNOT14fgmMergeFCNot14(not 画面色)or 前景色 含意 ⇔ pmMergeNotPen
pmMaskPenNot 7 R2_MASKPENNOT 5 fgmMaskFCNot 5 (not 画面色) and 前景色逆非含意 ⇔ pmMaskNotPen
pmMergeNotPen8 R2_MERGENOTPEN12fgmMergeNotFC12画面色 or (not 前景色) 逆含意 ⇔ pmMergePenNot
pmMaskNotPen 9 R2_MASKNOTPEN 3 fgmMaskNotFC 3 画面色 and (not 前景色)非含意 ⇔ pmMaskPenNot
pmMerge 10R2_MERGEPEN 15fgmMerge 15前景色 or 画像色 論理和
pmNotMerge 11R2_NOTMERGEPEN2 fgmNotMerge 2 not (画面色 or 前景色) 否定論理和
pmMask 12R2_MASKPEN 9 fgmMask 9 画面色 and 前景色 論理積
pmNotMask 13R2_NOTMASKPEN 8 fgmNotMask 8 not (画面色 and 前景色)否定論理積
pmXor 14R2_XORPEN 7 fgmXor 7 画面色 xor 前景色 排他的論理和
pmNotXor 15R2_NOTXORPEN 10fgmNotXor 10not (画面色 xor 前景色)否定排他的論理和 / 同値

 論理結合子の列に ⇔ がないものは、画面の色と前景色が逆でもビット演算した色は同じになります (オペランドを入れ替えても得られる値は同じ)。⇔ が書いてあるものは、画面の色と前景色が逆だと色が異なります (オペランドを入れ替えると結果が異なる)。逆にした時に同じ色を得るための定数が右側に書かれています。

 例えば、画面が青で前景色が赤の場合、pmCopy だと得られる色は赤です。逆に画面が赤で前景色が青の場合に得られる色を赤にしたいのであれば、pmNop を使うという事になります。

 真理値表に 左上から右下へ対角線を引いて、値が線対称になっていればオペランドを入れ替えても結果は同じになります。非対称のものはオペランドを入れ替えると結果が異なります...この場合、対角線を軸に裏返すとオペランドを逆にした時に同じ値を得る真理値表を得ることができます。

 ...対角線を軸に裏返せば P と Q が入れ替わるので、よくよく考えてみれば当たり前のことなのですが。

 TForegroundMode とあるのはですね、

[VCL.CanvasHelper.pas]
unit VCL.CanvasHelper;

interface

uses
  Windows, Graphics;

type
  TBackgroundMode = (bgmTransparent = TRANSPARENT,   // 透過
                     bgmOpaque      = OPAQUE);       // 不透過

  TForegroundMode = (fgmBlack      = R2_BLACK,       // False (黒)             : (偽 / 矛盾                     )
                     fgmNotMerge   = R2_NOTMERGEPEN, // not (画面色 or 前景色) : (否定論理和                    )
                     fgmMaskNotFC  = R2_MASKNOTPEN,  // 画面色 and (not 前景色): (非含意        ⇔ fgmMaskFCNot )
                     fgmNotCopy    = R2_NOTCOPYPEN,  // not 前景色             : (否定 [前景色] ⇔ fgmNot       )
                     fgmMaskFCNot  = R2_MASKPENNOT,  // (not 画面色) and 前景色: (逆非含意      ⇔ fgmMaskNotFC )
                     fgmNot        = R2_NOT,         // not 画面色             : (否定 [画面色] ⇔ fgmNotCopy   )
                     fgmXor        = R2_XORPEN,      // 画面色 xor 前景色      : (排他的論理和                  )
                     fgmNotMask    = R2_NOTMASKPEN,  // not (画面色 and 前景色): (否定論理積                    )
                     fgmMask       = R2_MASKPEN,     // 画面色 and 前景色      : (論理積                        )
                     fgmNotXor     = R2_NOTXORPEN,   // not (画面色 xor 前景色): (否定排他的論理和 / 同値       )
                     fgmNop        = R2_NOP,         // 画面色(変更されない)   : (命題 [画面色] ⇔ fgmCopy      )
                     fgmMergeNotFC = R2_MERGENOTPEN, // 画面色 or (not 前景色) : (逆含意        ⇔ fgmMergeFCNot)
                     fgmCopy       = R2_COPYPEN,     // 前景色                 : (命題 [前景色] ⇔ fgmNop       )
                     fgmMergeFCNot = R2_MERGEPENNOT, // (not 画面色)or 前景色 : (含意          ⇔ fgmMergeNotFC)
                     fgmMerge      = R2_MERGEPEN,    // 前景色 or 画像色       : (論理和                        )
                     fgmWhite      = R2_WHITE);      // True (白)              : (真 / 恒真 / トートロジー      )

  TCanvasExtention = class Helper for TCanvas
  private
    function GetBackgroundMode: TBackgroundMode;
    procedure SetBackgroundMode(const Value: TBackgroundMode);
    function GetForegroundMode: TForegroundMode;
    procedure SetForegroundMode(const Value: TForegroundMode);
    function GetBackgroundColor: TColor;
    procedure SetBackgroundColor(const Value: TColor);
  public
    procedure FillRect2(const Rect: TRect);
    property BackgroundColor: TColor read GetBackgroundColor write SetBackgroundColor;
    property BackgroundMode: TBackgroundMode read GetBackgroundMode write SetBackgroundMode;
    property ForegroundMode: TForegroundMode read GetForegroundMode write SetForegroundMode;
  end;

const
  PenModes: array [TPenMode] of Word =
    (R2_BLACK      , R2_WHITE     , R2_NOP        , R2_NOT        ,
     R2_COPYPEN    , R2_NOTCOPYPEN, R2_MERGEPENNOT, R2_MASKPENNOT ,
     R2_MERGENOTPEN, R2_MASKNOTPEN, R2_MERGEPEN   , R2_NOTMERGEPEN,
     R2_MASKPEN    , R2_NOTMASKPEN, R2_XORPEN     , R2_NOTXORPEN  );

  ForegroundModes: array [TForegroundMode] of TPenMode =
   (pmBlack        , pmNotMerge   , pmMaskNotPen  , pmNotCopy     ,
    pmMaskPenNot   , pmNot        , pmXor         , pmNotMask     ,
    pmMask         , pmNotXor     , pmNop         , pmMergeNotPen ,
    pmCopy         , pmMergePenNot, pmMerge       , pmWhite      );

{ functions }
  function PenModeToForegroundMode(PenMode: TPenMode): TForegroundMode;
  function ForegroundModeToPenMode(ForegroundMode: TForegroundMode): TPenMode;

implementation

{ TCanvasExtention }

procedure TCanvasExtention.FillRect2(const Rect: TRect);
var
  R: TRect;
  OPenWidth: Integer;
  OPenStyle: TPenStyle;
begin
  OPenWidth := Self.Pen.Width;
  OPenStyle := Self.Pen.Style;
  try
    R := Rect;
    with Self.Pen do
      begin
        Width := 1;
        Style := psClear;
      end;
    R.Left := R.Left - 1;
    R.Top  := R.Top  - 1;
    Rectangle(R);
  finally
    Self.Pen.Width := OPenWidth;
    Self.Pen.Style := OPenStyle;
  end;
end;

function TCanvasExtention.GetBackgroundColor: TColor;
begin
  result := GetBkColor(Self.Handle);
end;

function TCanvasExtention.GetBackgroundMode: TBackgroundMode;
begin
  result := TBackgroundMode(GetBkMode(Self.Handle));
end;

function TCanvasExtention.GetForegroundMode: TForegroundMode;
begin
  result := TForegroundMode(GetROP2(Self.Handle));
end;

procedure TCanvasExtention.SetBackgroundColor(const Value: TColor);
begin
  SetBkColor(Self.Handle, ColorToRGB(Value));
end;

procedure TCanvasExtention.SetBackgroundMode(const Value: TBackgroundMode);
begin
  SetBkMode(Self.Handle, Integer(Value));
end;

procedure TCanvasExtention.SetForegroundMode(const Value: TForegroundMode);
begin
  SetROP2(Self.Handle, Integer(Value));
  Self.Pen.Mode := ForegroundModeToPenMode(Value);
end;


{ functions }
function PenModeToForegroundMode(PenMode: TPenMode): TForegroundMode;
begin
  result := TForegroundMode(PenModes[PenMode]);
end;

function ForegroundModeToPenMode(ForegroundMode: TForegroundMode): TPenMode;
begin
  result := TPenMode(ForegroundModes[ForegroundMode]);
end;
end.

 こんな TCanvas 用クラスヘルパ用の定数なのです。このクラスヘルパを使うと、TCanvas に

 が追加されます。TForegroundMode は SetROP2 用の定数と互換性がありますのでキャストしても大丈夫ですし、TPenMode と相互変換するための関数も用意されています。

uses
  ..., VCL.CanvasHelper;

procedure TForm1.PaintBox_Hatch_Transparent(Sender: TObject);
begin
  with TPaintBox(Sender) do
    begin
      Canvas.Pen.Color       := clRed;          // ペン色     (赤)
      Canvas.Brush.Color     := clBlue;         // ブラシ色   (青)
      Canvas.Brush.Style     := bsDiagCross;    // ブラシ     (ハッチブラシ)
      Canvas.BackgroundColor := clGreen;        // 背景色     (緑)
      Canvas.BackgroundMode  := bgmTransparent; // 背景モード (透過)
      case Tag of

        // 青 (Brush.Color) でハッチが描画され、
        // 背景は透過 (BackgroundMode) する。
        0: Canvas.FillRect2(ClientRect);

        // 赤 (Pen.Color) で枠が描画され、
        // 青 (Brush.Color) でハッチが描画され、
        // 背景は透過 (BackgroundMode) する。
        1: Canvas.Rectangle(ClientRect);

      end;
    end;
end;

procedure TForm1.PaintBox_Hatch_Opaque(Sender: TObject);
begin
  with TPaintBox(Sender) do
    begin
      Canvas.Pen.Color       := clRed;          // ペン色     (赤)
      Canvas.Brush.Color     := clBlue;         // ブラシ色   (青)
      Canvas.Brush.Style     := bsDiagCross;    // ブラシ     (ハッチブラシ)
      Canvas.BackgroundColor := clGreen;        // 背景色     (緑)
      Canvas.BackgroundMode  := bgmOpaque;      // 背景モード (不透過)
      case Tag of

        // 青 (Brush.Color) でハッチが描画され、
        // 緑 (BackgroundColor) の背景色になる。
        0: Canvas.FillRect2(ClientRect);

        // 赤(Pen.Color) で枠が描画され、
        // 青 (Brush.Color) でハッチが描画され
        // 緑 (BackgroundColor) の背景色になる。
        1: Canvas.Rectangle(ClientRect);

      end;
    end;
end;

 このような使い方ができます。

 今日は みんな大好きビット演算! のお話でした。

オペランドって何さ?

 一応書いておきます。

 演算子ってのは聞いたことがあると思います。+ - * / OR AND ...これらは演算子です。"オペレータ" と呼ばれます...で、演算対象となるもの... A OR B であれば A や B の事ですが、これは被演算子です。"オペランド" と呼ばれます。

 not 演算は not A のようにオペランドは一つしかありません。not は 単項演算子 です。さっきの OR 演算には オペランドが 2 つあります。OR は 二項演算子 です。C 言語などにはオペランドを 3 つ取る 三項演算子 があります (A ? B : C)。Delphi には似たような動作をする Math.IfThen() / StrUtils.IfThen() または Indy の IdGlobal.iif() がありますが、これらは関数であり演算子ではありません。

 「"(not 画面色)or 前景色" なんてオペランド入れ替えられないじゃん!」 と思った方はスルドイです。ここで言っているオペランドは 論理演算のオペランド で、P を画面の色 (なオペランド)、Q を前景色 (なオペランド) として考えて頂ければ話は通ると思います。論理結合子の 16 個すべてが二項演算子として存在するものと思ってみてください。


2013/03/25

使っていますか?ビルドグループ (XE 以降)

 XE 以降だとプロジェクトマネージャに "ビルドグループ" 管理機能がついています。

 赤丸のボタンを押すと "ビルドグループ" が表示され、その名の通りビルドするグループを管理できます

 ビルドグループペイン内にある [ビルドグループを新規作成] ボタンを押下しグループ名を入力すると、ビルドするグループをまとめられます。コンボボックスでグループを選び、

 一括ビルドボタンを押すと、すべてのビルドが開始されます。XE では "プロジェクトの有効 / 無効" と "構成" が管理できます。XE2 以降だと加えて "プラットフォーム" も管理できるのでさらに便利です。画像の例だと一括ビルドで 23 の実行バイナリ (すべてリリースビルド) が一気に生成されます

 複数のサブプロジェクト (或いはパッケージ、或いは DLL) が共通で利用しているクラスや関数にバグが見つかったり仕様変更があった際に一括ビルドできるので、"このモジュールだけビルドし忘れてた" 的なミスを減らす事ができます。

使っていますか?構成一括変更&プラットフォーム一括変更 (XE2 以降)

 XE 以前であればむしろこっちが先に欲しかった機能なのですが、

 ドロップダウンして構成を選択するとプロジェクトグループ内のプロジェクトの構成 (デバッグ/リリース) がすべて一度に切り替えられます。独自に作成した構成 (オプションセット) があれば、そちらにも切り替えられます...リモートデバッグ構成とかですね。

 同様にプラットフォームも切り替えられます。これら 2 つのドロップダウンリストはカレントプロジェクトの構成 / プラットフォームを切り替えるものではありません。プロジェクトグループ内のすべての構成 / プラットフォームを一発で切り替えてくれます (プロジェクトグループ内に 1 プロジェクトしかないのであれば、カレントプロジェクトと同義ですが)。

 すべてのプロジェクトの 64bit 版だけをビルドしたい場合とかに便利です。知ってるヒトには何を今更かもしれませんが、最近よく使うので紹介してみました。


2013/03/26

Windows 8 で動くアプリケーションと Windows 8 対応アプリケーションと Windows 8 ストア用のアプリケーション (Delphi)

 XE3 は Windows 8 対応 (デスクトップ) アプリケーションを作れます。ここに嘘はありません。本当です。

 Windows 8 のデスクトップアプリケーションを作るためのガイドラインは以下にあります。

 現在では、Windows ストアにデスクトップ用のアプリケーションを登録できるようになっています (ストアで〔Win〕+Q して検索するとデスクトップアプリも出てきます)。しかしこれがまたハードルが高い。

 この時点で個人は無理です。(日本で言う所の) フリーウェアを登録する事のメリットはありません。デスクトップアプリは ModernUI 用アプリと違い、ストアから DL する方式ではないからです...つまり、単なる広告にしかなりません

 個人的にはデスクトップアプリを Windows 8 ストアに載せる必要性があるとは思えませんが、それでもやってみたいという方がいらっしゃるかもしれません。結論から言えば Delphi XE3 で "Windows 8 ストア用デスクトップアプリを作るのは事実上無理" です。

 理由は "Windows 8 デスクトップ アプリ認定要件" の中に書いてあります。

 3.1 (.NET の話) / 3.5 (特殊な事やらないとできないんじゃ?) はいいとして、/NXCOMPAT (3.3) /DYNAMICBASE (3.4)は PE ヘッダのオプションフラグ ({$SetPEOptFlags}) を設定する事によって回避できます。

const 
  // http://msdn.microsoft.com/en-us/library/windows/desktop/ms680339%28v=vs.85%29.aspx
  IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE          = $0040;
  IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY       = $0080;
  IMAGE_DLLCHARACTERISTICS_NX_COMPAT             = $0100;
  IMAGE_DLLCHARACTERISTICS_NO_ISOLATION          = $0200;
  IMAGE_DLLCHARACTERISTICS_NO_SEH                = $0400;
  IMAGE_DLLCHARACTERISTICS_NO_BIND               = $0800;
  IMAGE_DLLCHARACTERISTICS_WDM_DRIVER            = $2000;
  IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = $8000;

 このように定義して、

// /DYNAMICBASE 
{$DYNAMICBASE ON} // {$SetPEOptFlags IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE}   
// /NXCOMPAT
{$SetPEOptFlags IMAGE_DLLCHARACTERISTICS_NX_COMPAT} 

 *.dpr にコンパイラ指令を加えれば OK です。古い Delphi は {$SetPEOptFlags} に対応していませんので、外部ツール (EDITBIN.exe 等) を使ってフラグを書き換える必要があります。アプリケーションマニフェストも適切に記述しなくてはなりません。

 「ただし /SafeSEH (3.2)...テメーはダメだ!」 回避のしようがありません。詳細は "/SAFESEH (安全な例外ハンドラがあるイメージ)" をお読みください。/SafeSEH は PE ヘッダのオプションフラグではなく、機構なのです。ちなみに C++Builder は対応しています

 コンパイラのサポートなしに SEH 対応させるとなると、それが可能になったとしても既存のアプリケーションのコードを大幅に書き換えなくてはならなくなるでしょう。/SafeSEH の件は 32bit アプリケーション限定の話ですので、64bit とはコードが別になってしまいますし、OS X (FMX) とかには関係のない話です。逆に言えば、64bit 専用アプリであれば Windows ストアに登録できるかもしれません。

 「/SafeSEH どうにかしてよー」 の QC は QC#106781 です。

 理屈的には /SafeSEH 以外の問題は現行の Delphi でも対応できると思いますので、Windows ストアへ 32bit デスクトップアプリを登録したい方は Vote してください。個人的には Windows ストアへデスクトップアプリを登録する必要性 / 必然性を感じていませんが、できないよりはできた方がいいのは当たり前の事なので Vote しておきました。

 元ネタは

 でした。

Windows アプリ認定キット (ACK) と Delphi XE3 のアプリケーション

 "Windows アプリ認定キット (Windows App Certification Kit)" は Windows SDK に含まれています。Windows SDK は以下から DL できます。

 インストールが完了すると、スタートメニューに ACK が登録されます。

 このツールに食わせるのはインストーラでなくてはなりません...つまり、最終的なインストールイメージを先に作成する必要があります。EXE 単体をテストする事はできません。ちなみにこのツールの実行にはアフォのように長い時間が掛かります。コーヒー飲んで待ってるとかいうレベルじゃありませんし、途中でアカウント切り替えとかも行われます。

 ここまで書いて思ったのですが、「EXE が自動更新 / 追加ダウンロード機構を備えていれば、最初のアプリ (ローダ) さえ通してしまえば何でもアリなんじゃね?」という気がしないでもないですが...ここのトコはどうなってるのでしょうね?それと、理屈からいって作成したアプリ (EXE) が通ったとしても、サードパーティの DLL 等を使っていればそちらが引っ掛かる可能性もありますよね。

 さらにハードルが上がったような。

 ...さて。gdgd 書いていたのは、Delphi XE3 で作ったシンプルなバイナリを Install Aware 2012 でインストールパッケージ化し、それを ACK に食わせていたからです。

program Project1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

const
  IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE          = $0040;
  IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY       = $0080;
  IMAGE_DLLCHARACTERISTICS_NX_COMPAT             = $0100;
  IMAGE_DLLCHARACTERISTICS_NO_ISOLATION          = $0200;
  IMAGE_DLLCHARACTERISTICS_NO_SEH                = $0400;
  IMAGE_DLLCHARACTERISTICS_NO_BIND               = $0800;
  IMAGE_DLLCHARACTERISTICS_WDM_DRIVER            = $2000;
  IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE = $8000;

{$DYNAMICBASE ON}
{$SetPEOptFlags IMAGE_DLLCHARACTERISTICS_NX_COMPAT}

{$R *.res}

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.

 フォームには何も貼っておらず、初期状態です。

 ちょっと失敗しちゃいました。ユーザー切り替えの際に放っておかないと、マルチユーザー セッションのテストに不合格になります。ログイン画面になってもそのまましばらく放置しなければならないようです。

 全体の結果は以下にあります。「やっぱダメじゃん!」 と早急な結論を出す前にちゃんと中身を確認してみてください。

 警告またはエラーになった箇所は以下の通り。

 アレ?これって /SafeSEH 対応してなくても Windows ストア通過するんじゃ?試したのは 32bit バイナリなんですけど?予想外 (?) でしたが、少なくとも素の XE3 バイナリには問題らしい問題はないようですね。多分、XE2 でも似たような結果になると思います。

 Windows ストアは "64bit 版も用意されている事が望ましい" とされているようですので、基本的に XE2 以降を使った開発となりそうです。

続・Windows アプリ認定キット (ACK) と Delphi XE3 のアプリケーション

 二度目のトライ。WM_QUERYENDSESSION / WM_ENDSESSION のコードを以下のように書いて、

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, 
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs;

type
  TForm1 = class(TForm)
  private
    { Private 宣言 }
    procedure WMQueryEndSession(var Msg: TWMQueryEndSession); message WM_QUERYENDSESSION;
    procedure WMEndSession(var Msg: TWMEndSession); message WM_ENDSESSION;
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TForm1 }

procedure TForm1.WMQueryEndSession(var Msg: TWMQueryEndSession);
begin
  Msg.Result := 1;
end;

procedure TForm1.WMEndSession(var Msg: TWMEndSession);
begin
  Close;
end;

end.

 アプリケーションマニフェストも書き換えてみました。

  <assemblyIdentity
        type="win32"
        name="DEKO.Windows.Project1"
        version="1.0.0.0"
        processorArchitecture="*"/>

 コレでダメなら何なのだろう?前回のエラーメッセージのパスはアプリケーション (の EXE) のものだったし...お、レポートが生成されたようです。

 一応合格しました。全体の結果は以下にあります。

 "システムの再起動マネージャーのメッセージに従う" のトコと "マルチユーザー セッションのテスト" のトコは、インストーラ絡みのようですね。サイレントインストールできないタイプのインストーラだと警告を消せないかもしれません...何故なら、ログオフした後の別セッションでインストーラが起動しても、入力項目がある限りインストールはそこから先に進まないからです。

 アプリケーションマニフェストのワーニングが意味不明ですが、警告で済んでいるのでなんとかなるかもしれません。というか、これって半分はインストーラの作り方 (設定方法) にコツがあるんじゃないでしょうかね?

 ACK の動きやレポート結果にも疑問が残りますし、結局のトコロ Windows ストアにデスクトップアプリを登録できるかどうかは出たとこ勝負 なんじゃないでしょうか?


 BACK   古いのを読む   新しいのを読む