C#呼び出しC/C++ダイナミックライブラリ封入構造体、構造体配列
7816 ワード
実験室画像処理のアルゴリズムはすべてOpenCVの下で書かれているため、またナビゲーションのアルゴリズムもC++で書かれており、インタフェース部分はC#の下で書くことが要求されているため、Socket通信でもOpenCVを呼び出すDLLモジュールでも、C#とC++データ型の対応、構造体のパッケージ使用が設計されている.誇張言語呼び出しの面では、JavaとC#はCフォーマットでエクスポートされたダイナミックライブラリしか呼び出せない.Cデータ型が比較的単一で、マッピングが容易であるため、両方ともローカル側にマッピングされたC#またはJavaの記述インタフェースを提供し、このマッピング関係を下位層で処理することで呼び出しの目的を達成する.
一.構造体の伝達
C#で対応する構造体定義:
管理されていない関数SetVersionPtrは、2つの方法で呼び出すことができます.
1.方式一(伝達構造体参照)、C#において、構造体は値を伝達する方式で伝達され、クラスはアドレスを伝達する方式で伝達され、キーワードrefを加えればよい.C端は2つの異なるタイプのパラメータを伝達し,いずれも参照によって解決できる.
2.方式二(IntPtr(プラットフォーム共通ポインタ)への入力)
対応する関数のC#での宣言:
テストコード:
上記の2つの方法の違いは、
1つ目の方法は,まず等価な構造体を作成し,その後,この構造体の参照をDLL中のインタフェース関数に渡すことである.第2の方法は、構造体サイズの非管理メモリを申請し、メモリにデータを書き込むことです.呼び出すときは、この管理されていないメモリのポインタだけをDLLのインタフェース関数に渡します.実は第2の方法は,構造体を事前に定義する必要がなく,構造体の大きさを知るだけで,対応する非管理メモリを申請すればよい.
二.構造体配列の伝達
DLL内の非管理コード:
コールコードインタフェース:
テストコード:
上のコードには2つ説明する必要があります.
(1)C#のIntPtrポインタが対応する演算をする場合は、まずIntPtrを対応する変換をしてから演算します.演算が終了した後、それに応じた変換を行い、元のIntPtrに復元します.
(2)Marshal.Copyという関数の機能は、管理配列から非管理メモリポインタにデータをコピーするか、非管理メモリポインタから管理配列にデータをコピーすることです.
三.複雑な構造体の伝達
1.出力パラメータ、構造体がポインタとして出力
管理されていない部分コード:
上のDLLのエクスポート関数では、渡されるパラメータがカスタムClass構造体配列であることが要求されています.では、C#で呼び出すときも対応する構造体をカスタマイズします.
次のように定義できます.
注意が必要なのは、この2つの構造体の配列の大きさは必ずC++の限定と同じ大きさでなければならないことです.次に、このAPIをどのように使ってデータを正確に取得するか、多くの人はこのような処理方法を考えているかもしれません.
そう、このような処理は大丈夫ですが、私たちのAPIのパラメータはClass配列で、この処理方式はClass構造体のパラメータを渡すだけなので、この方式はここではあまり適切ではありません.
では、まずClass[]myclass=new Class[MaxClass];そしてマーシャルを使っていますAllochGlobalはmyclassデータのポインタを取得し、
実はこれも間違っていて、Class構造に含まれていて、直接封入できないStudio構造なので、どうしても上記の考えは間違っています!
では、どうすればいいのでしょうか.実は簡単です.まず、非管理メモリを割り当て、APIを呼び出してから、非管理コンテンツデータを管理構造体データに読み込むことです.
例のデモコードは次のとおりです.
5月19日学習内容:
http://tcspecial.iteye.com/blog/1675621
http://www.cnblogs.com/naiking/archive/2013/01/17/2864132.html
http://blog.csdn.net/zhangj1012003_2007/article/details/6283032
http://blog.csdn.net/xiaowei_cqu/article/details/7693985
一.構造体の伝達
#define JNAAPI extern "C" __declspec(dllexport) // C
typedef struct
{
int osVersion;
int majorVersion;
int minorVersion;
int buildNum;
int platFormId;
char szVersion[128];
}OSINFO;
// ( )
JNAAPI bool SetVersionPtr(OSINFO *info)
{
char * str = "Hello DLL";
info->osVersion = 100;
strcpy(info->szVersion, str);
return true;
}
// ( )
JNAAPI bool SetVersionRef(OSINFO &info)
{
char * str = "Hello DLL";
info.osVersion = 101;
strcpy(info.szVersion, str);
return true;
}
C#で対応する構造体定義:
// OSINFO
[StructLayout(LayoutKind.Sequential)]
public struct OSINFO
{
public int osVersion;
public int majorVersion;
public int minorVersion;
public int buildNum;
public int platFormId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szVersion;
}
管理されていない関数SetVersionPtrは、2つの方法で呼び出すことができます.
1.方式一(伝達構造体参照)、C#において、構造体は値を伝達する方式で伝達され、クラスはアドレスを伝達する方式で伝達され、キーワードrefを加えればよい.C端は2つの異なるタイプのパラメータを伝達し,いずれも参照によって解決できる.
[DllImport("jnalib.dll", EntryPoint = "GetVersionPtr")]
public static extern bool GetVersionPtr(ref OSINFO info);
public static extern bool GetVersionRef(ref OSINFO info);
2.方式二(IntPtr(プラットフォーム共通ポインタ)への入力)
対応する関数のC#での宣言:
[DllImport("CSharpInvokeCpp_CppDemo.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool SetVersionPtr(IntPtr pv);
テストコード:
IntPtr pv = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(OSINFO)));
//
// string
for (int i = 0; i < 5; i++)
{
Marshal.WriteInt32(pv, i * Marshal.SizeOf(typeof(Int32)), ((Int32)(i + 1)));
}
if (CppDLL.SetVersionPtr(pv))
{
Console.WriteLine("--osVersion:{0}", Marshal.ReadInt32(pv, 0));
Console.WriteLine("--Major:{0}", Marshal.ReadInt32(pv, 4));
Console.WriteLine("--Minor:{0}", Marshal.ReadInt32(pv, 8));
Console.WriteLine("--BuildNum:{0}", Marshal.ReadInt32(pv, 12));
Console.WriteLine("--szVersion:{0}", Marshal.PtrToStringAnsi((IntPtr)(pv.ToInt32()+20)));
}
Marshal.FreeHGlobal(pv);
上記の2つの方法の違いは、
1つ目の方法は,まず等価な構造体を作成し,その後,この構造体の参照をDLL中のインタフェース関数に渡すことである.第2の方法は、構造体サイズの非管理メモリを申請し、メモリにデータを書き込むことです.呼び出すときは、この管理されていないメモリのポインタだけをDLLのインタフェース関数に渡します.実は第2の方法は,構造体を事前に定義する必要がなく,構造体の大きさを知るだけで,対応する非管理メモリを申請すればよい.
二.構造体配列の伝達
DLL内の非管理コード:
//
JNAAPI bool SetVersionArray(OSINFO *info, int nLen)
{
char *str = "Hello DLL";
for(int i = 0; i < nLen; i++)
{
info[i].osVersion = 100;
strcpy(info[i].szVersion, str);
}
return true;
}
コールコードインタフェース:
//C# , , IntPtr
[DllImport("CSharpInvokeCpp_CppDemo.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern bool SetVersionArray(IntPtr pv, int nLen);
テストコード:
//
OSINFO[] infos = new OSINFO[2];
for (int i = 0; i < infos.Length; i++ )
{
infos[i] = new OSINFO();
}
IntPtr[] ptrArr = new IntPtr[1];
ptrArr[0] = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(OSINFO)) * 2);
IntPtr pt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(OSINFO)));
Marshal.Copy(ptrArr, 0, pt, 1);
CppDLL.SetVersionArray(pt, 2);
for (int i = 0; i < infos.Length; i++)
{
infos[i] = (OSINFO)Marshal.PtrToStructure((IntPtr)(pt.ToInt32() + i * Marshal.SizeOf(typeof(OSINFO))), typeof(OSINFO));
Console.WriteLine("OsVersion:{0} szVersion:{1}", infos[i].osVersion, infos[i].szVersion);
}
Marshal.FreeHGlobal(ptrArr[0]);
Marshal.FreeHGlobal(pt);
上のコードには2つ説明する必要があります.
(1)C#のIntPtrポインタが対応する演算をする場合は、まずIntPtrを対応する変換をしてから演算します.演算が終了した後、それに応じた変換を行い、元のIntPtrに復元します.
(2)Marshal.Copyという関数の機能は、管理配列から非管理メモリポインタにデータをコピーするか、非管理メモリポインタから管理配列にデータをコピーすることです.
三.複雑な構造体の伝達
1.出力パラメータ、構造体がポインタとして出力
管理されていない部分コード:
typedef struct
{
char name[20];
int age;
double scores[32];
}Student;
//Class
typedef struct
{
int number;
Student stedents[50];
}Class;
JNAAPI int GetClass(Class *pClass,int len)
{
for(int i = 0; i < len; i++)
{
pClass[i].number = i;
for(int j = 0; j< 50; j++)
{
// name 20 0
memset(pClass[i].stedents[j].name, 0, 20);
//
sprintf(pClass[i].stedents[j].name, "name_%d_%d", i, j);
pClass[i].stedents[j].age = j % 2 == 0 ? 15:20;
}//for
}//for
return 0;
}
上のDLLのエクスポート関数では、渡されるパラメータがカスタムClass構造体配列であることが要求されています.では、C#で呼び出すときも対応する構造体をカスタマイズします.
次のように定義できます.
[StructLayout(LayoutKind.Sequential)]
struct Student
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
public string name;
public int age;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
public double[] scores;
}
[StructLayout(LayoutKind.Sequential)]
struct Class
{
public int number;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
public Student[] students;
}
注意が必要なのは、この2つの構造体の配列の大きさは必ずC++の限定と同じ大きさでなければならないことです.次に、このAPIをどのように使ってデータを正確に取得するか、多くの人はこのような処理方法を考えているかもしれません.
Class myclass = new Class();
IntPtr ptr=Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Class)));
GetClass(ptr);
Marshal.FreeHGlobal(ptr);
そう、このような処理は大丈夫ですが、私たちのAPIのパラメータはClass配列で、この処理方式はClass構造体のパラメータを渡すだけなので、この方式はここではあまり適切ではありません.
では、まずClass[]myclass=new Class[MaxClass];そしてマーシャルを使っていますAllochGlobalはmyclassデータのポインタを取得し、
実はこれも間違っていて、Class構造に含まれていて、直接封入できないStudio構造なので、どうしても上記の考えは間違っています!
では、どうすればいいのでしょうか.実は簡単です.まず、非管理メモリを割り当て、APIを呼び出してから、非管理コンテンツデータを管理構造体データに読み込むことです.
例のデモコードは次のとおりです.
//
[DllImport("CSharpInvokeCpp_CppDemo.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetClass(IntPtr pv, int len);
//
int size = Marshal.SizeOf(typeof(Class)) * 50;
IntPtr pBuff = Marshal.AllocHGlobal(size);
CppDLL.GetClass(pBuff, 50);
Class[] pClass = new Class[50];
for (int i = 0; i < 50; i++)
{
IntPtr pr = new IntPtr(pBuff.ToInt64() + Marshal.SizeOf(typeof(Class)) * i);
pClass[i] = (Class)Marshal.PtrToStructure(pr, typeof(Class));
}
Marshal.FreeHGlobal(pBuff);
5月19日学習内容:
http://tcspecial.iteye.com/blog/1675621
http://www.cnblogs.com/naiking/archive/2013/01/17/2864132.html
http://blog.csdn.net/zhangj1012003_2007/article/details/6283032
http://blog.csdn.net/xiaowei_cqu/article/details/7693985