从 C++ 调用导出的 delphi 函数的应用程序崩溃

Application crash calling exported delphi function from C++

我有一个在 Delphi 7 中编译的 .DLL,它导出一个函数。我正在尝试从 C++ 调用该函数。

procedure SystemReload(lpMessage: PAnsiChar; dwIcon: byte; dwColor: byte);
var
dwMessage: cardinal;
procedure SystemReload_Real(lpMessage: PAnsiChar); assembler;
asm
...
end;
begin
dwMessage := [=11=]415B30;
ShowGameMessage_Real(lpMessage);
end;

exports SystemReload name 'SystemReload';

begin
end.

然后是我用来调用函数的 C++ 代码:

int ShowGameMessage(char* Message, BYTE Icon, BYTE Color)
{
    int ret;

    if (exist("SysReload.dll"))
    {
        HMODULE hLib = LoadLibrary("SysReload.dll");

        if (hLib)
        {

            typedef int(__stdcall *SGMessage)(char*, BYTE, BYTE);

            SGMessage ShowGameMessage = (SGMessage)GetProcAddress(hLib, "SystemReload");

            ret = (*ShowGameMessage)(Message, Icon, Color);

        } else { FreeLibrary(hLib); }

        FreeLibrary(hLib);

    }

    return ret;
}

C++ 代码在调用导出的 Delphi 函数时崩溃。

如何在不使应用程序崩溃的情况下正确执行操作?

您没有在 Delphi 代码中指定调用约定。 Delphi 中的默认调用约定是 register(在 C++Builder 中称为 __fastcall,并且不受任何其他 C++ 编译器支持)。您的 C++ 代码对导入的函数使用 __stdcall(C++ 中的默认调用约定通常是 __cdecl)。混合调用约定是未定义的行为,会导致各种问题,包括崩溃。您需要在两种语言中指定相同的调用约定。在这种情况下,您应该在 Delphi 代码中使用 stdcall 以匹配您在 C++ 代码中使用 __stdcall

procedure SystemReload(lpMessage: PAnsiChar; dwIcon: byte; dwColor: byte); stdcall;

此外,您的 Delphi 代码将导出函数声明为 procedure,这意味着它没有 return 值。但是您的 C++ 代码将导入的函数声明为具有 int return 类型。您应该更改您的 C++ 代码以使用 void 以匹配您在 Delphi 代码中对 procedure 的使用:

typedef void (__stdcall *SGMessage)(char*, BYTE, BYTE);

此外,与此无关,如果 LoadLibrary() 失败,您的 C++ 代码将调用 FreeLibrary() 两次。如果 LoadLibrary() 失败,您根本不应该调用 FreeLibrary()。如果 LoadLibrary() 成功,只调用一次。您应该将对 FreeLibrary() 的调用移至 if (hLib) 块内:

void ShowGameMessage(char* Message, BYTE Icon, BYTE Color)
{
    HMODULE hLib = LoadLibrary("SysReload.dll");
    if (hLib)
    {
        typedef void (__stdcall *SGMessage)(char*, BYTE, BYTE);
        SGMessage ShowGameMessage = (SGMessage) GetProcAddress(hLib, "SystemReload");
        if (ShowGameMessage)
            (*ShowGameMessage)(Message, Icon, Color);
        FreeLibrary(hLib);
    }
}

您的 Delphi 导出看起来不像 __stdcall

因此,如果您使用的是 C++Builder,请将其声明为 __fastcall,或者在 DLL 中将其声明为 stdcall。由于它是一个 DLL 导出,stdcall 可能是更好的选择。

如果您不使用 C++Builder,而是使用另一个 C++,那么 __fastcall 不是一个选项,因为那样的话,您的 __fastcall 与 Delphi 不兼容' s 默认 register 调用约定。最好将 DLL 函数声明为 stdcall(或 cdecl,尽管对于 DLL,stdcall 更常见)。

FWIW,出于上述原因,使用默认的 register DLL 导出调用约定是禁忌。

更多信息:DLL dos and don'ts -- Calling convention