📢 Webサイト閉鎖と移転のお知らせ
このWebサイトは2026年9月に閉鎖いたします。
新しい記事は移転先で追加しております。(旧サイトでは記事を追加しておりません)
編集の要約なし |
|||
| (同じ利用者による、間の29版が非表示) | |||
| 1行目: | 1行目: | ||
== 概要 == | == 概要 == | ||
C# EXEからC++ | C# EXEからC++ DLLへ様々なデータ型の変数を渡したい場合がある。<br> | ||
<br> | |||
しかし、C#の変数とC++の変数はメモリへの配置が基本型以外異なるため、直接、C#からC++に渡すことができない。<br> | |||
<br> | |||
例えば、文字列を扱う場合、C#のstring型とC++のstd::string型は同一ではないため、マーシャリングの処理が必要となる。<br> | |||
マーシャリングとは、異なる2つのシステム間において、データを交換できるようにデータを操作する処理を指す。<br> | |||
<br><br> | <br><br> | ||
== DllImport属性 == | == DllImport属性 == | ||
<code>DLLImport</code>属性は、DLLエントリポイントを定義する関数を記述することで、DLLファイルに定義された関数を呼び出すことができる。<br> | <code>DLLImport</code>属性は、DLLエントリポイントを定義する関数を記述することで、DLLファイルに定義された関数を呼び出すことができる。<br> | ||
< | <syntaxhighlight lang="csharp"> | ||
[ DllImport( "DLL名" ) ] | [ DllImport( "DLL名" ) ] | ||
</ | </syntaxhighlight> | ||
<br> | <br> | ||
また、DLLファイルに定義された関数を、関数名または序数で指定するには、<code>DllImport</code>属性の<code>EntryPoint</code>フィールドを使用する。<br> | また、DLLファイルに定義された関数を、関数名または序数で指定するには、<code>DllImport</code>属性の<code>EntryPoint</code>フィールドを使用する。<br> | ||
| 14行目: | 19行目: | ||
<br> | <br> | ||
以下の例では、<code>EntryPoint</code>フィールドを使用して、<code>MessageBoxA</code>をMsgBoxAに置き換える方法を示している。<br> | 以下の例では、<code>EntryPoint</code>フィールドを使用して、<code>MessageBoxA</code>をMsgBoxAに置き換える方法を示している。<br> | ||
< | <syntaxhighlight lang="csharp"> | ||
using System; | using System; | ||
using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||
| 23行目: | 28行目: | ||
internal static extern int MsgBoxA(IntPtr hWnd, string lpText, string lpCaption, uint uType); | internal static extern int MsgBoxA(IntPtr hWnd, string lpText, string lpCaption, uint uType); | ||
} | } | ||
</ | </syntaxhighlight> | ||
<br> | <br> | ||
また、以下の例では、DLLファイルに定義された関数を序数で指定している。<br> | また、以下の例では、DLLファイルに定義された関数を序数で指定している。<br> | ||
< | <syntaxhighlight lang="csharp"> | ||
[DllImport("DllName", EntryPoint = "Functionname")] | [DllImport("DllName", EntryPoint = "Functionname")] | ||
[DllImport("DllName", EntryPoint = "#123")] // 序数を使用するには、序数値の前にシャープ記号#を付ける必要がある | [DllImport("DllName", EntryPoint = "#123")] // 序数を使用するには、序数値の前にシャープ記号#を付ける必要がある | ||
</ | </syntaxhighlight> | ||
<br> | <br> | ||
<code>DllImport</code>属性には、DLLファイル名を指定する以外にも、下表のような引数を与えることができる。<br> | <code>DllImport</code>属性には、DLLファイル名を指定する以外にも、下表のような引数を与えることができる。<br> | ||
<center> | <center> | ||
{| class="wikitable" | {| class="wikitable" style="background-color:#fefefe;" | ||
|- | |- | ||
! 名称 !! | ! style="background-color:#66CCFF;" | 名称 | ||
! style="background-color:#66CCFF;" | 説明 | |||
! style="background-color:#66CCFF;" | 既定値 | |||
|- | |- | ||
| EntryPoint || 呼び出すDLLエントリポイントの名前または序数を指定する。<br>DLLの関数名とC#上で使用する関数名を異なる名前にする時に指定する。 | | EntryPoint || 呼び出すDLLエントリポイントの名前または序数を指定する。<br>DLLの関数名とC#上で使用する関数名を異なる名前にする時に指定する。 || | ||
|- | |- | ||
| CharSet || 文字列パラメータをメソッドにマーシャリングして、名前マングルを制御する方法を指定する。<br>文字コードの相互変換する時に指定する。<br>指定なしの場合は、<code>CharSet.Auto</code>となる。 | | CharSet || 文字列パラメータをメソッドにマーシャリングして、名前マングルを制御する方法を指定する。<br>文字コードの相互変換する時に指定する。<br>指定なしの場合は、<code>CharSet.Auto</code>となる。 || CharSet.Auto | ||
|- | |- | ||
| SetLastError || Win32エラー情報を維持するかどうか指定する。<br>指定なしの場合は、<code>false</code>となる。 | | SetLastError || Win32エラー情報を維持するかどうか指定する。<br>指定なしの場合は、<code>false</code>となる。 || FALSE | ||
|- | |- | ||
| ExactSpelling || エントリポイントの関数名を厳密に一致させるかどうか指定する。<br>指定なしの場合は、<code>false</code>となる。 | | ExactSpelling || エントリポイントの関数名を厳密に一致させるかどうか指定する。<br>指定なしの場合は、<code>false</code>となる。 || FALSE | ||
|- | |- | ||
| PreserveSig || 定義通りのメソッドのシグネチャを維持するかどうか指定する。 | | PreserveSig || 定義通りのメソッドのシグネチャを維持するかどうか指定する。 | ||
|- | |- | ||
| CallingConvention || アンマネージドコードを呼び出すための<u>エントリポイントの呼び出し規約</u>を、明示的に指定できる。<br>指定なしの場合は、<code>__stdcall</code>(StdCall)となる。<br>詳細は、下表を参照すること。 | | CallingConvention || アンマネージドコードを呼び出すための<u>エントリポイントの呼び出し規約</u>を、明示的に指定できる。<br>指定なしの場合は、<code>__stdcall</code>(StdCall)となる。<br>詳細は、下表を参照すること。 || StdCall | ||
|} | |} | ||
</center> | </center> | ||
| 67行目: | 74行目: | ||
<br> | <br> | ||
<center> | <center> | ||
{| class="wikitable" | {| class="wikitable" style="background-color:#fefefe;" | ||
|- | |- | ||
! 列挙子 ! | ! style="background-color:#66CCFF;" | 列挙子 | ||
! style="background-color:#66CCFF;" | 説明 | |||
|- | |- | ||
| Cdecl || 呼び出し元がスタックを消去する。<br>これを使用すると、<code>varargs</code>で関数を呼び出すことができる。<br>これは、<code>printf</code>関数等の可変長引数のメソッドの呼び出しで使用する。 | | Cdecl || 呼び出し元がスタックを消去する。<br>これを使用すると、<code>varargs</code>で関数を呼び出すことができる。<br>これは、<code>printf</code>関数等の可変長引数のメソッドの呼び出しで使用する。 | ||
| 85行目: | 93行目: | ||
以下の例では、呼び出し規約を適用する方法をCdeclとしている。<br> | 以下の例では、呼び出し規約を適用する方法をCdeclとしている。<br> | ||
これは、呼び出し元がスタックをクリーンアップするために使用する必要がある。<br> | これは、呼び出し元がスタックをクリーンアップするために使用する必要がある。<br> | ||
< | <syntaxhighlight lang="csharp"> | ||
using System; | using System; | ||
using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||
| 111行目: | 119行目: | ||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
詳細は、以下のMSDNのWebサイトを参照すること。<br> | 詳細は、以下のMSDNのWebサイトを参照すること。<br> | ||
[https://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.callingconvention.aspx CallingConvention 列挙型 (System.Runtime.InteropServices) | MSDN]<br> | [https://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.callingconvention.aspx CallingConvention 列挙型 (System.Runtime.InteropServices) | MSDN]<br> | ||
| 118行目: | 126行目: | ||
== StructLayout属性 == | == StructLayout属性 == | ||
StructLayout属性とは、クラスまたは構造体のデータメンバを、メモリ内でどのように配置するかを表す。<br> | StructLayout属性とは、クラスまたは構造体のデータメンバを、メモリ内でどのように配置するかを表す。<br> | ||
< | <syntaxhighlight lang="csharp"> | ||
[ StructLayout( LayoutKind列挙 ) ] | [ StructLayout( LayoutKind列挙 ) ] | ||
</ | </syntaxhighlight> | ||
<br> | <br> | ||
下表に、LayoutKind列挙子を示す。<br> | 下表に、LayoutKind列挙子を示す。<br> | ||
| 150行目: | 158行目: | ||
</center> | </center> | ||
<br> | <br> | ||
< | <syntaxhighlight lang="csharp"> | ||
[StructLayout( LayoutKind.Sequential )] | [StructLayout( LayoutKind.Sequential )] | ||
public struct Position | public struct Position | ||
| 158行目: | 166行目: | ||
public double z; | public double z; | ||
} | } | ||
</ | </syntaxhighlight> | ||
<br> | <br> | ||
詳細は、以下のMSDNのWebサイトを参照すること。<br> | 詳細は、以下のMSDNのWebサイトを参照すること。<br> | ||
| 171行目: | 179行目: | ||
<br> | <br> | ||
以下の例では、UInt32型はCLSに準拠しないため、<code>CLSCompliant(false)</code>と指定する必要がある。<br> | 以下の例では、UInt32型はCLSに準拠しないため、<code>CLSCompliant(false)</code>と指定する必要がある。<br> | ||
< | <syntaxhighlight lang="csharp"> | ||
[CLSCompliant( false )] | [CLSCompliant( false )] | ||
public int SetValue( UInt32 value ); | public int SetValue( UInt32 value ); | ||
</ | </syntaxhighlight> | ||
<br> | <br> | ||
以下の例では、CLSCompliantAttribute属性をアセンブリ全体に適用する。<br> | 以下の例では、CLSCompliantAttribute属性をアセンブリ全体に適用する。<br> | ||
< | <syntaxhighlight lang="csharp"> | ||
using System; | using System; | ||
[assembly: CLSCompliant(true)] | [assembly: CLSCompliant(true)] | ||
</ | </syntaxhighlight> | ||
<br> | <br> | ||
詳細は、以下のMSDNのWebサイトを参照すること。<br> | 詳細は、以下のMSDNのWebサイトを参照すること。<br> | ||
| 198行目: | 206行目: | ||
詳細は、以下のMSDNのWebサイトを参照すること。<br> | 詳細は、以下のMSDNのWebサイトを参照すること。<br> | ||
[https://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.marshalasattribute.aspx MarshalAsAttribute クラス (System.Runtime.InteropServices) | MSDN]<br> | [https://msdn.microsoft.com/ja-jp/library/system.runtime.interopservices.marshalasattribute.aspx MarshalAsAttribute クラス (System.Runtime.InteropServices) | MSDN]<br> | ||
< | <syntaxhighlight lang="csharp"> | ||
// パラメータへの適用 | // パラメータへの適用 | ||
public void M1([MarshalAs(UnmanagedType.LPWStr)]string msg) | public void M1([MarshalAs(UnmanagedType.LPWStr)]string msg) | ||
| 218行目: | 226行目: | ||
return "Hello World"; | return "Hello World"; | ||
} | } | ||
</ | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
| 234行目: | 242行目: | ||
</center> | </center> | ||
<br> | <br> | ||
< | <syntaxhighlight lang="csharp"> | ||
void Method([in] int[] array); | void Method([in] int[] array); | ||
</ | </syntaxhighlight> | ||
<br><br> | <br><br> | ||
== C# | == C#のデータ型とC++のデータ型 == | ||
<center> | <center> | ||
{| class="wikitable" | {| class="wikitable" | style="background-color:#fefefe;" | ||
|- | |||
! style="background-color:#66CCFF;" | Windows APIのデータ型<br>(括弧内は対応するC言語の型) | |||
! style="background-color:#66CCFF;" | 対応するC#のデータ型<br>(括弧内は.NET Frameworkでの型名) | |||
! style="background-color:#66CCFF;" | 備考 | |||
|- | |||
| HANDLE (void *)<br>HMODULE<br>HINSTANCE<br>HWND<br>HWM<br>HGDIOBJ<br>HDC || System.IntPtr<br>System.UIntPtr || x86は4バイト<br>x64は8バイト | |||
|- | |||
| char 配列名 [配列サイズ] || MarshalAs(UnmanagedType.ByValTStr, SizeConst = 配列のサイズ)] public string 配列名 || | |||
|- | |||
| char ** || 文字を渡すとき<br>string (System.String)<br>文字を受け取るとき<br>System.Text.StringBuilder || | |||
|- | |- | ||
| BYTE (unsigned char)<br>UCHAR || byte (System.Byte) || UCHAR型は、C#のstring型およびIntPtr型でも可能。 | |||
|- | |- | ||
| | | BYTE[] (unsigned char *)<br>PBYTE (byte *) || System.Byte[]<br>[MarshalAs(UnmanagedType.LPArray)] byte[]<br>[MarshalAs(UnmanagedType.LPArray)] Intptr || | ||
|- | |- | ||
| | | unsigned char & || ref byte || | ||
|- | |- | ||
| SHORT (short) || short (System.Int16) || | | SHORT (short) || short (System.Int16) || | ||
| 253行目: | 271行目: | ||
| WORD (unsigned short) || ushort (System.UInt16) || | | WORD (unsigned short) || ushort (System.UInt16) || | ||
|- | |- | ||
| INT (int)<br>LONG (long) || int (System.Int32) || | | INT (int)<br>LONG (long)<br>DWORDLONG<br>HPARAM<br>LPARAM<br>WPARAM || int (System.Int32) || System.Int16でも取得できる可能性がある。 | ||
|- | |||
| UINT (unsigned int)<br>DWORD (unsigned long)<br>ULONG (unsigned long) || uint (System.UInt32) || | |||
|- | |||
| DECIMAL || decimal (System.Decimal) || | |||
|- | |- | ||
| | | bool || bool (System.Boolean) || | ||
|- | |- | ||
| BOOL (long) || bool (System.Boolean) || | | BOOL (long) || bool (System.Boolean) || | ||
| 261行目: | 283行目: | ||
| CHAR (char) || 文字を渡すとき<br>char (System.Char)<br>文字を受け取るとき<br>StringBuilder || | | CHAR (char) || 文字を渡すとき<br>char (System.Char)<br>文字を受け取るとき<br>StringBuilder || | ||
|- | |- | ||
| WCHAR(wchar_t) || 文字を渡すとき<br>char (System.Char)<br>文字を受け取るとき<br>StringBuilder || | | WCHAR (wchar_t) || 文字を渡すとき<br>char (System.Char)<br>文字を受け取るとき<br>StringBuilder || | ||
|- | |- | ||
| LPSTR (char *, char[])<br>LPWSTR (wchar_t *, wchar_t[]) || 文字を渡すとき<br>string (System.String)<br>文字を受け取るとき<br>System.Text.StringBuilder || | | PCAHR (char *)<br>LPSTR (char *, char[])<br>LPWSTR (wchar_t *, wchar_t[]) || 文字を渡すとき<br>string (System.String)<br>文字を受け取るとき<br>System.Text.StringBuilder || | ||
|- | |- | ||
| LPCSTR (const char *, const char[])<br>LPCWSTR (const wchar_t *, const wchar_t[]) || 文字を渡すとき<br>string (System.String)<br>文字を受け取るとき<br>System.Text.StringBuilder || | | LPCSTR (const char *, const char[])<br>LPCWSTR (const wchar_t *, const wchar_t[]) || 文字を渡すとき<br>string (System.String)<br>文字を受け取るとき<br>System.Text.StringBuilder || | ||
|- | |||
| LPTSTR (char * または wchar_t *) || 文字を渡すとき<br>[MarshalAs(UnmanagedType.LPTStr)] string<br>文字を受け取るとき<br>System.Text.StringBuilder || | |||
|- | |||
| LPCTSTR (const char * または const wchar_t *) || 文字を渡すとき<br>string (System.String)<br>文字を受け取るとき<br>System.Text.StringBuilder || | |||
|- | |||
| BSTR || 文字を渡すとき<br>string (System.String)<br>文字を受け取るとき<br>System.Text.StringBuilder || | |||
|- | |- | ||
| FLOAT (float) || float (System.Single) || | | FLOAT (float) || float (System.Single) || | ||
|- | |||
| float <配列名>[]<br>float <配列名> [配列サイズ] || float[]<br>ref float[] || | |||
|- | |- | ||
| DOUBLE (double) || double (System.Double) || | | DOUBLE (double) || double (System.Double) || | ||
|- | |||
| double <配列名>[]<br>double <配列名> [配列サイズ] || double[]<br>ref double[] || | |||
|- | |||
| VARIANT || System.Object || | |||
|- | |||
| COLORREF || uint || | |||
|- | |||
| GUID || Guid || | |||
|- | |||
| struct <構造体名> <変数名> || ref <構造体名> <変数名> || | |||
|- | |||
| struct <構造体名> || public struct <構造体名> || <u>out <構造体名></u>であらかじめ構造体をインスタンス化して、変数名を宣言する。 | |||
|} | |} | ||
</center> | </center> | ||
<br><br> | <br><br> | ||
== | == 整数型および浮動小数点型のマーシャリング == | ||
==== | ==== Linux ==== | ||
下記に、Linux向けC++ライブラリを記述する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleLib.cpp | |||
#include <cstdio> | |||
#include <cstring> | |||
extern "C" int __attribute__((visibility("default"))) SampleFunc01(int a) | |||
{ | |||
printf(--<SampleDll:SampleFunc01>--\r\n"); | |||
printf("a = %d\r\n", a); | |||
printf("---------------------------\r\n"); | |||
return 10; | |||
} | |||
extern "C" double __attribute__((visibility("default"))) SampleFunc02(double a) | |||
{ | |||
printf("--<SampleDll:SampleFunc02>--\r\n"); | |||
printf("a = %f\r\n", a); | |||
printf("---------------------------\r\n"); | |||
return 3.14; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== Windows ==== | |||
Windows向けC++ DLLの作成方法は[[ライブラリの基礎 - DLLの作成(C/C++/MFC)|ライブラリの基礎 - DLLの作成(C/C++/MFC)]]を参照する。<br> | |||
<br> | |||
下記に、C++ DLLを記述する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDLL.h | |||
int __stdcall SampleFunc01(int a); | |||
double __stdcall SampleFunc02(double a); | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDll.cpp | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include "SampleDll.h" | |||
int __stdcall SampleFunc01(int a) | |||
{ | |||
printf(--<SampleDll:SampleFunc01>--\r\n"); | |||
printf("a = %d\r\n", a); | |||
printf("---------------------------\r\n"); | |||
return 10; | |||
} | |||
double __stdcall SampleFunc02(double a) | |||
{ | |||
printf("--<SampleDll:SampleFunc02>--\r\n"); | |||
printf("a = %f\r\n", a); | |||
printf("---------------------------\r\n"); | |||
return 3.14; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDll.def // モジュール定義ファイル | |||
LIBRARY SampleDll | |||
EXPORTS | |||
; 公開する関数名をリストアップ | |||
SampleFunc01 @1 | |||
SampleFunc02 @2 | |||
</syntaxhighlight> | |||
<br> | |||
==== Linux / Windows ==== | |||
次に、C# EXEからC++ DLLを呼び出す方法を記述する。<br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
using System; | |||
using System.Text; | |||
using System.Runtime.InteropServices; | |||
namespace SampleEXE | |||
{ | |||
class Program | |||
{ | |||
[DllImport("SampleDLL.dll")] // Windows | |||
[DllImport("SampleLib.so")] // Linux | |||
private static extern int SampleFunc01(int a); | |||
[DllImport("SampleDLL.dll")] // Windows | |||
[DllImport("SampleLib.so")] // Linux | |||
private static extern double SampleFunc02(double a); | |||
static void Main(string[] args) | |||
{ | |||
var iRet= SampleFunc01(1); | |||
Console.WriteLine(iRet); | |||
Console.WriteLine(); | |||
var dRet= SampleFunc01(0.01); | |||
Console.WriteLine(dRet); | |||
Console.WriteLine(); | |||
Console.ReadKey(); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br><br> | |||
== ポインタのマーシャリング == | |||
例えば、C++ DLLから次のような関数がエクスポートされているとする。<br> | 例えば、C++ DLLから次のような関数がエクスポートされているとする。<br> | ||
< | <syntaxhighlight lang="c++"> | ||
void WINAPI | void WINAPI ConvertToString(char *pstrRet); | ||
</ | void WINAPI ConvertToShort(short *psRet); | ||
void WINAPI ConvertToDouble(double *pdRet); | |||
</syntaxhighlight> | |||
<br> | <br> | ||
上記の関数において、C# | 上記の関数において、C# EXEから使用する時、ConvertToString関数の引数の<code>char*</code>型は文字列なので、<code>string</code>型または<code>StringBuilder</code>型を渡す。<br> | ||
ConvertToShort関数の引数の<code>short*</code>型は、<code>IntPtr</code>型を渡す。<br> | |||
(<code>IntPtr</code>型は汎用ポインタを表す型であり、<code>void*</code>型とほぼ同義)<br> | |||
ConvertToDouble関数の引数の<code>double*</code>型は、<code>IntPtr</code>型を渡す。<br> | |||
<br> | <br> | ||
ただし、C# | ただし、C#は厳しい型付け言語のため、曖昧さを解決するために、変換メソッドを経由する必要がある。<br> | ||
<br> | <br> | ||
具体的には、<code>IntPtr</code>型の変数に<code>Marshal.AllocHGlobal</code> | 具体的には、<code>IntPtr</code>型の変数に<code>Marshal.AllocHGlobal</code>メソッドで必要なサイズのメモリを確保して、それをC++ DLLの関数へ受け渡しを行う。<br> | ||
<code>Marshal.ReadInt16</code> | <code>Marshal.ReadInt16</code>メソッド(型により異なる)等で変換した後、確保したメモリを<code>Marshal.FreeHGlobal</code>関数で解放するというプロセスを経る必要がある。<br> | ||
<br> | <br> | ||
以下の例では、C++ DLLを呼ぶC# EXEのソースコードを記述している。<br> | 以下の例では、C++ DLLを呼ぶC# EXEのソースコードを記述している。<br> | ||
< | <syntaxhighlight lang="c#"> | ||
// DllImportを使用するために必要 | using System.Runtime.InteropServices; // DllImportを使用するために必要 | ||
// 呼び出し元の関数名を変更する | |||
// C++ DLL側の文字コードがUnicodeの場合は"CharSet = CharSet.Unicode"と明示的に指定する | |||
[DllImport("Sample.dll", EntryPoint = "ConvertToString", CharSet = CharSet.Unicode)] | |||
extern static void _ConvertToString(StringBuilder pstrRet); | |||
[DllImport("Sample.dll", EntryPoint = "ConvertToShort")] | |||
extern static void _ConvertToShort(IntPtr psRet); | |||
[DllImport("Sample.dll", EntryPoint = "ConvertToDouble")] | |||
extern static void _ConvertToDouble(IntPtr pdRet); | |||
public static string ConvertToString() | |||
{ | |||
// 値を受け渡しを行う場合、StringBuilderクラスを使用する | |||
var strRet = = new System.Text.StringBuilder(256); | |||
// 値を渡すだけの場合、string型を使用する | |||
//string strRet = ""; | |||
// C++ DLLの関数を呼ぶ | |||
_ConvertToString(strRet); | |||
return strRet; | |||
} | |||
public static short ConvertToShort() | |||
{ | |||
// 2バイトのメモリ確保 | |||
IntPtr buffer = new IntPtr(); | |||
buffer = Marshal.AllocHGlobal(2); | |||
// C++ DLLの関数を呼ぶ | |||
_ConvertToShort(buffer); | |||
// 2バイトのメモリをshort型に変換 | |||
short sRet = Marshal.ReadInt16(buffer); | |||
// メモリの開放 | |||
Marshal.FreeHGlobal(buffer); | |||
return sRet; | |||
} | |||
public static | public static double ConvertToDouble() | ||
{ | { | ||
// 8バイトのメモリ確保 | |||
IntPtr buffer = new IntPtr(); | IntPtr buffer = new IntPtr(); | ||
buffer = Marshal.AllocHGlobal( | buffer = Marshal.AllocHGlobal(8); | ||
// C++ DLLの関数を呼ぶ | |||
Marshal.FreeHGlobal(buffer); | _ConvertToDouble(buffer); | ||
return | |||
// IntPtr型からdouble型の数値を取得する | |||
// Int64型へ変換後、BitConverter.Int64BitsToDoubleメソッドでdouble型に変換する | |||
var i64Ret = Marshal.ReadInt64(buffer); | |||
var dRet = BitConverter.Int64BitsToDouble(i64Ret); | |||
// 必ずメモリを解放する | |||
Marshal.FreeHGlobal(buffer); | |||
return dRet; | |||
} | } | ||
</ | </syntaxhighlight> | ||
<br> | <br> | ||
<code>IntPtr</code>型の変数は様々なものが入る。<br> | <code>IntPtr</code>型の変数は様々なものが入る。<br> | ||
構造体を取得することもできるが、C# EXEで構造体を定義しなければならない。<br> | |||
ネイティブコードと.NET Frameworkでは型の管理方法が違うため、実際には型の相互変換(マーシャリング)が行われる。<br> | ネイティブコードと.NET Frameworkでは型の管理方法が違うため、実際には型の相互変換(マーシャリング)が行われる。<br> | ||
<br> | <br> | ||
なお、Windows APIではBOOL型の実体はLONG型なので、.NET Frameworkではboolの代わりにintを指定することも可能である。<br> | なお、Windows APIではBOOL型の実体はLONG型なので、.NET Frameworkではboolの代わりにintを指定することも可能である。<br> | ||
<br><br> | |||
== 配列のマーシャリング == | |||
このセクションでは、C# EXEからC++ DLLへ配列を渡す方法を記載する。<br> | |||
<br> | <br> | ||
==== | まず、以下にC++ DLLを記述する。<br> | ||
<syntaxhighlight lang="c++"> | |||
// SampleDLL.h | |||
#pragma once | |||
void __stdcall SampleArray01(int array[], int length); | |||
int* __stdcall SampleArray02(int length); | |||
void __stdcall SampleArray03(int array[], int length); | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDll.cpp | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include "SampleDll.h" | |||
void __stdcall SampleArray01(int array[], int length) | |||
{ | |||
for (int i = 0; i < length; i++) | |||
{ | |||
std::cout << array[i] << std::endl; | |||
} | |||
} | |||
int* __stdcall SampleArray02(int length) | |||
{ | |||
int* iary = new int[length]; | |||
for (int i = 0; i < length; i++) | |||
{ | |||
iary[i] = i; | |||
} | |||
return iary; | |||
} | |||
void __stdcall SampleArray03(int array[], int length) | |||
{ | |||
for (int i = 0; i < length; i++) | |||
{ | |||
arr[i] = i; | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// モジュール定義ファイル | |||
// SampleDll.def | |||
LIBRARY SampleDll | |||
EXPORTS | |||
; 公開する関数名をリストアップ | |||
SampleArray01 @1 | |||
SampleArray02 @2 | |||
SampleArray03 @3 | |||
</syntaxhighlight> | |||
<br> | |||
次に、C# EXEからC++ DLLを呼び出す方法を記述する。<br> | |||
<br> | |||
手順としては、以下の処理の流れとなる。<br> | |||
# C# EXEでマネージド配列(C# EXEの配列)を定義する。 | |||
# C# EXEにおいて、アンマネージド配列(C++ DLLの配列)のメモリを確保する。 | |||
# マネージド配列の内容を、上記で確保したアンマネージド配列のメモリにコピーする。 | |||
# C++ DLLの関数を実行する時、ポインタを渡す。 | |||
# 使用したアンマネージド配列のメモリを解放する。 | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
namespace SampleEXE | |||
{ | |||
[DllImport("SampleDll.dll", EntryPoint = "SampleArray01", CallingConvention = CallingConvention.Cdecl)] | |||
static extern void _SampleArray01(IntPtr array, int length); | |||
[DllImport("SampleDll.dll", EntryPoint = "SampleArray02", CallingConvention = CallingConvention.Cdecl)] | |||
static extern IntPtr _SampleArray02(int length); | |||
[DllImport("SampleDll.dll", EntryPoint = "SampleArray03", CallingConvention = CallingConvention.Cdecl)] | |||
static extern IntPtr _SampleArray03(IntPtr array, int length); | |||
static void Main() | |||
{ | |||
SampleArray01(); | |||
SampleArray02(); | |||
SampleArray03(); | |||
} | |||
// SampleArray01 | |||
// 配列を渡す場合 | |||
private static void SampleArray01() | |||
{ | |||
// 配列の定義 | |||
var array = new int[] { 0, 1, 2, 3, 4 }; | |||
// アンマネージド配列のメモリの確保 | |||
IntPtr ptrRet = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * array.Length); | |||
// マネージド配列をアンマネージドにコピーする | |||
Marshal.Copy(array, 0, ptrRet, array.Length); | |||
// C++ DLLに配列を渡す(ポインタを渡す) | |||
_SampleArray01(ptrRet, array.Length); | |||
// アンマネージドメモリの解放 | |||
Marshal.FreeCoTaskMem(ptrRet); | |||
} | |||
// SampleArray02 | |||
// 配列を受ける場合 (戻り値がint型ポインタ) | |||
private static void SampleArray02() | |||
{ | |||
// 空の配列の定義 | |||
int[] array = new int[5]; | |||
// 戻り値のポインタをIntPtrで受け取る | |||
IntPtr ptrRet = _SampleArray02(array.Length); | |||
// アンマネージド配列からマネージド配列へコピー | |||
Marshal.Copy(ptrRet, array, 0, array.Length); | |||
foreach(int n in array) | |||
{ | |||
Console.WriteLine(n + " "); | |||
} | |||
} | |||
// SampleArray03 | |||
// 配列を受ける場合 (引数がint型の配列) | |||
private static void SampleArray03() | |||
{ | |||
// 空の配列の定義 | |||
int[] array = new int[5]; | |||
// アンマネージド配列のメモリを確保 | |||
IntPtr ptrRet = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(int)) * array.Length); | |||
// 引数でポインタを渡す | |||
_SampleArray03(ptrRet, array.Length); | |||
// アンマネージド配列からマネージド配列へコピー | |||
Marshal.Copy(ptrRet, array, 0, array.Length); | |||
// アンマネージド配列のメモリを解放 | |||
Marshal.FreeCoTaskMem(ptrRet); | |||
foreach(int n in array) | |||
{ | |||
Console.WriteLine(n + " "); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br><br> | |||
== 文字列のマーシャリング (1) == | |||
C++ DLLの作成方法は[[ライブラリの基礎 - DLLの作成(C/C++/MFC)|ライブラリの基礎 - DLLの作成(C/C++/MFC)]]を参照する。<br> | C++ DLLの作成方法は[[ライブラリの基礎 - DLLの作成(C/C++/MFC)|ライブラリの基礎 - DLLの作成(C/C++/MFC)]]を参照する。<br> | ||
<br> | <br> | ||
下記にもC++ DLLを記述する。<br> | 下記にもC++ DLLを記述する。<br> | ||
< | <syntaxhighlight lang="c++"> | ||
SampleDLL.h | // SampleDLL.h | ||
int __stdcall Mul(int x, int y) | |||
void | void __stdcall MulUsePointer(int *x, int *y, int *result) | ||
void | void __stdcall SetNameStr(const char *t) | ||
const char* __stdcall GetNameStr() | |||
void | void __stdcall SetNameWStr(const wchar_t *t) | ||
</ | const wchar_t* __stdcall GetNameWStr() | ||
</syntaxhighlight> | |||
<br> | <br> | ||
< | <syntaxhighlight lang="c++"> | ||
SampleDll.cpp | // SampleDll.cpp | ||
#include <stdio.h> | #include <stdio.h> | ||
| 335行目: | 703行目: | ||
#include "SampleDll.h" | #include "SampleDll.h" | ||
static std::string _str = ""; | |||
static std::wstring _wstr = L""; | |||
int __stdcall Mul(int x, int y) | |||
{ | |||
return x * y; | |||
} | |||
void __stdcall MulUsePointer(int *x, int *y, int *result) | |||
{ | |||
*result = (*x) * (*y); | |||
} | |||
void __stdcall SetNameStr(const char *t) | |||
{ | |||
_str = std::string(t); | |||
} | |||
const char* __stdcall GetNameStr() | |||
{ | |||
return _str.c_str(); | |||
} | |||
void __stdcall SetNameWStr(const wchar_t *t) | |||
{ | |||
_wstr = std::wstring(t); | |||
} | |||
const wchar_t * __stdcall GetNameWStr() | |||
{ | |||
return _wstr.c_str(); | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDll.def // モジュール定義ファイル | |||
LIBRARY SampleDll | |||
EXPORTS | |||
; 公開する関数名をリストアップ | |||
Mul @1 | |||
MulUsePointer @2 | |||
SetNameStr @3 | |||
GetNameStr @4 | |||
SetNameWStr @5 | |||
GetNameWStr @6 | |||
</syntaxhighlight> | |||
<br> | |||
次に、C# EXEからC++ DLLを呼び出す方法を記述する。<br> | |||
<br> | |||
DllImpoort文は、DLLファイル名を指定する。<br> | |||
この時、C#の実行ファイルと同一階層にDLLファイルがある場合、DLLファイル名のみを指定するだけでよい。<br> | |||
異なる階層にDLLファイルがある場合、フルパスで指定する必要がある。<br> | |||
<br> | |||
C++ DLLの関数を実行する時、<u>"エントリーポイントが見つかりません。"</u>とエラーが出力される場合、DllImport文に誤りがある可能性が高い。<br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
using System; | |||
using System.Runtime.InteropServices; | |||
namespace SampleCSharp | |||
{ | |||
class Program | |||
{ | |||
[DllImport("SampleDLL", EntryPoint = "Mul")] | |||
static extern int _Mul(int x, int y); | |||
[DllImport("SampleDLL", EntryPoint = "MulUsePointer")] | |||
static extern void _MulUsePointer(ref int x, ref int y, out int result); | |||
[DllImport("SampleDLL", EntryPoint = "SetNameStr", CharSet = CharSet.Ansi)] | |||
static extern void _SetNameStr(string t); | |||
[DllImport("SampleDLL", EntryPoint = "GetNameStr", CharSet = CharSet.Ansi)] | |||
static extern IntPtr _GetNameStr(); | |||
[DllImport("SampleDLL", EntryPoint = "SetNameWStr", CharSet = CharSet.Unicode)] | |||
static extern void _SetNameWStr(string t); | |||
[DllImport("SampleDLL", EntryPoint = "GetNameWStr", CharSet = CharSet.Unicode)] | |||
static extern IntPtr _GetNameWStr(); | |||
static void Main(string[] args) | |||
{ | |||
int x = 10; | |||
int y = 20; | |||
int resultMul = _Mul(x, y); | |||
Console.WriteLine("Mul = " + resultMul); | |||
_MulUsePointer(ref x, ref y, out int resultMulUsePointer); | |||
Console.WriteLine("MulUsePointer = " + resultMulUsePointer); | |||
string testString = "東京都新宿 1-1"; | |||
_SetNameStr(testString); | |||
Console.WriteLine("GetNameStr = " + Marshal.PtrToStringAnsi(_GetNameStr())); | |||
_SetNameWStr(testString); | |||
Console.WriteLine("GetNameWStr = " + Marshal.PtrToStringUni(_GetNameWStr())); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br><br> | |||
== 文字列のマーシャリング (2) == | |||
==== Linux ==== | |||
Linux向けC++ライブラリを記述する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleLib.cpp | |||
#include <cstdio> | |||
extern "C" double __attribute__((visibility("default"))) SampleFunc01(int a) | |||
{ | |||
printf("--<SampleDll:SampleFunc01>--\r\n"); | |||
printf("a = %d\r\n", a); | |||
printf("---------------------------\r\n"); | |||
return 3.14; | |||
} | |||
extern "C" void __attribute__((visibility("default"))) SampleFunc02(int a, char *pstr) | |||
{ | { | ||
printf("--<SampleDll:SampleFunc02>--\r\n"); | |||
printf("[%d] %s\r\n", a, pstr); | |||
printf("-----------------------------\r\n"); | |||
} | } | ||
extern "C" void __attribute__((visibility("default"))) SampleFunc03(int a, char *pstr) | |||
{ | { | ||
printf("--<SampleDll:SampleFunc03>--\r\n"); | |||
double * | printf("[%d] %s\r\n", a, pstr); | ||
sprintf(pstr, u8"C++ DLL側から文字列を返す場合は、StringBuilderクラスを使用する"); | |||
printf("------------------------\r\n"); | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== Windows ==== | |||
Windows向けC++ DLLの作成方法は[[ライブラリの基礎 - DLLの作成(C/C++/MFC)|ライブラリの基礎 - DLLの作成(C/C++/MFC)]]を参照する。<br> | |||
<br> | |||
下記に、C++ DLLを記述する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDLL.h | |||
double __stdcall SampleFunc01(int a); | |||
void __stdcall SampleFunc02(int a, char *pstr) | |||
void __stdcall SampleFunc03(int a, char *pstr) | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDll.cpp | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include "SampleDll.h" | |||
double __stdcall SampleFunc01(int a) | double __stdcall SampleFunc01(int a) | ||
| 371行目: | 885行目: | ||
printf("------------------------\r\n"); | printf("------------------------\r\n"); | ||
} | } | ||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDll.def // モジュール定義ファイル | |||
LIBRARY SampleDll | |||
EXPORTS | |||
; 公開する関数名をリストアップ | |||
SampleFunc01 @1 | |||
SampleFunc02 @2 | |||
SampleFunc03 @3 | |||
</syntaxhighlight> | |||
<br> | |||
==== Linux / Windows ==== | |||
次に、C# EXEからC++ DLLを呼び出す方法を記述する。<br> | |||
<br> | |||
文字列をC++ DLL側に渡す場合は、<code>string</code>型を使用する。<br> | |||
文字列をC++ DLL側から返す場合は、<code>StringBuilder</code>クラスを使用する。<br> | |||
<u><code>StringBuilder</code>クラスは受け渡しの両方が可能なので、文字列は全て<code>StringBuilder</code>クラスを使用すべきである。</u><br> | |||
<br> | |||
<syntaxhighlight lang="c#"> | |||
using System; | |||
using System.Text; | |||
using System.Runtime.InteropServices; | |||
namespace SampleEXE | |||
{ | |||
class Program | |||
{ | |||
/// <summary> | |||
/// 最も基本的な関数のインポート例 | |||
/// </summary> | |||
/// <param name="a">4 バイト符号付き整数を指定します。</param> | |||
/// <returns>倍精度浮動小数を返します。</returns> | |||
[DllImport("SampleDLL.dll")] // Windows | |||
[DllImport("SampleLib.so")] // Linux | |||
private static extern double SampleFunc01(int a); | |||
[DllImport("SampleDLL.dll", CharSet = CharSet.Unicode)] // Windows | |||
[DllImport("SampleLib.so", CharSet = CharSet.Auto)] // Linux | |||
// C++ DLL側の文字コードがUnicodeの場合は"CharSet = CharSet.Unicode"と明示的に指定する | |||
private static extern void SampleFunc02(int a, string str); | |||
[DllImport("SampleDLL.dll", CharSet = CharSet.Unicode)] // Windows | |||
[DllImport("SampleLib.so", CharSet = CharSet.Auto)] // Linux | |||
// 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(); | |||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br><br> | |||
== 構造体のマーシャリング == | |||
C++ DLLの作成方法は[[ライブラリの基礎 - DLLの作成(C/C++/MFC)|ライブラリの基礎 - DLLの作成(C/C++/MFC)]]を参照する。<br> | |||
<br> | |||
下記にもC++ DLLを記述する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDLL.h | |||
void __stdcall DisplayFunc(SampleStruct1 Structure) | |||
void __stdcall GetFunc(SampleStruct1 Structure) | |||
void __stdcall SetFunc(SampleStruct2 *pStructure) | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDll.cpp | |||
void __stdcall | #include <stdio.h> | ||
#include <string.h> | |||
#include "SampleDll.h" | |||
typedef struct tagSampleStruct | |||
{ | |||
int index; | |||
char name[128]; | |||
int data[50]; | |||
} SampleStruct1, *pSampleStruct; | |||
typedef struct tagSampleStruct2 | |||
{ | |||
int length; | |||
double *data; | |||
} SampleStruct2, *pSampleStruct2; | |||
void __stdcall DisplayFunc(SampleStruct1 st) | |||
{ | { | ||
printf("--<SampleDll: | printf("--<SampleDll:DisplayFunc>--\r\n"); | ||
printf("index = %d\r\n", st.index); | printf("index = %d\r\n", st.index); | ||
printf("name = %s\r\n", st.name); | printf("name = %s\r\n", st.name); | ||
| 381行目: | 996行目: | ||
} | } | ||
void __stdcall | void __stdcall GetFunc(SampleStruct1 *pst) | ||
{ | { | ||
printf("--<SampleDll:GetFunc>--\r\n"); | |||
printf("--<SampleDll: | pst->index = 10; | ||
memcpy_s(pst->name, sizeof(pst->name), "Tarou Yamada", sizeof("Tarou Yamada")); | |||
memset(pst->data, 20, sizeof(pst->data)); | |||
printf("------------------------\r\n"); | |||
} | |||
void __stdcall SetFunc(SampleStruct2 *pStructure) | |||
{ | |||
printf("--<SampleDll:SetFunc>--\r\n"); | |||
memset(pStructure, 0, sizeof(SampleStruct2)); | memset(pStructure, 0, sizeof(SampleStruct2)); | ||
pStructure->length = 10; | |||
pStructure->data = | // double dAryData[256] = {0.0f}; | ||
pStructure->length = 10; | |||
double dAryData[pStructure->length] = {0.0f}; | |||
pStructure->data = dAryData; | |||
for(int i = 0; i < pStructure->length; i++) | for(int i = 0; i < pStructure->length; i++) | ||
{ | { | ||
dAryData[i] = (i + 1) / 10.0f; | |||
} | } | ||
printf("------------------------\r\n"); | printf("------------------------\r\n"); | ||
} | } | ||
</ | </syntaxhighlight> | ||
< | <br> | ||
SampleDll.def // モジュール定義ファイル | <syntaxhighlight lang="c++"> | ||
// SampleDll.def // モジュール定義ファイル | |||
LIBRARY SampleDll | LIBRARY SampleDll | ||
| 402行目: | 1,032行目: | ||
EXPORTS | EXPORTS | ||
; 公開する関数名をリストアップ | ; 公開する関数名をリストアップ | ||
DisplayFunc @1 | |||
GetFunc @2 | |||
SetFunc @3 | |||
</syntaxhighlight> | |||
</ | |||
<br> | <br> | ||
次に、C# EXEからC++ DLLを呼び出す方法を記述する。<br> | 次に、C# EXEからC++ DLLを呼び出す方法を記述する。<br> | ||
<br> | <br> | ||
C++では構造体のサイズはコンパイル時に決定されるが、C#では実行時に決定される。<br> | C++では構造体のサイズはコンパイル時に決定されるが、C#では実行時に決定される。<br> | ||
したがって、C#側で構造体のサイズを予め指定する必要がある。<br> | したがって、C#側で構造体のサイズを予め指定する必要がある。<br> | ||
この時、構造体は固定長サイズとなるため、配列等を定義する場合は異なるサイズの配列を後からインスタンス化することができなくなる。<br> | |||
<br> | <br> | ||
構造体をC++ | 構造体をC++ DLL側から返す場合、<code>IntPtr</code>型から<code>double</code>型の配列を取得する時は、<code>Int64</code>型へ変換した後、<code>BitConverter.Int64BitsToDouble</code>メソッドで<code>double</code>型に変換する。<br> | ||
< | <syntaxhighlight lang="c#"> | ||
using System; | using System; | ||
using System.Text; | using System.Text; | ||
| 429行目: | 1,054行目: | ||
{ | { | ||
/// <summary> | /// <summary> | ||
/// | /// 構造体を引数に持つ関数のインポート例 | ||
/// </summary> | /// </summary> | ||
/// <param name=" | /// <param name="st">DLL 側に渡す構造体を指定します</param> | ||
[DllImport("SampleDLL.dll", EntryPoint = "DisplayFunc", CallingConvention = CallingConvention.Cdecl)] | |||
private static extern void _DisplayFunc(SampleStruct1 st); | |||
[DllImport("SampleDLL.dll", | |||
private static extern void | |||
/// <summary> | /// <summary> | ||
/// | /// DLL側からメンバにポインタを含む構造体を受け取る関数のインポート例 | ||
/// </summary> | /// </summary> | ||
/// <param name=" | /// <param name="pst">受け渡す構造体の先頭アドレスを示すポインタを指定する</param> | ||
[DllImport("SampleDLL.dll")] | [DllImport("SampleDLL.dll", EntryPoint = "GetFunc", CallingConvention = CallingConvention.Cdecl)] | ||
private static extern void | private static extern void _GetFunc(IntPtr pst); | ||
/// <summary> | /// <summary> | ||
| 455行目: | 1,071行目: | ||
/// </summary> | /// </summary> | ||
/// <param name="pst">受け渡す構造体の先頭アドレスを示すポインタを指定する</param> | /// <param name="pst">受け渡す構造体の先頭アドレスを示すポインタを指定する</param> | ||
[DllImport("SampleDLL.dll")] | [DllImport("SampleDLL.dll", EntryPoint = "SetFunc", CallingConvention = CallingConvention.Cdecl)] | ||
private static extern void | private static extern void _SetFunc(IntPtr pst); | ||
/// <summary> | /// <summary> | ||
| 462行目: | 1,078行目: | ||
/// LayoutKind.Sequentialを指定することで、C/C++同様、変数の宣言順通りにメモリに配置されるようになる | /// LayoutKind.Sequentialを指定することで、C/C++同様、変数の宣言順通りにメモリに配置されるようになる | ||
/// </summary> | /// </summary> | ||
[StructLayout(LayoutKind.Sequential)] | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | ||
private struct | private struct SampleStruct1 | ||
{ | { | ||
/// <summary> | /// <summary> | ||
| 497行目: | 1,113行目: | ||
static void Main(string[] args) | static void Main(string[] args) | ||
{ | { | ||
SampleFunc01(); | |||
SampleFunc02(); | |||
SampleFunc03(); | |||
Console.ReadKey(); | |||
} | |||
private static void SampleFunc01() | |||
{ | |||
var structure = new SampleStruct1() | |||
var | |||
{ | { | ||
index = 4, | index = 4, | ||
| 516行目: | 1,129行目: | ||
}; | }; | ||
structure.data[0] = 11; | |||
structure.data[1] = 22; | |||
structure.data[2] = 33; | |||
_SampleFunc01(structure); | |||
} | |||
private static void SampleFunc02() | |||
{ | |||
var structure = new SampleStruct1() | |||
{ | |||
index = 0, | |||
name = "", | |||
data = new int[50], | |||
}; | |||
// COMタスクメモリアロケータから、C#の構造体のサイズ分のメモリブロックを割り当てる | |||
IntPtr structurePtr = Marshal.AllocCoTaskMem(Marshal.SizeOf(structure)); | |||
_GetFunc(structurePtr); | |||
// Marshal.PtrToStructureメソッドを使用して、IntPtr変数が示すメモリに格納された情報をC#の構造体にコピーする | |||
structure = (SampleStruct1)Marshal.PtrToStructure(structurePtr, structure.GetType()); | |||
Marshal.FreeCoTaskMem(structurePtr); | |||
Console.WriteLine($"index : {structure.index}"); | |||
Console.WriteLine($"name : {structure.name}"); | |||
Console.WriteLine($"data : {structure.data}"); | |||
} | |||
private static void SampleFunc03() | |||
{ | |||
// SampleStruct2構造体のサイズを取得する | // SampleStruct2構造体のサイズを取得する | ||
// | // 指定サイズ分だけメモリ領域を確保して、その先頭アドレスをstructure1に格納する | ||
var | var structure1 = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SampleStruct2))); | ||
try | try | ||
{ | { | ||
// C++ DLLのSampleFunc02関数の実行 | |||
// | SetFunc(structure1); | ||
var | |||
for (int i = 0; i < | // 受け取ったstructure1からSampleStruct2構造体の情報に構築し直す | ||
var structure2 = (SampleStruct2)Marshal.PtrToStructure(structure1, typeof(SampleStruct2)); | |||
for (int i = 0; i < structure2.length; i++) | |||
{ | { | ||
// IntPtr型からdouble型の数値を取得するときは、一度Int64型に変換して、これをBitConverter.Int64BitsToDoubleメソッドでdouble型に変換する | // IntPtr型からdouble型の数値を取得するときは、一度Int64型に変換して、これをBitConverter.Int64BitsToDoubleメソッドでdouble型に変換する | ||
var v = Marshal.ReadInt64( | var v = Marshal.ReadInt64(structure2.data, i * sizeof(double)); | ||
Console.WriteLine("data[{0}] = {1}", i, BitConverter.Int64BitsToDouble(v)); | Console.WriteLine("data[{0}] = {1}", i, BitConverter.Int64BitsToDouble(v)); | ||
} | } | ||
| 543行目: | 1,186行目: | ||
{ | { | ||
// 必ずメモリを解放する | // 必ずメモリを解放する | ||
Marshal.FreeHGlobal( | Marshal.FreeHGlobal(structure1); | ||
} | } | ||
} | |||
} | |||
} | |||
</syntaxhighlight> | |||
<br><br> | |||
== 関数ポインタのマーシャリング == | |||
C# EXEからC++ DLLへコールバック関数を渡す。<br> | |||
<br> | |||
ただし、<code>Func</code>型、<code>Action</code>型、構造体内にある<code>char</code>型の配列のようなNon-Bittable型(非Blittable型)が含まれている場合、以下の例外が発生する。<br> | |||
このような型の場合は、属性を用いたマーシャルの変換は不可能である。<br> | |||
Non-blittable generic types cannot be marshaled. | |||
<br> | |||
==== Linux ==== | |||
Linux向けのC++ライブラリを記述する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleLib.cpp | |||
#include <cstdio> | |||
#include <cstring> | |||
using UnManagedCallback = bool (*)(int); | |||
Console.ReadKey(); | int __stdcall SampleCallback(UnManagedCallback CallbackFunc, int lPalam) | ||
{ | |||
int iRet = 0; | |||
bool bRet = CallbackFunc(lPalam); | |||
if(bRet) | |||
{ | |||
iRet = 1; | |||
} | |||
else | |||
{ | |||
iRet = -1; | |||
} | |||
return iRet; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
==== Windows ==== | |||
Windows向けのC++ DLLを記述する。<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDLL.h | |||
#pragma once | |||
int __stdcall SampleCallback(int handle, int lPalam); | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// SampleDll.cpp | |||
#include <stdio.h> | |||
#include <string.h> | |||
#include "SampleDll.h" | |||
using UnManagedCallback = bool (*)(int); | |||
int __stdcall SampleCallback(UnManagedCallback CallbackFunc, int lPalam) | |||
{ | |||
int iRet = 0; | |||
bool bRet = CallbackFunc(lPalam); | |||
if(bRet) | |||
{ | |||
iRet = 1; | |||
} | |||
else | |||
{ | |||
iRet = -1; | |||
} | |||
return iRet; | |||
} | |||
</syntaxhighlight> | |||
<br> | |||
<syntaxhighlight lang="c++"> | |||
// モジュール定義ファイル | |||
// SampleDll.def | |||
LIBRARY SampleDll | |||
EXPORTS | |||
; 公開する関数名をリストアップ | |||
SampleCallback @1 | |||
</syntaxhighlight> | |||
<br> | |||
==== Linux / Windows ==== | |||
次に、C# EXEからC++ DLLを呼び出す方法を記述する。<br> | |||
<br> | |||
マネージドコールバック関数を定義する。<br> | |||
以下の例では、ManagedCallBackというデリゲートを宣言している。<br> | |||
C++ DLLのSampleCallBack関数にデリゲートを引数として渡すことで、既知のコールバック形式に自動的に変換される。<br> | |||
<syntaxhighlight lang="c#"> | |||
using System; | |||
using System.Runtime.InteropServices; | |||
namespace ConsoleSample01 | |||
{ | |||
delegate bool ManagedCallBack(int x); | |||
class Program | |||
{ | |||
[DllImport("SampleDLL.dll")] // Windows | |||
[DllImport("SampleLib.so")] // Linux | |||
private static extern int SampleCallback(ManagedCallBack CallBack, int lPalam); | |||
static void Main(string[] args) | |||
{ | |||
var CallBack = new ManagedCallBack(CallBackFunction); | |||
var iRet = SampleCallback(CallBack, 0); | |||
Console.WriteLine($"{iRet}"); | |||
Console.ReadKey(); | |||
} | |||
public static bool CallBackFunction(int x) | |||
{ | |||
Console.WriteLine($"{x}"); | |||
return true; | |||
} | |||
} | } | ||
} | } | ||
</ | </syntaxhighlight> | ||
<br><br> | |||
== トラブル対処 == | |||
可能な限り、デバッグビルドしたDLLとそのPDBファイルを用意する。<br> | |||
<br> | |||
==== デバッグ ==== | |||
C++DLLがデバッグできない場合、以下の手順で、混合モードデバッグを有効にする。<br> | |||
# [ソリューションエクスプローラー]からC#プロジェクトを右クリックして、[プロパティ]を選択する。<br> | |||
# [プロパティページ]画面が表示されるので、[デバッグ]タブ - [アンマネージ コード デバッグを有効にする]([ネイティブコードのデバッグを有効にする])を選択する。<br> | |||
# [プロパティページ]画面を閉じる。 | |||
<br> | |||
[https://msdn.microsoft.com/ja-jp/library/2wcdezs5.aspx#Anchor_3 [デバッガーを有効にする<nowiki>]</nowiki> - [デバッグ<nowiki>]</nowiki> ページ (プロジェクト デザイナー) | MSDN ネイティブ コードのデバッグ | MSDN]<br> | |||
<br> | |||
この設定をしていない場合、C++DLLからのエラーにより、アプリケーションが終了することがある。<br> | |||
<br> | |||
<u>※注意</u><br> | |||
<u>Visual Studio 2017以降、プロジェクトのプロパティの代わりにlaunchSettings.jsonファイルを使用して、</u><br> | |||
<u>.NET Coreアプリでネイティブコードの混合モードデバッグを有効にする必要がある。</u><br> | |||
<br> | |||
<u>詳細については、[https://docs.microsoft.com/ja-jp/visualstudio/debugger/how-to-debug-managed-and-native-code?view=vs-2019 マネージドコードとネイティブコードのデバッグに関するページ]を参照すること。</u><br> | |||
<br><br> | <br><br> | ||
__FORCETOC__ | __FORCETOC__ | ||
[[カテゴリ:C_Sharp]] | [[カテゴリ:C_Sharp]] | ||