muduo 网络库学习项目引入 Boost 依赖

news/2024/9/17 7:20:12/ 标签: 学习, 服务器, c++, 编译, cmake

文章目录

  • 下载 [Boost 源码库](https://www.boost.org/)编译
  • 通过 CMake 引入 Boost 项目
  • Boost 链接库指引文档的问题

muduo 这个项目比较老旧了,但是仍然是学习网络库实现的一个不错的参考,所以还是得看看书自己动手实践一下。

没想到跟着书上的代码练手上来就有依赖问题,boost::noncopyable 其实这个就是把拷贝构造函数和拷贝运算符 delete 了的一个基类,就算子类不显式声明拷贝构造函数和拷贝运算符删除,但是实际进行对象的拷贝构造的时候还是会链式构造递归到基类的构造过程,这个时候发现拷贝构造函数被显式地标记为 delete 就会报错了:

/home/fredom/workspace/muduo/test/misc/boost_valid.cpp: In function ‘int main()’:
/home/fredom/workspace/muduo/test/misc/boost_valid.cpp:33:20: error: use of deleted function ‘MyClass::MyClass(const MyClass&)’33 |     MyClass obj2 = obj1;  // 这里会报错,尝试拷贝构造|                    ^~~~
/home/fredom/workspace/muduo/test/misc/boost_valid.cpp:16:7: note: ‘MyClass::MyClass(const MyClass&)’ is implicitly deleted because the default definition would be ill-formed:16 | class MyClass : public NonCopyable {|       ^~~~~~~
/home/fredom/workspace/muduo/test/misc/boost_valid.cpp:16:7: error: use of deleted function ‘NonCopyable::NonCopyable(const NonCopyable&)’
/home/fredom/workspace/muduo/test/misc/boost_valid.cpp:11:5: note: declared here11 |     NonCopyable(const NonCopyable&) = delete;|     ^~~~~~~~~~~
/home/fredom/workspace/muduo/test/misc/boost_valid.cpp:35:12: error: use of deleted function ‘MyClass& MyClass::operator=(const MyClass&)’35 |     obj3 = obj1;          // 这里也会报错,尝试拷贝赋值|            ^~~~
/home/fredom/workspace/muduo/test/misc/boost_valid.cpp:16:7: note: ‘MyClass& MyClass::operator=(const MyClass&)’ is implicitly deleted because the default definition would be ill-formed:16 | class MyClass : public NonCopyable {|       ^~~~~~~
/home/fredom/workspace/muduo/test/misc/boost_valid.cpp:16:7: error: use of deleted function ‘NonCopyable& NonCopyable::operator=(const NonCopyable&)’
/home/fredom/workspace/muduo/test/misc/boost_valid.cpp:13:18: note: declared here13 |     NonCopyable& operator=(const NonCopyable&) = delete;|                  ^~~~~~~~
make[2]: *** [test/CMakeFiles/boost_valid.dir/build.make:76: test/CMakeFiles/boost_valid.dir/misc/boost_valid.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:134: test/CMakeFiles/boost_valid.dir/all] Error 2
make: *** [Makefile:136: all] Error 2
已完成 生成,但出现错误。
#include <boost/filesystem.hpp>
#include <iostream>class NonCopyable {
public:// 默认构造函数和析构函数NonCopyable() = default;~NonCopyable() = default;// 禁用拷贝构造函数NonCopyable(const NonCopyable&) = delete;// 禁用拷贝赋值运算符NonCopyable& operator=(const NonCopyable&) = delete;
};class MyClass : public NonCopyable {
public:MyClass() = default;~MyClass() = default;// 其他成员函数可以正常定义void doSomething() {// 示例操作}
};int main() {boost::filesystem::path path(".");std::cout << "Current path: " << boost::filesystem::absolute(path) << std::endl;MyClass obj1;MyClass obj2 = obj1;  // 这里会报错,尝试拷贝构造MyClass obj3;obj3 = obj1;          // 这里也会报错,尝试拷贝赋值return 0;
}

但是 Boost 库其实也为使用者提供了一个这样的基类,所以不用用户自己编写直接继承自 Boost 提供的基类完成这个功能也是可以的,而我们确实应该尽量使用公共开源的库避免重复造轮子,一个最重要的原因我认为是这些公开的库经过了社区甚至是生产环境的大量测试和验证,出问题的情况非常少,而我们自己写的轮子往往无法得到充分的测试。

下载 Boost 源码库编译

在这里插入图片描述

![[Pasted image 20240908145830.png]]

![[Pasted image 20240908150756.png]]

这里根据操作系统的平台选择对应的源码压缩包。下载下来之后解压,然后在项目根目录新建 install 目录用于存放编译产物。

./bootstrap
b2 install --prefix=$PWD/install

现在开始编译,前提是系统中已经安装了对应的编译工具链比如 MinGW 或者 GNU gcc/g++ 。这个过程可能需要等一会,然后在 install 目录下就会有编译生成的产物了。

┌[fredom@HM-3080Ti 15:11:31] ~/workspace/boost_1_86_0
└> ls
total 2.8M
-rwxrwxr-x   1 fredom fredom 721K Sep  6 17:32 b2*
drwxrwxr-x   6 fredom fredom 4.0K Sep  6 17:46 bin.v2/
drwxr-xr-x 143 fredom fredom  12K Aug  8 06:43 boost/
-rw-r--r--   1 fredom fredom  851 Aug  8 06:07 boost-build.jam
-rw-r--r--   1 fredom fredom  21K Aug  8 06:07 boostcpp.jam
-rw-r--r--   1 fredom fredom  989 Aug  8 06:07 boost.css
-rw-r--r--   1 fredom fredom 6.2K Aug  8 06:07 boost.png
-rw-r--r--   1 fredom fredom 2.5K Aug  8 06:07 bootstrap.bat
-rwxr-xr-x   1 fredom fredom  11K Aug  8 06:07 bootstrap.sh*
-rw-rw-r--   1 fredom fredom 1.9M Sep  6 17:46 build.log
drwxr-xr-x   8 fredom fredom 4.0K Aug  8 06:43 doc/
-rw-r--r--   1 fredom fredom  769 Aug  8 06:07 index.htm
-rw-r--r--   1 fredom fredom 5.3K Aug  8 06:43 index.html
drwxrwxr-x   4 fredom fredom 4.0K Sep  6 17:45 install/
-rw-r--r--   1 fredom fredom  291 Aug  8 06:07 INSTALL
-rw-r--r--   1 fredom fredom  17K Aug  8 06:07 Jamroot
drwxr-xr-x 152 fredom fredom 4.0K Aug  8 06:43 libs/
-rw-r--r--   1 fredom fredom 1.4K Aug  8 06:07 LICENSE_1_0.txt
drwxr-xr-x   4 fredom fredom 4.0K Aug  8 06:07 more/
-rw-rw-r--   1 fredom fredom  986 Sep  6 17:32 project-config.jam
-rw-r--r--   1 fredom fredom  541 Aug  8 06:07 README.md
-rw-r--r--   1 fredom fredom 2.6K Aug  8 06:07 rst.css
drwxr-xr-x   2 fredom fredom 4.0K Aug  8 06:07 status/
drwxr-xr-x  14 fredom fredom 4.0K Aug  8 06:07 tools/┌[fredom@HM-3080Ti 15:11:33] ~/workspace/boost_1_86_0
└> duone
1M      ./more
180M    ./boost
287M    ./bin.v2
227M    ./install
5M      ./status
28M     ./tools
613M    ./libs
121M    ./doc
1462M   .┌[fredom@HM-3080Ti 15:12:27] ~/workspace/boost_1_86_0/install
└> tree . -L 2
.
├── include
│   └── boost
└── lib└── cmake

通过 CMake 引入 Boost 项目

Boost 程序库大部分都是头文件库,也就是通过 include 头文件到源文件中直接编译即可获得对应的功能,但是部分常用库还是需要额外的源文件生成的库通过链接之后才能使用,因此需要在 CMake 中指定这些需要链接的库的引入方式。

在配置 Boost 的 CMake 中,学习到了一些 CMake 的相关知识。一般 CMake 引入外部项目都是通过 find_package 完成,然后外部项目对应的需要提供 Find<PackageName>.cmake 或者 <PackageName>Config.cmake / <packagename>-config.cmake 文件,其中前者是比较早期的 CMake 引入项目的模式,现在是推荐使用 config 模式来引入项目,两者对应的用户设置搜索路径为:

  1. module modeset(CMAKE_MODULE_PATH path/to/FindXXX.cmake)
  2. config modeset(CMAKE_PREFIX_PATH) path/to/XXXConfig.cmake)

CMake constructs a set of possible installation prefixes for the package. Under each prefix several directories are searched for a configuration file. The tables below show the directories searched. Each entry is meant for installation trees following Windows (W), UNIX (U), or Apple (A) conventions

EntryConvention
<prefix>/W
<prefix>/(cmake|CMake)/W
<prefix>/<name>*/W
<prefix>/<name>*/(cmake|CMake)/W
<prefix>/<name>*/(cmake|CMake)/<name>*/ 1W
<prefix>/(lib/<arch>|lib*|share)/cmake/<name>*/U
<prefix>/(lib/<arch>|lib*|share)/<name>*/U
<prefix>/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/U
<prefix>/<name>*/(lib/<arch>|lib*|share)/cmake/<name>*/W/U
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/W/U
<prefix>/<name>*/(lib/<arch>|lib*|share)/<name>*/(cmake|CMake)/W/U

所以这里我踩了一个坑,把 install/lib/cmake 目录复制到当前的 muduo 学习项目的 cmake 目录中,然后设置 CMAKE_PREFIX_PATH 指向这个目录,虽然这样子确实能找到指定版本的 Boost ,并且设置了变量 Boost_FOUND=TRUE ,但是实际上给出的变量展开的值全都是错误的(比如头文件 include 目录居然指向的是 muduo 项目下一个不存在的目录),可以看一眼 Boost 的头文件查找变量是如何设置的:

cmake">┌[fredom@HM-3080Ti 15:22:02] ~/workspace/boost_1_86_0/install/lib/cmake/boost_headers-1.86.0
└> cat boost_headers-config.cmake 
# Generated by Boost 1.86.0if(TARGET Boost::headers)return()
endif()if(Boost_VERBOSE OR Boost_DEBUG)message(STATUS "Found boost_headers ${boost_headers_VERSION} at ${boost_headers_DIR}")
endif()mark_as_advanced(boost_headers_DIR)# Compute the include and library directories relative to this file.get_filename_component(_BOOST_CMAKEDIR "${CMAKE_CURRENT_LIST_DIR}/../" REALPATH)# If the computed and the original directories are symlink-equivalent, use original
if(EXISTS "/home/fredom/workspace/boost_1_86_0/install/lib/cmake")get_filename_component(_BOOST_CMAKEDIR_ORIGINAL "/home/fredom/workspace/boost_1_86_0/install/lib/cmake" REALPATH)if(_BOOST_CMAKEDIR STREQUAL _BOOST_CMAKEDIR_ORIGINAL)set(_BOOST_CMAKEDIR "/home/fredom/workspace/boost_1_86_0/install/lib/cmake")endif()unset(_BOOST_CMAKEDIR_ORIGINAL)
endif()get_filename_component(_BOOST_INCLUDEDIR "${_BOOST_CMAKEDIR}/../../include/" ABSOLUTE)add_library(Boost::headers INTERFACE IMPORTED)set_target_properties(Boost::headers PROPERTIESINTERFACE_INCLUDE_DIRECTORIES "${_BOOST_INCLUDEDIR}"
)unset(_BOOST_INCLUDEDIR)
unset(_BOOST_CMAKEDIR)

注意到这一行:

cmake">get_filename_component(_BOOST_INCLUDEDIR "${_BOOST_CMAKEDIR}/../../include/" ABSOLUTE)

所以说 Boost 是通过相对于这个 config 文件所在的路径来写死 BOOST_INCLUDE_DIRS ,怪不得移动了 Boost 的 cmake 目录到其他项目下没法用,所以只能额外在项目目录下设置 Boost 库的安装路径(这里有点膈应,万一我想把这个项目和它的依赖都打包到另一个主机上开发怎么办,这路径全都不对了,还必须去另外一台主机上编译安装一遍,我就是因为自己的云服务器性能不够换到实验室的机器上来编译 Boost,但是还是想回云服务器开发的)。

以下是我的 muduo 项目目录 CMakeLists.txt 配置,引入 Boost 1.86.0 。

cmake">cmake_minimum_required(VERSION 3.20)
project(muduo)cmake_policy(SET CMP0167 OLD)set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/bin)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/lib)
set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_BUILD_TYPE}/install)
set(CMAKE_PREFIX_PATH /home/fredom/workspace/boost_1_86_0/install/lib/cmake)
# set(CMAKE_MODULE_PATH /home/fredom/workspace/boost_1_86_0/install/lib/cmake) # outdatedmessage("build type ${CMAKE_BUILD_TYPE}")set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)include_directories(include)# other dependencies
find_package(Boost 1.86.0 EXACT REQUIRED CONFIGCOMPONENTS filesystemregexatomicchrono
)
if (Boost_FOUND)message("found boost ${Boost_VERSION}")message("Boost_INCLUDE_DIRS ${Boost_INCLUDE_DIRS}")message("Boost_LIBRARIES ${Boost_LIBRARIES}")include_directories(${Boost_INCLUDE_DIRS})set(Boost_USE_STATIC_LIBS OFF) set(Boost_USE_MULTITHREADED ON)  set(Boost_USE_STATIC_RUNTIME OFF)
endif()add_subdirectory(src)
add_subdirectory(app)
add_subdirectory(test)install(FILES README.mdDESTINATION ${CMAKE_INSTALL_PREFIX}/share
)

