2013年9月19日木曜日

Hello world maniacs !

CodeIQ の @cielavenir さんからの言語指定の問題「Restricted Words - 出力したい値をどうやって手に入れるか?」に挑戦しました。

問題は
標準出力に
Hello World
と出力するプログラムを作成して下さい。

ただし、数値、文字及び文字列リテラルを解答に含めることはできません。
Perlのqqやqw、Rubyの%Q、%q、%wなども避けたほうが評価が高くなります。
言語仕様をフル活用して下さい!
というものです。
ここで注目したいのが!!
プログラミング言語は
AppleScript(osascript)/C/C++/C#/Clojure/D/Erlang/Fortran/Go/Groovy/Haskell/
Hello Algorithm/HSP/Java/JavaScript(Node.js)/Kuin/Lisp/Lua/OCaml/Pascal/
Perl/PHP/Pike/Python/R/Ruby/Scala/Scheme/Smalltalk/VB.Net
のいずれかを使用して下さい。指定された言語以外での解答は評価されません。

Pascal キター!!!!!
いままで CodeIQ では Pascal / Delphi は全く相手にされてなかったのに!!
ということで、軽く挑戦してみることにしました。

すぐに思いついた方法は3通り

  1. brainf*ck のようなチューリングマシン的な方法
  2. リフレクションを使ってクラスやメソッド名から抽出する方法
  3. 文字コードを列挙型で表す方法

このうち、1の方法は簡単すぎてつまらないので却下。
2の方法もあくまで言語指定が Pascal なので却下。
ということで、3の方法で行くことにしました。

ここで面倒だったのが言語指定が Pascal であること。
Pure Pascal では type とか var といったブロックは位置が決まっているし、Program には input / output を付けないといけないとか、const 指定子が使えないとか色々あります。
ということで、1回 Delphi でコンパイルした後 gpc 使ってコンパイルを試しました……ところが!!
gpc は最早 Pure Pascal ではなく Borland 拡張……つまり Delphi の文法が多分に採り入れられていたのです!!
すごいすんなりコンパイルされちゃう!! \(^o^)/
といっても、これが ISO 標準であり、これこそが Pure Pascal なのかなあなんて思ったりもしたのですが、 Pure Pascal は初版の物だろう!!ファースト以外は認めない!!……ということで、僕の頭の中にある Pure Pascal 文法で書きました。
それが以下になります。

program HelloWorld(Output);
 
type
THex = (
hx00, hx01, hx02, hx03, hx04, hx05, hx06, hx07,
hx08, hx09, hx0a, hx0b, hx0c, hx0d, hx0e, hx0f,
hxCount
);
 
procedure WriteChar(h, l: THex);
begin
Write(Chr(Ord(h) * Ord(hxCount) + Ord(l)));
end;
 
begin
WriteChar(hx04, hx08); // H
WriteChar(hx06, hx05); // e
WriteChar(hx06, hx0c); // l
WriteChar(hx06, hx0c); // l
WriteChar(hx06, hx0f); // o
WriteChar(hx02, hx00); //
WriteChar(hx05, hx07); // W
WriteChar(hx06, hx0f); // o
WriteChar(hx07, hx02); // r
WriteChar(hx06, hx0c); // l
WriteChar(hx06, hx04); // d
end.

まあ、どっちにせよ、あんまり面白くはならなかったのですが……。
特筆すべき事は何もないです。
ちょっと悔やまれるのは、hxCount なんてのをつくったところ。
そうじゃなくて、shl Ord(hx04) ってすれば良かった。

出題者の @cielavenir さんのコードはこちら。
その他の言語は一個上に。

1 を取得してから、それを使って他の数を取得、という方法のようですね。
それで、ちょっと疑問なのが、Delphi や gpc ではコンパイル通るんだけど、Integer を Char にキャストして値を得ていること。
Pure Pascal だと、これできないんじゃなかったかなあ……そのための Chr 関数な訳だし……。
C 系で言うところの 1byte を表す型は Byte っていう型があるんですよ-。
2013/09/19 13:10 追記
cielavenir さんに修正していただきました!

後日、解説記事がアップされるそうなので、解説記事がアップされたら、ここに追記します!

2013年7月1日月曜日

constructor 制約について

Facebook の Delphi Talks で「型制約について、疑問があります」というスレッドが立ちました。
内容は「ジェネリクス型で型指定するときに、レコード型と値型と文字列型、のみ指定することは可能か?」という話でした。
これについては、スレッド中で「できない」と、結論が出ました。
ただ、このスレッド中で「constructor 制約がついたジェネリクス型に文字列型や値型が渡せる」という事が判りました(僕が知りました)。
と、いうことで、ちょっと調べてみました。

