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



我正在努力提高对基本库链接、依赖项等的理解。我创建了一个包含三个项目的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;
}

使用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链接。

注意这个问题是如何解决的,因为我已经从main.cpp中消除了对MathLib(通过MathDll.hpp)的传递依赖,因为现在#include "MathLib.hpp"的包含是在翻译单元MathDll.cpp中完成的(因为根据上面的规则,它实际上只需要在那里),因此它被构建到二进制工件(在这种情况下是DLL)中,而不存在于它的接口中。

了解所有这些对于使用C/C++进行正确的本机软件开发非常重要,所以提前问这个问题真的很好。我经常遇到不知道/不理解这一点的人,这对他们(业余爱好者)和我们来说都是一场噩梦,当我们不得不处理他们编写的糟糕软件时。。。

当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习惯用法将所有内容封装到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();
}

相关内容

最新更新