ムラサメ研究ブログ

主にゲームやプログラミングのログ

【今更だが】 CComPtrのoperator&でassertで落ちる場合は read目的なら .p()を使うといいぞ

はじめに

なぜいまさらCOMなのか? と言われると DirectXを触っているからである

CComPtrとは?

COMオブジェクトを扱いやすいようにした テンプレート。 いわゆるスマートポインタ

COMは、shared_ptrのような参照カウント方式のスマートポインタだが、shared_ptrと明らかに違うのは 参照カウントのメンバとメソッドが、shared_ptrの場合は shared_ptrが持っているが COMの場合は オブジェクトのベースクラス IUnknownオブジェクトが持っている点である ゆえに COMオブジェクトを shared_ptrで運用するのは あまり効果的でない

boost::intrusive_ptr は、オブジェクトに参照カウントを保持するスマートポインタに対応しているので CComPtrにかえて使うことも出来る

基本的には

void intrusive_ptr_add_ref(IUnknown* p) { p->AddRef(); }
void intrusive_ptr_release(IUnknown* p) { p->Release(); }

のように、参照カウンタアクセス関数を置き換えれば良い

CComPtrを使う

基本的には、今まで生ポインタで扱っていたものを置き換えればよい

ID3D11Device                *device;
ID3D11DeviceContext         *context;

D3D11CreateDevice( ほげほげ, &device, ほげ, &context )


...

context->Release();
device->Release();

CComPtr<ID3D11Device>                 device;
CComPtr<ID3D11DeviceContext>          context;

D3D11CreateDevice( ほげほげ, &device, ほげ, &context )

と、するだけでよい。

変わったのは CComPtr を使い、スマートポインタにしたこと (x)->Release() がなくなったこと

の2点。

CComPtrでは、値の代入時に Addref()が呼ばれ参照カウントをインクリメントし スコープから消えると Release()が呼び出され参照カウントがデクリメントされる ので、Releaseをする必要がない。

たとえ例外等でRelease呼ぶ前にスコープが消滅しても Releaseが呼ばれるようになった とても安全!

もちろんこの後 device->ほげ と、通常のポインタと同じように使うことが出来る

operator&問題

上記の CreateDeviceでは、新規にオブジェクトを作成したので問題が生じなかったが たとえば オブジェクトのポインタを必要とする時は問題が発生する

通常は、元々中身はCOMオブジェクトへのポインタであり、Create時以外は ポインタのポインタを使うことがないのだが DirectXでいえば、バッファー、テクスチャ配列、レンダーターゲット配列等で ポインタのポインタが必要となるケースが有る たとえば

ID3D11Buffer      *vertexbuffer;

...
device->CreateBuffer(&bd, &InitDatta, &vertexbuffer);

...
context->IASetVertexBuffers(0, 1, &vertexbuffer, &stride, &offset);

を愚直にCComPtrにすると

CComPtr<ID3D11Buffer>     vertexbuffer;

...
device->CreateBuffer(&bd, &InitDatta, &vertexbuffer);

...
context->IASetVertexBuffers(0, 1, &vertexbuffer, &stride, &offset);

となるが、device->CreateBuffer の部分で、DebugAssertion Failed! が発生する

f:id:murasame-labo:20161212153806p:plain

コードを追っかけると

    T** operator&() throw()
    {
        ATLASSERT(p==NULL);
        return &p;
    }

CComPtrでアドレスを取る時(operator&)は、内部ポインタが 非NULL時は Assertで落ちるようになっている

おそらく、内部ポインタが存在する時に Create等でポインタを書き換えると元のポインタがリークするので そのための警告と思えるが 今回の IASetVertexBuffersは、ポインタを書き換える事はない。単純な参照なので Assertを突破したい

が、スマートポインタ等でよくある get()等がみつからない この場合は ポインタの内部メンバ pを直接参照するといい

CComPtr<ID3D11Buffer>     vertexbuffer;

...
device->CreateBuffer(&bd, &InitDatta, &vertexbuffer.p);

...
context->IASetVertexBuffers(0, 1, &vertexbuffer, &stride, &offset);

これで問題ないコードができた