2012年11月18日日曜日

Direct3D10.1の概要と導入

Direct3Dのバージョン10.1は、バージョン10にこんな機能 が追加されています。また、Direct2Dとの連携が出来ます(Direct2D と GDI の相互運用性の概要)。
対応OSはVistaのサービスパック1適用以降みたいです。つまり、ほとんどDirect3D10の要件と同じです。ただし以前のビデオカードには、10までしか対応していないものがあるので、少し動作環境が狭まります。

APIの扱い方は10時代とほとんど変わりません。Direct3D10のチュートリアルを以下の様に変更すれば使えます。

・「d3d10.h」の代わりに「d3d10_1.h」をインクルードします。
・ リンクするライブラリは「D3D10.lib」ではなく「D3D10_1.lib」です。
(プロパティー→リンカー→入力の追加の依存ファイルを変えましょう。)
・ID3D10DeviceではなくID3D10Device1を使います。
そしてD3D10CreateDeviceAndSwapChain()ではなく、最後に1が付く
D3D10CreateDeviceAndSwapChain1()を使って初期化します。
これは10時代のメソッドと違い、第5引数に
D3D10_FEATURE_LEVEL1   HardwareLevel,
が加わっています。ここには
D3D10_FEATURE_LEVEL_10_1
を入れましょう。
またその次の第6引数はSDKのバージョンなのですが、
  D3D10_1_SDK_VERSION,
を入れましょう。

2012年10月25日木曜日

C++のご案内

このサイトではC++の解説を網羅していません。入門書的な役割すら果たしていません。いくらか、私が気の向いたトピックを書いただけであり、他にも重要な話題が沢山あります。
Webや書店を探せば入門的な内容は沢山あるので、探してみて下さい。
このサイトにもいくらかリンクを貼っておきます。

C++は、C言語をベースに構築されたプログラミング言語です。そのためC++の解説と言ってもC言語の理解を前提としている場合と、C言語すら知らない人のために書かれている場合のどちらもあり得ます。C++の解説だけ見ても意味が判らんわという人はC言語入門的な解説で基礎を固めるのが良いかも知れません。
また、C入門や、C++入門的な解説の中にも、プログラミング自体が初心者だという人に向けた解説と、プログラミング自体は歴戦の猛者だけどCやC++は初めてという人に向けた解説というのもあり得ますので、そこはご注意を。

2012年10月23日火曜日

Direct3Dのバージョンと動作環境

Direct3Dは登場以来、年々バージョンアップを重ね、現在は11に至ってようやく停滞気味になっています。基本的にはバージョンが高いほど機能性が高く、出来る事も豊富になりますが、動作環境は制限されます。ビデオカードがDirect3Dのどのバージョンまで対応しているか、使っているOSは何か?等が動作環境に含まれます。
ビデオカードは、時代が新しくなれば例え安物でも新しいDirect3Dのバージョンに対応するのが普通です。
OSに関してはWinodwsXPや、それより昔のOSではDirectX9までしか動きません。(10や11は非対応です。)

Direct3D10、10.1、11で作られたプログラムは Windows Vista(サービスパック更新プログラムを適用済み) かWindows 7で動きます。8やそれ以降のOSが出ても当分は動くでしょう。

アプリケーションを制作する時にプログラマーは始めに機能レベルというのもを選択出来ます。機能レベルをDirectX9レベルのものまで下げてプログラムを作れば、そのプログラムの中でその機能レベル相当の機能しか使えませんが、DirectX9対応のビデオカード等でも動くようです。(どうせVista以降のOSはDirectX10以降の性能を備えたチップを搭載しているんじゃないかという気もするんですが。)

今になって見ると、Direct3D10と10.1と11は、使えるOSは同じで、使えるビデオカードが違うだけです。今時のビデオカードならDirect3D11以降に対応しているものが発売されているでしょう。


このブログでは主にDirect3D10の解説をする予定です。

参考
Windows のグラフィック API
Direct3Dから10への以降
Direct3D10.1の機能
Direct3D 11 の機能

2012年10月22日月曜日

Direct3D11からDirect3D10.1へ移行を検討中

移行を検討中って言ったってまだ何も作ってないんで何のダメージもない訳でして・・・。

このサイトではDirect3D11の解説を中心に書くつもりだったのだが、Direct3D10のサンプルやドキュメントの方が遥かに充実しており、初心者はDirect3D10から入った方が素直である。もしもDirect3D11でしか実現できない仕組みが見えてきたならその時点でそっちへ首を突っ込めば良い。D3D11はD3D10にちょこちょこと機能を追加したバージョンに過ぎないのでDirect3D10の知識は無駄にはならない。基礎的な内容において、既述の仕方はほとんど同じだ。D3D11の機能を使わなければ動作可能環境も広がるのだ。学習も捗る。D3D10.1ならDirect2D,Writeともすんなり連携できる。メリットだらけでね?
むしろなんで今までDirect3D11を?

2012年10月21日日曜日

COMインターフェースの基礎知識

COMの概要に関するMSの解説ページはここが分かり易そうです。

Direct3Dに限らず、Windowsの多くのライブラリがCOM(Component Object Model)というソフトウェア規格の元に実装されています。

例えばDirect3D11のチュートリアル中に登場する変数の型名で・・・

ID3D11Device
ID3D11DeviceContext
IDXGISwapChain
ID3D11RenderTargetView

の様に頭に「I」が付く型は、COMのインターフェースを表しています。インターフェースは普通のクラス型ではなく、COMという規格に基づいた型なのです。
(C++やJavaなどのオブジェクト指向言語では、抽象クラスに相当する機構をインターフェースと呼んだりしますが、ここではそっちのインターフェースの話はしないのでインターフェース=COMインターフェースと思って下さい。)

ただし、インターフェースと言ってもほとんど普通のクラスの様に扱えます。注意すべき点は、初期化時にnewやコンストラクタを使わない事です。
Direct3Dでは大抵、そのインターフェース専用の初期化用の関数が用意されているので、その関数にインターフェースのポインタを渡します。
メソッド(メンバ関数)へのアクセスは->を使います。

基本的に、派生されたインターフェースが派生元のインターフェースのメソッドを利用できる点もクラスと同じです。

例えばDirect3Dを学ぶ時におそらく真っ先に知る
ID3D11Device
というインターフェースの説明のページに

IUnknown
   ID3D11Device
という記述がありますが、
これはID3D11DeviceがIUnknownから派生している事を示しています。従ってID3D11Deviceというインターフェースを通してIUnknownが持つメソッドも使えます。

実はID3D11Deviceだけでなく、COMのあらゆるインターフェースは、最初の継承元としてIUnknownを持ちます。

では、IUnknownという哲学的な(?)名前のインターフェースにはどの様な役割があるのでしょうか?
Direct3Dを使うに当たって一番重要なのはIUnknownが持つRelease()というメソッドです。これは

インターフェースの解放処理

を行います。つまり、そのインターフェースに使われたオブジェクトやメモリを開放します。通常のクラス型をポインタにnewして構築した場合はdeleteによってオブジェクトのメモリを解放するのですが、COMインターフェースの場合はdeleteせず、替わりにRelease()を呼ぶという分けです。
既に解放済みのインターフェースを再度Release()してはいけないのはdeleteと同じです。


インターフェースの宣言
例としてTutorial01のデバイスの生涯を追ってみましょう。
デバイスは システム中のグラフィック装置を代表するDirect3Dにおいて最も基本的なインターフェースであり、名前はID3D11Deviceです。Tutorial01.cppのソースコードの初めの方に
//------------------------
// Global Variables
//------------------------
ID3D11Device* g_pd3dDevice = NULL;

という風にグローバル変数として宣言されています。
(デバイス以外の変数は省略しました)

グローバル変数として、(つまりあらゆる{}の外で)宣言されているので、このTutorial01.cpp中の、あらゆる場所からこの変数にアクセスする事が出来ます。
まずこれに g_pd3dDevice = NULL;
という風にNULLを入れています。
(NULLは、そこにマウスカーソルを当てれば分かるように
 #define NULL 0
です。)
つまりこの時点では ID3D11Device*型の変数 g_pd3dDevice に0が代入されています。
ご存知だと思いますが、ポインタに0を入れておくのは、それがまだ初期化前の状態だという事を明らかにするためです。C++ではグローバル変数は元々0で初期化されるのですが、=NULLと書いておくとより安心感があるかも知れません。

インターフェースの初期化
デバイスを初期化してインターフェースを利用可能状態にしているのは、ソースコード中腹にあるInitDevice()関数の中です。

InitDevice() の中の真ん中辺りでD3D11CreateDeviceAndSwapChain()という関数を呼んでいます。この関数の引数にg_pd3dDeivceのアドレスを入れる事でデバイスの作成が完了します。(その他にも様々な引数を入れていますが、その辺の解説は別のページでする予定です。)

さて、このデバイスにはDirect3D11関係の他のインターフェースを作成する機能があります。 InitDevice() のさらに下の方に
hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );
という箇所があります。これはデバイスを使ってレンダーターゲットビューというインターフェースを作成してる部分です。ただしここで覚えて欲しいのは、デバイスが持つ具体的な関数の内容ではなく、初期化を終えたインターフェースは->(アロー演算子)を通してメソッドにアクセス出来るという事です。

インターフェースの解体
さて、こうして作成、利用したインターフェースも最後は開放してやらないメモリリークになってしまいます。しかも自分で作ったクラスのnewとdeleteと同じような仕組みではメモリリークを検知できません。
チュートリアルではインターフェースの開放処理はCleanupDevice()という関数の中で指示されています。この関数の中でデバイスは
if( g_pd3dDevice ) g_pd3dDevice->Release();
という風に解放処理が書かれていると思います。
まずif(g_pd3dDevice)によってg_pd3dDeviceに値が入っている (つまり0(NULL)ではない) かどうかをチェックします。宣言時にはNULLを入れていましたが、 D3D11CreateDeviceAndSwapChain()によってうまくインターフェースが作成出来たなら、この時点でNULL以外が入っているでしょうから、ここでRelease()を呼ばれ、インターフェースが解放される事になります。また、解放済みのインターフェースを再度開放しないように、解放後にNULLを入れておくのが良い場合があります。

g_pd3dDevice = NULL;

これらをまとめて行うマクロを書くと、次の様になります。
マクロの定義例
#define SAFE_REL(COMI) if(COMI){COMI->Release(); COMI = NULL;}
マクロの利用例
SAFE_REL(g_pd3dDevice)

マクロを利用すると書くのが楽になるし「三回同じ名前のインターフェース名を書いたつもりが、一部に他のインターフェースの名前が混じり込んでいた」という様なミスを撲滅出来ます。

話がそれますが 、インターフェースではなく普通のポインタ解放用のマクロも用意しておくと便利だと思います。

#define SAFE_DEL(x) if(x){ delete x; x=NULL;}

あと、ベクター配列に確保したインターフェース解放マクロもあると便利かも知れません。
#define SAFE_REL_VEC(COMIVec) for(unsigned int i = 0; i < COMIVec.size(); ++i)\
{ SAFE_REL(COMIVec[i]) COMIVec[i] = NULL; };

 コピーする場合

インターフェースは、ポインタを使って扱う訳ですが、これをコピーして使う場合、どういうことになるのでしょうか?
例えば上の例ではデバイスを作りました。

ID3D11Device* g_pd3dDevice;

そしてこれを D3D11CreateDeviceAndSwapChain()によって初期化しました。
さてここで、このデバイスを他のスコープから利用できるようにしたい場合があるかも知れません。その場合は、もう一つポインタを用意する事になるでしょう。例として新しいデバイスのポインタの名前をDevice2にするとしましょう。

 ID3D11Device* Device2;

この時、元のインターフェースのポインタをDevice2にコピーしてやればDevice2を使っても、同じデバイスインターフェースにアクセスできる様になります。

 Device2 =   g_pd3dDevice;

この様に、同じインターフェースのインスタンスを共同で使いたい場合は、単にポインタをコピーしてアクセス方法を増やすことが出来ます。

ただし、
コピーしたインターフェースを解放する
場合はどうするのでしょうか?

これは、 コピー先かコピー元の一つだけのインターフェースポインタをRelease()します。
普通のオブジェクトのポインタに対するnewとdeleteと話の本質は同じです。上の例ではインターフェース自体は一つしか作らず、ポインタをコピーしてアクセス方法を増やしただけなので、何度もリリースすれば多重解放を引き起こします。

