c#编程指南(十四) 平台调用P-INVOKE完全掌握, 反汇编细解结构体作为返回值
2010-08-27 11:41
555 查看
这篇解决上篇那个结构体作为返回值的问题。我们结合反汇编来探索这里面的秘密。如何反汇编?
方法如下:在C++函数内下断点,调试到断点断下,右键菜单,选择"反汇编",反汇编是VS自带功能。
下面是几个简单的类:
分别用上面的方法来逐个分析汇编:汇编里有我详细的注释:
总结一下,这个结构体作为返回值,主要依赖于编译器对于超过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);
返回值不同却都可以调用?嘿嘿!!
方法如下:在C++函数内下断点,调试到断点断下,右键菜单,选择"反汇编",反汇编是VS自带功能。
下面是几个简单的类:
struct Test1 { int Count1; }; struct Test2 { int Count1; int Count2; }; struct Test3 { int Count1; int Count2; int Count3; };
分别用上面的方法来逐个分析汇编:汇编里有我详细的注释:
当返回Test1的时候:汇编如下: 函数内: ;----------------------------GetTest函数内------------------------------------------------- _test.Count = 3333; 00411B0E mov dword ptr [_test (4174C4h)],0D05h return _test; 00411B18 mov eax,dword ptr [_test (4174C4h)] ; //把返回值放到EAX寄存器 ;---------------------------主函数调用GetTest---------------------------------------------- Test test = GetTest(); 004135FE call GetTest (4110E1h) 00413603 mov dword ptr [ebp-0D4h],eax ;//将返回值存储在栈中分配的变量中。 ;--------------------------------------------------------------------------------------------------- 看来虽然Test1是结构体,但是由于就占4个字节,所以通过EAX正常返回。 看下Test2的反汇编: ;----------------------------GetTest函数内------------------------------------------------- _test.Count = 3333; 00411B0E mov dword ptr [_test (4174C4h)],0D05h _test.Count2 = 4444; 00411B18 mov dword ptr [_test+4 (4174C8h)],115Ch return _test; 00411B22 mov eax,dword ptr [_test (4174C4h)] ;返回值通过EAX,EDX返回 00411B27 mov edx,dword ptr [_test+4 (4174C8h)] ;---------------------------主函数调用GetTest---------------------------------------------- Test test = GetTest(); 004135FE call GetTest (4110E1h) 00413603 mov dword ptr [ebp-0DCh],eax ;返回值存入栈中变量 00413609 mov dword ptr [ebp-0D8h],edx ;--------------------------------------------------------------------------------------------------- 虽然Test2占8个字节,但是编译器通过组合EAX,EDX可以正确返回。 看下Test3的反汇编: ;----------------------------GetTest函数内------------------------------------------------- 00411470 push ebp 00411471 mov ebp,esp ;构建栈帧 00411473 sub esp,0C0h ;分配局部变量 00411479 push ebx 0041147A push esi 0041147B push edi ;保存寄存器环境 0041147C lea edi,[ebp-0C0h] 00411482 mov ecx,30h 00411487 mov eax,0CCCCCCCCh 0041148C rep stos dword ptr es:[edi] ;设置trap area. _test.Count1 = 3333; 0041148E mov dword ptr [_test (417140h)],0D05h _test.Count2 = 4444; 00411498 mov dword ptr [_test+4 (417144h)],115Ch _test.Count3 = 5555; 004114A2 mov dword ptr [_test+8 (417148h)],15B3h return _test; 004114AC mov eax,dword ptr [ebp+8] ;得到第一个参数,注意__stdcall从右向左压栈. 004114AF mov ecx,dword ptr [_test (417140h)] 004114B5 mov dword ptr [eax],ecx ;参数是一个指针,写入第一个成员; 004114B7 mov edx,dword ptr [_test+4 (417144h)] 004114BD mov dword ptr [eax+4],edx ;参数是一个指针,写入第二个成员; 004114C0 mov ecx,dword ptr [_test+8 (417148h)] 004114C6 mov dword ptr [eax+8],ecx ;参数是一个指针,写入第三个成员; 004114C9 mov eax,dword ptr [ebp+8] ;---------------------------主函数调用GetTest---------------------------------------------- Test test = GetTest(); 004113BE lea eax,[ebp-0E4h] 004113C4 push eax ;压栈参数 004113C5 call GetTest (4110E1h) 004113CA add esp,4 ;回收栈帧 004113CD mov ecx,dword ptr [eax] ;返回参数。 004113CF mov dword ptr [ebp-0F8h],ecx ; 写入局部变量test 004113D5 mov edx,dword ptr [eax+4] 004113D8 mov dword ptr [ebp-0F4h],edx 004113DE mov eax,dword ptr [eax+8] 004113E1 mov dword ptr [ebp-0F0h],eax 004113E7 mov ecx,dword ptr [ebp-0F8h] 004113ED mov dword ptr [test],ecx 004113F0 mov edx,dword ptr [ebp-0F4h] 004113F6 mov dword ptr [ebp-0Ch],edx 004113F9 mov eax,dword ptr [ebp-0F0h] 004113FF mov dword ptr [ebp-8],eax return 0; 00411402 xor eax,eax ;--------------------------------------------------------------------------------------------------- Test3占12字节,无法正常通EAX,EDX返回,所以编译器把函数编译成带输入参数的函数, 就好像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);
返回值不同却都可以调用?嘿嘿!!
相关文章推荐
- 平台调用P-INVOKE完全掌握, 反汇编细解结构体作为返回值
- c#编程指南——平台调用P-INVOKE完全掌握,C#和C++互相调用
- c#编程指南(十) 平台调用P-INVOKE完全掌握, 字符串和指针
- c#编程指南(九) 平台调用P-INVOKE完全掌握,C#和C++互相调用
- c#编程指南(十一) 平台调用P-INVOKE完全掌握, 指针大全
- c#编程指南(十五) 平台调用P-INVOKE完全掌握(完结篇),自定义Mashaler
- c#编程指南(十) 平台调用P-INVOKE完全掌握, 字符串和指针
- C#调用C++ 平台调用P/Invoke 结构体--输入输出参数、返回值、返出值、结构体数组作为参数【五】
- c#编程指南(十三) 平台调用P-INVOKE完全掌握, 结构体和结构体指针
- 平台调用 4000 P-INVOKE完全掌握,C#和C++互相调用
- c#编程指南(十) 平台调用P-INVOKE完全掌握, 字符串和指针
- c#编程指南(十二) 平台调用P-INVOKE完全掌握, 结构体边界对齐和内存布局
- 平台调用P-INVOKE完全掌握, 指针大全
- 平台调用P-INVOKE完全掌握, 结构体和结构体指针
- 平台调用P-INVOKE完全掌握, 字符串和指针
- 平台调用P-INVOKE完全掌握, 结构体边界对齐和内存布局
- 平台调用P-INVOKE完全掌握, 结构体和结构体指针
- 如何:实现和调用自定义扩展方法(C# 编程指南)
- 使用结构(C# 编程指南)
- c#平台调用传嵌套结构体问题