CMake 配置输出:

[cmake] build type Debug
[cmake] Not searching for unused variables given on the command line.
[cmake] found boost 1.86.0
[cmake] Boost_INCLUDE_DIRS /home/fredom/workspace/boost_1_86_0/install/include
[cmake] Boost_LIBRARIES Boost::filesystem;Boost::regex;Boost::atomic;Boost::chrono
[cmake] -- Configuring done (0.0s)
[cmake] -- Generating done (0.0s)
[cmake] -- Build files have been written to: /home/fredom/workspace/muduo/build

可以看到引入的 COMPONENTS 组件对应的依赖 target 都引入了。

这里我在 find_package 时指定了 EXACT 关键字参数,因为实验室服务器用的人太多了,所以早就安装了另一个版本的 Boost 到 /usr/local/include 里面,这个路径也在 CMake 的默认查找路径里,但是我不想用那个比较旧的版本,所以指定必须搜索用户给定的版本否则报错中止 CMake 配置流程。

cmake">SET(sub_dir_listmisc
)SET(cpp_src_list)
# SET(c_src_list)FOREACH(_sub_dir ${sub_dir_list})FILE(GLOB sub_dir_src_list ${_sub_dir}/*.cpp)LIST(APPEND cpp_src_list ${sub_dir_src_list})# FILE(#     GLOB sub_dir_src_list ${_sub_dir}/*.c# )# LIST(APPEND c_src_list ${sub_dir_src_list})
ENDFOREACH()FOREACH(_src_file ${cpp_src_list})GET_FILENAME_COMPONENT(_src_name ${_src_file} NAME_WE)ADD_EXECUTABLE(${_src_name} ${_src_file})TARGET_LINK_LIBRARIES(${_src_name} PRIVATE ${Boost_LIBRARIES})
ENDFOREACH()

