【问题标题】:How to use a provided DLL file with HPP header?如何使用提供的带有 HPP 标头的 DLL 文件?
【发布时间】:2021-06-26 06:26:05
【问题描述】:

我目前在尝试与 Verifone PoS 集成时遇到一些问题。

我们合作的银行为我们提供了一个 .dll 文件、一个 .h 文件和一个 .hpp 文件。

我通常是 Java 或 PHP 开发人员,所以在过去的几天里,我浏览了我在网上找到的关于 DLL 文件以及如何使用它们的所有内容,但到目前为止似乎没有一个有效。我遇到了很多错误,其中很多类似于“无效的 dll”。

我在网上发现,除了一个 dll 文件,应该还有一个 .lib 文件。我问过第三方,但显然

没有 .lib 文件。 .dll 文件包含集成所需的所有信息

从他们的图书馆文档中我发现了这个:

提供的二进制文件的形式是一个动态库。就其本质而言,动态库允许更轻松的更新和更正,不需要重新编译或重新链接客户端(调用)代码,只要过程原型(函数参数和返回类型)保持不变。

用于库实现的语言是 C++。

为了访问库二进制文件中实现的功能,提供了一个 C 风格的头文件接口。这包括可调用的函数原型以及结果结构的类型,需要通过这些类型解释返回的数据以对先前访问的功能(特定请求的事务)有意义。

是的,.h 文件仅包含数据类型,而 .hpp 文件包含一些如下所示的声明:

extern "C" __declspec(dllexport) bool doSomething(int param);

在他们的文档中还有一个示例,说明实现的外观(而且相当简单):

bool someVar = doSomething(1); 

看起来这些函数可以这么简单地调用,但它们不能。如果我尝试这样做,我会收到“未定义函数”(或类似的)错误。

此时似乎唯一起作用的(也许)是使用 LoadLibrary 函数加载 DLL。但除了我尝试调用的任何函数,无论使用任何参数,它都会返回 false,我根本不使用 .hpp 文件似乎有点错误。

所以我们到了。我应该如何解决这个问题?有没有办法加载 DLL 并使用提供的 HPP 文件作为函数定义?如果没有,除了 LoadLibrary + GetProcAddress 组合之外还有其他方法吗?

谢谢!

【问题讨论】:

  • 它是 .net DLL 吗?你可以使用#using。 docs.microsoft.com/en-us/cpp/preprocessor/…
  • 您是否尝试在 32 位进程上使用 64 位 DLL,反之亦然。看看dependencywalker.com中的DLL内容@
  • 头文件真的包含__declspec(dllexport) 的声明还是隐藏在某些宏后面?因为__declspec(dllexport) 仅在您是库作者 时才有效。这些注释将确保导入库(在 msvc 中是一个 .lib 文件)包含您要导出的所有函数。如果您是 dll 的 用户,则应该是 __declspec(dllimport),但您仍然需要一个 .lib 文件,否则您必须手动 GetProcAddress,afaik。如果它是托管 dll,就像 cup 说的那样,你可以#using 它,但是给定的头文件看起来像原生的。
  • @cup 我使用了依赖遍历器,它看起来像一个 32 位的(有许多其他依赖项,如 user32.dll、shell32.dll、kernel32.dll 等)。此外,该应用程序在解析时生成警告:错误:未找到至少一个必需的隐式或转发依赖项。警告:至少没有找到一个延迟加载依赖模块。警告:由于延迟加载依赖模块中缺少导出功能,至少有一个模块存在未解析的导入。
  • @Timo 头文件的开头是这样的:#ifndef PROTOCOL_V4_INTERFACE #define PROTOCOL_V4_INTERFACE 下一行与我描述的完全一样,使用 dllexport。我读到 dll 导出是为那些生成 dll 的人准备的,但我是这里的用户。我应该用 dllimport 改变那些吗?

标签: c++ visual-studio dll


【解决方案1】:

我假设 dll 是本机 dll,而不是托管程序集 (.net dll)。

通常,dll 作者会在构建系统中添加预处理器定义,例如DLL_EXPORT。因此,如果作者编译 dll,导入库(一个小的 .lib 文件)将包含所有使用 DLL_API 宏的函数。然后作者可以将相同的标头发送给用户。因为该用户不会定义DLL_EXPORT 宏,DLL_API 将解析为 dllimport,这基本上表示在导入库中定义了带注释的函数。

