「ライブラリの基礎 - C++DLL」の版間の差分

提供:MochiuWiki : SUSE, EC, PCB
ナビゲーションに移動 検索に移動
編集の要約なし
76行目: 76行目:


== サンプルコード ==
== サンプルコード ==
C++ DLLの作成方法は[[http://raspberrypi/mediawiki/index.php/DLLを作成する(MFC)#DLLの作成方法(defファイルを作成する)|コチラを参照]]する。<br>
C++ DLLの作成方法は[[DLLを作成する(MFC)|コチラを参照]]する。<br>
念のため、下記にもC++ DLLを記述する。<br>
念のため、下記にもC++ DLLを記述する。<br>
  <source lang="c++">
  <source lang="c++">
82行目: 82行目:
   
   
  double __stdcall SampleFunc01(int a);
  double __stdcall SampleFunc01(int a);
void  __stdcall SampleFunc02(int a, char *pstr)
void  __stdcall SampleFunc03(int a, char *pstr)
  </source>
  </source>
  <source lang="c++">
  <source lang="c++">
90行目: 92行目:
     printf(--<SampleDll:SampleFunc01>--\r\n");
     printf(--<SampleDll:SampleFunc01>--\r\n");
     printf("a = %d\r\n", a);
     printf("a = %d\r\n", a);
     printf("-------------------------\r\n");
     printf("---------------------------\r\n");
   
   
     return 3.14;
     return 3.14;
}
void __stdcall Sample02(int a, char *pstr)
{
    printf("--<SampleDll:SampleFunc02>--\r\n");
    printf("[%d] %s\r\n", a, str);
    printf("-----------------------------\r\n");
}
void __stdcall SampleFunc03(int a, char *pstr)
{
    printf("--<SampleDll:SampleFunc03>--\r\n");
    printf("[%d] %s\r\n", a, str);
    sprintf_s(str, 256, "C++ DLL側から文字列を返す場合は、StringBuilderクラスを使用する");
    printf("------------------------\r\n");
  }
  }
  </source>
  </source>
103行目: 120行目:
           ; 公開する関数名をリストアップ
           ; 公開する関数名をリストアップ
  SampleFunc01  @1
  SampleFunc01  @1
          SampleFunc02  @2
          SampleFunc03  @3
  </source>
  </source>
<br><br>
<br><br>
122行目: 141行目:
       [DllImport("SampleDLL.dll")]
       [DllImport("SampleDLL.dll")]
       private static extern double SampleFunc01(int a);
       private static extern double SampleFunc01(int a);
      [DllImport("SampleDLL.dll", CharSet = CharSet.Unicode)]
      // C++ DLL側の文字コードがUnicodeの場合は"CharSet = CharSet.Unicode"と明示的に指定する
      private static extern void SampleFunc02(int a, string str);
      [DllImport("SampleDLL.dll", CharSet = CharSet.Unicode)]
      // C++ DLL側の文字コードがUnicodeの場合は"CharSet = CharSet.Unicode"と明示的に指定する
      private static extern void SampleFunc03(int a, StringBuilder str);
   
   
       static void Main(string[] args)
       static void Main(string[] args)
       {
       {
           var dRet= Sample01(1);
           var dRet= SampleFunc01(1);
           Console.WriteLine(dRet);
           Console.WriteLine(dRet);
           Console.WriteLine();
           Console.WriteLine();
          var str = "string型で文字列を渡すことができます。";
          SampleFunc02(2, str);
          var strb = new System.Text.StringBuilder(256);
          strb.Append("文字列のバッファを渡す場合は StringBuilder クラスで受け渡します。");
          SampleFunc03(3, strb);
          Console.WriteLine(strb);
   
   
           Console.ReadKey();
           Console.ReadKey();

2019年11月2日 (土) 08:13時点における版

概要

C# EXEからC++ DLLへ様々なデータ型の変数を渡したいときがある。

例えば、C++ DLLから次のような関数がエクスポートされているとする。

 void WINAPI ConvertToShort(char *pstr, short *pret);


上記の関数において、C# EXEから使用するときは、char*型は文字列なのでstring型を渡す。
short*型はIntPtr型を渡す。(IntPtr型は汎用ポインタを表す型であり、void*型とほぼ同義)

但し、C#は厳しい型付け言語なので、曖昧さを解決するために変換メソッドを経由する必要がある。
具体的には、IntPtr型の変数にMarshal.AllocHGlobal関数で必要なサイズのメモリを確保して、それをC++ DLLに渡した後、
Marshal.ReadInt16関数(型によって異なる)等で変換した後、確保したメモリをMarshal.FreeHGlobal関数で解放するというプロセスを経る必要がある。


サンプルコード

下記に、C++ DLLを呼ぶC# EXEのソースコードを記載する。

 // DllImportを使用するために必要
 using System.Runtime.InteropServices;
 
 [DllImport("DrsUtil.dll", EntryPoint = "ConvertToShort")]
 extern static void _ConvertToShort(string pstr, IntPtr pret); //呼び出し元の名前変えちゃう
 
 public static short ConvertToShort(string str)
 {
    IntPtr buffer = new IntPtr();
    buffer = Marshal.AllocHGlobal(2);		// 2バイトのメモリ確保
    _ConvertToShort(str, buffer);		// C++/DLLの関数を呼ぶ
    short sval = Marshal.ReadInt16(buffer);	// 変換
    Marshal.FreeHGlobal(buffer);		// メモリ開放
    return sval;
 }


IntPtr型の変数は様々なものが入るので、例えば、構造体を取得することも可能だが、C# EXEで構造体を定義しなければいけない。
WindowsのDLL(Win32 API)と.NET Frameworkでは型の管理方法が違うため、実際には型の相互変換(マーシャリング)が行われる。
尚、BOOL型の実体はLONG型と同じなので、boolの代わりにintを指定することも可能である。

表1. Win32 APIでの型名と対応するC#の型
APIの型名
(括弧内は対応するC言語の型)
対応するC#の型
(括弧内は.NET Frameworkでの型名)
備考
HANDLE (void *) System.IntPtr
System.UIntPtr
x86は4バイト
x64は8バイト
BYTE (unsigned char) byte (System.Byte)
SHORT (short) short (System.Int16)
WORD (unsigned short) ushort (System.UInt16)
INT (int)
LONG (long)
int (System.Int32)
UINT (unsigned int)
DWORD, ULONG (unsigned long)
uint (System.UInt32)
BOOL (long) bool (System.Boolean)
CHAR (char) 文字を渡すとき
char (System.Char)
文字を受け取るとき
StringBuilder
WCHAR(wchar_t) 文字を渡すとき
char (System.Char)
文字を受け取るとき
StringBuilder
LPSTR (char *, char[])
LPWSTR (wchar_t *, wchar_t[])
文字を渡すとき
string (System.String)
文字を受け取るとき
System.Text.StringBuilder
LPCSTR (const char *, const char[])
LPCWSTR (const wchar_t *, const wchar_t[])
文字を渡すとき
string (System.String)
文字を受け取るとき
System.Text.StringBuilder
FLOAT (float) float (System.Single)
DOUBLE (double) double (System.Double)



サンプルコード

C++ DLLの作成方法はコチラを参照する。
念のため、下記にもC++ DLLを記述する。

 SampleDLL.h
 
 double __stdcall SampleFunc01(int a);
 void   __stdcall SampleFunc02(int a, char *pstr)
 void   __stdcall SampleFunc03(int a, char *pstr)
 SampleDll.cpp
  
 double __stdcall SampleFunc01(int a)
 {
    printf(--<SampleDll:SampleFunc01>--\r\n");
    printf("a = %d\r\n", a);
    printf("---------------------------\r\n");
 
    return 3.14;
 }
 
 void __stdcall Sample02(int a, char *pstr)
 {
    printf("--<SampleDll:SampleFunc02>--\r\n");
    printf("[%d] %s\r\n", a, str);
    printf("-----------------------------\r\n");
 }
 
 void __stdcall SampleFunc03(int a, char *pstr)
 {
    printf("--<SampleDll:SampleFunc03>--\r\n");
    printf("[%d] %s\r\n", a, str);
    sprintf_s(str, 256, "C++ DLL側から文字列を返す場合は、StringBuilderクラスを使用する");
    printf("------------------------\r\n");
 }
 SampleDll.def  // モジュール定義ファイル
 
 LIBRARY SampleDll
 
 EXPORTS
          ; 公開する関数名をリストアップ
	  SampleFunc01   @1
          SampleFunc02   @2
          SampleFunc03   @3



次に、

 using System;
 using System.Runtime.InteropServices;
 
 namespace SampleEXE
 {
    class Program
    {
       /// <summary>
       /// 最も基本的な関数のインポート例
       /// </summary>
       /// <param name="a">4 バイト符号付き整数を指定します。</param>
       /// <returns>倍精度浮動小数を返します。</returns>
       [DllImport("SampleDLL.dll")]
       private static extern double SampleFunc01(int a);
 
       [DllImport("SampleDLL.dll", CharSet = CharSet.Unicode)]
       // C++ DLL側の文字コードがUnicodeの場合は"CharSet = CharSet.Unicode"と明示的に指定する
       private static extern void SampleFunc02(int a, string str);
 
       [DllImport("SampleDLL.dll", CharSet = CharSet.Unicode)]
       // C++ DLL側の文字コードがUnicodeの場合は"CharSet = CharSet.Unicode"と明示的に指定する
       private static extern void SampleFunc03(int a, StringBuilder str);
 
       static void Main(string[] args)
       {
          var dRet= SampleFunc01(1);
          Console.WriteLine(dRet);
          Console.WriteLine();
 
          var str = "string型で文字列を渡すことができます。";
          SampleFunc02(2, str);
 
          var strb = new System.Text.StringBuilder(256);
          strb.Append("文字列のバッファを渡す場合は StringBuilder クラスで受け渡します。");
          SampleFunc03(3, strb);
          Console.WriteLine(strb);
 
          Console.ReadKey();
       }
    }
 }


DllImport属性には、DLLファイルのパスを指定する以外に次のような引数を与えることもできる。
これ他にも細かい設定をするための引数が用意されているので、DllImportAttributeクラスで検索すること。

名称 説明
CallingConvention エントリポイントの呼び出し規約を明示的に指定できる。
指定なしの場合は__stdcallとなる。
CharSet 文字列パラメータをメソッドにマーシャリングし、名前マングルを制御する方法を指定する。
文字コードの相互変換する時に指定する。
EntryPoint 呼び出すDLLエントリポイントの名前または序数を指定する。
DLLの関数名とC#上で使用する関数名を異なる名前にする時に指定する。