我正在努力增加对基本库链接,依赖关系等的理解.我创建了一个包含三个项目的Visual Studio解决方案
静态lib
使用/MTd
单个类(Foo
),一个方法int GetNum() { return 5; }
与单个class()共享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; }
Alexander Sh.. 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)
的导入库.MathDll
DLL.在MSVC上,您可以通过添加/DCONSOLETEST_API
到编译器的调用来实现此目的.最后,在构建这个DLL时,肯定会将它与之前构建的静态库链接起来MathLib.lib
.
注意:最好像我上面那样导出整个类class CONSOLETEST_API MathDll
,而不是单独导出所有方法.
main.cpp
:
#include "MathDll.hpp" #includeint _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
from)from main.cpp
,因为现在#include "MathLib.hpp"
包含是在翻译单元中完成的MathDll.cpp
(因为它实际上只是根据上述规则需要),因此被构建为二进制工件(在这种情况下为DLL)并且不存在于其接口中.
了解所有这些对于使用C/C++进行适当的本机软件开发非常重要,所以事先提出这个问题真是太好了.我遇到了那些经常不了解/不了解这一点的人,这对他们(业余爱好者)和我们来说是彻头彻尾的噩梦,当我们不得不处理他们写的那些糟糕的软件时......
用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)
的导入库.MathDll
DLL.在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
from)from main.cpp
,因为现在#include "MathLib.hpp"
包含是在翻译单元中完成的MathDll.cpp
(因为它实际上只是根据上述规则需要),因此被构建为二进制工件(在这种情况下为DLL)并且不存在于其接口中.
了解所有这些对于使用C/C++进行适当的本机软件开发非常重要,所以事先提出这个问题真是太好了.我遇到了那些经常不了解/不了解这一点的人,这对他们(业余爱好者)和我们来说是彻头彻尾的噩梦,当我们不得不处理他们写的那些糟糕的软件时......