平台调用P-INVOKE完全掌握, 结构体和结构体指针

这篇讲关于结构体和结构体指针的P-INVOKE,关键有4个P-INVOKE类型,结构体作为输入输出参数。结构体指针作为输入输出参数。还有结构体内的成员类型分为:数组,指针,指针数组,结构体,结构体指针,结构体数组,结构体指针数组。当然还有类继承(这里只介绍了单继承)。

  其中有一个比较费解的是结构体作为返回值的P-INVOKE的奇怪现象,下一篇结合反汇编讲解。

  第一:C++结构体和C#结构体对应关系,看下面。这里提到一点C# 声明结构体中的成员是数组的必须像下面那样声明:使用[MarshalAs(UnmanagedType.ByValArray, SizeConst = N)]

  C++代码不多,全部贴到这里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 1 struct Base
 2 {
 3   int BaseInt;
 4 };
 5 
 6  struct Test : Base
 7 {
 8   int TestIntArray[2];
 9   //
10    int * TestIntPointer;
11   int * TestIntPointerArray[2];
12   //
13    Base TestBase;
14   Base * TestBasePoint;
15   Base TestBaseArray[2];
16   Base * TestBasePointerArray[2];
17 };

  再来看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
 1  [StructLayout(LayoutKind.Sequential)]
 2   public struct Base
 3   {
 4     public int BaseInt;
 5   }
 6 
 7   [StructLayout(LayoutKind.Sequential)]
 8   public struct Test
 9   {
10     public Base _base;//把继承的基类放在第一个元素的位置。
11     //
12      [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
13     public int[] TestIntArray;
14     //
15      public IntPtr TestIntPointer;
16     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
17     public IntPtr[] TestIntPointerArray;
18     //
19      public Base TestBase;
20     public IntPtr TestBasePoint;
21     //
22      [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
23     public Base[] TestBaseArray;
24     [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)]
25     public IntPtr[] TestBasePointerArray;
26   }
27  

  第二。C++导出函数和C# P-INVOKE函数的对应。

  C++:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 1 static Test _test;
 2  void SetTest(Test test)
 3 {
 4   _test = test;
 5   PrintTest();
 6 }
 7 
 8  void SetTestPointer(Test * lptest)
 9 {
10   _test = * lptest;
11   PrintTest();
12 }
13 
14 Test GetTest()
15 {
16   return _test;  
17 }
18 
19 Test * GetTestPointer()
20 {
21   return &_test;
22 }

  C#:   注意结构体作为返回值的P-INVOKE声明是不是很奇怪。不过运行很正常。下一篇结合反汇编说。

1
2
3
4
5
6
7
8
9
10
11
 1     [DllImport("TestDll")]
 2     public static extern void SetTest(Test test);
 3 
 4     [DllImport("TestDll")]
 5     public static extern void SetTestPointer(IntPtr lptest);
 6 
 7     [DllImport("TestDll")]
 8     public static extern void GetTest(IntPtr lptest); //注意声明。
 9  
10     [DllImport("TestDll")]
11     public static extern IntPtr GetTestPointer();

  第三:看下C#如何调用,这里用到了Marshal.AllocHGlobal 方法,和Alloc功能基本一样,会造成内存泄露,使用完了记住使用Marshal.FreeHGlobal函数释放申请的内存。

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
 1     private Test _test = new Test();
 2 
 3     public void Run()
 4     {
 5       InitTest();
 6       //#########################
 7        SetTest(_test);
 8       Console.WriteLine("-------------------------------------------------------------\n");
 9       //#########################  
10        _test._base.BaseInt = 9999;
11   //Marshal.AllocHGlobal 和WIN32 API, Alloc功能基本一样,
12   //这个方法不要多用,可能造成内存泄露。
13   //记住使用Marshal.FreeHGlobal函数释放申请的内存
14        IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Test)));
15       Marshal.StructureToPtr(_test,p,false);
16       SetTestPointer(p);
17       Console.WriteLine("-------------------------------------------------------------\n");
18       //#########################
19        IntPtr pp = GetTestPointer();
20       Test temp = (Test)Marshal.PtrToStructure(pp, typeof(Test));
21       PrintTest(temp);
22       Console.WriteLine("-------------------------------------------------------------\n");
23 
24       //#########################
25        IntPtr pp2 = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Test)));
26       GetTest(pp2);
27       Test temp2 = (Test)Marshal.PtrToStructure(pp2, typeof(Test));
28       PrintTest(temp2);
29       
30     }

  总结一下:Marshal.StructureToPtr从托管类复制数据到未托管的内存中,Marshal.PtrToStructure恰好相反。Marshal.AllocHGlobal申请非托管内存,Marshal.FreeHGlobal函数释放非托管内存。使用 Marshal.Read系列读写指针,使用Marshal.ReadIntPtr来读写二级指针。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值