ネイティブなバイト配列を処理する関数があるとします。
これに対して、managedなバイト配列を渡して処理させたいとします。
パフォーマンスに影響するため、1バイトずつコピーする処理は避けたいと思います。
以上の条件を実現したサンプルコードを書いてみました。
サンプルソース §
Visual C++ Express 2005 Beta2で、managedコンソールアプリケーションのテンプレートでプロジェクト作成後に、以下のソースを入力します。
#include "stdafx.h"
#include <stdio.h>
using namespace System;
array<Byte>^ createByteArray()
{
array<Byte>^ ar = gcnew array<Byte>(3);
ar[0] = 1;
ar[1] = 2;
ar[2] = 3;
return ar;
}
#pragma unmanaged
void dumpArray( Byte ar[], int length )
{
for( int i=0; i<length; i++ )
{
printf("%d\n",ar[i]);
}
}
#pragma managed
int main(array<System::String ^> ^args)
{
array<Byte>^ ar = createByteArray();
pin_ptr<Byte> pNative = &ar[0];
dumpArray(pNative,ar->Length);
return 0;
}
実行結果 §
1
2
3
解説 §
managedな配列は"array<型>"の書式で型を指定します。あまりにC++らしくないので、うっかり忘れると思い出せない罠があります。(うっかりはまった!)
データ型は、blittable型と、非blittable型に分類できます。blittable型は、managedとunmanaged(ネイティブ)で内部表現が同じもの。非blittable型は同じにならないものです。blittable型には整数などがあり(Byte型を含む)、これらはマーシャリングに悩むことなくmanagedとunmanagedの世界を往復できます。
blittable型の配列も、blittable型になります。
よって、Byte型の配列もblittable型になります。
つまり、managedなByte配列を、そのままネイティブなByte配列を処理する関数に渡しても正常に動作します。
問題は2つ。いかにしてmanagedなByte配列を指し示すネイティブなポインタを手に入れるか。そして、ネイティブコードからアクセス中の安全性をいかにして確保するか (ガベージコレクタに移動させられたくない) です。
前者は、配列の最初の要素のアドレスを取得するコードを書けばOKです。ここでは、"&ar[0]"というコードを書いています。これは単に"ar"と書いては不可です。managedコード側の都合としては、"ar"とは配列"オブジェクト"であって、Byte型の値の入れ物とは性質が違うものであると判断されるためだと推測します。
後者は、pin_ptrによって実現します。pin_ptrは、cli名前空間に属する言語要素で、ガベージコレクタに対して固定されたポインタを宣言します。つまり、ここでは"pin_ptr<Byte>"によって、Byte型の固定ポインタとして宣言されたことで、変数arに対応する配列オブジェクトは固定されます。その結果、ガベージコレクタが発動したとしても、配列オブジェクトは回収されたり移動されることが無くなります。その結果、ネイティブコード関数は、思う存分、心ゆくまで、配列が常に固定アドレスに存在することを前提に、あらゆるトリックを使って処理を行うことができます。
このことは、あらゆるトリックを使うべきだという意味ではありません。既にあらゆるトリックを使って書いてしまったネイティブコード関数を、managedコードから利用できることを意味するわけです。
感想 §
やりたい放題。
危ないコードもバッチリOK。
それは、新規に記述するコードでは全くお勧めではありませんが、既に書かれているコードの活用という観点からは心強い特徴です。