忍者ブログ

Memeplexes

プログラミング、3DCGとその他いろいろについて

C++/CLI マネージ配列からネイティブの配列へ変換

さて、今Direct3D 10をC++/CLIから使おうとしているのですが、ここで困ったことがありました。

それは、「せっかくC++/CLIを使っているのだから、.netの配列を使いたい!でもそれをD3D10に渡すやり方がわからない(笑)」というものです。
D3D10のVertexBuffer(モデルの頂点データ)やInputLayout(xnaでいうVertexDeclaration。つまりグラフィックスカードに入力する頂点データがどのように解釈されるかを定義するオブジェクト)では、おそらく.netの配列を使うと便利です(作るのに配列のサイズが必要だからです。sizeofとかで計算してもいいですが、Array.Lengthを使った方が美しいような気がします)

しかし、.netのマネージ配列をそのままネイティブの関数や変数やメソッドとかに渡そうとすると、コンパイルエラーが出ます(cannot convert from 'cli::array<Type> ^' to 'unsigned char *' with [Type=unsigned char])。
おそらく.netのオブジェクトはガベージコレクションが動いた時にアドレスが動くので、そのままではネイティブに渡せないのでしょう(固定しなきゃいけない?)。
これはどうあがいても不可能で、あきらめなきゃいけない・・・ということはないでしょう。
C#ではマネージ配列をネイティブに渡せるので、C++/CLIに出来ないはずがありません。
なにか簡単な方法があるはずです。

で、調べてみたのですが、ありました。(英語ですが)
みんな同じことを考えるんですね。

convert managed array to unmanaged array.  (c++/c#) - GameDev.Net Discussion Forums

つまり、C++/CLIのpin_ptrを使えばいいんです。
これでどうやらメモリを固定できるようです。

なんとmsdnにサンプルもあります。
うーん、きちんとチェックしとけばよかったですね。

How to: Pin Pointers and Arrays

msdnのサンプルが十分わかりやすいので、二番煎じ感が拭えませんが、一応サンプルを作ってみましょう。
#include<stdio.h>

using namespace System;

int main()
{
    array<Byte>^ managedText = gcnew array<Byte>(6);
    managedText[0] = 'H';
    managedText[1] = 'e';
    managedText[2] = 'l';
    managedText[3] = 'l';
    managedText[4] = 'o';
    managedText[5] = '\0';

    pin_ptr<Byte> pinnedText = &managedText[0];
    printf((char *)pinnedText);
}

実行結果は

Hello

です。

これでかんたん(?)に、マネージ配列をネイティブな配列に変換することが出来ます。

拍手[1回]

PR

[C++/CLI] .netオブジェクトを作る(gcnew)

gcnew

C++/CLIでは、.netオブジェクトを作るのに、(newではなく)gcnewを使うそうです。
(gcnewはマネージトな型のインスタンスを作るやつで、これによって作られたものは参照型だろうと値型だろうとガベージコレクションのヒープに確保されます。)

そしてそのgcnewによって返されるのも、ポインタではなくハンドルというものになるそうです。(*ではなく^を使うことになります。)

なお、ハンドルを使うからといってポインタがなくなってしまったわけではなく、C++/CLIではネイティブのクラスをnewしたりとかはできます。(でなきゃなんでC++なんて使うのかわかりませんしね)

HelloWorld.cpp
#using<System.Windows.Forms.dll>
#using<System.dll>

using namespace System::Windows::Forms;

int main()
{
	Form ^form = gcnew Form();
	Application::Run(form);
}

このプログラムはWindowsFormsでウィンドウを表示します。
ここでは.netオブジェクトのformを作っているため、gcnewを使ってインスタンスを生成しています。(newだとコンパイルエラーになります)

※ちなみに、ここでSystem.Windows.Forms.dllだけでなくSystem.dllまで#usingしているのは、System.ComponentModel.Componentを使っているからです。(これがないとコンパイルエラーになります。)


メンバへのアクセス

gcnewとnewはいろいろ違うところがありますが、メンバへのアクセスは同じで、->を使います。
#using<System.Windows.Forms.dll>
#using<System.dll>

using namespace System::Windows::Forms;

int main()
{
	Form ^form = gcnew Form();
	form->Text = "Hello World!";
	Application::Run(form);
}


nullptr

ハンドルの変数は、ポインタと違ってNULL(0)にすることができません。
かわりになるのがnullptrです。

Form ^form = nullptr;

ちょっとややこしいですが、nullptrは、ハンドルだけではなくポインタの変数にも代入することが出来ます。

int *p = nullptr;

拍手[6回]


[C++/CLI] .Netアセンブリをインポート

C++/CLIでの.netアセンブリのインポートに手間取ったのでメモしておきます。

アセンブリのインポートというのは、たとえばWindowsFormsを使いたい時に参照としてSystem.Windows.Forms.dllを追加するアレです。
C#のコンパイラcsc.exeでいう/refereceオプションでやるやつです。これをしないとコンパイルできません。
しかしC++/CLIのコンパイラ、cl.exeにはそのようなオプションがぱっと見、みあたりません。(後でわかったのですが、/FUがそれっぽいです)

どうやらC++/CLIでは#usingでソースコード中に指定できるようです。

HelloWorld.cpp
#using<System.Windows.Forms.dll>

int main()
{
	System::Windows::Forms::MessageBox::Show("Hello World");
	return 0;
}

コマンドラインは特に変わったところはありません。

cl /clr HelloWorld.cpp

b22bb76d.JPG

よし!できました!
コンパイラのオプションではなくコード中で指定できるとは意外でした。


もちろん/FUオプションを使ってもOKです。(#usingを使ったかのようにコンパイルすることを強制(force)するという意味のようです。)
HelloWorld.cpp
int main()
{
	System::Windows::Forms::MessageBox::Show("Hello World");
	return 0;
}


Visual Studio 2005 Command Prompt
cl /clr /FUSystem.Windows.Forms.dll HelloWorld.cpp






拍手[0回]


[C++/CLI]でSystem::Console::WriteLine

DirectX10をそのうちやる予定なのでC++のリハビリをしなくちゃいけません。

というわけでC++/CLIの練習です。(本当はやりたくないんですが・・・)

HelloWorld

簡単なものから行くつもりなのでVisual Studioはオーバーキルでしょうから、コマンドラインからコンパイルすることにします。

C++/CLIのコマンドラインのコンパイラはcl.exeで、Visual StudioのツールのVisual Studio 2005 Command Promptから使えます。(これはVisual C++ Expressについてくるみたいです。"~から使えます"といいましたが、むしろこれがないと困ります。普通のコマンドラインからだとclのパスを通してもコンパイルが上手くいきません。)

まずはさっそくHello Worldです。
HelloWorld.cpp
#include<iostream>

int main()
{
	std::cout << "Hello World!" << std::endl;
	return 0;
}

なんの変哲もないHello Worldです。
これをコンパイルするには、Visual Studio 2005 Command Promptからこうやります:
cl HelloWorld.cpp

これで異常な量の警告と共に(笑)、いちおうコンパイルが成功します。

.Net

次はC++/CLIから.Net Frameworkのライブラリを使ってみす。
これが使えてこそのC++/CLIですからね!

HelloWorld.cpp
int main()
{
	System::Console::WriteLine("C++/CLI!");
	return 0;
}

コマンドラインはさっきとちょっと違って/clrオプションが必要になるようです。(でないとエラーが出てコンパイルできません。コンパイラが'System'はクラスでもネームスペースでもないとか文句を言い出します)

cl /clr HelloWorld.cpp

/clrオプションを指定するとCommon Language Runtime用にコンパイルするそうです。
で、こうやってコンパイルするとXMLのマニフェストファイルと共に、HelloWorld.exeが作られます。

ここでは.net のライブラリだけを使いましたが、混ぜることも出来るようです。

HelloWorld.cpp
#include<iostream>

int main()
{
	std::cout << "Hello World" << std::endl;
	System::Console::WriteLine("C++/CLI!");
	return 0;
}

cl /clr HelloWorld.cpp

拍手[0回]


C++/CLIのコンパイラをコマンドラインから使う

以前、
C++/CLIのコンパイラ、cl.exeをコマンドラインから使うとエラーがでる!
直すにはパスを設定しなくちゃ!

みたいなことを書いたような気がしますが、実はそんなことをしなくても良かったみたいです。

このページ
http://dev.tyzoh.jp/trac/secure-sbm/wiki/VC-Install
の最後の方にきちんとした説明がありますね。

「スタート」→「Visual C++ 2005」→「Visual Stdio Tools」→「Visual Studio 2005 コマンド プロンプト」
できちんとcl.exeが使えるようです。(かんたんなHelloWorldで試してみたらうまくコンパイルできました)

ぼくが今まで苦労していたのは何だったんでしょうね……。

拍手[0回]