C#呼び出しC++DLL伝達構造体配列の究極の解決策

8255 ワード

プロジェクト開発時に、C++パッケージのDLLを呼び出すには、通常のタイプのC#に対応し、DLLから関数を導入するにはDllImportで転送すればよい.しかし、構造体、構造体配列、または構造体ポインタが渡されると、C#に対応できるタイプはないことがわかります.このときどうするかというと、第一反応はC#も構造体を定義し、パラメータとして弟に伝えることです.しかし、構造体を定義した後にパラメータを渡したい場合、異常を投げたり、構造体に入ったりしますが、戻り値は私たちが望んでいるものではありません.デバッグ追跡後、それらの値はまったく変更されていません.コードは以下の通りです.
 [DllImport("workStation.dll")]
        private static extern bool fetchInfos(Info[] infos);
        public struct Info
        {
            public int OrderNO;

            public byte[] UniqueCode;

            public float CpuPercent;                  

        };
        private void buttonTest_Click(object sender, EventArgs e)
        {
            try
            {
		        Info[] infos=new Info[128];
                if (fetchInfos(infos))
                {
                    MessageBox.Show("Fail");
                }
		        else
		        {
                    string message = "";
                    foreach (Info info in infos)
                    {
                        message += string.Format("OrderNO={0}\r
UniqueCode={1}\r
Cpu={2}", info.OrderNO, Encoding.UTF8.GetString(info.UniqueCode), info.CpuPercent ); } MessageBox.Show(message); } } catch (System.Exception ex) { MessageBox.Show(ex.Message); } }

その後、資料を探してみると、C#は管理メモリに属しているという文書があり、構造体の配列を渡し、属性が管理メモリではないので、Marshで空間を指定してから渡す必要があります.そこで構造体を以下のように変更した.
   [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
        public struct Info
        {
            public int OrderNO;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            public byte[] UniqueCode;

            public float CpuPercent;                  

        };

しかし、このような改善を経ても、実行結果は依然として理想的ではなく、値が間違っているか、変更されていないかのいずれかです.これはいったい何の原因ですか.絶えず資料を探して、ついに1篇を見て、中は構造体の伝達に言及して、あるのは上述のようにすることができて、しかしあるのはだめで、特にパラメータがC++の中で構造体のポインタあるいは構造体の配列のポインタである時、C#の呼び出しの地方でもポインタで対応して、後で以下のコードを改善します.
    [DllImport("workStation.dll")]
        private static extern bool fetchInfos(IntPtr infosIntPtr);
        [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
        public struct Info
        {
            public int OrderNO;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            public byte[] UniqueCode;

            public float CpuPercent;

        };
        private void buttonTest_Click(object sender, EventArgs e)
        {
            try
            {
                int workStationCount = 128;
                int size = Marshal.SizeOf(typeof(Info));
                IntPtr infosIntptr = Marshal.AllocHGlobal(size * workStationCount);
                Info[] infos = new Info[workStationCount];
                if (fetchInfos(infosIntptr))
                {
                    MessageBox.Show("Fail");
                    return;
                }
                for (int inkIndex = 0; inkIndex < workStationCount; inkIndex++)
                {
                    IntPtr ptr = (IntPtr)((UInt32)infosIntptr + inkIndex * size);
                    infos[inkIndex] = (Info)Marshal.PtrToStructure(ptr, typeof(Info));
                }

                Marshal.FreeHGlobal(infosIntptr);

                string message = "";
                foreach (Info info in infos)
                {
                    message += string.Format("OrderNO={0}\r
UniqueCode={1}\r
Cpu={2}", info.OrderNO, Encoding.UTF8.GetString(info.UniqueCode), info.CpuPercent ); } MessageBox.Show(message); } catch (System.Exception ex) { MessageBox.Show(ex.Message); } }
インタフェースがIntPtrに変更されたことに注意してください.以上のようにして,やっと構造体配列を伝達した.ただし、ここでは、上記の構造体など、異なるコンパイラによる構造体の大きさが必ずしも一定ではないことに注意してください.
BCBでバイトアラインメントがないと、一般的な構造体より2バイト多い場合があります.BCBはデフォルトで2バイトソート、VCはデフォルトで1バイトソートです.この問題を解決するには、BCBの構造体にバイトアラインメントを追加するか、C#に2バイト以上(複数ある場合)を追加します.バイト整列コードは以下の通りです.
#pragma pack(push,1)
   struct Info
{
    int OrderNO;
           
    char UniqueCode[32];

    float CpuPercent;
};
#pragma pack(pop)

マーシュでAllochGlobalは構造体ポインタのためにメモリ空間を開き、非管理メモリを変更することを目的としているが、Marshを使用しない場合は.AllochGlobal、他に方法はありませんか?
実は、C++のポインタでも配列でも、最終的にはメモリに1バイトずつ格納されています.つまり、最終的には1次元のバイト配列の形で表示されるので、等大の1次元配列を開くと、それでいいのではないでしょうか.答えは可能で、以下に実現を示します.
[DllImport("workStation.dll")]
        private static extern bool fetchInfos(IntPtr infosIntPtr);
        [DllImport("workStation.dll")]
        private static extern bool fetchInfos(byte[] infos);
        [StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
        public struct Info
        {
            public int OrderNO;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)]
            public byte[] UniqueCode;

            public float CpuPercent;

        };

   
        private void buttonTest_Click(object sender, EventArgs e)
        {
            try
            {
                int count = 128;
                int size = Marshal.SizeOf(typeof(Info));
                byte[] inkInfosBytes = new byte[count * size];                
                if (fetchInfos(inkInfosBytes))
                {
                    MessageBox.Show("Fail");
                    return;
                }
                Info[] infos = new Info[count];
                for (int inkIndex = 0; inkIndex < count; inkIndex++)
                {
                    byte[] inkInfoBytes = new byte[size];
                    Array.Copy(inkInfosBytes, inkIndex * size, inkInfoBytes, 0, size);
                    infos[inkIndex] = (Info)bytesToStruct(inkInfoBytes, typeof(Info));
                }

                string message = "";
                foreach (Info info in infos)
                {
                    message += string.Format("OrderNO={0}\r
UniqueCode={1}\r
Cpu={2}", info.OrderNO, Encoding.UTF8.GetString(info.UniqueCode), info.CpuPercent ); } MessageBox.Show(message); } catch (System.Exception ex) { MessageBox.Show(ex.Message); } } #region bytesToStruct /// /// Byte array to struct or classs. /// /// Byte array /// Struct type or class type. /// Egg:class Human{...}; /// Human human=new Human(); /// Type type=human.GetType(); /// Destination struct or class. public static object bytesToStruct(byte[] bytes, Type type) { int size = Marshal.SizeOf(type);//Get size of the struct or class. if (bytes.Length < size) { return null; } IntPtr structPtr = Marshal.AllocHGlobal(size);//Allocate memory space of the struct or class. Marshal.Copy(bytes, 0, structPtr, size);//Copy byte array to the memory space. object obj = Marshal.PtrToStructure(structPtr, type);//Convert memory space to destination struct or class. Marshal.FreeHGlobal(structPtr);//Release memory space. return obj; } #endregion
データをどのように伝えるか本当に思いつかない場合は、byte配列を考慮して(整数でも4バイト(32ビット以下)を開く限り)、開いた長さに対応する上で、データを取得した後、タイプルールに従って所望のデータに変換すれば、一般的に目的を達成することができます.
転載は出典を明記してくださいhttp://blog.csdn.net/xxdddail/article/details/11781003