【问题标题】:Encapsulating static libraries in dynamic-link libraries (DLL)在动态链接库 (DLL) 中封装静态库
【发布时间】:2014-01-04 19:11:16
【问题描述】:

我正在努力增加对基本库链接、依赖项等的理解。我创建了一个包含三个项目的 Visual Studio 解决方案

  1. 静态 lib 使用 /MTd 和单个类 (Foo),一种方法 int GetNum() { return 5; }

  2. 使用 /MDd 与单个类 (Bar) 共享 dll,一种方法 int GetNum() { Foo f; return f.GetNum(); }

  3. Win32 控制台应用程序。那叫Bar b; std::cout << b.GetNum() << std::endl

当我尝试构建它时,它抱怨找不到我的 dll 的关联库。做了一些研究,发现我需要将__declspec(dllexport) 添加到我的GetNum() 方法中,我会得到一个.lib。很酷。

接下来是控制台应用程序说它找不到Foo 的静态库。我将它添加到我的参考文献中,一切都构建并运行良好。

我的问题是 - 为什么我的 exe 需要了解有关 Foo 的任何信息?我想有效地将​​我所有的依赖项“烘焙”到 dll 中,这样我就可以分享它,链接到它,然后一切顺利。

这不是语言的工作方式还是我缺少的设置/模式?我的最终目标是能够构建一个 dll 来封装第三方 .lib 的使用,而不需要客户端应用程序担心添加对所有这些的引用。

更新

这是大部分代码。

    // ---------------------- Lib (e.g. Foo)
    #pragma once
    class MathLib
    {
    public:
        MathLib(void);
        ~MathLib(void);
        int GetNum() { return 83; }
    };

    // ---------------------- DLL (e.g. Bar)
    #pragma once

    #ifdef CONSOLETEST_EXPORT
        #define CONSOLETEST_API __declspec(dllexport)
    #else
        #define CONSOLETEST_API __declspec(dllimport)
    #endif

    #include "MathLib.h"

    class MathDll
    {
    public:
        __declspec(dllexport) MathDll(void);
        __declspec(dllexport) ~MathDll(void);
        __declspec(dllexport) int GetNumFromDyn() 
        {
            MathLib m;
            return m.GetNum();
        }

    };


    // ---------------------- exe
    int _tmain(int argc, _TCHAR* argv[])
    {
        MathDll m;
        std::cout << "num is " << m.GetNumFromDyn() << std::endl;
        return 0;
    }

【问题讨论】:

  • 您在包含在控制台应用程序中的 DLL 标头中是否有 Foo?给你看代码,否则很难说是怎么回事。
  • 我确实有来自Bar.h#include "Foo.h",否则我应该如何将Foo 封装在Bar 中?我会更新代码。
  • 看...这正是问题所在。你没有烘焙它。Foo 应该是Bar 使用的实现细节,在你的情况下,它仍然存在于接口中,因为它明确包含在Bar 中标题。我会写答案如何解决这个问题。等等。
  • 注意/MT/MD 想要构建静态库还是动态库无关。这是how the C runtime library is linked 进入您的代码。对您拥有的所有 lib/dll 使用相同的设置!

标签: c++ visual-c++ dll compiler-construction linker


【解决方案1】:

对于 C/C++,在标头(例如hhpphxxh++ 等)和中正确构建代码非常重要翻译单元(通常称为来源,例如ccppcxxc++等)。当你设计一个库时,你应该不断思考什么属于它的接口(即应该被消费者看到)和什么属于它的实现 em>(即不应该被消费者看到)。

记住经验法则 - 任何标题中出现的所有符号都会被消费者看到(如果包含),因此消费者需要将在稍后的某个时间点在链接阶段解决!

这基本上就是在您的玩具示例中发生在您身上的事情。因此,让我们使用一个简单的规则来解决它,您应该牢记这一点:尽可能多地放入翻译单元,即尽量减少标题。现在让我们用你的例子来展示它是如何工作的:

MathLib.hpp:

#pragma once

class MathLib {
public:
  MathLib();
  ~MathLib();
  int GetNum();
};

MathLib.cpp:

#include "MathLib.hpp"

MathLib::MathLib() {}

MathLib::~MathLib() {}

int MathLib::GetNum() { return 83; }

现在将MathLib.cpp 构建为静态库

MathDll.hpp:

#pragma once