然后在其他源码子目录使用 ${Boost_LIBRARIES} 就可以指代引入的 COMPONENTS 组件的依赖库了,目前“组件”这个概念我还不是很清楚怎么在 CMake 中使用,反正如果自己想做一个开源的项目分发给其他人用,有关 CMake 的打包、安装、组件的知识是必须掌握的,这部分后面要继续学习。如果在 find_package 中没有指定任何 COMPONENTS 那么 ${Boost_LIBRARIES} 变量为空,这有点奇怪,另外 CMake 有没有能在导入项目的时候如果项目设置了 COMPONENTS 自动输出导入了哪些 COMPONENTS ?不然每次使用 Boost 库还要对着网络上的 Boost 文档吗,这也太不方便了,对网络的依赖性太强这可不好,我在公司万一只有内网,主板也没有 WIFI 连接手机热点,那咋办?

Boost 链接库指引文档的问题

官方给的一个链接库的测试例程:

![[Pasted image 20240908153540.png]]

问题是 Boost::regex 这个模块不链接动态库/静态库好像也能编译成功啊?我后面是使用 Boost::filesystemBoost::system 完成库链接的验证。


http://www.ppmy.cn/news/1522571.html

相关文章

2024国赛数学建模备赛|30种常用的算法模型之最优算法-非线性规划