constructor 制約とは、下記のようにジェネリクスの型指定に constructor と書く事で定義される制約です。

type
TConstructorConstraint<T: constructor> = class
end;

具体的には、この制約を課されると「引数無しの Create を持った型しか指定できない」という制約です。
この制約の最大の特徴は T が 「どんな型か全く知らなくても」インスタンスを 生成できる、ということです。
これによって、何も知らなくても有効なインスタンスの存在を保証できます。

……と、まあ原義はさておき、上述の様にプリミティブ型である String 型や Integer 型を指定できるのです。
当然プリミティブな型なので Create なんてコンストラクタメソッドを持っているわけがありません。
これはどういった事でしょうか。
これを検証するために、下記のコードを書きました。

unit Unit1;
 
interface
 
type
// interface 部に定義すると全員に見える
// 引数無しの Create は持っていないクラス
TBar = class
public
constructor Create(const iDummy: Integer); reintroduce;
end;
 
procedure Test;
 
implementation
 
uses
System.Rtti;
 
type
// constructor 制約を課したジェネリック型クラス
TConstructorConstraint<T: constructor> = class
private
FValue: T;
public
constructor Create; reintroduce;
function ToString: String; override;
end;
 
// 引数無しの Create を持つクラス
TFoo = class
public
constructor Create; reintroduce;
end;
 
// 動的配列型
TStringDynArray = array of String;
 
// 集合型
TFactor = (Windows, MacOSX, Android, iOS, WindowsPhone);
TSet = set of TFactor;
 
{ TConstructorConstraint<T> }
 
constructor TConstructorConstraint<T>.Create;
begin
inherited Create;
 
// T の型は知らないけど Create を呼び出せる!
FValue := T.Create;
end;
 
// T の型情報を出力する
function TConstructorConstraint<T>.ToString: String;
var
Rtti: TRttiContext;
Field: TRttiField;
FieldType: TRttiType;
begin
Result := '';
 
Rtti := TRttiContext.Create;
try
Field := Rtti.GetType(ClassInfo).GetField('FValue');
FieldType := Field.FieldType;
 
Result := Field.Name + ': ' + FieldType.ToString + ';';
 
if (FieldType.IsPublicType) then
Result := Result + ' Public;';
 
if (FieldType.IsManaged) then
Result := Result + ' Manged;';
 
if (FieldType.IsInstance) then
Result := Result + ' Instance;';
 
if (FieldType.IsOrdinal) then
Result := Result + ' Ordinal;';
 
// Record は constructor 制約では指定できないので、ここは表示されない
if (FieldType.IsRecord) then
Result := Result + ' Record;';
 
if (FieldType.IsSet) then
Result := Result + ' Set;';
finally
Rtti.Free;
end;
end;
 
{ TFoo }
 
constructor TFoo.Create;
begin
inherited Create;
 
// 生成時に表示される
Writeln('TFoo Created !');
end;
 
{ TBar }
 
constructor TBar.Create(const iDummy: Integer);
begin
inherited Create;
 
// 引数無しの Create ではないため、表示されない
Writeln('TBar Created !');
end;
 
// 生成して情報を出力する
procedure Test;
var
Foo: TConstructorConstraint<TFoo>;
Bar: TConstructorConstraint<TBar>;
Str: TConstructorConstraint<String>;
Int: TConstructorConstraint<Integer>;
Ary: TConstructorConstraint<TStringDynArray>;
Sets: TConstructorConstraint<TSet>;
begin
Foo := nil;
Bar := nil;
Str := nil;
Int := nil;
Ary := nil;
Sets := nil;
try
Foo := TConstructorConstraint<TFoo>.Create;
Bar := TConstructorConstraint<TBar>.Create;
Str := TConstructorConstraint<String>.Create;
Int := TConstructorConstraint<Integer>.Create;
Ary := TConstructorConstraint<TStringDynArray>.Create;
Sets := TConstructorConstraint<TSet>.Create;
 
Writeln(Foo.ToString);
Writeln(Bar.ToString);
Writeln(Str.ToString);
Writeln(Int.ToString);
Writeln(Ary.ToString);
Writeln(Sets.ToString);
 
Readln;
finally
Sets.Free;
Ary.Free;
Int.Free;
Str.Free;
Bar.Free;
Foo.Free;
end;
end;
 
end.

様々なジェネリック型を定義して、生成、その情報を出力、とするだけのプログラムです。
ここでは、クラス型、文字列型、整数型、動的配列型、集合型、を指定してみました。
重要なのは、クラス型以外、Create なんてメソッドは持っていない!ということです。