AddRef()の利用
COMのMS公式の解説では、コピーする度にインターフェースをAddRef()する事を推奨しています。その場合はコピーしたインターフェース毎にRelease()をする必要があります。(詳しくはこれから参照カウントの項で説明します。)
AddRef()というのもIUnknownのメソッドです。AddRef()を呼び出す度に内部の参照カウントがカウントアップされます。

参照カウントについて
COMインターフェースは参照カウント用の変数を保持しています。「参照カウント」というのは、そのインターフェースをどれだけのポインタから参照しているかを表す数字です。

例えばデバイスを例にするなら
ID3D11Device* g_pd3dDevice; 
と書いた時点では、まだインターフェースのポインタを宣言しただけなので、参照カウントは0です。
 D3D11CreateDeviceAndSwapChain()を呼んでデバイスインターフェースを初期化した時点で参照カウントが1になります。
そしてもし、
ID3D11Device* Device2 =   g_pd3dDevice;
と書いたら事実上、デバイスへの参照は2つになる訳です。
しかしプログラマーが手動で
Device2->AddRef();
みたいに書いてやらないと、デバイス内の参照カウントは2になりません。(1のままです。)

Release()のメカニズム
実はRelease()を呼び出した時、まずは参照カウントがマイナス1されます。そしてその際、参照カウントが0に至れば実際にインターフェースが解体されます。つまり参照カウントが0にならなければ、他にもそのインターフェースを参照しているポインタがあるはずなので、解体しないというポリシーなのです。

MSが推奨しているのは、コピーする度にAddRef()し、そのコピーが不要になったらRelease()する事で、インターフェースの寿命を管理する事です。
事実上は、コピー時にAddRef()を全くしないで一度だけRelease()を呼んでも同じ事です。
ライブラリに用意されている関数を使って初期化した時だけはAddRef()を呼ばなくても自動的に参照カウントがプラス1されるので、このあと
プログラマがそのインターフェースのポインタをコピーしたとしても、あるいは、しなかったとしてもAddRef()さえしなければ、Release()も一回のままで済みます。

解放忘れのチェック

さて、何やかんや言って、実際に自分がコーディングした時にインターフェース(COMオブジェクト)の解放がちゃんと行えているかどうかは気になると思います。
Direct3D11なら
解放忘れのチェックを行う場合、デバイス作成時に
D3D11CreateDeviceAndSwapChain()の引数の
UINT Flagsの部分でD3D11_CREATE_DEVICE_DEBUGを指定していれば、アプリケーション終了時にインターフェースの解放漏れがあった場合、出力ウインドウに表示されます。
Tutorial01ではDebugビルド時には、この設定になるようにコーディングされています。

 UINT createDeviceFlags = 0;
#ifdef _DEBUG
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

hr = D3D11CreateDeviceAndSwapChain( NULL, g_driverType, NULL, createDeviceFlags, featureLevels, numFeatureLevels,
D3D11_SDK_VERSION, &sd, &g_pSwapChain, &g_pd3dDevice, &g_featureLevel, &g_pImmediateContext );


これでデバッグビルド実行時にインターフェースの解放漏れがあると、上のようにダダダッとメッセージが出力されるでしょう。

Direct3D10の場合は…無理臭いな。
ググっても自分のサイトばっか引っかかってしまう…。

関数や目的の編集箇所の探し方

関数の探し方

 VC++で特定の関数を探す時はコード編集エリア上のプルダウンボックスみたいな奴から指定すると速いです。あと、クラスビューやオブジェクトブラザーを使っても良いでしょう。クラスビューやオブジェクトブラウザは上級者用の設定ならメニューの[表示]から出せます。
(上級者用の設定はメニューの[ツール]→[設定]から出来ます。)
あるいは、メニュー下のボタン類から出してもいいでしょう。


上の画像はInitDevice()という関数をプルダウンメニューから指定する例です。左右にあるのはクラスビューとオブジェクトブラウザであり、これらの関数名をダブルクリックしても、その箇所に飛べます。同じ関数名が二つ並んでいるのは関数の宣言と定義の2箇所にその関数名が使われているからです。
クラスビューやオブジェクトブラウザーで目的の関数を探す時は、その関数がグローバル関数の場合は「グローバル関数と変数」という箇所を一旦クリックしてから関数を探して下さい。何か特定のクラスに属する関数を探す場合は、そのクラス名をクリックしてからメンバ関数を探して下さい(まぁ分かると思います)。

呼び出し階層の表示

何かの関数名を右クリックして右クリックメニューの中から「呼び出し階層の表示」を選択すると、
・その関数内で呼び出している関数

・その関数を呼び出している関数
を検索して羅列表示する事が出来ます。検索結果はそれぞれ呼び出し階層ウインドウの
「○○からの呼び出し」と
「○○への呼び出し」言う名前のフォルダに収まっているので開いてから関数名をダブルクリックすると、その箇所へ飛べます。

