6

我正在做一个 cpp 项目。项目需要迁移到 64 位。它包含一些不能在 x64 上编译的内联汇编代码。这是包含汇编代码的函数:

void ExternalFunctionCall::callFunction(ArgType resultType, void* resultBuffer)
{
#if defined(_NT_) || defined(__OS2__)

    // I386

    // just copy the args buffer to the stack (it's already layed out correctly)
    int* begin = m_argsBegin;
    int* ptr = m_argsEnd;
    int arr[1000], i=0;
    while (ptr > begin) {
        int val = *(--ptr);

        __asm push val
    }

    void* functionAddress = m_functionAddress;

    // call the function & handle the return value.  use __stdcall calling convention
    switch (resultType) {
    case voidType:
        __asm {
            call functionAddress
        }
        break;
    case pointerType:
    case int32Type:
        __asm {
            call functionAddress
            mov ebx, resultBuffer
            mov dword ptr [ebx],eax
        }
        break;
    case floatType:
        __asm {
            call functionAddress
            mov ebx, resultBuffer
            fstp dword ptr [ebx]
        }
        break;
    case doubleType:
        __asm {
            call functionAddress
            mov ebx, resultBuffer
            fstp qword ptr [ebx]
        }
        break;
    }

我使用堆栈、数组来迁移这个“asm push val”,但没有用。虽然,它不会引发任何编译错误,但逻辑不起作用。

所以,我想问,我可以在 C++ 中使用什么来代替“__asm push val”。任何帮助将不胜感激。

4

3 回答 3

13

这个问题一般是无法解决的;那是因为评论

// call the function & handle the return value.  use __stdcall calling convention

表示依赖32 位调用约定

在 32 位 x86 中,stdcall意味着所有参数都以相反的顺序在堆栈上传递(即最后一个参数首先被压入。也就是说,如果arg[0]是 at addrthen arg[1],无论它是什么类型,都是 at addr + sizeof(arg[0]))。这就是示例中包含以下代码的原因:

// just copy the args buffer to the stack (it's already layed out correctly)
int* begin = m_argsBegin;
int* ptr = m_argsEnd;
int arr[1000], i=0;
while (ptr > begin) {
    int val = *(--ptr);

    __asm push val
}

实际上可以工作 - 因为它根本不关心究竟是什么,参数是什么类型;所有相关的是它们中的每一个都在一个已知的内存位置,并且已知在内存中是连续的。如果你知道参数N是 at addr,那么你可以知道参数N+1是 at addr + sizeof(arg[N])

这就是评论所说的“它的布局已经正确” - 不幸的是,这在 64 位模式下是不正确的。因此代码不能被“移植”;没有等价于端口。

至少部分基于寄存器的调用约定 - x64(64 位 x86)上的 Win64 的行为不同。对于那些,这取决于被调用函数采用什么类型的参数(在 Windows 中,您可以在通用寄存器中传递四个整数类型参数加上寄存器中的一些浮点类型参数XMM)。因此,您需要更多地了解您调用的函数的签名(原型),而不仅仅是“它需要N参数”,以便能够正确地从像上面这样的“anycall”类型包装器中编组参数。在 64 位模式下,对于您希望通过包装器调用的每个函数,您不仅需要知道总共有多少个 arg,还需要知道有多少在通用 reg 中,有多少在XMMreg 中,以及有多少在堆栈上。

“通过指针调用函数并将返回值复制到已知位置”部分是可移植的,可以用纯 C/C++ 表示。但是,如上所述,为此获得参数的部分并没有以任何直接的方式在 32 位和我所知道的任何 64 位 x86 调用约定之间移植(Win64/x64 和 UN*X约定都不允许预测位置和函数的所有参数的总堆栈内存使用量仅给出参数的数量和类型,但不考虑它们的顺序)。stdcallx86_64

您究竟需要做什么更多地取决于上述调用者/用户,而class ExternalFunctionCall不是您展示的内联汇编的小样本。尤其有必要知道成员m_argsBeginm_argsEnd是如何初始化的以及在哪里初始化的。您能否提供更多关于类的外观(所有成员变量/函数)的详细信息,以及其实际使用的示例?

于 2013-04-22T11:54:10.473 回答
0

您需要解决几件事。(我在另一个问题上查找了您的代码)。据我了解,此代码是一个包装器,用于调用位于指定地址的非常抽象的函数,该函数期望堆栈中有一定数量的数据,并且可以返回基于 ArgType 的不同内容。

如果你想通过普通的 C 来包装它,你必须定义几个函数原型(基于返回值)然后在你的 switch 上使用,但是你必须解决另一个问题,这更棘手。

在 C 中移植这些东西的问题是你不知道你必须预先推入堆栈的参数数量(数据大小),所以你在定义原型时会遇到麻烦。

假设 func(char c) 肯定会推入堆栈 1 个字节(但由于数据对齐,也不总是正确的).. 在您的情况下,您必须考虑具有与数据大小相等的参数集的解决方案你需要在堆栈上。乍一看,这不是您可以立即做的事情。

UPD。你可以用 func( char [] param ); 但它也有上面回答中解释的问题。

于 2013-04-22T10:13:31.770 回答
0

我意识到这是一个有点老的问题,但我偶然发现:xbyak

也许你在找什么?

于 2014-01-11T13:27:14.603 回答