スマートポインタの使い方 その1:unique_ptr

スマートポインタって何?

C++において、operator newでメモリ領域(ヒープ領域)を動的に確保した場合、その領域はoperator deleteでプログラマが責任を持って解放してやらなければならない。しかし、deleteを書き忘れたり、例外が発生したときの処理を怠った場合など、それが正しく行われないことはよくある。正しく解放されなかった領域はOSやプロセスが使用可能なメモリ領域を「不正占拠」し、それが積もり積もると、OSやプロセスが停止する場合がある。

#include <memory>
class Mess {};
void f(Point p1, Point p2)
{
  Rectangle* r(new Rectangle(p1, p2));
  r->rotate(45);  // 矩形を45度回転
  // ...
  if (in_a_mess) throw Mess(); // 例外を投げてみる

  // ねぇ、例外が投げられたときの後始末は?
  delete r;
}

そこで、プログラマの負担を軽減すべく、そのメモリ領域の「寿命」の管理を自動的に行う「スマートポインタ」がC++の拡張ライブラリであるBoostに実装された。その後、C++の新規格であるC++0x(C++11)でBoostの実装をベースにしたスマートポインタがC++標準ライブラリに取り込まれた。*1
スマートポインタとは文字通り"smart"(気の利いた)なポインタのことで、スマートポインタが管理しているメモリ領域が不要になったら自動的に解放してくれる。*2メモリ領域を解放するタイミングの違いによりいくつかのタイプがある。

  • std::unique_ptr (boost::scoped_ptr) : 変数の寿命が尽きるとそのメモリ領域を開放する。*3
  • std::shared_ptr (boost::shared_ptr) : 参照カウントを持ち、参照カウントがゼロになるとそのメモリ領域を開放する。
  • boost::intrusive_ptr : プログラマが参照カウントを管理する。
  • boost::scoped_array : boost::scoped_ptrの配列版
  • boost::shared_array : boost::shared_ptrの配列版

もちろん、振る舞いはあくまでも「ポインタ」なので、見た目は通常のポインタ関連のコードと何ら変わりが無い。

#include <memory>
class Mess {};
void f(Point p1, Point p2)
{
  std::unique_ptr<Rectangle> r(new Rectangle(p1, p2)); // unique_ptrはブロックの外に達するとメモリ領域を自動開放する
  r->rotate(45);  // 矩形を45度回転
  
  // ...
  if (in_a_mess) throw Mess(); // 例外を投げてみる
  // 途中で例外が投げられてもブロックの外に出たら自動的にメモリ領域の開放を行う
  // operator deleteは不要。スマートポインタが解放する。
}

std::unique_ptrを使ってみる

std::unique_ptrはブロックの外に抜けるなど変数の寿命が尽きたら、管理しているメモリ領域を自動的に開放するスマートポインタ。その名の通り"unique"(唯一)のメモリ領域しか管理しないので、複数のunique_ptrのインスタンスが同一のメモリ領域を管理することはあり得ない。元々、C++の標準ライブラリにはスマートポインタとしてauto_ptrがあったが、仕様に少々問題があった為にC++0xからは使用が非推奨となった。その替わりとしてunique_ptrが導入された。
使い方はコンストラクタの引数にoperator newで生成したポインタを渡すだけ。*4以後、そのメモリ領域はunique_ptrの管理下に置かれる。引数を与えなかった場合は、unique_ptrが管理しているメモリ領域は「無し」とみなす。使用するシチュエーションとして考えられるのは、pimplイディオムの実装部分や、Abstract Factoryパターンが生成したオブジェクトを自動的に解放したい場合など。

#include <iostream>
#include <memory>
class TestObject
{
public:
    TestObject()
    {
        std::cout << "TestObject::Constructor" << std::endl;
    }
    ~TestObject()
    {
        std::cout << "TestObject::Destructor" << std::endl;
    }
    void message()
    {
        std::cout << "TestObject::message" << std::endl;
    }
};

int main()
{
    std::unique_ptr<TestObject> pObject(new TestObject()); // newしたオブジェクトをコンストラクタに渡す
    pObject->message(); // メンバ関数の呼び出し
    return 0;
}

メンバへのアクセスはポインタと同様にアロー演算子(operator->)で行う。インスタンスへのアクセスはポインタ演算子(operator*)。当然、deleteも必要ない。ブロックの外に出ると変数の寿命が尽きるのでunique_ptrはデストラクタで自身が管理しているメモリ領域を開放する。ただし、operator=で代入することも出来ない。

void f(Point p1, Point p2)
{
  std::unique_ptr<Rectangle> rb = new Rectangle(p1, p2); // 代入は駄目。初期化は必ずコピーコンストラクタで。
}

