在C++中使用Delphi DLL - 如何正确调用函数?

4

在工作中,我们使用一种品牌的电池循环器,它具有专有的编码数据格式,我希望直接在我的脚本中读取它,而不是使用数据的更大ASCII版本。制造商提供了一个DLL(没有库或头文件),以及一些Delphi示例代码的使用,非常少的文档和一个示例可执行文件(无法运行,出现错误0xc000007b)。 按照此教程,我成功创建了.lib并将我的VS2018项目与DLL链接。

使用以下代码,我可以调用DLL中所需的一个函数:

#pragma comment(lib, "MacReadDataFileLIB.lib")

#include <string>

extern "C" int OpenDataFile(const char*);

int main()
{
    std::string path = "K:\\Testfile.036";
    auto test = OpenDataFile(path.c_str());
}

当我逐步执行代码时,函数返回-1001,即失败并抛出异常。

运行时检查失败#0-ESP的值在函数调用中未正确保存。这通常是由于使用不同调用约定声明的函数指针调用声明为一种调用约定的函数而导致的。

从文档中我知道使用了stdcall

来自文档的其他相关信息:

function OpenDataFile(FileName: PChar): int32; function LoadAndGetNextTimeData(Handle: int32; PTimeData: pointer): int32; procedure CloseDataFile(Handle: int32);

使用OpenDataFile打开Maccor数据文件并返回一个句柄。如果句柄大于或等于0,则文件成功打开。 (...) 由于通常需要读取整个数据文件,因此有一个具有双重目的的函数LoadAndGetNextTimeData,用于加载时间数据并获取/返回最常见的数据。 所有函数至少需要通过OpenDataFile返回的句柄。 (...) 要读取数据,请反复调用LoadAndGetNextTimeData,直到它返回0为止。 最后,必须使用CloseDataFile关闭文件以释放分配的内存。

extern "C" int __stdcall OpenDataFile(const char*);

它无法编译,出现了两个错误代码:

LNK1120 1个未解决的外部

LNK2019 未解决的外部符号_OpenDataFile@4在函数_main中被引用

我知道这是由于C++名称混淆导致的,但此时我卡住了。


1
你有没有查看这个问题的答案:成员函数指针运行时错误 - ESP 的值在函数调用中没有正确保存 - Ted Lyngmo
1
@JustSomeChemist 只需要在 Google 中搜索 GetProcAddress(和LoadLibrary)。 这将告诉您如何获取 DLL 中函数的地址,而与其调用签名无关。 然后,您可以通过适当声明的函数指针调用它,并以这种方式使其正常工作。 - Paul Sanders
1
@PaulSanders:看起来确实解决了问题。与此同时,我偶然发现了这个与Delphi无关的问题,该问题中的代码已经可以正常工作了。请将您的建议作为答案发布,以便我确认并感谢您。 - JustSomeChemist
1
别担心那个,我得写一些真正的代码!现在你能运行了我很高兴。 - Paul Sanders
1
@JustSomeChemist 你可以发布自己的答案。你的编辑应该被转移到一个答案中。 - Remy Lebeau
显示剩余7条评论
2个回答

4

When I change the function declaration to

extern "C" int __stdcall OpenDataFile(const char*);

It fails to compile with 2 error codes:

LNK1120 1 unresolved externals

LNK2019 unresolved external symbol _OpenDataFile@4 referenced in function _main

添加__stdcall是正确的做法。

为了解决链接器错误,您可以使用Visual Studio的lib.exe工具创建一个导入.lib文件,并使用/DEF选项指定一个.def文件,将编译器的修饰符号_OpenDataFile@4映射到DLL实际导出的未修饰符号OpenDataFile。请参见如何创建32位导入库而不需要.OBJ或源代码

或者,您可以使用MSVC的延迟加载DLL功能。继续使用您已经拥有的.lib文件,并在项目设置中标记DLL为延迟加载。然后,在您的代码中,您可以使用延迟加载通知钩子,使DLL函数在dliNotePreGetProcAddress阶段使用未装饰符号而不是装饰符号进行导入。

#include <string>
#include <cstring>
#include <delayimp.h>
#pragma comment(lib, "MacReadDataFileLIB.lib")

ExternC int __stdcall OpenDataFile(const char*);

FARPROC WINAPI myDelayLoadHook(unsigned dliNotify, PDelayLoadInfo pdli)
{
    if ((dliNotify == dliNotePreGetProcAddress) &&
        (std::strcmp(pdli->szDll, "MacReadDataFileLIB.dll") == 0) &&
        pdli->dlp.fImportByName &&
        (std::strcmp(pdli->dlp.szProcName, "_OpenDataFile@4") == 0))
    {
        return (FARPROC) GetProcAddress(pdli->hmodCur, "OpenDataFile");
    }
    return NULL;
}

extern "C" const PfnDliHook __pfnDliNotifyHook2 = myDelayLoadHook;

int main()
{
    std::string path = "K:\\Testfile.036";
    auto test = OpenDataFile(path.c_str());
}

1
我在一个与问题不直接相关的帖子中发现了一段代码,可以直接使用:
typedef int(__stdcall* tMyFunction)(const char* filename);

int main(int argc, char* argv[]) {

    std::string path = "K:\\Testfile.036";

    HINSTANCE m_dllHandle = LoadLibrary("MacReadDataFileLIB.dll");
    tMyFunction function = (tMyFunction)GetProcAddress(m_dllHandle, "OpenDataFileASCII");
    int value = function(path.c_str());

    FreeLibrary(m_dllHandle);
    m_dllHandle = NULL;

    return 0;
}

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