【问题标题】:Error when using dllimport in a DLL client在 DLL 客户端中使用 dllimport 时出错
【发布时间】:2012-09-01 17:01:39
【问题描述】:

我目前正在使用互联网上很多地方提到的存储过程创建一个 DLL 和与之配套的客户端。基本上,创建一个 DLL 项目,它实际上在 Project.h 文件中定义了一个 PROJECT_EXPORTS。

类似这样的:

// Assume the name of the project is SanProj and the header file is SanProj.h
#ifdef SANPROJ_EXPORTS
    #define SANPROJ_API __declspec(dllexport)
#else
    #define SANPROJ_API __declspec(dllimport)
#endif

现在,使用此标头的常规方法是将其包含在 API 类的所有标头中,并在 DLL 中使用 SANPROJ_EXPORTS 进行“导出”声明,并在用作客户端时使用“导入”声明。例如假设我们有一个带有货币类的头文件:

// currency.hpp
#include "SanProj.h"
#include <ostream>
#include <string>

namespace SanProj {

    class SANPROJ_API Currency {

    public:
        Currency();
        const std::string& name();
        const std::string& code();
        bool empty() const;

    protected:
        std::string name_;
        std::string code_;
    };

    SANPROJ_API bool operator==(const Currency&,
                    const Currency&);

    SANPROJ_API bool operator!=(const Currency&,
                    const Currency&);

    SANPROJ_API std::ostream& operator<<(std::ostream& out, Currency& c);
}

还有另一个带有特定货币的头文件:

// allccy.hpp
namespace SanProj {

    class SANPROJ_API USDCurrency : public Currency {
    public:
        USDCurrency() {
            name_ = "American Dollar";
            code_ = "USD";
        }
    };


    class SANPROJ_API CADCurrency : public Currency {
    public:
        CADCurrency() {
            name_ = "Canadian Dollar";
            code_ = "CAD";
        }
    };

}

上述类构成了 DLL 项目的契约。现在我们来看看客户端项目文件,它是一个带有main函数的单个类:

#include "currency.hpp"
#include "allccy.hpp"

#include <iostream>

using namespace SanProj;

int main(int argc, char* argv[])
{
    USDCurrency uccy;
    std::cout << uccy;
}

假设 Visual Studio 项目中已完成所有引用/设置,我在尝试编译客户端时收到以下错误:

1>testdll.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: __thiscall SanProj::USDCurrency::~USDCurrency(void)" (__imp_??1USDCurrency@SanProj@@QAE@XZ)
1>testdll.obj : error LNK2001: unresolved external symbol "__declspec(dllimport) public: __thiscall SanProj::USDCurrency::USDCurrency(void)" (__imp_??0USDCurrency@SanProj@@QAE@XZ)

毫不奇怪,当我从 SanProj.h 文件中删除 dllimport 部分并创建可执行文件时,此错误就会消失。

我的问题是,如果我们不能针对标头编译客户端,那么 IDE 生成 dllimport 的意义何在?有没有办法可以继续使用带有dllimportdllexports 的标头并删除链接器错误?另外,它为什么要尝试从 LIB 文件中解析具有 dllimport 的符号?

TIA,
/佐助

编辑: VisualStudio 使用的链接器命令;如您所见,它有 LIB 文件。

/OUT:"E:\vsprojects\SomeSln\Release\testdll.exe" /INCREMENTAL:NO /NOLOGO "E:\vsprojects\SomeSln\Release\SanProj.lib" "kernel32.lib" “user32.lib” “gdi32.lib” “winspool.lib” “comdlg32.lib” “advapi32.lib” “shell32.lib” “ole32.lib” “oleaut32.lib” “uuid.lib” “odbc32.lib” “odbccp32.lib”/清单 /ManifestFile:"Release\testdll.exe.intermediate.manifest" /ALLOWISOLATION /MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"E:\vsprojects\SomeSln\Release\testdll.pdb" /子系统:控制台 /OPT:REF /OPT:ICF /PGD:"E:\vsprojects\SomeSln\Release\testdll.pgd" /LTCG /TLBID:1 /DYNAMICBASE /NXCOMPAT /MACHINE:X86 /ERRORREPORT:QUEUE

【问题讨论】:

  • 您所要做的就是用dllexport 编译DLL,用dllimport 编译客户端并为链接器提供正确的库。错误消息表明链接器缺少库,因此实际上无法链接符号(import/export 不是问题)。
  • 您似乎没有将可执行文件链接到为您的 .dll 创建的导入库。
  • @peachykeen:这就是我现在正在做的事情。我知道路径/配置足够好,因为从SanProj.h 文件中删除dllimport 成功创建了可执行文件。如果是链接器找不到导入库的问题,即使我删除了dllimport,它也应该抱怨,不是吗?
  • @drescherjm:请阅读以上评论。
  • 我不相信编译器会自动将导入库添加到任何使用 dll 的可执行文件的链接设置中。在我编写 dll 的 15 年多的时间里,它从来没有为我这样做过。您可以使用您的 dll 将导入库添加到所有可执行文件的链接设置中的一种简单方法是使用编译指示,但我在您的代码中没有看到。

标签: c++ visual-studio visual-c++ dll


【解决方案1】:

编辑:当然我错了,因为 jcopenha 的答案就是答案。链接器抱怨缺少您未在 DLL 中导出的构造函数和析构函数。但是其余的仍然有效。

[...]