ヽ(=´ω`=)/ こいつは便利だぜ~

また、関数名の頭に付いている三角矢印をクリックすると、さらにその関数に対する呼び出し類を検索できます

あとまぁ

ブックマーク

とか

検索

とか抑えておくと良いかと

2012年10月20日土曜日

ライブラリのリンクをソースファイル内に記述する

VC++でリンクするライブラリを指定する時、上の画像みたいに、いちいちプロパティ→リンカー→入力に書き加えるのも面倒だったりします。

ソースコードによって、どんなライブラリが必要かは予め自明なので、インクルードと同じ感覚でソースコード中で指定できればそのソースコードを再利用する時に便利だったりします。

やり方は
ソースファイル中に
#pragma comment(lib,"ライブラリファイル名.lib")
というふうに書き込みます。

例えばDirect2DとDirectWriteを使いたいなら
d2d1.libとdwrite.libをリンクする必要があるので、
ソースコード中に

#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")
と書いておけばOKです。

Direct2Dのリソースの解放忘れをチェックする

Direct2Dでは標準ではリソースの解放忘れをチェックして警告してくれるようなデバッグモードは実装されていないらしいです。それはCOMっちゃうな~;と思った人はここからDirect2D用のデバッグレイヤーをダウンロードして実行しましょう。
この時 d2d1debug.dllがインストールされるらしいのですが ライブラリファイルの追加等のVC++の設定作業は不要です。Direct2Dが内部からd2d1debug.dllを呼べるみたいです。

後は、我々はただ単にD2D1CreateFactory()を呼ぶ時にデバッグオプションを指定すればいいだけです。
詳しくは以下をどうぞ。
Direct2Dデバッグレイヤーの概要

リソースの解放をチェックする場合の注意点としては、
1・あらゆるリソースの中でファクトリ(ID2D1Factory)を最後にRelease()する。
2・ファクトリの解放自体はチェックされない。

という事です。
ファクトリによってその他のリソースをチェックしている事が伺えます。

ファクトリをリリースした時点で未解放のリソースをがあればデバッグウインドウにメッセージが表示されます。

2012年10月8日月曜日

DrawTextLayout()を使った描画

DrawTextLayout()を使ってテキストを描画するには

IDWriteFactoryの作成
IDWriteTextFormat(テキストフォーマット)の作成
IDWriteTextLayout(テキストレイアウトの作成
DrawTextLayout()を呼ぶ

というステップが必要です。
前回 の手順IDWriteFactoryIDWriteTextFormat
の作り方を述べたので、今回はその時作った変数を使いまわします。

なので

1)CreateTextLayout()テキストレイアウトを作成
2) DrawTextLayout()を呼ぶ。


という2ステップで文字が描画できます。

1) テキストレイアウトを作成

以下はpTextLayoutという名前でテキストレイアウトを作成する例です。

std::wstring wst(L"abcd文字列");

IDWriteTextLayout* pTextLayout = NULL;

pWriteFactory_->CreateTextLayout(
wst.c_str(),
wst.size(),
pFom,
 1000, //レイアウト幅
 30,//レイアウト高さ
&pTextLayout
);

上から順に引数を解説します。
[in] const WCHAR *
描画する文字列です。DrawText()の時と違い、今回呼ぶDrawTextLayout()は描画する文字列を直接指定しません。代わりに今ここで、テキストレイアウト内の文字列として描画した文字列を指定して置きます。

UINT32
描画する文字数です。

IDWriteTextFormat * 
(前回作成した)テキストフォーマットです。

FLOAT  
レイアウトボックスの幅です。描画する文字列がどのくらいの幅になったら改行するのかを指定します。

FLOAT
レイアウトボックスの高さです。 DrawTextLayout()の第4引数でD2D1_DRAW_TEXT_OPTIONS_CLIPを指定した場合、この高さからはみ出す部分がトリミングされます。

[out] IDWriteTextLayout**
出力されるテキストレイアウトです。

2) DrawTextLayout()を呼ぶ 


D2D1_POINT_2F points;
points.x = 50.0f;
points.y = 50.0f;

pHwndRenderTarget->DrawTextLayout (points, pTextLayout , pBrush_);

引数は以下の通りです。

1:D2D1_POINT_2F
描画するテキストの左上隅座標です。

2:IDWriteTextLayout
ステップ1)で作成したテキストレイアウトです。

3:ID2D1Brush
描画用のブラシを表すDirect2Dのオブジェクトです。

4(オプション):D2D1_DRAW_TEXT_OPTIONS

オプション的に指定可能なD2D1_DRAW_TEXT_OPTIONS 列挙型です。文字列をCreateTextLayout()の引数で指定したレイアウトに合わせてトリミングしたい場合はD2D1_DRAW_TEXT_OPTIONS_CLIPを指定します。他の値は「ピクセル境界に対してスナップするかどうか」という表示における微妙な問題を指定できるっぽいです。

以上で、DrawTextLayout()を使った描画の完成です。 文字列の描画が不要になったら、最後は作ったインターフェースは Release() しましょう。

実はDrawText()の時の画像の使い回し

DrawText()による描画を経験した後だと新しい概念はさほど必要無かったと思います。
しかしDrawTextLayout()を使った描画では IDWriteTextLayoutメソッドによって描画したい文字列の設定を後から変更したり下線を付けたりも出来ます。

 下線取り消し線を付ける

下線の設定はテキストレイアウトメンバのSetUnderline()で出来ます。
上の表示は下のコード結果です。

//まず、構造体で対象文字列の設定をします
DWRITE_TEXT_RANGE dtr;
dtr.startPosition = 1;//配列何番目から
dtr.length = 6;//何文字分
pTextLayout->SetUnderline(TRUE, dtr);//TRUEで下線を引く
//描画
pHwndRenderTarget->DrawTextLayout (points, pTextLayout , pBrush_);

dtr.length =3;//今度は3文字だけ
pTextLayout->SetUnderline(FALSE, dtr);//FALSEで下線を取り消す
//さっきの文字列の50下に描画
points.y +=50;
pHwndRenderTarget->DrawTextLayout (points,pTextLayout , pBrush_);

dtr.startPosition = 11; //今度は別の場所に下線を加える
pTextLayout->SetUnderline(TRUE, dtr);
//さっきの文字列の50下に描画
points.y +=50;
pHwndRenderTarget->DrawTextLayout (points,pTextLayout , pBrush_);

取り消し線も同様の設定でSetStrikethrough()を呼べば付けられます。
例えば上の例でSetUnderlineと書いてある場所を
SetStrikethroughに置き換えれば、下線が付いている場所が取り消し線に替ります。


その他はIDWriteTextLayout インターフェイスをどうぞ

2012年10月7日日曜日

DrawText()を使ったテキストの描画

Direct2Dの基本的な文字描画関数にはDrawText()DrawTextLayout()がある事と、その際にDirectWriteのインターフェースもいくつか作成する必要がある事を前回の大まかな概念で説明しました。
文字列の描画に関わる基本要素

今回はとりあえず DrawText() で実際に文字を出す例を示そうと思います。 DrawTextLayout() で描画する時もここで解説する事の多くが役立つと思います。

なお、 DrawText()DrawTextLayout() もDirect2Dのレンダーターゲットインターフェースのメソッドですから、レンダーターゲットが作られている事が前提となります。レンダーターゲットの作り方は既に説明済みなのでここでは触れません。

おおまかな流れ

 下の図の番号は DrawText() を行うまでの基本的な手順です。

DrawText()までの手順

1)IDWriteFactoryの作成
2)CreateTextFormat()を利用してテキストフォーマットIDWriteTextFormat)を作成
3) DrawText() を呼ぶ
という3ステップに分けました。

我々は DrawText() を呼びたいだけなんですが、その引数で

どの様な書式の文字列を作成するか?

という情報をテキストフォーマットで指定する必要があります。
テキストフォーマットIDWriteTextFormat というDirectWriteのインターフェースなので先に示したの手順が必要になります。

…その前にDirectWriteを使えるようにVC++にライブラリ(DWrite.lib)を登録しましょう。
プロパティ→リンカ→入力→追加の依存ファイルにDWrite.libを加えればOKです。
また、ソースコード中にヘッダファイルのインクルードが必要です。
#include <D2d1.h>
#include <Dwrite.h>

1)IDWriteFactoryの作成

以下はpWriteFactory_という変数名でIDWriteFactoryを作成する例です。

IDWriteFactory* pWriteFactory_;
HRESULT hr;

hr = DWriteCreateFactory(
DWRITE_FACTORY_TYPE_SHARED,
__uuidof(IDWriteFactory),
reinterpret_cast<IUnknown**>(&pWriteFactory_)
);
//失敗時はメッセージBOXを表示
if (!SUCCEEDED(hr))
{
MessageBox(NULL,L"DWriteCreateFactory()に失敗",NULL,NULL);
return false;
 }

DWriteCreateFactory()という関数にIDWriteFactoryのポインタを入れて上げればOKです。ただしIUnknown型の引数を渡す必要があるのでキャストしています。

2)CreateTextFormat()テキストフォーマットを作成

下の例は pFom という変数名のテキストフォーマット(IDWriteTextFormat)を作成しています。
なお、成否の調査    if (SUCCEEDED(hr))は省略しました。
_________
IDWriteTextFormat* pFom = NULL;

pWriteFactory_->CreateTextFormat(
 L"メイリオ", // フォント(MS P明朝/MS Pゴシックとか)
 NULL, // フォントコレクション(NULL=システムフォント)
 DWRITE_FONT_WEIGHT_NORMAL, //太さ
 DWRITE_FONT_STYLE_NORMAL, //スタイル(傾き)
 DWRITE_FONT_STRETCH_NORMAL,//幅
 100.0f,//文字の大きさ
L"ja-jp",//ロケール(地域)/ja-jp/en-us
& pFom
);
________

CreateTextFormat()の引数でどのような書体=テキストフォーマットを作成するかを指定できます。DrawText()を呼ぶ時に、ここで作ったテキストフォーマットが利用される分けです。ただし、全てのフォントがこれらのパラメーターの全てをサポートしている分けではなかった様な気がします。

第1引数
フォントの種類です。 例えばメイリオだとかMS P明朝だとかMS Pゴシックなどです。(私の環境では)ここで間違った名前を入れちゃうとメイリオになるっぽいです。(メイリオっていうフォントは現状、一番綺麗にCleary Typeで描画される様な気がします。)
参考:フォントを列挙する方法

第2引数
フォントコレクションを指定します。フォントというのは誰がどれだけ作っているか分からないのでこの様な概念が必要になります。一つのフォントコレクションの中に、第一引数で選べた様な複数のフォントが格納されていたりします。NULLを指定すれば、システムフォントコレクションが利用されます。システムフォントコレクションというのはOSに最初から備わっているフォントコレクションの事です(多分)。

第3引数
 DWRITE_FONT_WEIGHT 列挙型で文字の太さを指定できます。

第4引数
DWRITE_FONT_STYLE 列挙型でイタリックの様な傾斜のあるスタイルを指定できます。

第5引数
DWRITE_FONT_STRETCH 列挙型で文字の幅を指定でき・・・るはずなのですが私はうまく行きませんでした。

第6引数
FLOATでフォントの大きさを指定します。

第7引数
ロケール名です。言語圏を指定します。

第8引数
ここに IDWriteTextFormatのポインタ変数のアドレスを入れます。関数呼び出し後にこの引数で指定した変数がテキストフォーマットとして完成する分けです。


3) DrawText() を呼ぶ

さて、ようやく最後のステップ、本命のDrawText()を呼び出して文字列の描画を行うことが出来ます。
たかが文字を描画するだけでどんだけ大変なんだアホ、バカ、ボケ!
ヽ(`Д´)ノ・・・と思っていた人も溜飲が下がる瞬間だと言えます。
(*´ー`*)ーЭ ふぅ・・・

大きさ100でテキストフォーマットを作った例

下の例では一旦std::wstringに描画したい文字列を格納しているので
#include <string>
だか何かのヘッダファイルも必要です。

//描画する文字列
std::wstring wst(L"abcd文字列");

//レイアウト
float width = 1300.0f;
float height = 100.0f;

//描画位置
D2D1_RECT_F rect;
rect.left = 50.0f;
rect.top = 50.0f;
rect.right = rect.left + width;
rect.bottom = rect.top + height;

//DrawText
 pHwndRenderTarget->DrawText(wst.c_str(), wst.size(), pFom, rect, pBrush_);


レンダーターゲットは pHwndRenderTargetという名前の変数を事前に用意してあります。レンダーターゲットの作り方はここで説明しました。
DrawText()の引数を解説します。

第1引数: WCHAR *
描画する文字列を表します。

第2数:UINT
描画する文字の数です。

第3引数:IDWriteTextFormat
テキストフォーマットです。

第4引数: D2D1_RECT_F
テキストの配置を表す矩形です。D2D1_RECT_Fという構造体メンバleftがテキスト表示範囲の左端の座標を、topが上端の座標を表します。
矩形の幅は文字列の行の長さを表します。
例としてこれを100に変えてみましょう。(これは上の例におけるレイアウトというコメントの部分を次の様に変えれば出来ます。)

//レイアウト
float width = 220.0f;
float height = 100.0f;

すると実行結果は次の様に改行されます。

長方形はレイアウト幅を示します

ちなみに文字列に改行コードを入れても改行出来ます。
改行コード

第5引数: ID2D1Brush
テキストを描画するブラシです。これはDirect2Dの基本的なインターフェースであり、描画色等を指定できます。
-----------------------
これ以降の引数はオプションです。上の例では指定していません。
------------------------
第6引数:D2D1_DRAW_TEXT_OPTIONS
描画オプションです。指定しないとデフォルト引数がD2D1_DRAW_TEXT_OPTIONS_NONEとなります。
ここで
D2D1_DRAW_TEXT_OPTIONS_CLIP
を指定するとどうなるでしょうか?
結果は下の様になります。

 第4引数で指定した矩形の中に収まらない部分はトリミングされます。

このレイアウトを表す矩形の概念は次回 IDWriteTextLayout(テキストレイアウト)を作る時にも登場します。

そう言えば、使い終わった後はお約束のインターフェースの解放処理もやった方が良いのだと思います。

例)
if(pWriteFactory_!= NULL) pWriteFactory_->Release();

pFomやDirect2D系のリソースも言わずもがな。

2012年10月6日土曜日

大まかな概念:DirectWriteを使った文字列描画

Direct2Dで文字を表示するにはDirectWriteというAPIの力を借ります。
DirectWriteはフォントデータの管理が主な仕事であって、それを描画するにはDirect2Dの様な別のAPIが必要みたいです。つまり2つのAPIが協力して文字を描画します。

下の図はDirect2DとDirectWriteを使って文字を表示する時に使う、関数、及びインタフェースをまとめてあります。

図の左半分がDirect2Dのインタフェースと関数です。
図の右半分がDirectWriteのインタフェースと関数です。

全体的な構造

Direct2D側にはID2D1RenderTarget(レンダーターゲット)というDirect2Dにおける基礎的なインタフェースとそれが持つ基本的な文字列描画関数であるDrawText( 図中左上 )DrawTextLayout(図中左下)を図示しました。この2つの関数のどちらを使っても文字列が描画出来ます。使い分けについては後で説明します。

DirectWrite側にはIDWriteFactoryというDirectWriteにおけるお母さんみたいな?インタフェースと、それが持つ関数CreateTextFromat(図中右上)CreateTextLayout(図中右下)を示しました。
この二つの関数はそれぞれIDWriteTextFormat(以下テキストフォーマット)と、IDWriteTextLayout(以下、 テキストレイアウト)というインターフェースを生成する役割があります。

このページでこれらの用語の色分けは図と一致させています。

DirectWriteを使って文字を描画するには今挙げた2つのインターフェースが重要な働きをします。簡単に説明すると

テキストフォーマット(IDWriteTextFormat)はフォントやフォントのスタイル等の書体情報を表します。

このインタフェースはCreateTextFormat()の引数としてポインタのポインタを渡す事で得られます。そして図中でそこから伸びる線を追うとDrawText()の引数、及びCreateTextLayout()の引数として使用されている様が確認できると思います。

・・・そしてもう一方の

テキストレイアウト(IDWriteTextLayout)テキストフォーマットを継承するインターフェースです。 テキストフォーマット が持つ書体情報に加え、描画する文字列自身の内容と、レイアウト(どんぐらいで改行するかとか)や、その他多くの情報も含みます。このインターフェースを作るには CreateTextLayout()を呼ぶ必要があるのですが、その際の引数として結局テキストフォーマットも必要になります。

さて、既にここまでで沢山のインターフェースや関数が登場した様に見えますが、ここで示した中で実際に文字を描画するメソッドはDrawText()DrawTextLayout()の2つしかありません。しかもDrawText()で文字を描画したい場合は図中の上半分を、DrawTextLayout()で文字を描画したい場合は図中の下半分に注目すれば概ねOKです。

細かいことはこれから説明しますがここでは
描画メソッドはDirect2DのDrawText()、もしくは DrawTextLayout() を使うという事と、そのメソッドが取る引数はDirectWriteのインタフェースであるテキストフォーマット、または テキストレイアウトを取るという事は覚えておいて下さい。


ここまでのまとめ
DrawText()  引数にテキストフォーマット (IDWriteTextFormat) が必要(だから事前に CreateTextFormat() も呼ぶ必要あり)
DrawTextLayout()  引数に テキストレイアウト(
IDWriteTextLayout) が必要(だから事前に CreateTextLayout()も呼ぶ必要があり、その前に結局CreateTextFormat()も呼ぶ必要あり。(´・ω・`))。

描画用メソッド

描画用のメソッドにはDrawText()DrawTextLayout()の2つがあると述べましたが役割の違いは何なのでしょうか?

構造上の大きな違い
DirectWriteのインターフェースである テキストレイアウト  IDWriteTextLayoutを必要とするかどうかです。 テキストレイアウトが必要な関数が DrawTextLayout()、必要ないのが DrawText()です。(関数名にLayoutが含まれているかどうかで判別できます。)

テキストレイアウトが必要な DrawTextLayout()は、言い換えれば  テキストレイアウトの機能を利用できる分けです。例えば テキストレイアウト のメソッドを使って描画する文字列に下線を付けるなどの操作が可能です。

ところで テキストレイアウト作成時にテキストフォーマットも作成する必要があると言いましたが、これは  テキストレイアウトテキストフォーマット の派生クラスであり、 テキストフォーマット が持つ情報は全て テキストレイアウトに含まれるという設計上の構造を反映しています。


テキストフォーマット を作る時は CreateTextFormat() の引数で、描画する文字列をどの様なフォントや書体にするか指定出来ますが、そこで指定した情報はそのままでは変更できません。しかし  CreateTextLayout() よって テキストレイアウト を生成すると、 テキストレイアウト のメソッドを通じてテキストフォーマット内部にあった書体情報も変更が可能になります。

DrawText() が必要とするDirectWriteのインターフェースは テキストフォーマット (IDWriteTextFormat) だけなので、使えるメソッド も少ないです。本来 テキストフォーマット の管轄である書体情報ですら書き換え不能なのは先程述べた通りです。

ところで、 IDWriteTextLayoutIDWriteTextFormat の派生クラスなので、 DrawText()IDWriteTextLayout を入れて使う事も出来ます。(ただしその場合、  テキストレイアウト で設定した項目は無視されます。)



2つの関数は実行速度にも大きな違いがあります。
DrawText()は一回きりの描画ならば DrawTextLayout()よりも若干速い様です。しかし2回以上の描画から、 DrawTextLayout()の方がずっと速くなります。
尚、どちらの関数を使うにしろ、細切れな短文を何度も出力するよりも沢山の文章を一度に出力した方が、高速です。

