c#编程指南(十四) 平台调用P-INVOKE完全掌握, 反汇编细解结构体作为返回值

这篇解决上篇那个结构体作为返回值的问题。我们结合反汇编来探索这里面的秘密。如何反汇编?

方法如下:在C++函数内下断点,调试到断点断下,右键菜单,选择"反汇编",反汇编是VS自带功能。

 

下面是几个简单的类:

1 struct Test1
2 {
3 int Count1;
4 };
5
6  struct Test2
7 {
8 int Count1;
9 int Count2;
10 };
11
12  struct Test3
13 {
14 int Count1;
15 int Count2;
16 int Count3;
17 };

 

分别用上面的方法来逐个分析汇编:汇编里有我详细的注释:

1
2 当返回Test1的时候:汇编如下:
3 函数内:
4  ;----------------------------GetTest函数内-------------------------------------------------
5   _test.Count = 3333;
6  00411B0E mov dword ptr [_test (4174C4h)],0D05h
7 return _test;
8 00411B18 mov eax,dword ptr [_test (4174C4h)] ; //把返回值放到EAX寄存器
9 ;---------------------------主函数调用GetTest----------------------------------------------
10 Test test = GetTest();
11 004135FE call GetTest (4110E1h)
12 00413603 mov dword ptr [ebp-0D4h],eax ;//将返回值存储在栈中分配的变量中。
13 ;---------------------------------------------------------------------------------------------------
14
15 看来虽然Test1是结构体,但是由于就占4个字节,所以通过EAX正常返回。
16
17
18 看下Test2的反汇编:
19 ;----------------------------GetTest函数内-------------------------------------------------
20 _test.Count = 3333;
21 00411B0E mov dword ptr [_test (4174C4h)],0D05h
22 _test.Count2 = 4444;
23 00411B18 mov dword ptr [_test+4 (4174C8h)],115Ch
24 return _test;
25 00411B22 mov eax,dword ptr [_test (4174C4h)] ;返回值通过EAX,EDX返回
26 00411B27 mov edx,dword ptr [_test+4 (4174C8h)]
27 ;---------------------------主函数调用GetTest----------------------------------------------
28 Test test = GetTest();
29 004135FE call GetTest (4110E1h)
30 00413603 mov dword ptr [ebp-0DCh],eax ;返回值存入栈中变量
31 00413609 mov dword ptr [ebp-0D8h],edx
32 ;---------------------------------------------------------------------------------------------------
33
34 虽然Test2占8个字节,但是编译器通过组合EAX,EDX可以正确返回。
35
36
37 看下Test3的反汇编:
38 ;----------------------------GetTest函数内-------------------------------------------------
39 00411470 push ebp
40 00411471 mov ebp,esp ;构建栈帧
41
42 00411473 sub esp,0C0h ;分配局部变量
43
44 00411479 push ebx
45 0041147A push esi
46 0041147B push edi ;保存寄存器环境
47
48 0041147C lea edi,[ebp-0C0h]
49 00411482 mov ecx,30h
50 00411487 mov eax,0CCCCCCCCh
51 0041148C rep stos dword ptr es:[edi] ;设置trap area.
52
53 _test.Count1 = 3333;
54 0041148E mov dword ptr [_test (417140h)],0D05h
55 _test.Count2 = 4444;
56 00411498 mov dword ptr [_test+4 (417144h)],115Ch
57 _test.Count3 = 5555;
58 004114A2 mov dword ptr [_test+8 (417148h)],15B3h
59
60
61 return _test;
62 004114AC mov eax,dword ptr [ebp+8] ;得到第一个参数,注意__stdcall从右向左压栈.
63
64 004114AF mov ecx,dword ptr [_test (417140h)]
65 004114B5 mov dword ptr [eax],ecx ;参数是一个指针,写入第一个成员;
66 004114B7 mov edx,dword ptr [_test+4 (417144h)]
67 004114BD mov dword ptr [eax+4],edx ;参数是一个指针,写入第二个成员;
68 004114C0 mov ecx,dword ptr [_test+8 (417148h)]
69 004114C6 mov dword ptr [eax+8],ecx ;参数是一个指针,写入第三个成员;
70 004114C9 mov eax,dword ptr [ebp+8]
71 ;---------------------------主函数调用GetTest----------------------------------------------
72 Test test = GetTest();
73 004113BE lea eax,[ebp-0E4h]
74 004113C4 push eax ;压栈参数
75 004113C5 call GetTest (4110E1h)
76 004113CA add esp,4 ;回收栈帧
77
78 004113CD mov ecx,dword ptr [eax] ;返回参数。
79
80 004113CF mov dword ptr [ebp-0F8h],ecx ; 写入局部变量test
81 004113D5 mov edx,dword ptr [eax+4]
82 004113D8 mov dword ptr [ebp-0F4h],edx
83 004113DE mov eax,dword ptr [eax+8]
84 004113E1 mov dword ptr [ebp-0F0h],eax
85 004113E7 mov ecx,dword ptr [ebp-0F8h]
86 004113ED mov dword ptr [test],ecx
87 004113F0 mov edx,dword ptr [ebp-0F4h]
88 004113F6 mov dword ptr [ebp-0Ch],edx
89 004113F9 mov eax,dword ptr [ebp-0F0h]
90 004113FF mov dword ptr [ebp-8],eax
91 return 0;
92 00411402 xor eax,eax
93 ;---------------------------------------------------------------------------------------------------
94
95 Test3占12字节,无法正常通EAX,EDX返回,所以编译器把函数编译成带输入参数的函数,
96 就好像Test test = GetTest(&test);一样。

 

 

总结一下,这个结构体作为返回值,主要依赖于编译器对于超过8字节的返回值的编译处理。
第一:不建议使用结构体作为返回值,因为这太依赖编译器了。

第二:如果没法改变,就用如下的方法判断:            

//if (Marshal.SizeOf(typeof(X)) > 8)            

//{            

//    返回值作为输出参数。            

//}            

//else           

 //{             

//    返回值正常返回。           

 //}

第三:<=8字节,正常的P-INVOKE,不用修改。

第四:>8字节,把返回值作为从左向右的第一个输入输出参数:下面是简单的示例

Test GetTest();   ====> IntPtr GetTest(IntPtr lptest);

Test GetTest(int i) ======> IntPtr GetTest(IntPtr lptest, int i);

 

最后:C#调用Test3 _stdcall GetTest3()为什么:        

[DllImport("TestDll")]        

public static extern IntPtr GetTest3(IntPtr lptest);


[DllImport("TestDll",EntryPoint="GetTest3")]        

public static extern void Test3Extra(IntPtr p);


返回值不同却都可以调用?嘿嘿!!

posted @ 2010-08-27 11:41  香山飘雪  阅读(1750)  评论(0编辑  收藏  举报