第19回 エンバカデロ・デベロッパーキャンプ開催形態変更

東北関東大震災の影響により、デベロッパーキャンプの開催形態がバーチャル(オンラインセミナー)形式に変更になりました。会場参加の場合、いつもは印刷したレジュメを会場で配るのですが、今回は事前にオンラインで配布することとなりますので注意してください。

で、おいらは、午後のセッションのトップバッターなのだけど、事前に配布したプレゼンにバグがあるかもしれない*1ので、その辺はご容赦ください。<(_ _)>

*1:まぁ、これは印刷したレジュメもそうなのだけど。

static_castとreinterpret_castの違いについて試してみた

昨日のネタのうち、キャスト関連は思い込みとか理解不足なところがあったので、実際に動かしてみた。

//---------------------------------------------------------------------------
#include <iostream>
#pragma hdrstop

#include <tchar.h>
//---------------------------------------------------------------------------

#pragma argsused
int _tmain(int argc, _TCHAR* argv[])
{
  double a = 123.4567;
//int i0 = reinterpret_cast<int>(a); // error!!! [BCC32 エラー] File1.cpp(12): E2031 'double' から 'int' へのキャストはできない
  int& i1 = reinterpret_cast<int&>(a);
  int* i2 = reinterpret_cast<int*>(&a);
  int  i3 = static_cast<int>(a);
//int* i4 = static_cast<int*>(&a);  // error!!! [BCC32 エラー] File1.cpp(12): E2031 'double *' から 'int *' へのキャストはできない
  int* i5 = (int*)&a;
  int& i6 = (int&)a;

  std::cout << sizeof(a) << std::endl;
  std::cout << sizeof(int) << std::endl;
  std::cout << i1 << std::endl;
  std::cout << *i2 << std::endl;
  std::cout << i3 << std::endl;

  return 0;
}
//---------------------------------------------------------------------------

感じとして、Cスタイルのキャストは何が何でも変換。結果についてはお構いなし。reinterpret_castはポインタ同士のように互いに関連性がありそうならばキャストする。static_castは暗黙の型変換が許されればキャストする。

上記コード例のint*とdouble*の場合、reinterpret_castは互いにポインタなのでOKだけど、static_castは型そのものが違うので、暗黙の型変換が存在しなくてエラー。「プログラミング言語 C++ 第3版」の170ページあたりと実際の実行結果(C++Builder XEとVC++2008で確認 )から解釈すると、こんな感じなのかな…。

「C++プログラマであるかを見分ける10の質問」に答えてみた。

「C++プログラマであるかを見分ける10の質問」に答えてみた。自分の解釈だとこんな感じ。正解かどうか、特に2のreinterpret_castと10は自信なし。ちなみに、Java版/.Jの日記で。

1. iterator の役割について説明せよ.
ポインタがメモリのアドレスを指し示すように、イテレータはコンテナの中身を指し示す。ポインタとの違いは、コンテナの「先頭」と「終端」を意味する値があること。

2. *_cast およびCスタイルのキャストそれぞれについて概要を説明せよ.
Cスタイルのキャスト:単なる型変換。数値ならば精度が落ちる場合がある。
static_cast:同上。C++での静的変換はCスタイルのキャストではなく、こっちを使用するべき。
reinterpret_cast:ポインタ同士、もしくは、ポインタと同サイズの整数型とのキャスト。メモリイメージの無理矢理コピー、精度落ちとかは無し。
dynamic_cast:継承関係が直系の子孫で無ければ、ポインタならばNULLとなり、参照ならばstd::bad_castが投げられる。*1
const_cast : constを外す。原則として使用不可。どうしても使用しなければならない場合のみ使うこと。代替手段は必ず存在する。

03/08:static_castとreinterpret_castについて勘違いがあった。orz http://d.hatena.ne.jp/A7M/20110308/1299558334

3. overload と override と hiding の違いについて説明せよ.
overload:メソッドの多重定義。同名でも引数の違いで区別する。
override:メソッドの上書き。サブクラスで同一名称、同一引数のクラスの挙動を変える。
hiding:名前隠蔽。サブクラスで継承元のメソッドやメンバ変数を参照できなくする。

4. const の機能について概要を説明せよ.
値が「不変」であることを保証する。てか、変えられない。

5. 多重継承について概要を説明せよ.
私は、プログラマであり、横浜FCサポーターであり、呑兵衛である。*2

assert(!dynamic_cast<FMarinosSuppoter*>(&A7M)); // 私は、マリノスサポではありません。

