SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

Monoでのプログラミング実例集

MonoでP/Invokeを試してみたよ!

Monoでマネージコードからアンマネージ関数を呼び出す

  • X ポスト
  • このエントリーをはてなブックマークに追加

自作Cライブラリ内関数の呼び出し

 自作のCライブラリ内の関数を呼び出し、文字列を渡す方法について解説します。

 呼び出し元コード(C#)として「PInvokeExample2.cs」、呼び出し先コード(C)として「pinvoke.c」(「libpinvoke.so」としてビルド)を使用します。

PInvokeExample2.cs
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace PInvokeExample
{
  [StructLayout(LayoutKind.Sequential)]
  public struct MyStr
  {
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=51)]
    public string buffer;
    public int    size;
  }

  public class Example2
  {
    [DllImport("libpinvoke")]
    static extern void sub_str1(string istr);

    [DllImport("libpinvoke")]
    static extern void sub_str2(byte[] iostr);

    [DllImport("libpinvoke")]
    static extern void sub_str3(string istr, StringBuilder ostr);

    [DllImport("libpinvoke")]
    static extern void sub_str4(ref MyStr iostr);

    [DllImport("libpinvoke")]
    static extern IntPtr fnc_str5(string istr);

    [DllImport("libpinvoke")]
    static extern IntPtr fnc_str6(string istr);

    [DllImport("libpinvoke", EntryPoint="fnc_str6")]
    static extern string fnc_str7(string istr);

    public static void Main(string[] args)
    {
      if (args.Length != 1)
      {
        Console.WriteLine("Usage: PInvokeExample2.exe [文字列]");
        return;
      }

      Encoding utf8Enc = Encoding.UTF8;

      // パターン1
      Console.WriteLine("Calling sub_str1...");
      Console.WriteLine("[managed  ] Send     string is {0}.", args[0]);
      sub_str1(args[0]);

      // パターン2
      byte[] iostr = utf8Enc.GetBytes(args[0]);
      Array.Resize(ref iostr, 51);
      Console.WriteLine("\nCalling sub_str2...");
      Console.WriteLine("[managed  ] Send     string is {0}.", utf8Enc.GetString(iostr));
      sub_str2(iostr);
      Console.WriteLine("[managed  ] Received string is {0}.", utf8Enc.GetString(iostr));

      // パターン3
      StringBuilder ostr = new StringBuilder(51);
      Console.WriteLine("\nCalling sub_str3...");
      Console.WriteLine("[managed  ] Send     string is {0}.", args[0]);
      sub_str3(args[0], ostr);
      Console.WriteLine("[managed  ] Received string is {0}.", ostr);

      // パターン4
      MyStr mstr;
      mstr.buffer = args[0];
      mstr.size = utf8Enc.GetByteCount(args[0]);
      Console.WriteLine("\nCalling sub_str4...");
      Console.WriteLine("[managed  ] Send     string/size is {0}/{1}.", mstr.buffer, mstr.size);
      sub_str4(ref mstr);
      Console.WriteLine("[managed  ] Received string/size is {0}/{1}.", mstr.buffer, mstr.size);

      // パターン5
      Console.WriteLine("\nCalling fnc_str5...");
      Console.WriteLine("[managed  ] Send     string is {0}.", args[0]);
      Console.WriteLine("[managed  ] Received string is {0}.", Marshal.PtrToStringAuto(fnc_str5(args[0])));

      // パターン6
      Console.WriteLine("\nCalling fnc_str6...");
      Console.WriteLine("[managed  ] Send     string is {0}.", args[0]);
      IntPtr rptr = fnc_str6(args[0]);
      Console.WriteLine("[managed  ] Received string is {0}.", Marshal.PtrToStringAuto(rptr));
      Marshal.FreeHGlobal(rptr);

      // パターン7
      Console.WriteLine("\nCalling fnc_str7...");
      Console.WriteLine("[managed  ] Send     string is {0}.", args[0]);
      string rstr = fnc_str7(args[0]);
      Console.WriteLine("[managed  ] Received string is {0}.", rstr);
    }
  }
}
/*
 * ビルド:
 *
 *   gmcs PInvokeExample2.cs
 *
 * 実行:
 *
 *   mono PInvokeExample2.exe [文字列]
 *
 */
pinvoke.c
#include <stdio.h>
#include <string.h>

void sub_str1( const char* istr );
void sub_str2( char iostr[51] );
void sub_str3( const char* istr, char ostr[51] );

typedef struct mystr
{
  char   buffer[51];
  int    size;
}MYSTR;

void sub_str4( MYSTR* iostr );
char* fnc_str5( const char* istr );
char* fnc_str6( const char* istr );

void sub_str1(const char* istr)
{
  printf("[unmanaged] Received string is %s.\n", istr);
}

void sub_str2(char iostr[51])
{
  char wstr[51] = "Hello ";
  size_t len;

  printf("[unmanaged] Received string is %s.\n", iostr);
  len = strlen(iostr);
  if (len <= 44)
  {
    strcat(wstr, iostr);
  }
  else
  {
    strncat(wstr, iostr, 44);
  }
  strcpy(iostr, wstr);
  printf("[unmanaged] Send     string is %s.\n", iostr);
}