DrawTextLayout()のデメリット
描画する文字列そのものを変更する時は、別の文字列で初期化したテキストレイアウトを用意する必要があります。



参考:テキストの書式設定とレイアウト(MSDN)

2012年9月28日金曜日

x64でのビルド

私の頭の中では
・x64でビルドしてもすぐにそのアプリケーションが64ビット向けのプログラムになるのかと言うと、そうではなくて、他にもVC++の設定が必要なんでね?
・64ビット用プログラムは、ギガ単位のメモリを使う場合に有効だが、それ以外では実行速度が速くなる様なメリットはない。
・64ビット用プログラムは未だ存在する32ビット環境では動くかどうか分からんが、逆に32ビット用のプログラム(x86向け)なら、64ビットの環境でも動くだろう。
と言った所です。

2012年9月8日土曜日

掲示板

何かあればコメントを使って書き込んで下さい

2012年8月12日日曜日

時間の測定

アニメーションなどの定期的に実行する処理を作る時は勿論、自分が何か変な事をやって異常な時間コストを払うプログラムを書いていないか等をチェックする時にも時間の計測が有効です。

windowsで高精度な時間の測定が行える関数は
QueryPerformanceCounter() です(以後Counterと呼ぶことにする)。
ただしこの関数で得られる時間は実行環境によって精度が異なるため
QueryPerformanceFrequency()(以後Frequencyと呼ぶことにする)
という関数を一度は呼び出して、その環境における精度を取得しておく必要があります。この精度は周波数と呼ばれ、Counterが1秒間に何回カウントするのかを示します。
Counterで得られる値は、システム起動後から刻一刻と加算されていく数字なので、何かの時間を計るにはCounterを2回呼び出して前後の差を求めます。そしてその結果をFrequencyで割れば秒単位の時間が求まります。
なお、これらの関数の利用には
#include <windows.h>のインクルードが必要です。

例)-------------
//精度の取得
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);

//現在の時間を取得
LARGE_INTEGER start;
QueryPerformanceCounter(&start);

/*ここに測定する処理の記述*/

//もう一度時間を取得
LARGE_INTEGER end;
QueryPerformanceCounter(&end);
double time = double (end.QuadPart - start.QuadPart) / double(freq. QuadPart);
---------------------
以上のように記述すると2回のCounter呼び出しの間に何秒間経過したかがdoubleのtimeという変数に入ります。
CounterやFrequencyが取る引数はLARGE_INTEGERという特殊な型であり、このままでは整数として扱えないので.QuadPartというメンバにアクセスして利用します。

以下は時間の測定結果をミリ秒単位で戻すクラスの定義例とその利用例です。
------------------------------
#include <windows.h>//QueryCounter、及びSleep()用
#include <sstream> //ods()用


//デバッグウインドウへ出力
template < typename T, typename T2 > void ods( T tep, T2 tep2, T tep3)
{
std::wstringstream ss;
std::wstring st;

ss << tep << tep2<< tep3 << L"\n";
st = ss.str();
OutputDebugString(st.c_str());
};

// ミリ秒単位でStart()からEnd()までの時間を取得するクラス
class QueryCounter
{
public:
QueryCounter(void)
{
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
dFreq_ = (double)freq.QuadPart / 1000.0;
}

void Start()
//時間計測の開始時に呼んで下さい
{
QueryPerformanceCounter(&start_);
}

double End()
//時間計測の終了時に呼んで下さい
{
LARGE_INTEGER end;
QueryPerformanceCounter(&end);
return (double (end.QuadPart - start_.QuadPart) / dFreq_ );
}

private:
LARGE_INTEGER start_;
double dFreq_;
};

int main()
{

QueryCounter qc;
qc.Start();
Sleep(10);
ods(L"Sleep(10): ", qc.End(),L" ㍉秒");

double d;
qc.Start();
Sleep(5);
d = qc.End();
ods(L"Sleep(5) : ", d ,L" ㍉秒");

getchar();
return 0;
}
-------実行結果-------------
Sleep(10): 9.99923 ㍉秒
Sleep(5) : 4.41183 ㍉秒
----------------------------

SleepはWindowsでミリ秒単位で休止時間を指定できる関数ですが、その精度はミリ秒とはいかない様です。(そもそも関数呼び出しのオーバーヘッドやCPUやコンパイラが行う最適化の都合などで極小の処理時間を計るのは難しい作業の様な・・・。)

QueryPerformanceCounterは万能か?

Windowsで経過時間を取得する方法は沢山あるのですが、その中でなぜこの関数が良いのでしょうか?
ゲームのタイミングとマルチコア プロセッサ
というMSのページでこの関数についての説明があります。

DXUTや他のWindowsのライブラリでも内部でこの関数を使っているっぽいので、益々この関数の重要性がまして来ると考えられる昨今、この関数に対応しない環境は減少に向かうのではないでしょうか?

WindowsXPでこの関数が使えなかったので色々と調べた人のページ
が参考になるかも知れません。

我らはDirectX11以降=vista以降の環境を対象にプログラミングをするからXPの話は問題にならないのだ!
と考えるのも良いし(てゆうかXPでも大体使えるだろうし)、一応、使えなさそうな時は

timeGetTime

を使うように処理を切り替える様な安全弁を設けておいても良いでしょう。
timeGetTime()を使うには以下のヘッダとライブラリが必要です。

#include <mmsystem.h>
#pragma comment(lib, "winmm.lib")

timeGetTime() 関数を比較的精度よく使う時は、またちょっとした手法が必要だったりします。WindowsNT系のOSでは
timeBeginPeriod()という関数で精度を指定しないと分解能が悪かったりするとうい話です。(ビスタや7も含め、Windows2000以降ぐらいからNT系のOSだったりします。)

引数はミリ秒単位で、最小1ミリ秒まで指定できます。
そして使い終わったら
timeEndPeriod()を呼ぶ必要があります。

timeBeginPeriod(1);
を自作タイマークラスのコンストラクタとかで呼んでおいて
timeEndPeriod(1);
をデストラクタで呼ぶ等すればいいのではないでしょうか?

環境によってはtimeBeginPeriodでタイマーの精度を上げるとCPUの負荷が増すなどのちょっとした副作用があるっぽいです。

2012年7月31日火曜日

単純な初期化の解説

前の節に続いてDirect2Dの解説をします。

Direct2Dの初期化

何を描画するかでどんなオブジェクトを初期化する必要があるかも変わってきますが、最低限必要なものとしてはファクトリとレンダーターゲットがあります。
以下はこの2つを初期化する例です。
-------------------
//ファクトリ
ID2D1Factory* pD2DFactory ;
//レンダーターゲット
ID2D1HwndRenderTarget* pHwndRenderTarget ;

//ファクトリの作成
D2D1CreateFactory(
D2D1_FACTORY_TYPE_SINGLE_THREADED,
&pD2DFactory);

//レンダーターゲットのサイズの準備
RECT rect;
::GetClientRect(hWnd, &rect);
D2D1_SIZE_U size =
D2D1::Size<UINT>(rect.right, rect.bottom);

//レンダーターゲットの作成
pD2DFactory->CreateHwndRenderTarget(
D2D1::RenderTargetProperties(),
D2D1::HwndRenderTargetProperties(hWnd, size),
&pHwndRenderTarget);
----------------------------
(完全なサンプルは前の節で示しています。)


ファクトリ( ID2D1Factory )というのはDirect2Dのリソース(描画用オブジェクト等)を作るための工場みたいなものです。Direct2Dを利用する時はまずはこれを作る必要があります。

ファクトリを作るにはD2D1CreateFactory()という関数を呼びます。
この時に引数で指定できるのはシングルスレッド用にするかマルチスレッド用にするかです。

ファクトリを使って次に説明するレンダーターゲットを作る事が出来ます。レンダーターゲットは重要なリソースです。


レンダーターゲット(ID2D1RenderTarget )は描画対象を管理するインターフェースです。この インターフェース を通して描画に必要な関数を呼び出せます。ブラシを作る時にも必要です。
この ID2D1RenderTarget にはいくつか派生の インターフェース があり、今回作ったのは普通のウインドウを描画対象にするための ID2D1HwndRenderTarge という インターフェース です。
(Direct3Dと同じ描画先に出力したい場合は別の インターフェース を使う必要があります。)

レンダーターゲットの作成は先ほど作成したファクトリのメソッド CreateHwndRenderTarget() を使って行います。
その際、いくつかの初期設定が行えるのですが、例で示したデフォルトの設定でハードウェアレンダリングによる高速な描画が期待できます。

描画の実行

をする時は、今作ったレンダーターゲットのメソッドBeginDraw()EndDraw()の間で行います。

例)
pHwndRenderTarget->BeginDraw();
//画面を一色で塗り潰す
pHwndRenderTarget->Clear(D2D1::ColorF(1.0F, 1.0F, 1.0F));

pHwndRenderTarget->EndDraw();