では、このプログラムを動かしてみると…

TFoo Created !
FValue: TFoo; Instance;
FValue: TBar; Public; Instance;
FValue: string; Public; Manged;
FValue: Integer; Public; Ordinal;
FValue: TStringDynArray; Manged;
FValue: TSet; Set;

こんな風になりました!
TFoo, TBar はクラスなので "Instance" と表示されています。
また、TBar は、interface 部で定義されているので公開されている型 "Public" と表示されています。
そして、string と動的配列型は "Managed" と表示されています。これはコンパイラがその型の生成と廃棄を担っていることを示します。
つまり、string と動的配列型は、本当は管理されたメモリを持つ参照型であるため、このように表示されています。
また、Integer は Ordinal…順序型, 集合型は Set と出ました。これはそれぞれの型そのものですね。

ということで、本当に値型や文字列型が Create というメソッドで生成されてしまいました。
本来、そのようなメソッドを持たない型にも関わらず、です。

では、これらの型の生成は実際にはどのようなコードになっているのでしょうか?
それを見るために CPU ビューで逆アセンブルされたコードを見てみました。

// TFoo の生成
Project1.dpr.28: FValue := T.Create;
00407D7A B201             mov dl,$01
00407D7C A158724000       mov eax,[$00407258]
00407D81 E85AF6FFFF       call TFoo.Create      // 初期化コード
00407D86 8B55FC           mov edx,[ebp-$04]
00407D89 894204           mov [edx+$04],eax


// TBar の生成
Project1.dpr.28: FValue := T.Create;
00407DCE B201             mov dl,$01
00407DD0 A114734000       mov eax,[$00407314]
00407DD5 E8BACDFFFF       call TObject.Create   // 初期化コード
00407DDA 8B55FC           mov edx,[ebp-$04]
00407DDD 894204           mov [edx+$04],eax


// 文字列の生成
Project1.dpr.28: FValue := T.Create;
00407E22 8B45FC           mov eax,[ebp-$04]
00407E25 83C004           add eax,$04
00407E28 E843DDFFFF       call @UStrClr         // 初期化コード


// 整数型(順序型)の生成
Project1.dpr.28: FValue := T.Create;
00407E6E 8B45FC           mov eax,[ebp-$04]
00407E71 33D2             xor edx,edx           // 初期化コード
00407E73 895004           mov [eax+$04],edx


// 動的配列の生成
Unit1.pas.50: FValue := T.Create;
004D50F6 8B45FC           mov eax,[ebp-$04]
004D50F9 83C004           add eax,$04
004D50FC 8B1500394D00     mov edx,[$004d3900]
004D5102 E87D68F3FF       call @DynArrayClear   // 初期化コード


// 集合の生成
Unit1.pas.50: FValue := T.Create;
004D540E 8B45FC           mov eax,[ebp-$04]
004D5411 8A153C544D00     mov dl,[$004d543c]    // 初期化コード?
004D5417 885004           mov [eax+$04],dl

なんと、コンパイラマジックによって自動的にそれぞれの型の初期化コードが走っていました。
文字列や動的配列であれば、それらの内容をクリアする関数をコールしています。
順序型は 0 が代入されています(同じレジスタを xor するとレジスタの内容は 0 になる)。
集合型については、ちょっと判りませんが……
また、TFoo, TBaz については、それぞれ「引数無しの Create」が呼ばれています。
TFoo は定義されているので良いですが TBaz の場合基底クラスである TObject の Create が呼ばれました。
クラス型は全て TObject から派生しているので、constructor で制限を掛けても全てのクラスが生成できてしまいます。その Create が有効如何に関わらずです。
これは、constructor 制約の意義に関わる注意点です……実質意味が無い気がします……

ということで、 constructor 制約で実際に生成されるコードを見てみました。
結論としては、コンパイラが上手いことやってくれてる、っていうだけのお話でした。

ちなみに <T: class, constructor> というように class の制限も一緒につけると値型や文字列型は指定できなくなります。
また、<T: record, constructor> とすると、プリミティブ型しか指定できなくなります。つまり、クラス型は指定できません。

constructor 制約に値型や文字列型がわたせるのは、上記の "class", "record" のどちらも指定していないため、どっちも通るよ!っていう事なのだと思います。

それと、軽く流してしまいましたがジェネリックで指定された型が何型なのかは拡張 RTTI メソッド群を使えば取得できます。
詳しくは、上記のコード中の TConstructorConstrain クラスの ToString メソッドを参照してください。

2013年6月15日土曜日

XE4 Update 1

2013/06/12 に XE4 Update 1 が発表されました。

XE4 Update 1

XE4 Update 1 リリースノート

これで、FireMonkey の「最小化問題」と「タスクバーのコンテキストメニュー問題」は解決したのですが、まだ問題が発生しているようです。
それは、タスクバーのボタンを押しても、最小化ができない、という問題です。
通常、タスクバーでアプリケーションのボタンを押すと、アプリケーションが最小化し、もう一度押すと元のサイズに戻る、という動作をしますが、今回の Update でも、この問題は治っていないようです。
僕も気づいていませんでしたが…。
というのも、前のバージョンでは先の記事通り、そもそもタスクバーのボタンが上手く動作していなかったためです。

多分、直すのはそんなに難しくないと思われるので、時間があったら FixFMXForm.pas をアップデートしたいと思います。

ちなみに、現状の FixFMXForm を使っても、治ると言えば治ります。

2013年5月28日火曜日

Delphi で iOS を開発するためのマシン構成について。

とても今更ですが、僕がどのような構成で iOS アプリを開発しているか、そのマシン構成図を上げておきます。
VAIO Z は "Power Media Dock" という拡張ドックがあるので、その HDMI / USB ポートと KVM スイッチが常時接続された状態になっています。

VAIO Z シリーズは、どうやら終了のようです。
外付け拡張ドックに GPU / HDMI / USB 3.0 / Blueray を逃すというアイデアは秀逸でした。
個人的には大好きな一台です。

ちなみに、これも Delphi ML で貼った図になります。
図中の Mac mini は、最低ランクの一番安いものです(型番:MD387J/A)。



2013年5月27日月曜日

FireMonkey の最小化問題と右クリック問題に対処する


元々リリースの早い段階から FireMonkey3 を Windows で使うと最小化時にタスクバーに収まらないという問題があることが知られていました。



今回さらに、Delphi ML で『タスクバーの右クリックで出てくるシステムメニューで「ウィンドウを閉じる」を選択してもアプリが終わらない』という問題が指摘されました。



実は2つとも原因は同じです。
原因は、TApplication の設計上のミスです。

このバグが出た背景として、MacOS X 対応が上げられます。
MacOS X は、メインフォームを閉じてもアプリが終了しません。
あくまで、メニューからアプリケーションの終了を選ばないと、終了しないのです!
しかし、Windows では、メインフォームの終了は即ちアプリケーションの終了です。
TApplication は Win と Mac 2つの環境で違う立ち振る舞いをすべきですが、今回は Windows への対応が甘くなっていました。
それは、ベータテスターの主な注目点が iOS 対応だったためだと考えています。
僕自身も Windows でのテストはせずに iOS と MacOS X 部分のみテストしていました。
今後のベータテストでは、Windows もしっかりと見ていく必要がありそうです。

最小化できない問題について


1.TForm の Owner に TApplication が設定されている
2.TForm の WndParent には DesktopWindow が設定されている

という2つの事象から発生しています。
オーナーが設定されている、かつ、拡張ウィンドウスタイルに WS_EX_APPWINDOW が設定されていないので、タスクバーにはオーナーウィンドウ(TApplication)のみが表示されます。
しかし、WndParent(親ウィンドウ)に DesktopWindow が指定されているため、最小化するとデスクトップウィンドウ内の子フォームとして最小化されてしまいます。

システムメニューで閉じない問題について


最小化できない問題のところで、タスクバーにはオーナーウィンドウ(TApplication)が表示されていると記しました。
もうおわかりかと思いますが、タスクバーを右クリックして出てきたシステムメニューは TApplication のモノです。



システムメニューをクリックしても TForm に WM_SYSCOMMAND メッセージは送出されず、TApplication に対して送出されます。
TApplication は、受け取った WM_SYSCOMMAND を、そのまま DefWindowProc に流しているだけなので、TApplication のウィンドウハンドルは閉じてしまい、無効になります。
しかし、プロセスを終了させていないので、プロセスは残り続けます。
この問題を解決するためには TApplication が WM_SYSCOMMAND を受け取った時に メインフォームの Close を呼ぶようにしてやるだけです。

ただ、それだけだと最小化の問題は解決できません。
そこで、今回、下記のユニットを作りました。
ユニット uFixFMXForm.pas は、uses するだけで、上記の2つの問題を解決します。
このユニットは Application のタスクバーボタンを消して、フォームのタスクバーボタンを表示する、という解決方法をとりました。

