D言語を使っていると、C言語のライブラリとか、COM(Windows)とかを使いたくなることが有ります。だれかが提供してくれている場合はいいですが、どうしても書かざるをえない場合もあります。
そんな時にはバインディングを書いたりするのですが、今回は、COMインターフェースのバインディングの書き方(とサボる方法)を少し説明します。
例として。ID2DFactory
interface ID2D1Factory: IUnknown
{
HRESULT ReloadSystemMetrics();
void GetDesktopDpi(FLOAT *dpiX, FLOAT *dpiY);
HRESULT CreateRectangleGeometry(const D2D1_RECT_F* rectangle, ID2D1RectangleGeometry* rectangleGeometry);
void foo04(); //HRESULT CreateRoundedRectangleGeometry(const D2D1_ROUNDED_RECT *roundedRectangle, ID2D1RoundedRectangleGeometry **roundedRectangleGeometry);
void foo05(); //HRESULT CreateEllipseGeometry(const D2D1_ELLIPSE *ellipse, ID2D1EllipseGeometry **ellipseGeometry);
void foo06(); //HRESULT CreateGeometryGroup(D2D1_FILL_MODE fillMode, ID2D1Geometry **geometries, UINT geometriesCount, ID2D1GeometryGroup **geometryGroup);
HRESULT CreateTransformedGeometry(ID2D1Geometry sourceGeometry, const D2D1_MATRIX_3X2_F* transform, ID2D1TransformedGeometry* transformedGeometry);
HRESULT CreatePathGeometry(ID2D1PathGeometry* pathGeometry);
void foo09(); //HRESULT CreateStrokeStyle(const D2D1_STROKE_STYLE_PROPERTIES *strokeStyleProperties, const FLOAT *dashes, UINT dashesCount, ID2D1StrokeStyle **strokeStyle);
void foo10(); //HRESULT CreateDrawingStateBlock(const D2D1_DRAWING_STATE_DESCRIPTION *drawingStateDescription, IDWriteRenderingParams *textRenderingParams, ID2D1DrawingStateBlock **drawingStateBlock);
HRESULT CreateWicBitmapRenderTarget(IWICBitmap target, const D2D1_RENDER_TARGET_PROPERTIES* renderTargetProperties, ID2D1RenderTarget* renderTarget);
HRESULT CreateHwndRenderTarget(const D2D1_RENDER_TARGET_PROPERTIES* renderTargetProperties, const D2D1_HWND_RENDER_TARGET_PROPERTIES* hwndRenderTargetProperties, ID2D1HwndRenderTarget* hwndRenderTarget);
void foo13(); //HRESULT CreateDxgiSurfaceRenderTarget(IDXGISurface *dxgiSurface, const D2D1_RENDER_TARGET_PROPERTIES *renderTargetProperties, ID2D1RenderTarget **renderTarget);
HRESULT CreateDCRenderTarget(const D2D1_RENDER_TARGET_PROPERTIES* renderTargetProperties, ID2D1DCRenderTarget *dcRenderTarget);
final:
HRESULT CreateRectangleGeometry(const ref D2D1_RECT_F rectangle, ref ID2D1RectangleGeometry rectangleGeometry) { return CreateRectangleGeometry(&rectangle, &rectangleGeometry); }
}
こんなかんじです。
ポイントは以下の点。
使わない関数は void fooXX(); とでもしておく(これ重要)。
重要なのは、仮想関数テーブルの枠を用意してあげることです。所定の場所に所定の関数ポインタを入れられる枠さえあれば、使わないなら別に名前はなんでもいいのです。
たとえば、サボらない場合、CreateRectangleGeometry
関数を定義しようとするだけで、ID2D1RectangleGeometry
インターフェースの定義が必要になります。それを定義するにはID2D1Geometry
が必要で、それにはID2D1Resource
とかID2D1SimplifiedGeometrySink
とかが必要で…と自分で定義しなければならないものが無限に発散していきます。人間の寿命は80年ほどしか無いので、時間は大切にするべきです。サボりましょう。
I~から始まるのはinterface。ポインタをひとつ外す。
たとえば、CreateRectangleGeometry
関数の第二引数にはID2D1RectangleGeometry
インターフェースを保持する変数へのポインタを指定する必要があります。C++では仮引数の型がID2D1RectangleGeometry**
となっているところ、D言語ではID2D1RectangleGeometry*
と、ポインタをひとつ外す必要があります。これは、C++のインターフェース(純粋仮想関数のみ含まれる構造体/クラス)は値型で、D言語のインターフェースは参照型であるためです。参照型の正体は、ただのポインタです。
HRESULT CreateRectangleGeometry(const D2D1_RECT_F* rectangle, ID2D1RectangleGeometry** rectangleGeometry);
HRESULT CreateRectangleGeometry(const D2D1_RECT_F* rectangle, ID2D1RectangleGeometry* rectangleGeometry);
final:のあとは仮想関数にならない。
ので、ヘルパ関数を書くことができます。上記例はC++だと
HRESULT CreateRectangleGeometry(const D2D1_RECT_F &rectangle, ID2D1RectangleGeometry **rectangleGeometry)
{ return CreateRectangleGeometry(&rectangle, rectangleGeometry); }
となるところ、D言語で
HRESULT CreateRectangleGeometry(const ref D2D1_RECT_F rectangle, out ID2D1RectangleGeometry rectangleGeometry)
{ return CreateRectangleGeometry(&rectangle, &rectangleGeometry); }
という感じに書き換えたもの。
とにかくCOMはインターフェースの量が多い。こればかりはVisualStudioとかWindowsSDKとかインストールするだけで利用できるようになるC++が正直羨ましい。
一応D言語でも上記のようにバインディングを作ればCOMのあれそれを利用できるようになります。