前節のサンプルでは描画テストをするRenderTest()という関数の中でブラシを作成していました。(リンク:ブラシの概要

// ブラシの作成 --------
ID2D1SolidColorBrush* pGreenBrush = NULL;

pHwndRenderTarget->CreateSolidColorBrush(
D2D1::ColorF(0.0F, 1.0F, 0.0F), &pGreenBrush);
-----------------------

そしてBeginDraw()とEndDraw()の間で 四角形の描画等をしていました。
//四角形の描画-----------
D2D1_RECT_F rect1 =
D2D1::RectF(100.0F, 50.0F, 300.0F, 100.F);
pHwndRenderTarget->FillRectangle(&rect1, pGreenBrush);
----------------------------
そして開放していました。
pGreenBrush->Release();

リソースについて

リソースというのは型通りの説明をするならばCOMのIUnknownというインターフェースを継承するID2D1Resource型の派生クラスです。
簡単に言ってしまえばDirect2Dで使用する描画関連の情報です。

リソースにはビデオメモリに配置する物と、そうでない物があります。今回使用したリソースは
・ファクトリ( ID2D1Factory
・レンダーターゲット( ID2D1HwndRenderTarge
・ブラシ (ID2D1Brush)
の3つであり、
この内レンダーターゲットとブラシはビデオメモリと関連付けられます。
ビデオメモリと関連付けられるリソースはデバイス依存、そうでないリソースはデバイス非依存と呼ばれたりもします。ここで言うデバイスはグラフィック装置というニュアンスです。

リソースは単なる構造体のデータなどとは違い、Direct2DAPIによってメモリ管理がなされる、言わば「重めのデータ」あるいは「高貴なデータ」と言った感じがします。

今回のサンプルではデバイスの初期化に比重を置き、ブラシを使った図形の描画はRenderTest()内にまとめました。しかし短い時間に何度も描画する場合はリソースを頻繁に作成するのではなく、ファクトリやレンダーターゲットと同様にクラスメンバとして管理するのが良いでしょう。
ただし、今回利用したSolidColorBrush(単色のブラシ)に関しては毎回作ってもさほど重くもないとか。…また、アプリケーションによってはウインドウのリサイズ時にしか再描画の必要がなくてパフォーマンスが気にならないというケースもあるでしょうが。

ブラシの色の変更

今回作ったブラシの名前が pGreenBrushなので使う色の種類毎にブラシを用意しなければならない様に思えるかも知れませんが、そんな事はありません。 ブラシには色を変更するメソッドが用意されています。
以下はいずれもブラシを赤色に再設定する例です

pGreenBrush->SetColor(D2D1::ColorF(1.0f, 0.0f, 0.0f));
pGreenBrush->SetColor(D2D1::ColorF(0xFF0000));
pGreenBrush->SetColor(D2D1::ColorF(D2D1::ColorF::Red));

SetColor()の引数の中では
namespace D2D1の関数を使って色を作っています。

透明度の設定
は以下のメソッドで出来ます。
 pGreenBrush->SetOpacity(0.5);//半透明

SetColorに4つめの引数を与えて色の設定と一緒に設定する事もできます。
pGreenBrush->SetColor(D2D1::ColorF(1.0f, 0.0f, 0.0f, 0.5f));

リソースの解放

リソース型は IUnknownというインターフェースを継承すると上で書きました。リソースを解放するにはこのIUnKnownのRelease()というメソッドを使います。

//ファクトリとレンダーターゲットの解放例

pHwndRenderTarget->Release();
pD2DFactory->Release();

C++でnewしたオブジェクトを2回deleteしてはいけないのと同様、リソースも通常は2回以上 Release() してはいけません。

今回のサンプルのクラスをコピー(代入)禁止にしたのは、デストラクタでリソースを解放する様にしたからです(より正確に言うとデストラクタの中のD2D::DestroyDevice()というメソッドで開放処理をしています)。

2012年7月30日月曜日

デバッグウインドウへ出力する

Win32APIのソフト開発なら
#include <windows.h>とインクルードしていると思いますが、この時
OutputDebugString(L"この文字列が出力される");
 という関数でプログラムの実行中に下に表示される「出力」というウインドウに文字列を表示できます。(文字列の前につけているLはユニコード設定でのコンパイル用です。)


数字の出力
以下は、数字も出力できるようにテンプレート関数を定義して使う例です。(ブラウザによっては改行コードが文字化けしているかも知れません。また、意味もなく文字の色が変わっているかも知れません。)



#include <windows.h>
#include <sstream> 

//引数一つ
template < typename T > void ods( T tep)
{
 std::wstringstream ss;
 std::wstring st;

 ss << tep<< L"\n";
 st = ss.str();
 OutputDebugString(st.c_str());
};
//引数2つバージョン
template < typename T, typename T2 > void ods( T tep, T2 tep2)
{
 std::wstringstream ss;
 std::wstring st;

 ss << tep << tep2<< L"\n";
 st = ss.str();
 OutputDebugString(st.c_str());
};
//引数3つバージョン(ただし型の種類は2つで交互に配置)
template < typename T, typename T2 > void ods( T tep, T2 tep2, T tep3)
{
 std::wstringstream ss;
 std::wstring st;

 ss << tep << tep2<< tep3 << L"\n";
 st = ss.str();
 OutputDebugString(st.c_str());
};

int main()
{
int x = 3;


ods(L"x = ", x, L" センチメートル");

  getchar();
  return 0;
}
--------出力結果----------
x = 3 センチメートル
-------------------------
以上の例で定義したodsという名前の関数は内部でストリングストリームというクラスを使って数字を文字列に変換しているためsstreamというヘッダが必要に成ります。

ストリングストリームを継承してみた


#include <windows.h>
#include  <sstream>

// デバッグウインドウへ文字列を出力する関数OutputDebug()があることと、その関数がデストラクタから呼ばれる事以外は
//wostringstreamと変わらないクラス
class DebugStringStream : public std::basic_ostringstream <wchar_t, std::char_traits<wchar_t> >
{
public:
              //デストラクタによる自動出力
 ~DebugStringStream ()
 {
  OutputDebug();
 }
              //引数で改行量を指定して出力
 void  OutputDebug(int kaigyou = 1)
 {
   for(int i = 0; i < kaigyou; ++i)
   {
    *this << L"\n";
   }
    //大域空間のOutputDebugStringを呼ぶ
    ::OutputDebugString(this->str().c_str());
    //一回呼ぶと内容はクリアされる
    this->str(L"");
 }   
};

int main()
{
DebugStringStream dss;
dss << L"あ~テステス " << 180 <<L"度";
dss.OutputDebug(2);//これで出力


 double pai = 3.14159265358979;
 //doubleやfloatの精度を設定
 dss.precision(15); 
 dss << L"円周率 = " << pai <<L"…";


//デストラクタが呼ばれても出力される
  return 0;
}

//---出力結果----
あ~テステス 180度

円周率 = 3.14159265358979…
ーーーーーーーーー
こちらはIOストリームの整形機能が利用できるし、沢山の出力をしたい場合、速度面で有利です。
まぁ普通にストリングストリームを使うのとさほど違いはありません。

分割コンパイルの仕組みとか

他のファイルに書いた機能を使いたい時は

#include "ファイル名.拡張子"


という風に書いてファイルをインクルードします。
そうするとビルド時、前処理としてその場所にファイルが読み込まれ、それからコンパイルが行われます。

インクルードの仕組みは単純で、つまりこの時、単にソースコードが複製されているのだと言えます。

コンパイルはファイル毎に行われるので、インクルードされたファイルはその場所でコンパイルされますが、さらに元々のファイルでもコンパイルされるのが普通です。

変数や関数などを使う時、宣言は複数あっても良いが、定義は1度だけというルールがあります。

だからもしインクルードで読み込んだファイルと読み込む側に同じ定義があればコンパイルエラーになります。

さらにインクルードで読み込んだファイルに何らかの定義があれば、例え読み込む側に同じ定義がなくてもリンクエラーになります

インクルードで読み込んだファイルに定義があれば読み込む側と読み込まれる側の両方で定義が発生しますが、 コンパイルの段階ではファイル毎に別々に処理が行われるので問題がありません。
しかしリンクの段階で、インクルードによって複製されたコードが統合されるので、2重定義となってしまうのです。


だから通常は宣言だけを書いたヘッダファイルと定義を書いたファイルに分けて、ヘッダファイルを複数のファイルからインクルードするようにします。(VC++ではヘッダファイルの拡張子は.h、定義などの通常のソースコードの拡張子は.cppにします。本来、拡張子はファイルの内容に影響を与えませんが、開発環境がそれを区別するという事はあり得ます。)

  

コンパイラの役割

上でも書きましたが宣言は複数あっても良いが、定義は1度だけというルールがあります。宣言というのは変数で言うならメモリを確保しない名前だけの提示を言います。


例えばグローバルスコープに

int a;

と書けば、aは0で初期化されるのでメモリが確保され、宣言も定義も書いていることに成ります。

関数は

int function();

のように、関数の内容を書かなければ定義にはなりません。これを関数の プロトタイプ宣言と言います。

コンパイラは内容がなくてもプロトタイプ宣言だけ見れば
きっとどこかでまともな関数が定義されているはずだ」と信用してくれます。
だからまだ中身を書いていない内から、あるいはそのコンパイル単位に中身が登場しなくたって、

int a = function();

という風にその関数を使ってもコンパイルが通ります。

ここでコンパイラが責任を持つのは「functionという名前の関数がint型を戻すという事と、aという名前の変数がint型であるので、この式は型の使い方に問題がない」という部分です。

コンパイルはファイル毎に行われので、全体の何処かに本当に定義があるのかどうかに関しては無知なのです。統括の責任を負うのはリンカの役割です。


テンプレート関数のコンパイル


テンプレート関数はコンパイラから見える範囲に定義を書かないとリンクエラーになります。
テンプレート関数は、それを使用するコードを書かない限りは、実際の関数が生成されなかったりします。実際に使われた型のバージョンの関数だけを生成して無駄を省くためです。
cppファイルなどにテンプレート関数の実体を定義し、ヘッダファイルにプロトタイプ宣言してそれをインクルードしてその関数を使った場合、コンパイラはそういう関数があると信じることはできるので、コンパイルは通ります。ここまでは普通の関数と同じです。
しかし普通の関数がcppファイルをコンパイルする際に関数がコンパイルされるのに対し、テンプレート関数はそのファイル内でその関数を利用しない限りは実体化が起こりません。それでリンクの時点でその関数が見つからずにエラーになるらしいです。
その代わりヘッダファイルに定義を書いても大丈夫なのでむしろ便利かも知れません。

クラス宣言とポインタ


どこにも定義していないクラスの名前を次のように書いてもエラーにはなりません。

class MyClass; //MyClassというクラスを使うという宣言
MyClass* myclass; //MyClassのポインタの宣言

class MyClass;
という記述は、どこかにMyClassというクラスを定義しているのだという事をコンパイラに信じさせます。
MyClass* myclass;
は、MyClass型のポインタを使うという宣言であり、メモリを確保しないので実際のMyClassの定義を必要とはしません。

つまり上に書いたclass MyClass;があれば、MyClass型のポインタの宣言が出来るのです。

しかし
MyClass myclass;
と言う風にmyclass変数の定義を書くとMyClassのコンストラクタを呼ぶ必要があるのでMyClass型の実際の定義情報が必要になります。

クラス宣言を使うと、あるクラスの中のメンバとして他のクラス型を持たせる場合、ヘッダファイルに他のクラスのヘッダファイルをインクルードしなくても済みます。

例)
class MyClass;

class tess
{
 MyClass* myclass;
};

この場合、
#include "MyClass.h"は不要です。

2012年7月29日日曜日

Direct2Dの初期化と単純な描画コード

Direct2Dの解説は
プログラミングガイド から色々たどれば基本的な事は分かるのではないかという気がします。

このページではDirect2Dの基本的な利用方法 を参考にデバイスの初期化をやってみます。

まず、win32API的な普通のウインドウを出せる事が前提です。
(これはVC++のファイル→新規作成→プロジェクト→Win32プロジェクトとかで簡単に作れるので問題ないかと思います。)

インポートライブラリの追加

ソリューションエクスプローラーのプロジェクトを右クリックするかメニューのプロジェクトからプロパティを開き、構成プロパティ→リンカ→入力を開きます。

追加のファイルに「D2D1.lib」を指定します。



 Direct2Dの初期化部分は通常のウインドウの初期化部分と明確に分けて見通しを良くしたかったのでD2Dという名前のクラスを作ってそこに記述しました。そのクラスのCreateDevice()という関数で初期化の例を示しています。

はじめにこのクラスのヘッダファイル(D2D.h)と本体(D2D.cpp)を掲載します。
次にこのクラスの使い方を説明します。
最後にDirect2Dの解説をしようかと思いましたが、それは次節に譲ります。

D2D.h

/*  D2D.h
D2D:このクラスの説明

Direct2Dの初期化を行います。
ただしこのクラスではDirect3DやGDIとの相互運用はできません。
(ID2D1HwndRenderTargetの初期化を行うクラスです。)

ウインドウ作成した後、
 CreateDevice(const HWND hWnd)
 で作成済みのウインドウハンドルを渡して下さい。

 初期化の内容は
 ID2D1FactoryとID2D1HwndRenderTargetの作成です。
 --------------------------------------------
作成したリソースの開放処理はデストラクタで行われます。
 --------------------------------------------
 ウインドウのサイズ変更時にはResizeWindow(HWND hWnd)にウインドウハンドルを
 渡してください。
 (ウインドウプロシージャでWM_SIZEメッセージを受け取った時に呼ぶ例)
 
 case WM_SIZE: 
     d2d.ResizeWindow(hWnd);
  break;

これをやらない場合は、ウインドウサイズに合わせて描画対象も変形します。
-------------------------------------------
 RenderTest()で描画テストが行えます。
 (ウインドウプロシージャのWM_PAINTメッセージ受信時に呼ぶ例)

  case WM_PAINT:
  hdc = BeginPaint(hWnd, &ps);
  d2d.RenderTest(hWnd);
  EndPaint(hWnd, &ps);
  break;
------------------------------------------
*/
#pragma once
#include <D2d1.h>

class D2D
{
public:
 D2D();

 ~D2D(void)
 {
  DestroyDevice();
 };

 bool CreateDevice(const HWND hWnd);
 //D2Dの機能を使う前の初期化を行います。
 //引数には描画対象となる作成済みウインドウのハンドルを渡して下さい

void RenderTest();
 //描画テストです
 //この関数を呼ぶ場合はCreateDeviceで初期化を済ませる必要があります。

 void ResizeWindow(HWND hWnd);
 // ウインドウのサイズを更新します。
 // ウインドウプロシージャでウインドウサイズの変更の
 //通知を受け取った時等に呼んで下さい。
 
private:
 //コピーや代入は禁止しています。
 D2D(const D2D& t); 
 D2D& operator=(const D2D& t);

 void DestroyDevice();
 //CreateDeviceで初期化したD2Dのデバイスを解体します。
 //デストラクタから呼ばれます。

 ID2D1Factory* pD2DFactory ;//ファクトリ
 ID2D1HwndRenderTarget* pHwndRenderTarget ;//レンダーターゲット

};

D2D.cpp


#include "D2D.h"
#include <crtdbg.h>

D2D::D2D(void)
{
 pD2DFactory = NULL;
 pHwndRenderTarget = NULL;
}

bool D2D::CreateDevice(HWND hWnd)
{
 HRESULT hr; //成否メッセージ戻り値

    // Direct2D ファクトリの作成
 // レンダーターゲットを作るためにこれが必要です

 if(pD2DFactory == NULL)
 {
   hr = ::D2D1CreateFactory(     
   D2D1_FACTORY_TYPE_SINGLE_THREADED,&pD2DFactory_);     
   
   if(!SUCCEEDED(hr)) 
     return false; 
   
 }

 //< レンダーターゲット(描画対象)の作成 >
 // ウインドウとDirect2Dの描画装置を関連付けます。
 // ウインドウと関連付けた描画装置は
 //レンダーターゲットという名前で呼びます。
 //ブラシなどの描画道具の作成もレンダーターゲットが行います。
    if( pHwndRenderTarget == NULL) 
    { 
        RECT rect; 
        ::GetClientRect(hWnd, &rect); 
        D2D1_SIZE_U size = 
            D2D1::Size<UINT>(rect.right, rect.bottom); 

        hr = pD2DFactory->CreateHwndRenderTarget(
                D2D1::RenderTargetProperties(),  
                D2D1::HwndRenderTargetProperties(hWnd, size), 
                &pHwndRenderTarget);
        if(!SUCCEEDED(hr)) 
   return false; 
    } 

    return true; 
}

void D2D::DestroyDevice()
{
    if(pHwndRenderTarget != NULL) 
    { 
        pHwndRenderTarget->Release(); 
        pHwndRenderTarget = NULL; 
    } 
  if(pD2DFactory != NULL) 
    { 
        pD2DFactory->Release();  
        pD2DFactory = NULL; 
    }  
}

void D2D::RenderTest()
{
 //RenderTestは描画テストをする関数です。
 //この関数を呼ぶ場合はCreateDeviceで初期化を済ませる必要があります。
 //初期化がうまくいっていないとアサートします。
 _ASSERT(pHwndRenderTarget);

 //ウィンドウが他のウィンドウに覆われていたら描画処理をしない
    if(pHwndRenderTarget->CheckWindowState() & D2D1_WINDOW_STATE_OCCLUDED) 
        return; 

 HRESULT hr; 
 // ブラシの作成 
 ID2D1SolidColorBrush* pGreenBrush  = NULL;

    hr = pHwndRenderTarget->CreateSolidColorBrush(        
            D2D1::ColorF(0.0F, 1.0F, 0.0F), &pGreenBrush); 
    if(!SUCCEEDED(hr))
  return;
 
 //---------描画開始--------
    pHwndRenderTarget->BeginDraw();  
 
    //背景クリア 
    pHwndRenderTarget->Clear(D2D1::ColorF(1.0F, 1.0F, 1.0F));      
 
    //円の描画 
    D2D1_ELLIPSE ellipse1 = 
        D2D1::Ellipse(D2D1::Point2F(120.0F, 120.0F), 100.0F, 100.0F); 
    pHwndRenderTarget->DrawEllipse(ellipse1, pGreenBrush, 10.0F); 
 
    //四角形の描画 
    D2D1_RECT_F rect1 =  
        D2D1::RectF(100.0F, 50.0F, 300.0F, 100.F); 
    pHwndRenderTarget->FillRectangle(&rect1, pGreenBrush);

    pHwndRenderTarget->EndDraw();
 //---------描画終了---------

 //ブラシの解放
 if(pGreenBrush != NULL) 
    { 
        pGreenBrush->Release(); 
        pGreenBrush = NULL; 
    }
}

void D2D::ResizeWindow(HWND hWnd)
{
    //レンダーターゲットの論理サイズの再設定 
 if(pHwndRenderTarget)
 {
  RECT rect; 
  ::GetClientRect(hWnd, &rect); 
  pHwndRenderTarget->Resize( D2D1::SizeU(rect.right, rect.bottom) ); 
 }
}

なお、VC++の設定及びwin32プロジェクトの作り方によってはD2D.cppファイルの頭に #include "StdAfx.h" を加える必要があります。

このクラスの利用方法


このクラス(D2D)のおおまかな利用方法は次の通りです。


1・D2Dクラスのオブジェクトを作成
2・ウインドウのハンドルを渡してDirect2Dを初期化
3・ ウインドウサイズの変更に対応
4・ 描画処理を記述

これらを、順を追って説明します。

1・D2Dのオブジェクトを作成


C++の基礎的な話になりますが、まずオブジェクトを作ります。
例えばオブジェクトの名前(変数名)をd2dにしたければ

 D2D d2d;

と言った感じです。

2・ウインドウのハンドルを渡して初期化

普通のwin32APIのプログラムならウインドウを作っている部分があると思います。例えば

hWnd = CreateWindow(・・・

 if (!hWnd)
{//初期化失敗時は終了
  return FALSE;
}

という様な部分です。
Direct2Dの初期化はこの後に D2DのCreateDevice(const HWND hWnd)を呼べばできます。その際、引数としてウインドウのハンドルを渡して下さい。

例えば次の通りです。

d2d.CreateDevice(hWnd)

CreateDevice(const HWND hWnd)は戻り値として初期化の成否を返すので、失敗した場合は次のようにしてアプリケーションを終わらせると良いかも知れません。

if (!d2d.CreateDevice(hWnd))
{
    //失敗したのアプリケーションを終わらせる。
}

3・ ウインドウサイズの変更に対応

ウインドウのサイズ変更時にはResizeWindow(HWND hWnd)にウインドウハンドルを 渡してください。
ウインドウプロシージャでWM_SIZEメッセージを受け取った時に呼ぶ例は
case WM_SIZE:
d2d.ResizeWindow(hWnd);
break;
といった感じです。
これをやらない場合は、ウインドウサイズに合わせて描画対象も変形します。

4・描画処理の記述

RenderTest()で描画テストが行えます。
 ウインドウプロシージャのWM_PAINTメッセージ受信時に呼ぶ例は

case WM_PAINT:
hdc = BeginPaint(hWnd, &amp;ps);
d2d.RenderTest();
EndPaint(hWnd, &amp;ps);
break;

という感じです。
実行結果はこんな感じです。


解体処理について

このクラスで使用したDirect2Dリソース等のオブジェクト類はデストラクタ任せで解体されます。つまり普通に作ったオブジェクトは何もしなくてもアプリケーション終了時に解体されます。

D2D* d2d;
d2d = new D2D;
という風にnewで作った場合は
delete d2d;
という風にdeleteした時点で解体されます。

Direct2Dの初期化

・・・さて、とりあえず上に書いたクラスの
CreateDevice(HWND hWnd)
というメソッドの中がDirect2Dを初期化する部分だと言えます。
簡単な解説は次の節で行うつもりです。

2012年7月23日月曜日

数値から文字列への変換(int,double,float→wchar_t文字列)

#include <string>
std::to_string(num);   
この関数に数値型を入れるとstd::string型が戻る。
今となってはこれだけの事なんだがC++11以前の環境では
下に昔書いた様なめんどくさい作業が必要だったのかも知れない。
============================================
#include <windows.h>
#include <iostream>
#include <sstream> //必須なのはこれ

int main()
{
 //変換したい数値を作成
 double pai = 3.1415926535897932;

 // 変換を担うストリングストリームの作成
 //basic_stringstreamテンプレートのwchara_t版がtypedefされたもの
 std::wstringstream ss; //それがwstringstream(char版はstringstream)

 //doubleやfloatの精度指定(任意)
 //整数と少数を合わせた有効な桁数でデフォルトは6
 ss.precision(17); 

 //始めに作った数値をストリングストリームに入れる
 ss << pai;
 
 //C++の標準出力もワイド文字様のwが付いたwcoutを使う
 std::wcout << ss.str();

 //文字を加えてみる
 ss << L" = 円周率";
 ss<< std::endl; //改行

 // 少数の精度を落とす
 ss.precision(7);
 // 小数の表示を指数形式にする
              ss.setf(std::ios_base::scientific, std::ios_base::floatfield);
             // 少数をストリングストリームに追加
             ss << 3.14;

 //デバッグウインドウに出力
 OutputDebugString(ss.str().c_str()); 
 
 //文字列をクリア
 ss.str(L""); 

              ss << std::endl;
 ss << 100;
 OutputDebugString(ss.str().c_str());
getchar();
}  
             


実行結果



解説

C++的な方法でintやfloat, double等の数字の型を文字列へ変換するには標準C++ライブラリの ストリングストリーム (basic_stringstream)というテンプレートクラスが使えます。
まず始めにこのクラスのオブジェクトに変換したい数字を入れます。
そして .str() というメンバでストリング(basic_string)というテンプレートクラスのオブジェクトに変換し、
さらにそのオブジェクトからC形式の文字列のポインタを得たいならストリングのc_str()というメンバを使います。

例としては・・上のサンプルで示した通りなのですが、簡単に説明するとwchar_tの文字列を作る場合なら、

std::wstringstream ss;
という風にストリングストリームを用意し、

ss << 3 <<L"これは3です";

という風によくあるC++のストリームのメソッドで文字や数字を入れて、
wchar_tのポインタを得る時は一旦wstringオブジェクトを作り、
例)
std::wstring wst = ss.str();

そこからポインタを引き出します。
例)
const wchar_t* pws = wst.c_str();

wstringオブジェクトが消えると、このポインタが指す文字列も一緒に消えるので注意しましょう。

なお、冒頭のサンプルに示したように
ss.str().c_str()
と書けば、見た目の上ではwstringの作成部分を省略できます。

最も簡単な例
std::wstringstream ss;
ss << L"円周率="<< 3.14 <<std::endl;
OutputDebugString(ss.str().c_str());

(OutputDebugString()はWindowsのIDEのデバッグウインドウに出力する関数です。)

関数にしておくと便利でしょうか?
template <typename T> std::wstring toWstr(T tep)
{
std::wstringstream ss;
ss << tep;
return ss.str();
};

使用例1
int number = 100;

OutputDebugString(toWstr(number).c_str());

使用例2
int number = 9999;
std::wstring wstr = L"この数字は…";
wstr += toWstr(number);
OutputDebugString(wstr.c_str());


なお、やたらOutputDebugString()するための関数を
定義しまくった例がこちらにあります…。

小数点型の精度

例中の
ss.precision(); は、
浮動小数点型用の精度設定のための関数であり、整数の部分と小数点以下の部分を合計した桁数で指定します。デフォルトは6です。


名前空間

 今回のプログラムは
using namespace std;
を打っておけば
std::
をはずして名前を使う事が出来ます。


charとwchar_tの切り替え


 初心者用の文字列処理の解説の多くが
std::string
とか
std::cout <<
 を使うのですが、このままでは近年のユニコード環境の関数へ渡すワイド文字(wchar_t型)の文字列は扱えません。


マルチバイト文字列(日本ではShift-JIS)とワイド文字列(Unicode, UTF-16)の違いの基礎的な話はこのサイトが参考になります。
ユニコードにも色々あるんですがWindowsプログラミングでのユニコードの文字列は16ビットのwchar_t型の配列で表し、これをワイド文字列と言います。

C++で定義されている文字列処理に関わるクラスの多くはそのままだと、char型の文字列です。しかしこれの頭にwを付けるとwchar_t型のクラスになります。
例えば
std::string はcharの配列を管理する文字列クラスですが、これのwchar_t版は
std::wstring;
です。

 細かい話をすると
これらのクラスはテンプレートで作られています。例えば

 stringは実際はbasic_stringという名前のテンプレートクラスであり、それををcharで作る時用に、typedefでstringと命名されています。wchar_tならwstringです。

↓こんな感じ
--------------
//stringの型
typedef basic_string<char, char_traits<char>, allocator<char> >
string;

//wsrtingの型
typedef basic_string<wchar_t, char_traits<wchar_t>, allocator<wchar_t> >
wstring;
 
-------------

同様に basic_stringstream は
---------
typedef basic_stringstream<char, char_traits<char>,
allocator<char> > stringstream;
typedef basic_stringstream<wchar_t, char_traits<wchar_t>,
 allocator<wchar_t> > wstringstream;
--------
となっています。


ストリングストリームの詳しい解説はここ
 IOStreamライブラリ自体の解説はここ
が参考になるかも知れません。

2012年7月21日土曜日

_ASSERTで警告ダイアログを出す

元々C言語にassert()というマクロがありますがVC++では

#include <crtdbg.h>
をインクルードして
_ASSERT(0)
とか
_ASSERTE(0)
でプログラムを中断させる事が出来ます。

C言語のassertは<assert.h>というヘッダをインクルードすれば利用できますがVC++版の方が警告発生時のコード追跡が便利だったりするかも知れません。

アサートの仕組みは
カッコの中が0(つまり条件式がfalse)ならダイアログボックスで警告が出ます。それ以外ならそのまま通過して何も起きません。
 Debugビルドで開発中に、「まぁ、こういう条件にはならないだろうけど、もしなった場合はすぐに知りたい」とか、 「開発中に起こり得る仕様外の出来事なので、これで知らせよう」という状況で使うのが一般的かと思います。
Releaseビルドした場合はアサートの文自体がカッコの中も含めて丸々消えてしまうのでこのマクロを使いまくってもパフォーマンスの低下は起きません。

使用例

int*p = 0;

int GetNum()
{
 //pが初期化前(0のまま)だったらアサート
 _ASSERT(p);

//無事通過したので内容を返す
 return *p;
}

・・・つまり
クラス内のメンバが初期化前に使われそうになった時などに有効でしょう。
if文で分岐してエラーを返したい場合もあるかも知れませんが、想定外のパターンをすぐに発覚させていきたいという場合にはアサートしましょう。

あとは配列の範囲チェックとかに使う?
void Set(int id, int num)
{
int p[10];
_ASSERTE(id>=0 && id <10);
p[id] = num;
 }

_ASSERTと_ASSERTEの違いは警告時に()の中の条件式が表示されるかどうかです。条件式が表示されると警告が出た時に問題の箇所が分かり安いという利点があるのですが、いずれにせよVC++でビルド実行中なら「再試行」ボタンを押せば問題の箇所に飛べるのでさほど利点がないかも知れません。

_ASSERT(0 && "Σ (゚Д゚;)カクカクシカジカでテラヤバス!");
と書くと

このようになります。
&&の左側がfalseの時点でfalse確定なので右側の式は評価されないので普通にアサートされて、()の内容がそのまま表示されるという仕組みです。条件式だけを書くよりも原因の特定を容易にします。

しかし、自分でデバッグする場合はコード中の_ASSERTのそばになぜアサートを設けたかをコメントしておけば済むという噂もあります。

文字列をやたら使うとメモリを食うから、それが気になる場合はこちらではなく_ASSERTを使うようにとMSDNのCRT アサーションというページには書いてありました。

2012年7月20日金曜日

メモリリークを検出する

要点
#include <crtdbg.h>
とインクルードして、main()関数の頭とかに

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
を書いておくと、メモリリークがあった場合、アプリケーション終了時にデバッグウインドウにレポートが出力されます。


ーーー経緯ーーーーー
_CrtDumpMemoryLeaks()という関数を使うとデバッグ実行中にnewで確保したメモリをdeleteし忘れていないかチェックする事が出来ます。

例)
#ifdef _DEBUG
   #ifndef DBG_NEW
      #define DBG_NEW new ( _NORMAL_BLOCK , __FILE__ , __LINE__ )
      #define new DBG_NEW
   #endif