这样的头文件可能看起来像这样(整个#if 条件通常在它自己的头文件中,然后包含在所有导出函数的头文件中):

#ifdef DLL_EXPORT
#   define DLL_API __declspec(dllexport)
#else
#   define DLL_API __declspec(dllimport)
#endif

extern "C" 
{
    void DLL_API SomeFunction(int x);
    void DLL_API AnotherFunction(int x);
}

如果作者构建项目(在 msvc 中),编译器将生成 dll 文件一个小的 .lib 文件,即导入库。这个库基本上会做你现在必须做的事情:调用LoadLibraryGetProcAddress来解析所有被__declspec(dllexport)注解的函数。

以下部分有点推测,我在这里猜测一下。

__declspec(dllimport) 所做的只是告诉消费者这个 dll 包含这些功能。但是链接器必须将声明链接到它的定义(实现),所以函数必须在 compiletime 的某个地方定义。而那个地方就是导入库(.lib)。如果不链接导入库,在构建项目时会出现链接器错误。

这意味着简单地将dllexport 更改为dllimport 不会解决您的问题。如果没有导入库,您唯一的选择是使用 LoadLibrary 手动加载 dll 并搜索每个函数。

如果我是你,我会向作者索要一个使用 dll 的示例项目。 AFAIK,使用本机 dll 的唯一方法是链接到导入库或手动加载所有内容。

从dll手动生成导入库

我已经对此进行了测试以确保它有效。

首先,修复头文件以使用我在上面示例中所做的宏,或者直接使用dllimport

其次,打开 VS 的开发者命令提示符 并按照this 回答中的步骤操作。确保使用正确的文件名和目标架构(x64 或 x86)。现在你应该有一个 .lib 文件了。

第三,将库添加到您的项目中。

  1. 添加 lib 的目录(将其放在项目附近的某个位置,以便您可以使用相对路径)。打开项目属性并按照此图像中的步骤操作: 确保 ConfigurationPlatform 是正确的(您可能希望像图片中那样)。您也可以使用相对路径。点击按钮查看所有可用的预定义路径。
  2. 将 lib 添加到链接器依赖项中:
  3. 将标头放在项目中可以访问的位置。

现在您可以简单地在项目中的任何位置包含标头并使用其中声明的函数。但请注意,dll 文件必须放在LoadLibrary 可以找到它的地方。最好与项目的可执行文件所在的目录相同。

奖励事实

定义文件(.def)其实很简单。我上面的示例代码的 def 文件是:

LIBRARY MyLibrary
EXPORTS
AnotherFunction
SomeFunction

如果我删除声明周围的 extern "C" 块,我的函数名称将被破坏,并且 def 文件如下所示:

LIBRARY MyLibrary
EXPORTS
?AnotherFunction@@YAXH@Z
?SomeFunction@@YAXH@Z

如果您将这些函数放在命名空间中(例如FooSpace),则该命名空间名称也将成为函数名称的一部分:

LIBRARY MyLibrary
EXPORTS
?AnotherFunction@FooSpace@@YAXH@Z
?SomeFunction@FooSpace@@YAXH@Z

请注意,所有extern "C" 实体都将忽略命名空间,这意味着所有extern "C" 函数、变量、类型……都将被放入全局命名空间,无论您是否在命名空间中定义它们。

如果您手动执行,这些也是您必须传递给 GetProcAddress 的名称。

【讨论】:

  • 非常同意这个优秀的评论。我认为接下来要做的就是举个例子。他们没有为他们的 DLL 或源代码提供“存根”以使调用更容易,这对我来说确实很奇怪。直觉告诉我“这应该是不费吹灰之力”,所以也许你只是错过了一些东西。
  • 感谢您的回答。不幸的是,我已经要求一个使用这个 dll 的示例项目,他们只能说他们不负责实现它,所以他们没有任何示例代码......我知道这很奇怪......
  • 另外,我今天尝试了一些东西,但我有一些问题。我可以 100% 确定使用 LoadLibrary + GetProdAddress 将访问正确的功能吗?此外,我看到一些建议您可以生成 .lib 文件。这是一个选项,生成库并假装它是提供的库吗? (我在尝试这样做时也遇到了错误,但也许我错过了其他东西)
  • @AcerX 好点。我添加了关于如何创建和使用您自己的导入库的说明。是的,如果您正确拼写名称,GetProcAddress 将获得正确的功能。 Dll 成员由其名称唯一标识。但是,如果您使用 C++ 链接(即省略 extern "C" 部分),您的函数名称将被破坏并且看起来很奇怪。在那种情况下,GetProcAddress("SomeFunction") 会给你一个nullptr,因为这个函数名在 dll 中是不同的。不过,对于 extern "C" 实体,这会很好。
  • @Timo 按照该链接中的描述创建库 + 使用 dllimport 更改 dllexport 就像一个魅力。现在程序可以使用包含的 dll 进行编译,并且可以调用函数而无需任何其他麻烦。谢谢你,祝你有美好的一天:D
猜你喜欢
  • 1970-01-01
  • 2013-12-08
  • 2020-04-18
  • 1970-01-01
  • 2020-10-11
  • 1970-01-01
  • 2021-05-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多