void sub_str3(const char* istr, char ostr[51])
{
  char wstr[51] = "Hello ";
  size_t len;

  printf("[unmanaged] Received string is %s.\n", istr);
  len = strlen(istr);
  if (len <= 44)
  {
    strcat(wstr, istr);
  }
  else
  {
    strncat(wstr, istr, 44);
  }
  strcpy(ostr, wstr);
  printf("[unmanaged] Send     string is %s.\n", ostr);
}

void sub_str4(MYSTR* iostr)
{
  char wstr[51] = "Hello ";

  printf("[unmanaged] Received string/size is %s/%d.\n", iostr->buffer, iostr->size);
  if (iostr->size <= 44)
  {
    strcat(wstr, iostr->buffer);
  }
  else
  {
    strncat(wstr, iostr->buffer, 44);
  }
  strcpy(iostr->buffer, wstr);
  iostr->size = strlen(wstr);
  printf("[unmanaged] Send     string/size is %s/%d.\n", iostr->buffer, iostr->size);
}

char* fnc_str5(const char* istr)
{
  static char ostr[51] = "Hello ";
  size_t len;

  printf("[unmanaged] Received string is %s.\n", istr);
  len = strlen(istr);
  if (len <= 44)
  {
    strcat(ostr, istr);
  }
  else
  {
    strncat(ostr, istr, 44);
  }
  printf("[unmanaged] Send     string is %s.\n", ostr);
  return ostr;
}

char* fnc_str6(const char* istr)
{
  char*  ostr = NULL;
  size_t len;

  printf("[unmanaged] Received string is %s.\n", istr);

  len = strlen(istr);
  ostr = (char*)malloc(sizeof(char) * (len + 7));
  if (ostr == NULL)
  {
    printf("[unmanaged] malloc failed.\n");
  }
  else
  {
    strcpy(ostr, "Hello ");
    strcat(ostr, istr);
    printf("[unmanaged] Send     string is %s.\n", ostr);
  }

  return ostr;
}

 作業する上でのディレクトリ構造は、下表の通りとします。

C#コード格納先 ~/src/cs/PInvokeExample
Cコード格納先 ~/src/c
自作ライブラリ格納先 ~/lib
作業用シェルスクリプト格納先 ~/bin(環境変数「PATH」に設定済み)

 また、~/.profile等で環境変数「LD_LIBRARY_PATH」に自作ライブラリ格納先を設定します。

LD_LIBRARY_PATH 設定例
if test -z "$LD_LIBRARY_PATH" ; then
    export LD_LIBRARY_PATH="$HOME/lib"
else
    export LD_LIBRARY_PATH="$HOME/lib:$LD_LIBRARY_PATH"
fi

 「pinvoke.c」のビルドに次のようなシェルスクリプト「c2so.sh」を使用しました。「~/bin」に保存し、実行権限を与えておきます。

c2so.sh
#!/bin/sh
USERLIB="$HOME/lib"
USERSRC="$HOME/src/c"
exec gcc -fPIC -shared -O -o $USERLIB/lib$1.so $USERSRC/$1.c
pinvoke.c のビルド実行例
> c2so.sh pinvoke
PInvokeExample2.cs のビルド実行例
> cd ~/src/cs/PInvokeExample
> gmcs PInvokeExample2.cs

 以下、今回テストしたパターンについて解説します。

ただ文字列を渡してみる

 パターン1は、呼び出し元から呼び出し先へ文字列を渡すだけです。呼び出し元と呼び出し先の対応は以下になります。

PInvokeExample2.cs から抜粋
[DllImport("libpinvoke")]
static extern void sub_str1(string istr);
pinvoke.c から抜粋
void sub_str1( const char* istr );

 呼び出し元で「string」型(参照型)の引数を「値渡し」しているので、方向属性は[In]となり、呼び出し先での引数の内容の変更は行えません(反映されません)。 呼び出し先の引数は「char*」でも動作可能ですが、変更不可ということで「const」指定が安全だと思います。方向属性については『方向属性』(msdn)を参照して下さい。

 呼び出し元でのプロトタイプ宣言をより詳細に記述すると、以下の様になります。

[DllImport("libpinvoke", CharSet=CharSet.Ansi)]
static extern void sub_str1([In] string istr);
実行結果例
> mono PInvokeExample2.exe testテスト
Calling sub_str1...
[managed  ] Send     string is testテスト.
[unmanaged] Received string is testテスト.
...

文字列を渡して、編集された文字列を受け取ってみる

 パターン2は、呼び出し先で受け取った文字列を編集して引数の内容を変更します。呼び出し元でプロトタイプ宣言したメソッド(関数)の引数の型として「byte[]」を使用しています。

