您的位置:首页 > 编程语言 > C#

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

2010-08-27 11:41 555 查看
这篇解决上篇那个结构体作为返回值的问题。我们结合反汇编来探索这里面的秘密。如何反汇编?

方法如下:在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);

返回值不同却都可以调用?嘿嘿!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: