マネージド環境(C#)からアンマネージド環境になんか渡す時にマーシャリングというのが自動で起きて、
変数の相互型変換をしてくれるようだ。
名前がわかりにくすぎる。

C#からC++に文字列の配列を渡すとこ

C#でC++dllの関数使うよって宣言

[DllImport("pcp.dll")]
private static extern int call_array([MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 1)]string[] files, int length);

C#側コード

string[] files = new string[2];
files[0] = "日本語1";
files[1] = "日本語2";
int ret = call_array(files,2);
return ret;

C++側

int call_array( wchar_t** files, int length ) {
    setlocale(LC_ALL,"japanese");
    for ( int i=0; i<length; i++ ) {
        wprintf(L"%d: %s\n",i,*(files+i));
    }
    return 10;
}

defファイルに書いとく。

C++からC#のコールバックに文字列の配列を渡す

C#のコールバック

public static int onReceivedFiles(IntPtr ptr, int nVal) {
    IntPtr pptr;
    string[] files = new string[nVal];
    for( int n = 0; n < nVal; n++ ) {
        // スタックに積まれて来るのはポインタのみなので
        pptr = Marshal.ReadIntPtr( ptr, n * 4 );
        files[n] = Marshal.PtrToStringAuto( pptr );
    }
    return 1;
}

コールバック関数を渡す先のC++側関数とdelegate

[DllImport("pcp.dll")]
private static extern int start(StringArrayCallback cb);
public delegate int StringArrayCallback(IntPtr ptr, int nVal);

C++にコールバック関数を渡す

StringArrayCallback cb = new StringArrayCallback(onReceivedFiles);
int ret = start(cb);

C++からC#のコールバック関数を呼ぶ

std::wstring message1_ = L"日本語1";
std::wstring message2_ = L"日本語2";
wchar_t* messages_[2] = { (wchar_t*)message1_.c_str(), (wchar_t*)message2_.c_str() };
int ret_ = callback(messages_, sizeof(messages_)/sizeof(messages_[0]));