PInvokeExample2.cs から抜粋
[DllImport("libpinvoke")]
static extern void sub_str2(byte[] iostr);
pinvoke.c から抜粋
void sub_str2( char iostr[51] );

 当初、呼び出し元の引数の型として「StringBuilder」型を試してみましたが、呼び出し先で変更した内容が呼び出し元で反映されなかったり、日本語文字列を渡すと呼び出し先に正しく渡らなかったり(文字化け状態での表示)など、なかなかうまくいきません。そこで引数の型として「byte[]」を使用しました。

 「StringBuilder」型を引数として使用した場合、方向属性は[In/Out]になりますが、「byte」型の配列ではどうなるのか、無指定で問題ないので、[In/Out]ということになりますが、あやふやな場合は[In/Out]指定をしたほうが無難だと思います。

[DllImport("libpinvoke")]
static extern void sub_str2([In, Out] byte[] iostr);

 また、パターン3のように、入力と出力で引数を分ける形で「StringBuilder」型を試してみると、問題は発生しませんでした。

PInvokeExample2.cs から抜粋
[DllImport("libpinvoke")]
static extern void sub_str3(string istr, StringBuilder ostr);
pinvoke.c から抜粋
void sub_str3( const char* istr, char ostr[51] );
実行結果例
> mono PInvokeExample2.exe testテスト
...
Calling sub_str2...
[managed  ] Send     string is testテスト.
[unmanaged] Received string is testテスト.
[unmanaged] Send     string is Hello testテスト.
[managed  ] Received string is Hello testテスト.

Calling sub_str3...
[managed  ] Send     string is testテスト.
[unmanaged] Received string is testテスト.
[unmanaged] Send     string is Hello testテスト.
[managed  ] Received string is Hello testテスト.
...

構造体を渡してみる

 パターン4では、構造体に固定長文字列を組み込む形で、構造体として渡します。

PInvokeExample2.cs から抜粋
[StructLayout(LayoutKind.Sequential)]
public struct MyStr
{
  [MarshalAs(UnmanagedType.ByValTStr, SizeConst=51)]
  public string buffer;
  public int    size;
}
  ...

  [DllImport("libpinvoke")]
  static extern void sub_str4(ref MyStr iostr);
pinvoke.c から抜粋
typedef struct mystr
{
  char   buffer[51];
  int    size;
}MYSTR;

void sub_str4( MYSTR* iostr );

 呼び出し元での構造体の定義では、StructLayoutAttribute属性で構造体の各メンバーのメモリ内での配置の制御を、MarshalAsAttribute属性で呼び出し先との対応(固定長文字配列として)を設定しています。

 呼び出し元の引数にパラメータ修飾子「ref」をつけることで、構造体(値型)の「参照渡し」を行うことになります。この指定により、引数の方向属性は「In/Out」となり、引数の内容の変更が呼び出し先で可能になります。

実行結果例
> mono PInvokeExample2.exe testテスト
...
Calling sub_str4...
[managed  ] Send     string/size is testテスト/13.
[unmanaged] Received string/size is testテスト/13.
[unmanaged] Send     string/size is Hello testテスト/19.
[managed  ] Received string/size is Hello testテスト/19.
...

文字列を渡して、編集された文字列を戻り値として受け取ってみる

 パターン5では、呼び出し元のプロトタイプ宣言で、戻り値の型として「IntPtr」型を使用しています。呼び出し先で返しているのは、修飾子「static」を付けた文字配列(の先頭アドレス)で、この場合、呼び出し元では「string」型で受け取ることはできません。

 パターン6では、呼び出し先で動的に割り当てたメモリ領域を「char」型のポインタとして返し、呼び出し元で「IntPtr」型で受け取り、手動で、受け取ったポインタの開放を行っています。また、パターン6と同じ関数を呼び出して、戻り値を「string」型として受け取ることが可能です(パターン7)。

PInvokeExample2.cs から抜粋
[DllImport("libpinvoke")]
static extern IntPtr fnc_str5(string istr);

[DllImport("libpinvoke")]
static extern IntPtr fnc_str6(string istr);

[DllImport("libpinvoke", EntryPoint="fnc_str6")]
static extern string fnc_str7(string istr);
pinvoke.cから抜粋
char* fnc_str5( const char* );
char* fnc_str6( const char* );
実行結果例
> mono PInvokeExample2.exe testテスト
...
Calling fnc_str5...
[managed  ] Send     string is testテスト.
[unmanaged] Received string is testテスト.
[unmanaged] Send     string is Hello testテスト.
[managed  ] Received string is Hello testテスト.

Calling fnc_str6...
[managed  ] Send     string is testテスト.
[unmanaged] Received string is testテスト.
[unmanaged] Send     string is Hello testテスト.
[managed  ] Received string is Hello testテスト.

Calling fnc_str7...
[managed  ] Send     string is testテスト.
[unmanaged] Received string is testテスト.
[unmanaged] Send     string is Hello testテスト.
[managed  ] Received string is Hello testテスト.

次のページ
5.OpenCOBOLで作成したライブラリ内関数の呼び出し

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
Monoでのプログラミング実例集連載記事一覧
この記事の著者

sta(エステーエー)

風来坊blog:sta.blockhead

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/3460 2009/02/19 14:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング