ブログ

割とコンピュータよりの情報をお届けします。

2018年8月11日

C#とCとのやり取りでCから配列変数を渡す例

C#からDllImportする場合について可変長の配列を渡しあうのがつかれた。
DLLから可変長文字列を渡す場合にはstringを渡せばよい。stringとchar*(ただし,最後に\0が付いている文字列に限る)の変換関係は支援がついている。
固定長の配列はDllImportの時の記述で引数型としてByte[]やInt32[]やDouble[] などを書いておけばよい(固定長といっても,呼び出し時に配列サイズが分かっていればよいだけで,配列自体は呼び出し前までにサイズが固定されればよい(決まればよい))。この方法は,.NET Compact Frameworkでも使用できる。ただし,長さが.NET側で分からなくて,呼び出された先のDLLの側で決まる場合には少しトリックが必要である。その場合,DLL側ではSAFEARRAYとして扱うとよさそう。

string型だけ特別簡単になっている。そのほかの型の長さがDLL側で決まる場合には対応が面倒になる。

例えば次のような関数をDLL側に用意することになる。

sample.cpp

// sample.cpp : アプリケーションのエントリ ポイントを定義します。
//

#include "stdafx.h"

// 簡単な呼び出し用サンプル
SAMPLE_API int __stdcall sample(int a, int b)
{
return a + b;
}

// 引数で文字列をDLLから返す例 割と簡単
SAMPLE_API int __stdcall sample2(char** a)
{
    char szSample[] = "TEST1";
    size_t ulSize = strlen(szSample) + sizeof(char);
    (*a) = (char*)::CoTaskMemAlloc(ulSize);
    strcpy_s((*a), ulSize, szSample);
    return 0;
}

// 戻り値でDLLから返す例 最も簡単
SAMPLE_API char* __stdcall sample3()
{
    char szSample[] = "TEST2";
    size_t ulSize = strlen(szSample) + sizeof(char);
    char* pszReturn = NULL;
    
    pszReturn = (char*)::CoTaskMemAlloc(ulSize);
    strcpy_s(pszReturn, ulSize, szSample);
    return pszReturn;
}

// 引数で配列をDLLから返す例 少し難しい
SAMPLE_API int __stdcall sample4(SAFEARRAY*& a)
{
    SAFEARRAY* pWkData = SafeArrayCreateVector(VT_I1, 0, 4);
    char* p;
    HRESULT hr = SafeArrayAccessData(pWkData, (void**)&p);
    p[0] = 1;
    p[1] = 2;
    p[2] = 3;
    p[2] = 4;
    hr = SafeArrayUnaccessData(pWkData);
    a = pWkData;
    return 0;
}

// 引数で配列をDLLに渡す例
SAMPLE_API int __stdcall sample5(SAFEARRAY*& a)
{
    SAFEARRAY* pWkData = a;
    char* p = 0;
    HRESULT hr = SafeArrayAccessData(pWkData, (void**)&p);
    p[0] = 3;
    p[1] = 2;
    p[2] = 1;
    hr = SafeArrayUnaccessData(pWkData);
    return 0;
}

呼び出し側については例えば

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;

namespace ConsoleApp1
{
    class Program
    {
        [DllImport("sample.dll")]
        [return: MarshalAs(UnmanagedType.LPStr)]
        public static extern string sample3();

        [DllImport("sample.dll")]
        public static extern int sample2([Out, MarshalAs(UnmanagedType.LPStr)] out string a);

        [DllImport("sample.dll")]
        public static extern int sample4([Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_I1)] out byte[] a);

        [DllImport("sample.dll")]
        public static extern int sample5([MarshalAs(UnmanagedType.SafeArray)] ref byte[] a);

        static void Main(string[] args)
        {
            string test = sample3();
            Console.WriteLine(test);

            string test2;
            sample2(out test2);
            Console.WriteLine(test2);

            byte[] test3;
            sample4(out test3);

            byte[] test4 = new byte[10];
            sample5(ref test4);

        }
    }
}

返された変数test3などはちゃんとガーベージコレクションの対象になっているようだ(メモリさえ十分にあれば多少のオーバーヘッドはあるにしろDLL側でSAFEARRAYを作ってC#に配列を渡した場合はC#の普通の配列のようにふるまうようだ。(もちろん,メモリ領域の実態はコピーされてそう見えているかもしれないが))。

ところで,sample4()の引数をSAFEARRAY*& aにしている部分は別にSAFEARRAY** aでもLPSAFEARRAY* aよい。ただし,最後に以下のようにするなど帳尻だけ合わせておく必要がある(ポインタ変数へのポインタ変数なのかポインタ変数へのアドレス(ポインタ)なのか)。

(*a) = pWkData;

≫ 続きを読む

2018/08/11 コンピュータ   TakeMe