配列として連続した複数個のメモリ領域が欲しい場合は、[]をつけて宣言する。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int[]> a(new int[10]);

  for (int i = 0; i < 10; ++i) {
	a[i] = i;
  }

  for (int i = 0; i < 10; ++i) {
	std::cout << a[i] << std::endl;
  }
  return 0;
}

生ポインタの取得

unique_ptrはあくまでもポインタをラップするユーティリティクラスである。コードの見た目をポインタのように見せているだけであり、型そのものが違う。Windows APIのような標準C++ではないライブラリやAPIを呼び出す場合、unique_ptrが管理しているメモリ領域の生ポインタが必要になる場合がある。unique_ptrが管理しているメモリ領域の生ポインタを取得するにはgetメソッドを使用する。注意しなければならないのはgetメソッドはメンバ演算子(operator.)で呼び出す。

// 地図の名前を取得するAPI
int get_map_name(int MapID, char* buffer);
void process_map_name(int MapID)
{
  int length = get_map_name(MapID, NULL);  // 必要な領域サイズを取得
  std::unique_ptr<char[]> buffer(new char[length]);  // 領域を確保

  get_map_name(MapID, buffer); // エラー!!!
  get_map_name(MapID, buffer.get()); // getメソッドで生ポインタを取得する
}

取得した生ポインタはあくまでもunique_ptrの管理下にあるので、自分でdeleteしてはならない。unique_ptrがメモリ領域を管理しているか否かは、getメソッドで生ポインタを取得するか、あるいは、operator boolでチェックする。

void f() 
{
  std::unique_ptr<TestObject> obj;  // どこも管理していないunique_ptr
  if (!obj) {
    std::cout << "ptr is not enabled" << std::endl;
  }
}

unique_ptrの再初期化

Abstract Factoryパターンのようにサブクラスで振る舞いが変化するオブジェクトを動的に生成する場合など、文脈によってunique_ptrを再初期化する必要が出てくる。unique_ptrはoperator=で代入(上書き)出来ないが、resetメソッドで別のポインタで再初期化できる。このとき、unique_ptrは自身が管理しているメモリ領域を解放するので、プログラマがdeleteでメモリ領域を明示的に開放する必要は無い。

#include <iostream>
#include <memory>

class TestObject
{
public:
  TestObject(int ID) : m_ID(ID)
  {
    std::cout << "TestObject::Constructor" << "ID = " << m_ID << std::endl;
  }
  ~TestObject()
  {
    std::cout << "TestObject::Destructor" << "ID = " << m_ID << std::endl;
  }
  void message()
  {
    std::cout << "TestObject::message" << std::endl;
  }
  int m_ID;
};

int main()
{
  std::unique_ptr<TestObject> pObject(new TestObject(100)); 
  pObject->message(); // メンバ関数の呼び出し

  // 再初期化
  pObject.reset(new TestObject(200));
  return 0;
}

上記コードの実行結果

TestObject::ConstructorID = 100
TestObject::message
TestObject::ConstructorID = 200
TestObject::DestructorID = 100
TestObject::DestructorID = 200

resetの引数にNULLポインタを渡せば、明示的にunique_ptrが管理しているメモリ領域を解放することが出来る。メモリ領域を開放せずに、ただ単にunique_ptrとメモリ領域の関係を切り離す場合はreleaseメソッドを使用する。戻り値としてメモリ領域の生ポインタが返るので、その後は通常のポインタとして扱える。当然、解放が必要であれば明示的に解放しなければならない。

void f()
{
  std::unique_ptr<TestObject> pObject(new TestObject()); 
  TestObject* pp = pObject.release(); // unique_ptrとメモリ領域の関係を切り離す

  delete pp; // 明示的に解放しないとリソースリークする
}

unique_ptrと代入演算子(operator=)

C++03まで標準であったauto_ptrが「問題」とされたのは、auto_ptr同士をoperator=で代入するとメモリ領域の所有者が=の右から左へ移り、代入元のauto_ptrの管理領域はNULLとなる為である。

#include <iostream>
#include <memory>
int main()
{
  std::auto_ptr<int> a(new int(100));
  std::auto_ptr<int> b(new int(200));

  // 単なるポインタ同士の代入のつもりだが、実はポインタの所有権が移動する
  b = a;
  
  // aはどこも参照していない抜け殻なので、Access Violationが発生
  std::cout << *a << std::endl;

  return 0;
}

一見問題なさそうなコードだが、単純な代入文であるにもかかわらず副作用が発生するのはよろしくないので、unique_ptrはoperator=を隠蔽して明示的な代入を出来なくした。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int> a(new int(100));
  std::unique_ptr<int> b;

  b = a; // operator=は隠蔽されているのでコンパイルエラーが発生する。
  std::cout << *a << std::endl;

  return 0;
}