1.1 非线性规划的实例与定义 如果目标函数或约束条件中包含非线性函数&#xff0c;就称这种规划问题为非线性规划问题。一般说来&#xff0c;解非线性规划要比解线性规划问题困难得多。而且&#xff0c;也不象线性规划有 单纯形法这一通用方法&#xff0c;非线性规划目前还没…

SpringBoot3+Vue3开发商店上货管理系统

系统介绍 上货管理系统是专门为各种类型商店打造的一款进货管理系统。针对整个商店进货流程&#xff0c;提供很多方便功能&#xff0c;帮助店家完成上货流程。比如上货清单管理功能、上货清单确认功能、供货商管理功能、商品管理功能等。 技术栈 后端&#xff1a;SpringBoot…

Spark MLlib模型训练—回归算法 Factorization Machines Regression

Spark MLlib模型训练—回归算法 Factorization Machines Regression 在大数据与机器学习领域,推荐系统、广告点击率预测以及评分预测等应用场景中,经常涉及到高度稀疏的特征数据,这对传统的回归模型提出了挑战。因子分解机(Factorization Machines, FMs)是一种广泛应用于…

python例子:相片处理工具(可视化)

作品名称&#xff1a;相片处理工具&#xff08;可视化&#xff09; 开发环境&#xff1a;PyCharm 2023.3.4 python3.7 用到的库&#xff1a;sys、os、cv2、numpy、math和random 作品简介&#xff1a;运行例子后&#xff0c;先选择需要处理的图片&#xff0c;然后可对图片进…