#ifdef CONSOLETEST_EXPORT
#  define CONSOLETEST_API __declspec(dllexport)
#else
#  define CONSOLETEST_API __declspec(dllimport)
#endif

class CONSOLETEST_API MathDll {
public:
  MathDll();
  ~MathDll();
  int GetNumFromDyn();
};

MathDll.cpp:

#include "MathDll.hpp"
#include "MathLib.hpp"

MathDll::MathDll() {}

MathDll::~MathDll() {}

int MathDll::GetNumFromDyn() { 
  MathLib m;
  return m.GetNum();
}

现在将 MathDll.cpp 构建为动态链接库 (DLL),不要忘记在构建过程中添加定义 CONSOLETEST_EXPORT,以便 CONSOLETEST_API__declspec(dllexport),因此,import library(即MathDll 类及其方法)。在 MSVC 上,您可以通过将 /DCONSOLETEST_API 添加到编译器的调用来实现此目的。最后,在构建这个 DLL 时,一定要将它与之前构建的静态库 MathLib.lib 链接起来。

注意:最好像我在上面使用class CONSOLETEST_API MathDll 所做的那样导出整个类,而不是单独导出所有方法。

main.cpp:

#include "MathDll.hpp"

#include <iostream>

int _tmain(int argc, _TCHAR* argv[]) {
  MathDll m;
  std::cout << "num is " << m.GetNumFromDyn() << std::endl;
  return 0;
}

现在将main.cpp 构建为控制台应用程序,并将其与先前为DLL 构建的导入库MathDll.lib 链接。

注意问题是如何消失的,因为我已经摆脱了从 main.cppMathLib(通过 MathDll.hpp)的传递依赖,因为现在 #include "MathLib.hpp" 包含在翻译单元 MathDll.cpp 中完成(因为它实际上只需要根据上述规则在那里),因此内置在二进制工件(在这种情况下为 DLL)中并且不存在于其接口中。

了解所有这些对于使用 C/C++ 进行适当的本机软件开发非常重要,所以提前问这个问题真的很好。我经常遇到不知道/不理解这一点的人,什么会导致他们(业余爱好者)和我们完全做噩梦,当我们不得不处理他们编写的那些糟糕的软件时......

【讨论】:

  • 简明扼要的答案。那么我可以假设当需要做更复杂的事情时,我会使用指向接口模式的指针来隐藏消费者的依赖关系吗?您似乎对此主题了解很多,也许您可​​以帮助我解决我面临的更大挑战(stackoverflow.com/questions/20913884/…)。
  • 是的,您的假设是正确的:有时为了避免包含并使用前向声明,您必须使用指针。关于更大的挑战,好吧,它再次缺少重要信息:一些示例、编译器输出等。
  • 明白了,我会试着做一个简洁的例子来说明这个问题。
  • /MTd/MDd 选项是否与我在 /MTd dll 中编写方法时看到 _crtisvalidheappointer 相关,该方法将字符串返回给正在使用的 /MDd dll?
【解决方案2】:

考虑 MathLib 是 MathDll 类的一部分的情况。

//MathDll.h
#include "MathLib.h"
class MathDll
{
private:
   MathLib m;

public:
    __declspec(dllexport) MathDll(void);
    __declspec(dllexport) ~MathDll(void);
    __declspec(dllexport) int GetNumFromDyn() 
    {
        return m.GetNum();
    }

};

您现在必须将 MathLib.h 包含到您的 MathDll.h 中,它也会传播到控制台应用程序。

你可以避免这种情况......

通过使用PIMPL idiom 将所有内容封装到DLL 中。 在标头中提供类 MathLib 的前向声明,其余的实现隐藏在 Dll 中。也可以考虑导出整个类。

//------------MathDll.h
// we do not include "MathLib.h" here. include it in the MathDll.cpp only
class MathLib;

class __declspec(dllexport) MathDll
{
private:
   MathLib* m;

public:
    MathDll(void);
    ~MathDll(void);
    int GetNumFromDyn();
};

//--------------MathDll.cpp
#include "MathLib.h"
#include "MathDll.h"

MathDll::MathDll(void)
{
     m = new MathLib();
}   

MathDll::~MathDll(void)
{
     delete m;
}   

int MathDll::GetNumFromDyn()
{
     return m->GetNum();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-15
    • 2015-07-10
    • 1970-01-01
    相关资源
    最近更新 更多