unique_ptrをメンバに持つクラスにoperator=を定義する場合など、文脈によってはunique_ptr同士で「値」をやりとりしなければならない場合がある。unique_ptrは単一の領域しか管理しないので、単一性を担保するための戦略として「中身」のやりとり以外に、管理しているメモリ領域のポインタ値の交換、もしくは譲渡が考えられる。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int> a(new int(100));
  std::unique_ptr<int> b(new int(200));

  std::cout << "a = " << *a << std::endl;
  std::cout << "b = " << *b << std::endl;

  *a = *b;  // unique_ptrの管理下にある領域の「中身」をやりとりする

  std::cout << "a = " << *a << std::endl;
  std::cout << "b = " << *b << std::endl;

  return 0;
}

unique_ptrが管理している領域のポインタ値を「交換」する場合はswapメソッドを使う。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int> a(new int(100));
  std::unique_ptr<int> b(new int(200));

  std::cout << "a = " << *a << std::endl;
  std::cout << "b = " << *b << std::endl;
  a.swap(b);  // unique_ptrが管理している領域を交換
  std::cout << "a = " << *a << std::endl;
  std::cout << "b = " << *b << std::endl;

  return 0;
}

ポインタを「譲渡」する場合は、C++0xで導入されたmove関数を使用する。

#include <iostream>
#include <memory>
int main()
{
  std::unique_ptr<int> a(new int(100));
  std::unique_ptr<int> b;

  if (a) std::cout << "a = " << *a << std::endl;
  if (b) std::cout << "b = " << *b << std::endl;
  // std::auto_ptrだと、b = a;に相当
  b = std::move(a); 	// unique_ptrが管理しているポインタを譲渡
  if (a) std::cout << "a = " << *a << std::endl;
  if (b) std::cout << "b = " << *b << std::endl;

  return 0;
}

move関数はオブジェクトが保持しているリソースを戻り値に譲渡*5させるヘルパ関数で、結果、譲渡元には何も残らない。C++0xで導入された「ムーブセマンティクス」と「rvalue参照」を用いて実装されている。

*1:よって、名前空間はstd(std::tr1)である。

*2:厳密にはヒープ領域だけでなく、ハンドルといったメモリとは少々違う意味合いのリソースも管理できるが、これは別項で。

*3:std::unique_ptrとboost::scoped_ptrはコンストラクタ等の実装の違いはあるけど、ほぼ同一と言っていいと思う。

*4:他に解放処理を決定するdeleterがあるが、滅多に使わないので省略。

*5:厳密には、lvalueをrvalueに変換

CentOS 5.6で最低限のWebアプリ開発環境を構築する

半ば個人的な手順書。少々怪しいかも。すべてCUIで行う場合を想定。

開発ツールのインストール

デフォルトではgccとか入らないので。

# yum groupinstall 'Development Tools' 'Development Libraries'

PHP5.3をインストール

