在动态链接库(DLL)中封装静态库

时间:2014-01-04 19:11:16

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

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

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

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

  3. Win32控制台应用。这会调用Bar b; std::cout << b.GetNum() << std::endl

  4. 当我尝试构建它时,它抱怨它无法找到我的dll的关联库。做了一点研究,看到我需要将__declspec(dllexport)添加到我的GetNum()方法中,然后我会得到一个.lib。凉。

    接下来是控制台应用程序说它无法找到Foo的静态库。我将它添加到我的参考资料中,它们都构建并运行良好。

    我的问题是 - 为什么我的exe需要了解Foo的任何内容?我想在所有依赖项中有效地“烘焙”到dll中,所以我可以分享它,链接到它,并且很高兴去。

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

    更新

    以下是大部分代码。

        // ---------------------- 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;
        }
    

2 个答案:

答案 0 :(得分:9)

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

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

这基本上就是你的玩具示例中发生的事情。因此,让我们通过使用一个简单的规则来解决它,你应该记住它:尽可能多地放入翻译单元,即保持标题最小。现在让我们用你的例子来说明它是如何工作的:

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),并且因此,为DLL生成带有导出符号(即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相关联。

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

了解所有这些对于使用C / C ++进行适当的本机软件开发非常重要,所以事先提出这个问题真的很好。我遇到了那些经常不了解/不了解这一点的人,这对他们(业余爱好者)和我们来说是彻头彻尾的噩梦,当我们必须处理他们写的那些糟糕的软件时......

答案 1 :(得分:0)

考虑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();
}