#endif  // _DEBUG

int main()
{
   int *p = new int;
   int *p2 = new int[10];

 //現時点で開放していないメモリをデバッグ窓に出力
 _CrtDumpMemoryLeaks();
}

写真をクリックすると拡大します。

int *pは4バイト、
int *p2は40バイト消費している事が分かります。


必要なヘッダ
#include <stdio.h>
#include <crtdbg.h> //必須

解説

写真17行目の

_CrtDumpMemoryLeaks();
によって、プログラムの現時点でnewしたのに開放していないメモリをデバッグ窓に出力します。
これでどのアドレスの何バイトのメモリが未解放かが分かるのですが、アドレス渡されても意味不明だし、バイト数だけのヒントも心もとないのでmain()関数の前に書いたマクロによって、そのメモリを何処のファイルの何行目で確保したかという追加の情報が出力される様にしています。
このマクロは、newで確保する以前に書いておく必要があります。

一般的な使い方としてはアプリケーション終了前に
_CrtDumpMemoryLeaks();
を呼んでメモリリークをチェックします。

でも、出力ウインドウなんて毎回チェックしないかも知れないのでアサートと組み合わせてすぐに分かるようにした方が良いでしょう。

_ASSERT(!_CrtDumpMemoryLeaks());

(_CrtDumpMemoryLeaks()は、メモリリークが見つかった場合にTRUE(=1)を返します。)

