我正在努力增加对基本库链接,依赖关系等的理解。我创建了一个包含三个项目的Visual Studio解决方案
静态lib
使用/MTd
一个类(Foo
),一种方法int GetNum() { return 5; }
使用dll
与一个班级/MDd
)共享Bar
,一种方法int GetNum() { Foo f; return f.GetNum(); }
Win32控制台应用。这会调用Bar b; std::cout << b.GetNum() << std::endl
当我尝试构建它时,它抱怨它无法找到我的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;
}
答案 0 :(得分:9)
使用C / C ++,在标题中正确构建代码非常重要(例如h
,hpp
,hxx
,h++
等等)和翻译单元(通常称为来源,例如c
,cpp
,cxx
,c++
等)。当你设计一个库时,你应该不断思考什么属于它的接口(即应该被消费者看到)以及什么属于它的实现(即不应该被消费者看到)。
记住经验法则 - 消费者会看到任何标题中存在的所有符号(如果包含),因此,消费者需要在某个时间点的链接阶段得到解决!
这基本上就是你的玩具示例中发生的事情。因此,让我们通过使用一个简单的规则来解决它,你应该记住它:尽可能多地放入翻译单元,即保持标题最小。现在让我们用你的例子来说明它是如何工作的:
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();
}