深入了解CSS混合模式

CSS混合模式&#xff08;也称为CSS Blend Modes&#xff09;是一种强大的功能&#xff0c;它允许开发者在CSS中控制元素如何与它们的背景或其他元素混合。这些模式类似于图像编辑软件&#xff08;如Photoshop&#xff09;中的混合模式&#xff0c;使得开发者能够创建出复杂而富…

vulhub Thinkphp5 2-rce远程代码执行漏洞

1.执行以下命令启动靶场环境并在浏览器访问 cd /vulhub/thinkphp/2-rce #进入漏洞环境所在目录 docker-compose up -d #启动靶场 docker ps #查看容器信息 2.访问网页 3.构造payload 192.168.157.142:8080?s/Index/index/L/${phpinfo()} 4、写入一句话木马&#xff0c;使用…

《JavaEE进阶》----12.<SpringIOCDI【扫描路径+DI详解+经典面试题+总结】>

本篇博客主要讲解 扫描路径 DI详解&#xff1a;三种注入方式及优缺点 经典面试题 总结 五、环境扫描路径 虽然我们没有告诉Spring扫描路径是什么&#xff0c;但是有一些注解已经告诉Spring扫描路径是什么了 如启动类注解SpringBootApplication。 里面有一个注解是componentS…

移动应用门户实现的技术方案

移动应用门户是专为移动设备&#xff08;如智能手机和平板电脑&#xff09;设计的应用程序&#xff0c;比如&#xff1a;小程序、APP等&#xff0c;用户可以通过应用商店下载并安装。这些应用程序提供了更好的用户体验&#xff0c;通常具有更高的性能和交互性&#xff0c;可以直…

数据结构的简单认识

数据结构是计算机存储、组织数据的方式。它可以分为逻辑结构和物理结构。 逻辑结构主要有集合、线性结构、树形结构和图形结构。集合中的数据元素间除“同属于一个集合”外&#xff0c;无其他关系&#xff1b;线性结构的数据元素之间存在一对一的关系&#xff0c;如链表、栈和队…

linux系统中,计算两个文件的相对路径