ただし、アプリケーション終了時に_CrtDumpMemoryLeaks()を書くと言っても、newを使わないグローバル領域のオブジェクトのデストラクタがその時点で呼ばれていないのは当然なので、そのオブジェクトの内部でnewで確保した領域があるとそれがメモリリークとして検出されてしまうという問題が発生します。

・・・という分けで今まで書いたことは忘れた方が良いかも知れません。

いくつか試しましたが結局、アプリケーションの開始時に
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
を書いとくという方法が無難だと感じました。

プログラミングはあなた自身の冒険の旅です。
もっと良い方法はあなた自身が探しだして下さい・・・。
ドラクエを超えるのはドラクエだけ(なんのこっちゃ)

参考にしたMSDNの情報
_CrtDumpMemoryLeaks
CRTライブラリを使用したメモリリークの検出

型はメモリと演算を規定する

要点
変数の型は変数が確保するメモリのサイズを決めます。
また、変数に対してどんな演算をするのかも決めます。
------------


変数という言葉はいつでも使える言葉です。

int x;
と書いたら、int型のxという名前の変数の宣言になります。

これに対してオブジェクト(またはインスタンス)という言葉は、変数がメモリとしての実体を持った状態を指します。
つまり

int x;
という宣言の後に
x = 3;
と定義を書いて
(あるいは
int x = 3;
という風に宣言と定義を同時に書いて)

そしてさらにプログラムをビルド、実行してこのコードの部分を制御が通過した時に初めてxはオブジェクトになります。

この時、x は 3で初期化したのでメモリの何処か8ビットは

00000011 (10進数で3)
になっているはずです。

windowsならint型は32ビットなので・・・

00000000 00000000 00000000 00000011
確保したビットの量はこれくらいあるでしょう。

この様に、変数の型は変数がオブジェクトになった時にどれだけメモリを確保するかを決めます
 
そして型にはもうひとつ重要な性質があります。
それが

演算の規定

です。
ここで言う演算というのはメモリの操作の事です。

C++によるプログラミングは基本的に、型の定義によって
メモリの確保や演算の仕方を決めて行くことになります。

例えば
----------------
int x, y;

x = 1; //1で初期化(メモリの確保という演算)
y = 3;
x = y; //yを代入(代入という演算)

とか
printf("%d",x); //関数に渡されて内部の引数にコピーされる
とか
ns[3] = x; //配列に代入
とか
float f = x; //xをfloat型に代入
-----------------
等の演算があります。
つまり変数の扱い全てが演算だと言えます。
これら演算は、型の種類毎に違うのが普通です。演算が全て同じならば別の型を定義する必要がないからです。

例えば次の様にabという名前の変数を別々の型で定義したとします。

int a = 10;
float b = 10.0f;

この時、初期化が発生している訳ですが、その演算内容はintとfloatでは違います。floatは少数が扱えるデータ構造にフォーマットしないといけないからです。当然ながら四則演算もメモリの配置が違うので同じやり方を適用できません。また、intとfloat等の別の型同士の演算をする時は、一方をもう一方の型に変換してから計算が行われます。この型変換というのも一種の演算であり、型同士の種類によって規則が違います。

intやfloatの様にC++に元から組み込みまれている基本型は初めからどのような演算を適用するかが決められていますが、構造体やクラス型はプログラマが演算を定義できます。(と言って大抵はその内部で基本型とその演算を使う事になります。)

2012年7月17日火曜日

newによるメモリの確保

----要約-----

//自由領域にオブジェクトを作りたい時
int* i = new int; //単体
int* is = new int[10]; //配列
MyClass* mc = new MyClass(9999);//クラス

//自由領域のオブジェクトを解体したい時
delete i; //単体
delete [] is; //配列
delete mc; //クラス

 -----解説----

変数を作る時には普通のスコープに従う作り方と自由領域に確保する作り方の2種類があります。

int i = 3;
と書いた時は、通常の作り方です。多分、勉強して始めに覚える作り方です。
一方
 int* i = new int;
と書いた時は自由領域に確保する作り方です。
これはnewという演算子で確保したint型のメモリのアドレスをポインタ型の変数で受け止めています。
ではnewで作ったオブジェクトと通常のオブジェクトは何が違うのでしょうか?
-----------------------------
その前にポインタの基本を確認しておきましょう。

newを使って作ったオブジェクトはポインタで受け止めるので、そのオブジェクトを使いたい時はポインタを使う事になります。
ただし、配列の場合は、普通の配列のように使えます。


int* i = new int;
int *is = new int[10];
MyClass* mc = new MyClass;

*i = 3;//iの値は3になる

for(int i = 0; i <10; ++i)
   is[i] = i; //配列の各要素に0~9を入れる

mc->GetNantoka();

->はアロー演算子といいます。これを使うとポインタが指すクラスや構造体オブジェクトのpublicメンバにアクセスできます。
(*mc). GetNantoka();
と書いても同じ事です。
----------------------------------
さて、newの解説に戻ります。
通常のローカル変数はスタックという一時的なメモリの出し入れをする機構に確保されます。しかしnewで確保したオブジェクトはそこがグローバルスコープであろうとローカルスコープであろうとヒープというプログラムの持続中に永続的に使うための場所にメモリが作られます。


この写真の例は、newで確保したオブジェクトのアドレスだけは他と大分違うという事を示しています。

まぁここで大切なのは具体的なアドレスではなく
newしたオブジェクトはスコープを持たない
という事です。永続的に存在するのだからスコープは関係ないのです。この点はグローバル変数と似たイメージです。

int a = 0;
{
int b = 0;
int* c = new int;
 }
と書いた時、ここ(中括弧の外)ではaにはアクセスできてもbとcにはアクセスできません。アクセス出来ないどころかstaticを付けていない局所変数(ローカル変数)はスコープを抜けた時点で解体されるので変数bとcはデストラクタが呼ばれて破壊されています。

しかしそれにもかかわらずcのメモリ領域は残り続けます。
なぜでしょうか?
それはcというポインタ型の変数は確かにスコープを抜けた時点で破壊されますが、cが指していたnewで確保したメモリ領域はcとは無関係に存在し続けるからです。
いま破壊されたのはcというポインタ変数であって、ポインタ変数が指していたオブジェクトではないのです。

これがもしcがグローバル変数を指すポインタだったらどうでしょうか?

int a = 0;
{
int* c = a;
 }

この場合、newはしていませんがカッコを抜けた後にやはりcは破壊され、それにも関わらずcが指していたメモリaは生存し続けます。つまりここだけ見るとnewによる自由領域のメモリ確保はグローバル変数と似ています。

グローバル変数と違うのはオブジェクトの解体方法です。
 aは newしていないので、aが存在するスコープを抜けた時点で解体されます。例えばaがグローバル変数だとすれば、アプリケーションの終了時に解体されます。
 しかしにnewで作ったメモリ領域のオブジェクトの寿命はスコープとは無関係に残り続けます。

自由領域のメモリを解体するには、そのアドレスを指すポインタに対してdeleteをします。

例)
int* c = new int; //自由領域にintオブジェクトを作成
delete c; //自由領域のintオブジェクトを削除

ただし上の方で見た例の様に

{
int b = 0;
int* c = new int;
 }
ここ、つまりスコープを抜けた後にはポインタのcが存在しないのでdelete c とも書けない分けです。
・・・という分けでnewしたスコープやクラスがきちんとdeleteの面倒を見るように注意を払う必要があります。

----メモリリークについて----
確保したメモリをプログラム的にはもう使わないにも関わらずに解放する事を忘れていると、知らない間に(管理意識の及ばない部分、つまりバグとして)使えるメモリが減っていきます。これをメモリリークと言います。ただしアプリケーション終了時にOSが開放してくれる場合がほとんどだと思います。
いずれにせよメモリの管理が出来ていないのはメモリが減っていくという事実以前にプログラムの整理、管理が出来ていない事と繋がる気がします。プログラミングはメモリを加工したり管理する作業だと言えるので、前向きに捉えるとメモリリークはメモリの存在を意識するための良い教材かも知れません。
-----------------------------

