配列とポインター(配列自体の変更)

概要

ポインタ経由で配列を渡します。ポインターが指し示す先の配列をC++側で入れ替えることで、新たな配列をC#へと伝達します。

配列とポインター(配列自体の変更)

C#とC++のやりとりは最終的には「ポインター」と「渡したデータのサイズ」の2つがあれば
あとはデータ内容を型に当てはめて解釈するだけとなります。 C#側で確保した(AllocCoTaskMem)メモリはC++側で解放(CoTaskMemFree)、C++側で確保したメモリ(CoTaskMemAlloc)はC#側で解放(FreeCoTaskMem)といった流れにやりやすいので、
その点を気をつけましょう。

  • C++側のソース

    Dll1.dllとしてコンパイル
    #include <windows.h>
    #include <cstddef>
    #include <iostream>
    
    using namespace std;
    
    #define DLLEXPORT __declspec(dllexport) WINAPI
    
    // int32_t配列受け取りと値の代入
    extern "C" int32_t DLLEXPORT dllInt32ArrayPointer(int32_t** ppArray, int32_t *pSize) {
    
    	int* orgArray = (*ppArray);
    
    	int orgArraySize = *pSize;
    
    	// 元の配列内容
    	cout << "元の配列内容" << endl;
    	for (int32_t i = 0; i < orgArraySize; i++) {
    		cout << orgArray[i] << endl;
    	}
    
    	int32_t newArraySize = 20;
    
    	int32_t* newArray = (int32_t*)CoTaskMemAlloc(sizeof(int32_t) * newArraySize);
    
    	if (newArray) {
    		// 新しい配列は全て-100で埋める
    		for (int32_t i = 0; i < newArraySize; i++) {
    			newArray[i] = -100;
    		}
    	}
    
      // C#側から渡ってきたバッファ内容はクリア
    	CoTaskMemFree(*ppArray);
    
      // C#側から渡ってきたポインタが新たな配列の場所を示すようにする
    	(*ppArray) = newArray;
    
    
    	if (newArray) {
        (*pSize) = newArraySize;
    		return newArraySize;
    	}
    	else {
        (*pSize) = 0;
    		return 0;
    	}
    }
    
  • C#側のソース

    test1.cs
    using System;
    using System.Runtime.InteropServices;
    
    namespace test1
    {
        internal class Program
        {
            [DllImport("Dll1.dll")]
            static extern Int32 dllInt32ArrayPointer(ref IntPtr arr, ref Int32 size);
    
    
            static void Main(string[] args)
            {
                Int32[] arr = new Int32[] { 1, 2, 3, 10, 22 };
    
                Int32 size = arr.Length;
    
                // バイト配列のバッファーにしてしまう
                // まずはポインタbufferに対してメモリサイズを確保
                IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(arr[0] * size));
    
                // 配列の内容をbufferへとコピー
                Marshal.Copy(arr, 0, buffer, size);
    
                // dll側で変更する。
                Int32 newArraySize = dllInt32ArrayPointer(ref buffer, ref size);
    
                // 変更後の配列も要素を持っているならば...
                if (size > 0)
                {
                    // 新しい配列を新たなサイズの分確保
                    Int32[] newArray = new int[newArraySize];
    
                    // 新たな配列に変更されたバッファーの内容をコピー
                    Marshal.Copy(buffer, newArray, 0, newArraySize);
    
                    // 現在バッファーが指し示しているものはC++側 CoTaskMemoAlloc で確保されているため、解放する
                    Marshal.FreeCoTaskMem(buffer);
    
                    foreach (var e in newArray)
                    {
                        Console.WriteLine(e);
                    }
                }
                else
                {
                    Console.WriteLine("C++側で新たに設定された配列は空っぽです。");
                }
    
    
                Console.ReadKey();
            }
        }
    }