在上一篇文章中介绍了 C#调用 C++类库函数的两个方法,在实际应用中可以根据需求的不同,利用不同的方法。其实,在 C#调用 C++编写的类库时,还是经常会遇到问题的,
本次就来进一步讲解,在这一过程中可能遇到的问题。

数据类型转换问题

C#是一门托管语言,运行于.NET 框架下的 CLR(Common Language Running)公共语言运行时,利用的是.NET 的基本数据类型,而这种数据类型与 C++是有所不同的。
因此,在函数调用过程中,我们需要将 C#的数据类型,转换为对应的 C++数据类型。下面列出了数据类型之间的对应关系:

C++数据类型 C#数据类型
char, INT8, SBYTE, CHAR sbyte
unsigned char, UINT8, UCHAR, BYTE byte
short, short int, INT16, SHORT short(Int16)
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR, __wchar_t UInt16
int, long, long int, INT32, INT, LONG32, BOOL int(Int32)
__int64, INT64, LONGLONG long(Int64)
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT UInt32
unsigned __int64, UINT64, DWORDLONG, ULONGLONG, UInt64
float, FLOAT Single
double, long double, DOUBLE Double

传递结构体

可能大家也注意到了,在上一篇文章中,给 DLL 类库的方法传参数时,我们使用了结构体,本次我们通过例子来说明如何向 C++函数传递结构体。

CPP 代码(定义结构体并导出函数):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
  typedef struct tarPointInfo {
    int SeqNo;
    long MemNo;
    char DataType[2 + 1];
    char MemName[69 + 1];
    char OutPutValue1[20 + 1];
    char OutPutValue2[20 + 1];
  }PointInfo;

  extern "C" __declspec(dllexport)
  int fnCPointReq(PointInfo* info)
  {
    if (info->SeqNo == 1 && info->MemNo == 100010) {
      strcpy_s(info->OutPutValue1, "rwqrqwerewr1");
      strcpy_s(info->OutPutValue2, "rwqrqwerewr2");
      return 0;
    }
    else {
      return 1;
    }
  }

C#代码调用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
  class Program
    {
        //注意特性里必须加入CallingConvention = CallingConvention.Cdecl,表面函数可以传递可变参数。
        //默认是CallingConvention.StdCall
        [DllImport(@"TESTFORCSHARPCALL.dll", EntryPoint = "fnCPointReq",
         CallingConvention = CallingConvention.Cdecl)]
        //传递ref参数,可以传值也可以得到返回值
        public extern static int fnCPointReq(ref PointInfo info);
        static void Main(string[] args)
        {
            PointInfo info = new PointInfo { SeqNo = 1, MemNo= 100010,
                                             DataType="TestData", MemName="TestName" };

            int result = fnCPointReq(ref info);

            if (result == 0)
            {
                Console.WriteLine(info.OutPutValue1);
                Console.WriteLine(info.OutPutValue2);
                Console.WriteLine("Success!!");
            }
            else
            {
                Console.WriteLine("Failed!!");
            }

            Console.ReadKey();
        }
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct PointInfo
    {
        public int SeqNo;
        public int MemNo;
        //char[]类型字符串,可以通过转换为UnmanagedType.ByValTStr类型来传递,但是必须标明SizeConst
        [MarshalAs(UnmanagedType.ByValTStr,SizeConst =3)]
        public string DataType;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 70)]
        public string MemName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]
        public string OutPutValue1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]
        public string OutPutValue2;
    }

以上就是关于参数类型相关的说明,最后要说明的是,C#侧定义结构体的参数顺序一定要与 C++的一致,否则会出现错误,因为 C#程序中的结构体是通过指针传递给 DLL 的,
DLL 在读取结构体数据时,时根据指针+偏移量的方式读取的,因此顺序至关重要。

对于复杂结构体作为传入和传出参数的说明,后续项目遇到会陆续补充。目前可以参照例子

(全文完)