ただし、副作用があって TApplication がオーナーのウィンドウは全てトップレベルウィンドウになります。
TApplication がオーナーでは無い ShowMessage などのダイアログ系はトップレベルにならないので、実用上の問題にはならないでしょう。

詳細はコード中のコメントを参照してください。

unit uFixFMXForm;
 
interface
 
implementation
 
// Win32 API を使いまくるので Windows 以外ではコンパイルされないようにする
{$IFDEF MSWINDOWS}
uses
System.SysUtils,
Winapi.Messages, Winapi.Windows;
 
var
GHookHandle: HHOOK; // フックハンドル
GAppWnd: HWND = 0; // TApplication のハンドル
 
// SendMessage でメッセージが送られたときに呼ばれる
function CallWndProc(
iNCode: Integer;
iWParam: WPARAM;
iLParam: LPARAM): LRESULT; stdcall;
var
ActiveThreadID: DWORD;
TargetID: DWORD;
begin
// フックチェインの他のフックハンドラを先に呼んでしまう
Result := CallNextHookEx(GHookHandle, iNCode, iWParam, iLParam);
 
// nCode が 0 以下の時は処理してはいけない
if (iNCode < 0) then
Exit;
 
// iLParam には CWPSTRUCT 型へのポインタが格納されている
// この型には SendMessage で送られたメッセージの詳細が入っている
with PCWPStruct(iLParam)^ do begin
case message of
// ウィンドウができるとき
WM_CREATE: begin
with PCREATESTRUCT(lParam)^ do begin
// まだ TApplication が生成されていない、かつ「ウィンドウクラス」が
// TFMAppClass(FireMonkey の TApplication のクラス名)だったとき
if (GAppWnd = 0) and (StrComp(lpszClass, 'TFMAppClass') = 0) then
// hwnd を TApplication のウィンドウハンドルとして保存しておく
GAppWnd := hwnd
else begin
// もしも TApplication が visible(=タスクバーに表示されている)
// なら、非表示にする!
if (GAppWnd <> 0) and (IsWindowVisible(GAppWnd)) then
ShowWindow(GAppWnd, SW_HIDE);
 
// オーナーウィンドウが TApplication なら TForm のインスタンスと
// みなして拡張ウィンドウスタイルに WS_EX_APPWINDOW を設定する
// WS_EX_APPWINDOW が設定されたフォームは、トップレベルウィンドウ
// となるので、タスクバーに表示される
if (GetWindow(hwnd, GW_OWNER) = GAppWnd) then
SetWindowLong(
hwnd,
GWL_EXSTYLE,
GetWindowLong(hwnd, GWL_EXSTYLE) or WS_EX_APPWINDOW);
end;
end;
end;
 
// ウィンドウが表示されるとき
WM_SHOWWINDOW: begin
// オーナーがあるのに拡張スタイルに WS_EX_APPWINDOW を指定していると
// 最前面に表示されない事があるので、ウィンドウが表示されるときは
// 強制的に最前面にする
// 強制最前面化処理は、下記のように AttachThreadInput を使うが
// 詳細は省略
if (GetWindow(hwnd, GW_OWNER) = GAppWnd) then begin
ActiveThreadID := GetWindowThreadProcessId(GetForegroundWindow, nil);
TargetID := GetWindowThreadProcessId(hwnd, nil);
 
AttachThreadInput(TargetID, ActiveThreadID, True);
try
SetForegroundWindow(hwnd);
SetActiveWindow(hwnd);
finally
AttachThreadInput(TargetID, ActiveThreadID, False);
end;
end;
end;
end;
end;
end;
 
initialization
begin
// WH_CALLWNDPROC フックを仕掛ける
// WH_CALLWNDPROC は SendMessage が呼ばれたときに呼ばれるフック
GHookHandle :=
SetWindowsHookEx(WH_CALLWNDPROC, CallWndProc, 0, GetCurrentThreadID);
end;
 
finalization
begin
// フックを解放
UnhookWIndowsHookEx(GHookHandle);
end;
{$ENDIF}
 
end.

と、まあ今回このようなユニットを作りましたが、近日リリースされるであろう XE4 Update1 で、このバグは治っている事でしょう。
ですから、無理してこのユニットを使わず、Update1 を待っても良いかも知れません。

2013年4月13日土曜日

Prezi を使ってみたよ

Prezi を使ってみたよ。

Prezi のサンプルなので中身の説明は超いい加減!


2013年4月3日水曜日

もうすぐ再開します!

もう4月!
この一月以上更新が無かった理由が、もうすぐ終わるので、それに関連した話題とかも上げていきます!
しばし待たれよ!