__stdcall的优势/用途是什么?

3
在发布这个问题之前,我已经阅读了一些文档,SO答案和观看了一些视频来理解__stdcall。到目前为止,我已经理解了它是一种调用约定,并指定将参数从右到左推到堆栈上。这也表示何时清除堆栈。
但我仍然无法理解使用__stdcall的优点和应用场景。
我遇到了以下代码,当关闭OPC-UA客户端时调用它。我看到这应该遵循__stdcall调用约定,但为什么?如果未为以下方法指定__stdcall,可能会发生什么?
OpcUa_StatusCode __stdcall opcua_client::onShutdownMessage(OpcUa_Handle hApplication, OpcUa_Handle hSession, OpcUa_String strShutdownMessage, void* extraParam)
{
    opcua_client* pOpcClient = NULL;
    if (extraParam)
    {
        try
        {
            pOpcClient = qobject_cast <opcua_client*>((QObject*)extraParam);
            throw(55);
        }
        catch (int exeption)
        {

        }
    }

    OpcUa_StatusCode uStatus = OpcUa_Good;

    QString strShutDownMsg = QString::fromUtf8(OpcUa_String_GetRawString(&strShutdownMessage));
    bool bOK;
    OpcUa_StatusCode uClientLibStatus = uClientLibStatus = strShutDownMsg.toUInt(&bOK, 16);

    if (pOpcClient)
    {
        if (uClientLibStatus > 0)
            pOpcClient->process_onShutdownMessage(uClientLibStatus);
    }
    else
    {
        return uStatus;
    }

    return uStatus;
}

这是32位代码中操作系统和COM库的标准调用约定。OPC严重依赖于COM,因此预计会看到很多stdcall。尽管onShutdownMessage()不是标准接口函数之一。也许您使用的工具包规定了stdcall,这并不罕见。 - Hans Passant
2个回答

3

调用函数时必须遵循调用约定。如果有调用者,则必须按特定方式调用此函数。当您拥有回调函数时,通常很重要。

除此之外,stdcall 旨在通过将参数清理代码从调用者移动到被调用者(可以在 x86 上使用 ret imm16)来减少代码大小与 cdecl 相比。

这是 IA-32 架构的一种古老微优化。MSVC 在其他架构上也接受 __stdcall 关键字,但在那里 没有任何作用

请查看此示例:

__declspec(noinline) int sum(int a, int b)
{
    return a + b;
}

int add1(int a)
{
    return sum(a, 1);
}

MSVC将其编译为:

int sum(int,int) PROC
  mov eax, DWORD PTR _a$[esp-4]
  add eax, DWORD PTR _b$[esp-4]
  ret 0 ; this instruction will be replaced
int sum(int,int) ENDP

int add1(int) PROC
  push 1
  push DWORD PTR _a$[esp]
  call int sum(int,int)
  add esp, 8 ; this instruction will be removed
  ret 0
int add1(int) ENDP

带有stdcall版本:

__declspec(noinline) int __stdcall sum(int a, int b)
{
    return a + b;
}

int add1(int a)
{
    return sum(a, 1);
}

编译成:

int sum(int,int) PROC
  mov eax, DWORD PTR _a$[esp-4]
  add eax, DWORD PTR _b$[esp-4]
  ret 8 ; 0 argument replaced with 8
int sum(int,int) ENDP

int add1(int) PROC
  push 1
  push DWORD PTR _a$[esp]
  call int sum(int,int)
  ret 0 ; extra instruction is missing here
int add1(int) ENDP

注意第二个示例如何使用ret 8,而调用者没有额外的add esp, 8

2

opcua_client::onShutdownMessage 看起来像是一个回调函数。也就是说,这是一个被发送到某个API的函数,该API稍后会调用该函数。回调函数必须具有API所期望的调用约定,在这种情况下是 __stdcall。由于在Win32平台上 __stdcall 是默认的调用约定,因此在为Win32构建时不需要指定它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接