6. ポインタの使用方法について,メモリーリーク問題等と絡ませながら戦略を述べよ.
おもちゃ(メモリ)で遊んだ後は自分でおもちゃを片付ける。ポインタはお部屋(OS)に広げたおもちゃがどこにあるかを知らせてくれる。
おもちゃが出しっぱなしならば、お部屋がおもちゃでいっぱいになって、自分がそのおもちゃに躓いて怪我をすることも。
スマートポインタマジ便利。スマートポインタは後片付けを自動的にしてくれる。

7. コピーコンストラクタおよび代入演算子の扱いにおける戦略について述べよ.
コピーコンストラクタ:無から有を生み出す。
代入演算子:すでにあるものを後から上書きする。

8. virtual デストラクタの概要および使用上の戦略について述べよ.
virtualの場合、オブジェクトを生成したコンストラクタに対応するデストラクタが実行される。virtualでない場合は、変数型のデストラクタが実行される。

9. コンストラクタ,デストラクタにおける例外処理についての戦略を述べよ.
コンストラクタの場合:料理を作りたくても、材料や道具が揃わないことはよくあります。未完成な料理は出せないでしょ?
デストラクタの場合:料理の後片付けをしている最中にトラブる可能性は十分考えられます。後始末はきっちりと。

10. 抽象クラスとテンプレートクラスの使い分けについてインターフェースと言う観点から述べよ.
抽象クラスの「振る舞い」は継承元を含めたクラスに依存する。
テンプレートクラスの「振る舞い」は継承関係に依存しないが、「名前」に依存する。

*1:自分の経験だと継承関係のあるクラスのポインタでしか使ったことが無い。

*2:「人間」を基底クラスにすれば、ダイアモンド継承になるのかも。

Open Tools API その4:続・ソースエディタへのアクセス ソースエディタの「中身」をいじる

エディタの編集バッファ

ビュー(IOTAEditView)のBufferプロパティがソースエディタ内部のバッファで、ソースファイルの中身を編集する。インターフェースはITOAEditBuffer。
ソースエディタが1つのファイルをオープンしている場合、ビューは複数あっても、バッファは同一のを参照する。ソースエディタ(IOTASourceEditor)、ビュー(IOTAEditView)、バッファ(ITOAEditBuffer)の関係はこんな感じ。

        ┌ ビュー[0] ┐
        ├ ビュー[1] ┤
ソースエディタ ┼ ビュー[2] ┼ バッファ
        │   …   │
        └ ビュー[n] ┘

バッファのEditBlockプロパティがエディタで選択されている範囲。以下は、エディタの選択部分の位置と内容を取得する例。

function GetCurrentSourceEditor: IOTASourceEditor;
var
  i: Integer;
  Editor: IOTAEditor;
  ISourceEditor: IOTASourceEditor;
  CurrentModule: IOTAModule;
begin
  Result := nil;
  CurrentModule := (BorlandIDEServices as IOTAModuleServices).CurrentModule;

  for i := 0 to CurrentModule.GetModuleFileCount - 1 do
  begin
    Editor := CurrentModule.GetModuleFileEditor(i);

    if Supports(Editor, IOTASourceEditor, ISourceEditor) then
    begin
      Result := ISourceEditor;
      Break;
    end;
  end;
end

procedure TfrmEditorStatus.Button2Click(Sender: TObject);
var
  SourceEditor: IOTASourceEditor;
  EditBuffer: IOTAEditBuffer;
  Start, After: TOTACharPos;
  BlockType: TOTABlockType;
begin
  SourceEditor := GetCurrentSourceEditor;  // 現在アクティブなエディタを取得(GExpters由来)
  if SourceEditor = nil then
    Exit;
  Start := SourceEditor.BlockStart;  // 選択範囲の開始位置
  After := SourceEditor.BlockAfter;  // 選択範囲の終了位置

  // 現在編集中のファイルのバッファを取得
  EditBuffer := (BorlandIDEServices as IOTAEditorServices).TopBuffer;
  if SourceEditor = nil then
    Exit;

  // 選択範囲の内容を取得
  Memo1.Clear;
  Memo1.Text := EditBuffer.EditBlock.Text;
end;

バッファへ直接アクセスする

セレクションではなく、バッファを直接アクセスする場合は、編集リーダー(IOTAEditReader)と編集ライター(IOTAEditWriter)を介してアクセスする。バッファはUTF-8で管理しているので、UnicodeStringからは明示的に変換してやらないと文字化けするので注意。
バッファの中身を読み取る手順は、エディタのCreateReaderメソッドで編集リーダーを生成し、GetTextメソッドで中身を取得する。
以下は、GExpertsのユーティリティ関数で、エディタのバッファからTStreamへ中身を読み込むもの。

// 編集リーダーからTStreamへ読み込む
procedure GxSaveReaderToStream(EditReader: IOTAEditReader; Stream: TStream;
  TrailingNull: Boolean);
const
  // Leave typed constant as is - needed for streaming code.
  TerminatingNullChar: AnsiChar = #0;
var
  EditReaderPos: Integer;
  ReadDataSize: Integer;
  Buffer: array [0 .. EditReaderBufferSize] of AnsiChar;
  // Array of bytes, might be UTF-8
begin
  Assert(EditReader <> nil);
  Assert(Stream <> nil);

  EditReaderPos := 0;
  ReadDataSize := EditReader.GetText(EditReaderPos, Buffer,
    EditReaderBufferSize);
  Inc(EditReaderPos, ReadDataSize);
  while ReadDataSize = EditReaderBufferSize do
  begin
    Stream.Write(Buffer, ReadDataSize);
    ReadDataSize := EditReader.GetText(EditReaderPos, Buffer,
      EditReaderBufferSize);
    Inc(EditReaderPos, ReadDataSize);
  end;
  Stream.Write(Buffer, ReadDataSize);
  if TrailingNull then
    Stream.Write(TerminatingNullChar, SizeOf(TerminatingNullChar));
  // The source parsers need this
end;

// ソースエディタの中身をTSTringsにロードする
procedure GxLoadSourceEditorToUnicodeStrings(SourceEditor: IOTASourceEditor; Data: TStrings);
var
  MemStream: TMemoryStream;
begin
  Data.Clear;
  if not Assigned(SourceEditor) then
    raise Exception.Create
      ('No source editor in GxOtaLoadSourceEditorToUnicodeStrings');
  // TODO: Check stream format for forms as text (Ansi with escaped unicode, or UTF-8) in Delphi 2007/2009
  MemStream := TMemoryStream.Create;
  try
    GxSaveReaderToStream(SourceEditor.CreateReader, MemStream, False);
    MemStream.Position := 0;
{$IFDEF UNICODE}
    Data.LoadFromStream(MemStream, TEncoding.UTF8);
{$ELSE}
    if RunningDelphi8OrGreater then
      SynUnicode.LoadFromStream(Data, MemStream, seUTF8)
    else
      SynUnicode.LoadFromStream(Data, MemStream, seAnsi);
{$ENDIF}
  finally
    FreeAndNil(MemStream);
  end;
end;

どうも、CreateReaderで生成した編集リーダーは明示的に破棄する必要はなさそう。
一方、書き込む場合はIOTASourceEditorのCreateUndoableWriter*1メソッドで編集ライターを生成して、DeleteToメソッドで中身を削除したり、Insertメソッドで挿入する。
これまた、GExpertsのコードの一部。この関数は、エディタの中身を文字列で丸々入れ替える例。

procedure GxReplaceEditorText(SourceEditor: IOTASourceEditor; Text: string);
var
  Writer: IOTAEditWriter;
begin
  Assert(Assigned(SourceEditor));
  Writer := SourceEditor.CreateUndoableWriter;
  if not Assigned(Writer) then
    raise Exception.Create('No edit writer');
  Writer.DeleteTo(MaxLongint);
  Writer.Insert(PAnsiChar(AnsiToUtf8(Text)));
  Writer := nil;
end;

文字列をAnsiToUtf8関数で明示的にUTF-8へ変換して、そのポインタを渡している。編集ライターも明示的に破棄する必要は無いみたい。

まとめ

これで、Open Tools APIについてのネタは終わり。他にもOpen Tools APIはプロパティエディタやフォームエディタをカスタマイズしたり、キーバインドも変更できるので、アイデア次第でいろいろできそう。
DelphiC++BuilderIDEVCLで構築されているので、普通にアプリを作る上でのテクニックが十分に使用可能。資料は少ないけどGExpertsのソースコードとToolsAPI.pasはかなり参考になる。試行錯誤の末なので間違いがあるかもしれないので、その場合はアドバイスください。<(_ _)>

*1:CreateWriterメソッドもあるけど、こちらはUNDO不可な編集ライターを生成。

Open Tools API その3:ソースエディタへのアクセス

モジュール