デフォルトのPHPは5.1なので、5.3をインストール。ただし、php53-pearが無いのでpearはPHP5.1のをインストール後アップグレード。最初はメッセージが色々出るけど気にしない。(参考:http://d.hatena.ne.jp/Akkiesoft/20110411/1302488866

# yum remove php php-*
# yum install php53 php53-*
# yum install php-pear
# pear upgrade --force Archive_Tar
# pear upgrade --force Console_Getopt
# pear upgrade PEAR

"pear list" でバージョンの確認が出来る。

Installed packages, channel pear.php.net:
=========================================
Package          Version State
Archive_Tar      1.3.7   stable
Console_Getopt   1.3.1   stable
PEAR             1.9.2   stable
Structures_Graph 1.0.4   stable
XML_RPC          1.5.0   stable
XML_Util         1.2.1   stable

Xdebugのインストール

PHPデバッグツールであるXdebugをインストール。

# pecl install xdebug

ビルドが終わったら/etc/php.iniに以下の項目を追加する。

[zend]
zend_extension="/usr/lib/php/modules/xdebug.so"
xdebug.remote_enable=On
xdebug.remote_host=192.168.X.X

xdebug.remote_hostはリモートデバッグをする場合の接続元のIPアドレス
"system-config-securitylevel"ツールを使用してXdebugが使用するポート9000を開ける。

# system-config-securitylevel

Apacheの動作を確認

# chkconfig --list | grep httpd
# chkconfig httpd on
# /etc/init.d/httpd restart

PHPの動作を確認

関数phpinfoはプラグインの状態も表示してくれる。Xdebugが正しくインストールされればその情報も。

<?php
phpinfo();
?>

動作しない場合は、コマンドラインで実行してエラーメッセージを調べる。

# php /var/www/html/index.php

SELinux関連

SELinuxの管理ツールをインストール。SELinuxは有効にした方が無難かも。*1

# yum install setroubleshoot
# chkconfig setroubleshoot on
# /etc/init.d/setroubleshoot start

SELinuxの生ログは/var/log/audit/audit.log。「sealert -a /var/log/audit/audit.log」で詳細な内容をわかりやすく表示してくれる。
SELinux関連はこれらが参考になる。

一般ユーザーとsambaの設定

お約束の一般ユーザーの追加。SELinuxが有効なときにSambaのファイル共有を許可するにはsamba_enable_home_dirsの値を1にする。

# adduser a7m
# passwd a7m
# pdbedit -a a7m
# setsebool -P samba_enable_home_dirs=1 
# chkconfig --list | grep smb
# chkconfig smb on
# /etc/init.d/smb start

MySQL関連

MySQLのインストール

インストールされるバージョンは5.0。

# yum groupinstall 'MySQL Database'
初期設定

/etc/my.cnfを編集する。エンジンはInnoDBを使用し、デフォルトのエンコーディングUTF-8にする。

[mysqld]
default-character-set = utf8  ; ←追加
default-storage-engine=InnoDB ; ←追加
[mysql]
default-character-set = utf8  ; ←追加
セキュリティ関連

"system-config-securitylevel"ツールを使用してMySQLが使用するポート3306を開ける。

# system-config-securitylevel
# setsebool -P httpd_can_network_connect=1

httpd_can_network_connectを1にしないと、httpdが外部DB鯖と通信できない模様。

MySQLのデーモンの起動
# chkconfig --list | grep mysqld
# chkconfig mysqld on
# /etc/init.d/mysqld start
初期データベースの構築

スクリプトmysql_secure_installationを実行して、メッセージに従えばOK。

# mysql_secure_installation
リモートからrootのログインを許可する

セキュリティ的には良くないけど、WindowsからGUIでいろいろ出来るので。

# mysql -u root -p
mysql> GRANT ALL PRIVILEGES ON *.* TO root@'%' IDENTIFIED BY 'rootpassword';

パッケージのアップデート

最後にインストール済みのパッケージを更新する。

# yum check-update
# yum update

*1:本当は完全に無効化したかったのだけど、ポリシーを理解すればそんなに敷居が高くない。

第19回 エンバカデロ・デベロッパーキャンプ セッションT3自己フォロー

参加者の皆様お疲れ様でした。プレゼンの最初でいきなりしくじってしまいましたが、何とか完走できたつもりです。不明な点等ありましたらコメントください。

C++Builderでの問題点

なぜか、IDEの言語が日本語だとC++Builderで作ったアドオンがデバッグ出来ない。*1どうも、IDEデバッグシンボルを読み込んでくれない模様。非常に後ろ向きな解決方法として、すっぴんの仮想マシンを作成してそれにRAD Studio XEをインストール。インストールする言語は日本語と英語。BDSSetLang.exeでIDEの言語を英語にすると大丈夫っぽい。でも、自分の実機ではそれでもうまくいかないので他のアドオンとかの影響もあるのかも。*2 *3

デバッグ時にIDEをハングアップさせない方法

OTAを使うとIDEの本体にも影響がありハングするなどデバッグ時に不便なことがあり、それについて言及したのだけど、DEKOさんから「代替レジストリを使って見たらどうか」という指摘があったので試してみた。
デバッグ実行時のオプションの[実行|実行時引数]に"-rregkey"を設定すると、デバッグ時のRAD Studioが参照するレジストリがデフォルトのHKCU\Software\Embarcadero\BDSからHKCU\Software\Embarcadero\regkeyとなるので、オリジナルの環境に影響を与えないでアドオンのデバッグが可能。regkeyは任意の名前をつけられる。

この方法に気づいていればプレゼンに入れられたのに。orz

DEKOさんへ業務連絡

Twitterのアカウントをとってくれると嬉しいかな。ちょっとした連絡事項等が(ほぼ)リアルタイムかつ、パブリックに公開できます。
あと、「s/築/筑/g」でございます。まぁ、自分の名字を説明するのに便利だし、この手の違いはずっと慣れているので、UNICODEの包摂基準なんてどうということ無いです。(ぉ

サンプルコードについて(04/06)

セッションで使用したサンプルコードを http://a7m.sakura.ne.jp/SOURCE/DevCamp19_T3_Sample.zip にアップロードしました。ご自由に使ってください。

*1:Windows 7x86x64の両方で確認。

*2:とっととQC送りしないといけないけど、英訳('A`)マンドクセ

*3:04/08にQC#92811送り完了。

第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不可な編集ライターを生成。