C++ 调用C++;使用参数和返回值来自程序集的方法
所以我以前问过这个问题,但细节要少得多。问题标题准确地描述了问题:我有一个C++方法,我试图从汇编(x86)调用,它既有参数又有返回值。我对汇编和对C++的理解有一个粗略的理解(否则我就不会承担这个问题)。以下是我所掌握的代码:C++ 调用C++;使用参数和返回值来自程序集的方法,c++,assembly,parameters,x86,return-value,C++,Assembly,Parameters,X86,Return Value,所以我以前问过这个问题,但细节要少得多。问题标题准确地描述了问题:我有一个C++方法,我试图从汇编(x86)调用,它既有参数又有返回值。我对汇编和对C++的理解有一个粗略的理解(否则我就不会承担这个问题)。以下是我所掌握的代码: // methodAddr is a pointer to the method address void* methodAddr = method->Address; // buffer is an int array of parameter values.
// methodAddr is a pointer to the method address
void* methodAddr = method->Address;
// buffer is an int array of parameter values. The parameters can be anything (of any type)
// but are copied into an int array so they can be pushed onto the stack in reverse order
// 4 bytes at a time (as in push (int)). I know there's an issue here that is irrelevent to my baseline testing, in that if any parameter is over 4 bytes it will be broken and
// reversed (which is not good) but for basic testing this isn't an issue, so overlook this.
for (int index = bufferElementCount - 1; index >= 0; index--)
{
int val = buffer[index];
__asm
{
push val
}
}
int returnValueCount = 0;
// if there is a return value, allocate some space for it and push that onto the stack after
// the parameters have been pushed on
if (method->HasReturnValue)
{
*returnSize = method->ReturnValueSize;
outVal = new char[*returnSize];
returnValueCount = (*returnSize / 4) + (*returnSize % 4 != 0 ? 1 : 0);
memset(outVal, 0, *returnSize);
for (int index = returnValueCount - 1; index >= 0; index--)
{
char* addr = ((char*)outVal) + (index * 4);
__asm
{
push addr
}
}
}
// calculate the stack pointer offset so after the call we can pop the parameters and return value
int espOffset = (bufferElementCount + returnValueCount) * 4;
// call the method
__asm
{
call methodAddr;
add esp, espOffset
};
对于我的基本测试,我使用具有以下特征的方法:
Person MyMethod3( int, char, int );
问题是:当从方法签名中忽略返回值时,所有参数值都被正确传递。但当我保持方法不变时,传递的参数数据不正确,但返回的值是正确的。很明显,我的问题是,到底出了什么问题?我尝试在参数之前将返回值空间推到堆栈上。人员结构如下:
class Person
{
public:
Text Name;
int Age;
float Cash;
ICollection<Person*>* Friends;
};
下面是对其的分解:
00CB0A20 mov dword ptr [one],36Ch
char two = 'X';
00CB0A27 mov byte ptr [two],58h
int three = 9738;
00CB0A2B mov dword ptr [three],260Ah
Person p = MyMethod3(one, two, three);
00CB0A32 push 10h
00CB0A34 lea ecx,[p]
00CB0A37 call Person::__autoclassinit2 (0C6AA2Ch)
00CB0A3C mov eax,dword ptr [three]
00CB0A3F push eax
00CB0A40 movzx ecx,byte ptr [two]
00CB0A44 push ecx
00CB0A45 mov edx,dword ptr [one]
00CB0A48 push edx
00CB0A49 lea eax,[p]
00CB0A4C push eax
00CB0A4D call MyMethod3 (0C6B783h)
00CB0A52 add esp,10h
00CB0A55 mov dword ptr [ebp-4],0
我对此的解释如下:
执行对局部变量的赋值。然后创建输出寄存器。然后将参数放入一个特定的寄存器(这里的顺序恰好是eax
、ecx
和edx
,这是有意义的(eax
和ebx
是一个寄存器,ecx
是两个寄存器,edx
和其他一些寄存器是最后一个参数?)。然后调用LEA
(加载有效地址),我不理解,但已理解为MOV
。然后它调用以地址作为参数的方法?然后移动堆栈指针以弹出参数和返回值
任何进一步的解释都是值得赞赏的,因为我确信我在这里的理解有点缺陷。出于某种原因,调用约定似乎在方法的两个版本之间发生了变化。你试过强迫它使用一个特定的平台吗?哪个平台?哪种ABI?哪个电话会议?您是否记得传递
this
指针?指定方法的调用约定很可能是stdcall
或thiscall
。我有办法确定两者之间的差异,但我只是测试传统的stdcall
。问题是被调用的方法可以是任何类型:它可以有任意数量的任何类型的参数,并且可能有返回值,也可能没有返回值。您已经有了一个引用实现:编译器。在C++中编写调用,编译它,然后查看编译器做了什么。你会发现它比你拥有的更复杂。事实上,您想要的是不可能的,因为编译器可以使用类似于自定义调用约定的优化。我尝试过查看反汇编,但它做了一些非常复杂的事情,涉及LEA和其他我不确定的术语。我会把DA寄出去。
00CB0A20 mov dword ptr [one],36Ch
char two = 'X';
00CB0A27 mov byte ptr [two],58h
int three = 9738;
00CB0A2B mov dword ptr [three],260Ah
Person p = MyMethod3(one, two, three);
00CB0A32 push 10h
00CB0A34 lea ecx,[p]
00CB0A37 call Person::__autoclassinit2 (0C6AA2Ch)
00CB0A3C mov eax,dword ptr [three]
00CB0A3F push eax
00CB0A40 movzx ecx,byte ptr [two]
00CB0A44 push ecx
00CB0A45 mov edx,dword ptr [one]
00CB0A48 push edx
00CB0A49 lea eax,[p]
00CB0A4C push eax
00CB0A4D call MyMethod3 (0C6B783h)
00CB0A52 add esp,10h
00CB0A55 mov dword ptr [ebp-4],0