モジュールとはIDEがアクセスする抽象的なエディタの組み合わせのことで、Delphi/C++Builderのプロジェクトにおけるユニットに相当。ユニットがソースファイル(*.pas/*.cpp)やヘッダファイル(*.h)、フォームファイル(*.dfm)の組み合わせであるように、モジュールは1つ以上のテキストエディタとフォームエディタなどから構成される。
モジュールに対するインターフェースを提供するのがIOTAModuleで、IDEからはIOTAModuleServiceより取得します。以下は、IDEが参照しているファイルの一覧を列挙する例。

procedure TfrmEditorStatus.Button6Click(Sender: TObject);
var
  I, J: Integer;
  ModuleServices: IOTAModuleServices;
  Editor: IOTAEditor;
  SourceEditor: IOTASourceEditor;
  FormEditor: IOTAFormEditor;
begin
  // モジュールの取得
  ModuleServices := (BorlandIDEServices as IOTAModuleServices);

  with ModuleServices do
  begin
    // IDEが開いているモジュールの列挙
    for I := 0 to ModuleCount - 1 do
    begin
      Memo1.Lines.Add(Modules[I].FileName);

      // モジュールが参照するエディタの列挙
      for J := 0 to Modules[I].GetModuleFileCount - 1 do
      begin
        Editor := Modules[I].GetModuleFileEditor(J);
        if Supports(Editor, IOTASourceEditor, SourceEditor) then
        begin
          // エディタはソースエディタ
          Memo1.Lines.Add('SourceEditor - ' + SourceEditor.FileName);
        end
        else if Supports(Editor, IOTAFormEditor, FormEditor) then
        begin
          // エディタはフォームエディタ
          Memo1.Lines.Add('FormEditor - ' + FormEditor.FileName);
        end else
        begin
          // 不明
          Memo1.Lines.Add('Unknown - ' + Editor.FileName);
        end;
      end;
    end;
  end;
end;

エディタの種類の判別には、Supports関数でそれぞれのインターフェースをサポートするかどうかで判別する。

ソースエディタ、ビュー

ソースエディタのインターフェースがIOTASourceEditorで、ソースエディタは複数個のビュー(IOTAEditView)から構成される。EditViewCountプロパティがビューの個数で、この値が0の場合はサブビューが隠れていることを表しす。ビューそのものはEditViews[n]でアクセスする。
ビューがエディタそのもので、カーソル位置やセレクション情報、内部バッファなどの情報をもつ。
IOTAEditViewの主なプロパティ

プロパティ 型名 内容
Block IOTAEditBlock ブロック:エディタの選択範囲
Buffer IOTABuffer バッファ:エディタの内部バッファ
Position IOTAEditPosition カーソル位置
TopRow Integer 最上部に表示されている行の行番号
RightColumn Integer ビューの右端の桁番号

第19回 エンバカデロ・デベロッパーキャンプでしゃべります。

毎度お馴染みですが、第19回 エンバカデロ・デベロッパーキャンプでまたしゃべります。
内容は、過去に掲載したOpen Tools APIに関する3点と、

現在執筆中のコードエディタへのアクセスの件*1をベースに、Twitter APIをラップするDelphiコンポーネントであるTTwitterを使って、Delphi/C++BuilderIDETwitterクライアント化してしまうという、デブキャンプ史上最も誰得な無茶振りネタ。

TTwitterの動作は確認しているけど、最悪のケースとして、RubyPython製のTwitterクライアントをIDEからコンソールアプリとして呼び出す可能性も無きにしも非ず。(ぉ

てか、とっとと、デモ用アプリとプレゼンを作れ!>俺

*1:何とか今度の連休中に完成させたい

C++Builder XE Starter/Delphi XE Starterが発表

かねてから要望が多かった個人向けエディションのC++Builder XE Starter/Delphi XE Starterが発表。

Delphi関連は有志の方がまとめてくれているのでC++Builderの分について少々。

個人的にC++Builder XE Starterの機能でカットされていて残念なのは、メモリリークやリソースリークとかを検知するCodeGuard。これ、バッファオーバーフローとかも検知してくれるから、C/C++初心者が嵌りやすいポインタ絡みの件を解決するのに便利なんだよね…。
MS HELP2もカットだから、これはdocwikiを直接参照する形式になるのかも。

あと、今度こそTurbo Explorerのように気がついたらフェードアウトではなく、少々のタイムラグがあってもPro/Ent/Arch版と同様にバージョンアップを続けて欲しい。

ということは、EclipseとかVisual Studio Expressも対象だから、実質\14,000か…。