您应该有两个构建目标(或项目,具体取决于您使用的环境)。

第一个目标将构建 DLL。根据您报告的内容,此目标需要构建的文件是:

currency.hpp
allccy.hpp

可能还有基类“货币”的实现。您必须在预处理器定义中定义 SANPROJ_EXPORTS 才能使用 currency.hpp 文件作为 DLL 导出的函数的定义。 该目标将生成一个 .DLL 文件,并且可能(取决于配置)一个 .lib 文件。它还可以生成其他文件,例如库导出的文本表示(.DEF 文件)。

然后,您需要构建您的应用程序(第二个目标/项目): 您需要的头文件与#include 部分的库完全相同。只需确保不定义 SANPROJ_EXPORTS 否则编译器将尝试再次导出符号而不是导入它们。 然后你需要在编译器和链接器中添加以下设置:

  • 将包含 .hpp 标头的目录添加到包含路径。

  • 在链接器 (lib) 的库路径中添加目录 包含 .lib 文件。

  • 告诉链接器也链接到您刚刚创建的 .lib(添加您的 lib 文件的全名,假设 DLL 被命名为“currency”,它可能是“currency.lib”。

在何处以及如何添加此设置取决于您使用的工具链/环境和编译器。

最后,请确保编译后的可执行文件能够在项目文件夹或系统目录(在 PATH 中)中找到 DLL,否则将无法启动。如果可执行文件位于与用于构建 DLL 的文件夹不同的文件夹中,只需使用构建后步骤复制 DLL。

删除 _dllimport 部分将构建项目的事实可能是由于编译器找到了头文件和您要导出的函数的实现,并将它们全部静态构建到可执行文件中。

假设您不在 .NET“托管世界”中,并且我们谈论的是 Windows,如果您想分发您的库,还有一些要点需要考虑,但这是另一个答案。

【讨论】:

  • +1;谢谢你的崩溃。我对第二步和第三步有点困惑,它们不一样吗?我的意思是现在我正在使用Linker -&gt; Input -&gt; Additional dependencies 来指定 LIB 的完整路径,并且运行良好。
  • 使用 Visual Studio,您可以直接在“链接器 -> 输入 -> 附加依赖项”中使用完整路径指定文件名。否则,您可以只在此处写入文件名并使用“链接器 -> 常规 -> 附加库目录”。 Visual Studio 将在此目录中搜索库。如果您使用多个配置(即调试/发布),则必须为每个配置设置此项或使用“所有配置”。
【解决方案2】:

它专门抱怨 USDCurrency 类的构造函数和析构函数,但您的代码没有显示这些方法被 SANPROJ_API 装饰。

并且由于您在标头中定义了 USDCurrency 构造函数,当您从类 USDCurrency 中删除 dllimport 时,您将获得当前项目中定义的实现,而不是对 DLL 中定义的实现的引用.

【讨论】:

  • 啊,这听起来合乎逻辑; +1。但问题是:我正在用 allccys.hpp 文件中的导出来装饰 USDCurrency 类,那么为什么会为构造函数发出错误?另外,鉴于大多数类不需要指定显式析构函数,除了从头文件中删除 dllimport 之外,还有其他方法可以解决这个问题吗?
【解决方案3】:

其他人都打败了这个,我也会。

重新编译器添加库,编译器就是这样做的:编译。链接器链接 (doh)。您的似乎是链接器配置问题,有多种方法可以解决此问题。

如果您有一个包含 DLL 项目和 EXE 项目的多项目解决方案文件 (.sln),您可以通过在 EXE 中将 DLL 项目设置为被 EXE 项目“引用”来建立显式依赖关系项目。在此范围内,确保“链接库依赖项”标记为“true”。

References 配置实际上是从 VS2005 开始的 .NET 附加功能,尽管它仍然适用于标准 C/C++ 项目。您可以跳过它并将导入库配置为在 EXE 项目的链接器/输入设置上隐式链接。一个名为“链接库依赖项”的设置也可以在那里标记为 true。这还需要您配置解决方案项目依赖项(Build/Project Dependencies...)。在您的情况下,您选择您的 EXE 项目作为“取决于..”并检查 DLL 项目。这样可以确保在重建 DLL 项目并创建新的导入库时重新链接您的 EXE。

如有要求,可提供所有这些的屏幕截图。经过几次复飞后设置它已成为旧习惯。在这一点上,我相当确定我可以蒙上眼睛。

【讨论】:

  • +1;正如我在编辑和其他 cmets 中已经指出的那样,我的措辞很糟糕。我实际上正在将库依赖项添加到 Visual Studio,但仍然出现错误。我通过去Linker -&gt; Input -&gt; Additional Library dependencies做到了。顺便说一句,如果你手边有屏幕,我很想看看它们并确认我的设置。
  • 你真的打对了。如果两个项目都在同一个解决方案中构建,您可以按照我描述的任何一种方式进行。完全了解基础知识并仅在附加库上从您的 DLL 中指定实际输出 imp lib 也可以。但是不要忘记设置您的项目构建依赖项(即,使您的 EXE 依赖于您的 DLL,以确保 DLL 代码更改将触发 EXE 重新链接。我会尝试找到一种方法将屏幕放在某个地方而不会跳动这个解决方案更进一步。
【解决方案4】:

这个问题似乎没有解决办法。我最终放弃了在客户端代码中使用 dllimport 并受到性能影响。 :(

【讨论】:

  • 您找到解决方案了吗?我也坚持这个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多