realpath --relative-to/home/itheima/smartnic/smartinc/blocks/ruby/seanet_diamond/tb/parser/test_parser_top /home/itheima/smartnic/smartinc/corundum/fpga/lib/eth/lib/axis/rtl/axis_fifo.v 检验方式就是直接在当前路径下&#xff0c;把输出的路径复制一份&#xff0…

Java | Leetcode Java题解之第386题字典序排数

题目&#xff1a; 题解&#xff1a; class Solution {public List<Integer> lexicalOrder(int n) {List<Integer> ret new ArrayList<Integer>();int number 1;for (int i 0; i < n; i) {ret.add(number);if (number * 10 < n) {number * 10;} els…

【RabbitMQ】基本概念以及安装教程

1. 什么是MQ MQ( Message queue),从字面意思上看,本质是个队列,FIFO 先入先出&#xff0c;只不过队列中存放的内容是消息(message)而已.消息可以非常简单,比如只包含文本字符串,JSON等,也可以很复杂,比如内嵌对象.MQ多用于分布式系统之间进行通信 系统之间的调用通常有两种方式…

揭秘 AMD GPU 上 PyTorch Profiler 的性能洞察

Unveiling performance insights with PyTorch Profiler on an AMD GPU — ROCm Blogs 2024年5月29日&#xff0c;作者&#xff1a;Phillip Dang。 在机器学习领域&#xff0c;优化性能通常和改进模型架构一样重要。在本文中&#xff0c;我们将深入探讨 PyTorch Profiler&#…

基于深度学习的结构优化与生成

基于深度学习的结构优化与生成技术应用于多种领域&#xff0c;例如建筑设计、机械工程、材料科学等。该技术通过使用深度学习模型分析和优化结构形状、材料分布、拓扑结构等因素&#xff0c;旨在提高结构性能、减少材料浪费、降低成本、并加快设计流程。 1. 结构优化与生成的核…

从零开始写论文:如何借助ChatGPT生成完美摘要?

AIPaperGPT&#xff0c;论文写作神器~ https://www.aipapergpt.com/ 在写论文的过程中&#xff0c;摘要是一个非常重要的部分&#xff0c;它能够帮助读者快速理解论文的核心内容&#xff0c;决定是否进一步阅读全文。但是许多学生在写摘要的时候常常感到困惑&#xff0c;不知…

怎么仿同款小程序的开发制作方法介绍

很多老板想要仿小程序系统&#xff0c;就是想要做个和别人界面功能类似的同款小程序系统&#xff0c;咨询瀚林问该怎么开发制作&#xff1f;本次瀚林就为大家介绍一下仿制同款小程序系统的方法。 1、确认功能需求 想要模仿同款小程序系统&#xff0c;那么首先需要找到自己想要…

24/9/3算法笔记 kaggle泰坦尼克

题目&#xff1a; 这次我用两种算法做了这道题 逻辑回归二分类算法 import pandas as pd from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler from sklearn.linear_model import LogisticRegression from sklearn.metr…

CentOS 常用指令及作用解析

CentOS 常用指令及作用解析 在使用CentOS操作系统时&#xff0c;了解并熟练掌握常用的Linux指令是非常重要的。这些指令可以帮助你进行文件管理、系统管理、网络管理等操作。本篇文章将介绍一些CentOS下常用的指令及其主要作用。 目录 文件和目录操作指令文件内容操作指令系…

5千多道安全生产证考试题库ACCESS\EXCEL数据库

安全生产是保护劳动者的安全、健康和国家财产&#xff0c;促进社会生产力发展的基本保证&#xff0c;也是保证社会主义经济发展&#xff0c;进一步实行改革开放的基本条件。因此&#xff0c;做好安全生产工作具有重要的意义。今天的数据即是安全生产资格证、许可证考试题库。 大…

Unity --- 各种关节(Joints)来模拟物体之间的连接

目录 一:2D关节 一:1 固定关节 (Fixed Joint 2D) 功能: 适用场景: 1. 平台游戏中的固定平台: 2. 拼图游戏中的固定部件: 3. 建筑游戏中的固定结构: 一:2 铰链关节 (Hinge Joint 2D) 功能: 适用场景: 一:3 弹簧关节 (Spring Joint 2D) 功能: 适用场景: 1. …