ところで
int* c = new int;
int* d = c;
delete d;

と書いた場合はどうなるのでしょうか?
まず2行目を見ましょう。dはポインタであり、それに対してcのアドレスをコピーしたのでdとcは同じメモリ領域を指すようになりました。
こうなるともうどちらが正当なnewしたポインタだったのか区別が付きません。文法上の意味はどちらも同じであり、どちらか一方が偽物だという訳でもありません。
だからdelete dした時点でdが指していたメモリ領域は消えます。誰か一人がdeleteしてくれれば良いのです。そもそも物理的に同じものを指していたのですから。
つまり
newの数とdeleteの数が合っていれば、ポインタがいくつあろうと問題ありません

さらに、この場合はdがクラス等の他のスコープに所属するポインタ変数でも良いことになります。誰か一人、newした領域を指すポインタさんがどこかのスコープに生きていれば、そしてdeleteする事を忘れなければいつかはその領域を壊すことが出来るでしょう。(ただし一つのスコープで作ったポインタを別のスコープで消すと管理がややこしくなるかも知れません。)

これは勇者と魔王の関係と言えるかも知れません。
つまり
int *p = new int;
と書いた時、pが勇者であり、newが魔王です。
new「がっはっははははは!メモリは頂いた!この領域は永遠に俺のものだ!スコープによって寿命が尽きる普通の変数とは生命力が違うわ!!」
p「メモリを制御するためにお前を生んだだけだ。しかしお前の存在が見えるのは私か私の同胞だけ・・・!例え末代になろうともお前を消す事は忘れぬぞ!」

・・・ちょっとポインタの存在を強調し過ぎたかも知れませんが、まずはnewしたメモリ領域はdeleteされるまで存在し続けるというイメージが大切です。そしてそれに対するアクセス手段がポインタであると考えましょう。
・・・元来、ポインタ変数はどこでどの様に作ってどう消そうと無害です。ポインタ変数の中身はアドレスという数字に過ぎないからです。
しょせんは人間のレベルだと言えます。
それに対し、newしたメモリとはポインタがどうのこうのとは全く別の次元の場所に存在する何かなのです。

例えば、ポインタ変数とそれが指していたメモリ領域は別物なのでdelete した後でもポインタ変数は使い回せます。
例)
int *p = new int(100);
delete p;
p = new int;
delete p;
p = new int[99];
delete [] p;
 

連続でdeleteしてはならない

既にdeleteで解放したメモリに対してnewで新たなメモリを割り当てる事なく再びdeleteする事は認められていません。やると、システムが監視してくれていれば警告ダイアログが出てアプリケーションを終了させるでしょう。いくら魔王といえど2度も殺してはいけないのです。
2回毒殺しようとしたら・・・
・・・・毒が裏返った!
とか・・・まぁ例えはどうでも良いですが。

2度のdeleteというミスを防ぐ簡単な方法としてはdeleteしたらすぐにポインタに0を入れておく事です。アドレス0のポインタをヌルポインタと呼び、これにはdeleteが働きません。

 ヌルポインタは普通にポインタを使う局面においても、 アドレスを指していない初期化前のポインタだという事を表すために使えます。アドレス0番はプログラマーが使うアドレスとしては実在しない事が保証されている様です。

例えばコンストラクタ内やdelete時にポインタに0を入れておき、
if(p != 0)
 等でチェックすれば、アドレスが入っている状態かどうかを確認できます。
また、数値型として0を使う変数ではなく、ヌルポインタだという事を示すために
#define NULL 0
というマクロが良く使われます。

例)
// プログラム開始時
int *p = NULL;

// 使用時
if(p != NULL)
{
   p->Use();
}
else
{
//ここでnewするか、あるいは警告メッセージをだしたり
}

//削除
delete p;
p = NULL;

また、ifで分岐させないでアサートを使って仕様外の状況に対処するのもありでしょう。
例)
_ASSERTE(p);

_ASSERTE()マクロを使うには
#include <crtdbg.h>

というヘッダが必要です。
詳しくは_ASSERTで警告ダイアログを出すをどうぞ

2012年7月14日土曜日

ポインタ、アドレス、リファレンス(渡し)


この出力結果は、以下のコードを実行したものです。
尚、printfの改行の書式が文字化けしているかも知れません。
(\nはバックスラッシュ(\n)じゃなくて半角の円マークを使うのが元々のコードです。)


#include <stdio.h>

int main()

{
 int i = 3; // 普通の整数を表すint型
 printf( " i = %d \n", i ); //iの内容を表示
 printf( " &i = %d \n", &i); // iのアドレスを表示

 int* pi; // int型を指すことが出来るポインタ型の宣言

 //pi = i; // ポインタ型に内容を入れるのは間違い
 pi = &i; // &をiの前に付けてアドレスを入れる
  
 printf( " pi = %d  \n", pi ); // piの内容、つまりiのアドレス
 printf( "&pi = %d \n", &pi );// pi自体のアドレス
 printf( "*pi = %d  \n", *pi );// iの内容
 
 int &ri = i; // iのリファレンスを作る
 printf( " ri = %d  \n", ri ); //iの内容
 //printf( " *ri = %d  \n", *ri ); //*は不要
 printf( "&ri = %d  \n", &ri ); //ri、つまりiのアドレス


        getchar();

}

解説 

変数のアドレス
&変数名
という書式で得る事が出来ます。アドレスは変数がメモリ上の何処にあるかを表す番号です。

例)
int i = 3; // 普通の整数を表すint型
&i ; // 3ではなく、iのアドレスを表す

アドレスが具体的にどんな数になるかは、実行環境によってその都度変わるだろうし、普段意識する必要がない部分です。

ポインタはアドレスを格納する変数です。
普通、アドレスは整数による番号で表されるため、
何らかの整数が格納されているという意味では
int等の整数型と大差ありません。

しかしプログラマーもコンパイラも
それが単なる整数型じゃなくてアドレスを保存する
特別な整数型として認識しているため、
その型の利用の仕方に特別な手段が用意されています。
それが&、*、->等です。
また足し算の意味も
アドレスを直接足すのではなく型のサイズを単位とした移動量となります。

&を使えば変数のアドレスを得られるため、
その値を直接ポインタに格納できます。

冒頭に示した実行結果に
&i = 1572576
pi =  1572576
とあります。2つ同じ数字が並んでいます。
この結果は事前の代入
pi = &i
によってポインタpi = 変数iのアドレスとなったからです。

ポインタはそのままではアドレスですが、
*ポインタ
けば、アドレスが指す変数の内容を得ることが出来ます
つまり例中では
*piと書けば
iのアドレスではなくiそのものとなります。

ちなみに
ポインタ型の宣言、つまり例中の
int* pi;
においても*という記号を使うので間際らしいかも知れませが、型の宣言のための*と内容を得るための*は無関係です。強いて言えば言語開発者がポインタ関連だから同じ記号を使ったのでしょうか?覚えやすいかも知れませんが意味的には逆の作用ですね・・・。
int* pi;(ちなみにint *pi;という風にスペースの区切りを変えてもOK)の時は、int型をポインタ型にしますが、一旦アドレスを確保したポインタに対して再びアスタリスク(*)を付けると今度はint型に戻るのですね・・・。まぁ戻るといっても瞬間的な話であって*を付けなければポインタであり続けるのですが。


また、
*ポインタ は、それが指す変数の中身を知るだけではなく、操作する事ができます。

例)
int n = 10;
int* pn = &n;
*pn =50;

こうするとnの値も50となります。ポインタは変数のコピーではなくアドレスです。そして*を付けている間はそのアドレスが指す変数そのものになってしまという事です。

また、ポインタ型はポインタ型同士で代入が出来ます。
例えばさっきの例に続いて
int* pn2 = pn;
*pn = 99;
と書くと nの値は99になります。
つまりこの状況はnの値はnという変数自身とpnというポインタとpn2というポインタの合計、3つの変数からアクセスされ得る分けです。(ポインタも変数の一種です。)しかし依然としてintの値を確保したメモリ自体は一つしか存在していません。


その他、 ここでは説明しませんがポインタには newによるオブジェクトの確保という使い道 やインクリメントやデクリメントという、配列とセットで使う演算があります。


リファレンス(参照)は変数の別名です。
例えば
int i =3;
int& ri = i;
と書けばriをiとまったく同様に扱えます。ポインタと違って内容を得るの時に*riと書く必要はありません。また、ポインタではないのでアドレスを格納できないので
ri = &i;
とは書けません。ポインタと違ってリファレンスは、宣言すると同時にメモリ実体のある変数を入れる必要があり、アドレスやポインタ型変数を入れることは出来ません。だからポインタと違ってリファレンスが指す内容が実体のあるメモリか既に破壊された領域かを気にするケースも通常はないでしょう。

---やや細かい話-------
ポインタが指すアドレスが必ずしも他の変数のアドレスではなく、newによって独自に確保したオブジェクトである可能性があるのに対し、リファレンスは確実に他の変数のです。アクセス手段こそ普通の変数と同じですが、どこか別の場所にも同じ内容を指す変数があるとうい事には注意が必要です。
さらに言えば、ポインタの様にアドレスは代入できませんが、ポインタが指す内容は代入可能です。
例)
int *pi = new int(10);
int& ri = *pi;
ですから、使い方によってはリファレンスが指す内容が消失している場合もあり得ます。
----------------------

リファレンスもややこしい事に?あるいは覚えやすくするために?
 型名の後に付ける&はリファレンスの作成を意味しますが
既に出来ている変数の前に付ける&はアドレスを得るアドレス演算子として働くので&の意味を混同しないで下さい。
(例)
int i = 3;
int &ri = i;
//これで変数iがriという名前でも扱えるようになった
&ri; //←これは3という値が保存されているアドレス
&i; //←これも3という値が保存されている&riと同じアドレス

この時、
ri =5にするとiも5になるし、
i=5にしてもriも5になります。
別の複数の名前が一つの変数の実体をいじれるというところはポインタと同様です。

関数引数のリファレンス

ここで問題
void func(int& i)
今使われた&の意味はどっちでしょうか?
iのアドレスでしょうか?
それともiは何かのリファレンスなのでしょうか?
見出しでバレバレかも知れませんが答えはリファレンスです。


 一般的に関数の引数では、容量の大きい構造体やクラスオブジェクトの無駄な生成を避けるために、あるいは渡されたオブジェクトの値をいじる時にリファレンスを使います。
その際、呼び出し元の変数を変更したいのかしたくないのか用途をはっきりさせるために、変更しない場合は
void func(const int& i)
という風にconstを付けるのが一般的です。
constを付けて初期化された変数は値を参照はできますが変更はできないので、関数内部でiの値を変更するコードを書くとコンパイルエラーとなります。

ちなみにconstの利用は関数内部に限った話ではありません。
constは定数を作る時に利用できます。
例)
const float pai = 3.141592f;
pai = 0; //コンパイルエラー


関数引数の文法と仕組み

単純に言えば、関数の引数においてもそれが呼びされた時点で
(型名 変数名)
という風に()の中で型の宣言に応じて変数の生成が行われます
つまり
 void func(int i)
という風に&を付けなければint iをここで生成します。その際、呼び出し元の引数の内容を新しく生成した変数のメモリにコピーします。intではピンと来ないでしょうがクラス型の場合コピーコンストラクタが呼ばれる事になります。こうして値こそ呼び出し元の引数と同じだけれど、メモリアドレスは別な部分に全く新しい変数が生成される分けです(どうせ関数のスコープを抜ければ解体されるのに)。

難しく聞こえたかも知れませんがこれは何も特別なことではなく、
int a = 3;
int b = a;
とやった時に、bはaからのコピーで初期化した分けです。
同様に
func(a);
を呼んでやれば
void func(int i)  
{
//この時点で int i = a;
//が発生している
}
という話です。

一方、
void func(int& i)
という風に&を付けてリファレンス型にしておくと余計な変数は生成せずに呼び出し元の引数のリファレンスが渡されます。(あるいは、 呼び出し元の引数のリファレンスを生成したと言い換えてもいいかも知れません。)

先程の例で言うと
int a =3;
int b& = a; //bはaのリファレンス
なのと同じように

func(a);
を呼んだ時に

void func(int& i)  
{
//この時点で int& i = a; が発生してる。
//つまりaのリファレンスiが作られた
}
という事になります。