IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发工具 -> 使用 Visual Studio 的属性表快速引用第三方的库 -> 正文阅读

[开发工具]使用 Visual Studio 的属性表快速引用第三方的库

1 项目配置

1.1 传统配置方式

自从我开始使用 Visual C++ 系列开发工具开始,配置项目头文件目录,库文件目录几乎形成了肌肉记忆。由于 C/C++ 语言的特点,这些目录的位置如果设置不当,近在眼皮子底下的库就是用不上,简直可以逼死强迫症。图(1)就是大家熟悉的附加头文件目录和附加库文件目录的设置选项。这种配置方式的烦恼之处在于每次新建项目,都要把这些库的路径重复配置一遍,一旦不小心填错,就是一堆编译错误。在 Visual Studio 2008 之前的 VC 版本,可以选择把头文件路径和库的目录信息放在全局配置中,避免每次新建项目都重复做第三方库的配置。但是放在全局配置中会导致所有的 VC 项目都受影响,项目用不到的头文件目录不仅导致编译时搜索头文件效率降低下,还会因为头文件的重名产生莫名其妙的编译错误。除此之外,如果是多人协同工作的项目,则每个人从库中拉回来代码,第一件事情就是修改项目中引用的第三方库的目录,因为每个人的电脑上放置第三方库的位置都不一样。入库的时候也要小心翼翼地避免自己修改的目录信息被推送的库中,影响别人。

?图(1)旧的项目配置方式

1.2 属性管理器

虽然 Visual Studio 2008 取消了全局配置选项,但是引入了属性表文件帮助用户简化项目配置操作。Visual Studio 2008 的属性表文件是扩展名为 vsprops 的 XML 文件,其内容就是正常的项目配置内容。但是 Visual Studio 2008 的属性表文件在使用时有一个很大的问题,就是引入属性表文件的时候,实际上是把属性表文件中的配置合并到项目配置中,这会导致多个属性表文件合并时产生冲突。从 Visual Studio 2010 开始,属性表文件中的配置与项目配置相互独立,并增加了一个属性管理器(Property Manager)管理多个属性表配置。如果一个项目中导入了多个属性表文件,则相关的属性配置按照属性管理器中的顺序“串联”到项目配置中。

?图(2)属性管理器界面

图(2)是 Visual Studio 2019 的属性管理器界面,其他版本的界面基本类似。图中橙色方框就是属性管理器的标签页,如果你的界面上没有这个标签页,可以通过 “View” 菜单中的 “Property Manager” 菜单项打开这个界面。图中蓝色方框中是演示项目使用的属性表,每个条目代表一个导入的属性表文件:

boost_1_70_0_share_runtime.propsjsoncpp-1.9.4_static.propslibIconv-1.16_static.props
libtomcrypt_static.propslibuchardet-0.0.7_static.propslog4cplus-2.0.4_share.props
soui2_share.props

从中可以看到这个名为“屎堆超级文件工具”的程序使用了 7 个第三方的开源库,如果按照传统的配置方式,把这 7 个库的目录都配置好就是一个体力活,但是如果事先准备好了属性表文件,则只需要在属性管理器中导入这些属性表文件即可,非常简单。

2 属性表的创建和使用

2.1 使用属性表文件

我们将在下一节介绍如何创建属性表文件,这里主要介绍属性表文件的使用。使用属性表文件主要有两个场景,第一个场景是你需要在自己的项目中使用第三方的库,另一个场景是你拿到一个已经通过属性表文件引入第三方的库的项目,但是属性表文件中的库的位置与你本地的库的位置不一致,这种情况多发生在多人协作的项目或参与开源项目的时候。先说说第一种情况,此时只需要在属性管理器中导入属性表文件即可。首先打开或切换到属性管理器界面,然后在项目名称上点击鼠标右键,在弹出的菜单中选择 “Add Existing Property Sheet...” 菜单项,如图(3)所示:

?图(3)添加属性表

最后在弹出的窗口中选择准备好的属性表文件即可。我写代码离不开 boot 库,所以我选择引入 boost 库作为例子,导入事先准备好的 boost 库的属性表文件后,在属性管理器中就可以看到导入的属性表,并且可以选择右键 “Properties” 菜单查看属性表文件为此项目引入的配置,如图(4)所示:

?图(4)查看属性表文件的配置内容

此时就可以在项目代码中直接使用 boost 的头文件了,不用再考虑路径问题,因为属性表文件都已经设置好了。

使用属性表文件的另一个场景是项目已经通过属性表引第三方的库,此时需要在本地准备好第三方的库,然后修改属性表文件中的附加头文件路径和附加库路径与本地一致。用 notepad 之类的文本编辑软件打开属性表文件,会发现其中的内容似曾相识,是的,它们都是 VC 的项目文件中的内容。如果要修改附加头文件搜索路径,可以找到名为“AdditionalIncludeDirectories” 的标签(tag),将其内容修改为本地准备好的第三方库的 include 目录,然后找到名为 “AdditionalLibraryDirectories” 的标签,将其内容修改为本地准备好的第三方库的 lib 目录。修改完这两个标签的内容,就完成了属性表文件的修改,此时不用对项目配置做任何修改就可以正常链接使用本地的第三方库了。当然,属性表文件还有很多未知的内容,我们将在第 3 节 “属性表文件详解” 部分做具体解释。

2.2 创建属性表文件

新的属性表文件可以用 Visual Studio 的属性管理器创建,也可以找一个规范一点的属性表文件自己手动修改其中相关的内容,两种做法都很简单。首先说说如何用属性管理器创建属性表文件。假设我们项目要使用 libiconv 这个字符编码转码的库,并且在本地已经编译好了这个库(使用静态库),相关路径如下:

E:\development\libIconv-1.16\include     ;头文件路径
E:\development\libIconv-1.16\bin\Debug\x86   ; 32 位调试版的位置
E:\development\libIconv-1.16\bin\Release\x86   ; 32 位发行版的位置
E:\development\libIconv-1.16\bin\Debug\x64   ; 64 位调试版的位置
E:\development\libIconv-1.16\bin\Release\x64   ; 64 位发行版的位置

打开 Visual Studio 的属性管理器,在项目名称上点击鼠标右键,在弹出的菜单中选择 “Add New Project Property Sheet...” 菜单,如图(5)在新建属性表的窗口中输入属性表文件的名称,并选择文件存放位置。考虑到属性表文件以后可以被其他项目使用,最好选一个有意义的名字,比如:libIconv-1.16-static.props。属性表文件的存放位置建议选择项目所在位置,这样如果是代码开源或与其他团队成员协同工作时,可以将属性表文件和项目文件一起提供(入代码库)。保存成功后可以复制一份存放在本地 libiconv 库所在位置,方便以后其他项目引用。

?图(5)新建属性表文件

单击 “Add” 按钮创建属性表,此时在属性管理器中可以看到一个名为 “libIconv-1.16-static” 的条目,并且指定的目录中也生成了名为 “libIconv-1.16-static.props” 的文件。此时属性表文件内容基本上是空的,需要手工设置相关的路径信息。在 “libIconv-1.16-static” 上点击鼠标右键,在弹出的菜单中选择 “Properties” 菜单打开属性配置窗口。属性配置窗口中的内容和项目属性配置窗口的内容一样,区别仅仅在于配置保存的地方不同,在这里做得配置,只会保存在这个属性表文件中。

?图(6)设置属性表内容,添加用户自定义宏

首先选择 “User Macros”,添加一个名为 “LIBICONV_ROOT” 的宏,指定 libiconv 库的根目录,如图(6)所示。这是我习惯的做法,在后续的配置中,就可以用这个宏代替根目录,今后如果要变更本地 libiconv 库的位置,只需要修改这个宏的值就可以了。其他人如果使用这个属性表文件,也只需要将宏修改为与自己本地库的位置一致就行了,简化了属性表文件的使用。

接下来设置附加头文件路径和附加库的路径,如图(7)所示,此时 “LIBICONV_ROOT” 宏就可以派上用场了。一般来说,VC 项目都有四个配置,分别是 Win32 Debug,Win32 Release,X64 Debug 和 X64 Release,需要点耐心把这四个配置都设置一下。恰当地使用 Visual Studio 的环境变量,比如 $(Platform)$(PlatformTarget)$(Configuration),可起到事半功倍的效果,C - V 大法此时也可派上用场。

?图(7)设置属性表内容,附加头文件路径

完成所有自定义配置后,直接点击属性管理器上方工具栏里的保存按钮,或者选择右键菜单中的 “Save libIconv-1.16-static” 菜单,就可以将配置内容保存到 libIconv-1.16-static.props 文件中。

如果感觉用属性管理器创建属性表文件比较麻烦,可以考虑找一个内容比较规范一点的属性表文件手动修改。手工修改属性表文件需要了解属性表文件中各种 XML 标签(tag)在 Viaual Studio 的项目配置中的意义,比如 “PropertyGroup”、 “ItemDefinitionGroup”、 “AdditionalDependencies” 以及 “AdditionalIncludeDirectories” 等等,看完下一节你就知道了。

3 属性表文件详解

3.1 全览

属性表文件其实就是一个格式化的 XML 文件,你在项目配置文件(扩展名为 vcxproj 或 vcproj)中能看到的配置条目,都可以在属性表文件中看到。Visual Studio 的配置条目繁多,内容冗杂,但是如果不是特别复杂的项目配置,比如仅仅为了方便引入第三方库这样的属性表文件,其内容其实非常简单。打开 libIconv-1.16-static.props 文件,可以看到这个 XML 文件只有二十几行:

<?xml version="1.0" encoding="utf-8"?>
 <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   <PropertyGroup Label="UserMacros">
     <LIBICONV_ROOT>E:\development\libIconv-1.16</LIBICONV_ROOT>
   </PropertyGroup>
   <ItemDefinitionGroup>
     <Link>
       <AdditionalDependencies>libiconv.lib;%(AdditionalDependencies)</AdditionalDependencies>
 ?
       <AdditionalLibraryDirectories Condition="'$(Configuration)' == 'Debug'">$(LIBICONV_ROOT)\bin\Debug\$(Platform);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
       <AdditionalLibraryDirectories Condition="'$(Configuration)' == 'Release'">$(LIBICONV_ROOT)\bin\Release\$(Platform);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
     </Link>
     <ClCompile>
       <AdditionalIncludeDirectories>$(LIBICONV_ROOT)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
     </ClCompile>
   </ItemDefinitionGroup>
   <ItemGroup>
     <BuildMacro Include="LIBICONV_ROOT">
       <Value>$(LIBICONV_ROOT)</Value>
       <EnvironmentVariable>true</EnvironmentVariable>
     </BuildMacro>
   </ItemGroup>
 </Project>

所有的内容都在 “project” 标签下,这个是固定内容,“PropertyGroup” 是 “project” 的子标签,通常定义一些宏或环境变量,格式都是固定的,通常用户自定义的内容都会被放在这个组中。“ItemGroup” 也是 “project” 的子标签,用户定义的宏或环境变量,就可以放在这里引用。这个例子中就通过 “BuildMacro” 标签应用了之前定义的宏 “LIBICONV_ROOT”,同时将其作为项目的环境变量使用。

对于一个引用第三方库的属性表文件来说,最重要的配置其实就是 “AdditionalIncludeDirectories”、“AdditionalLibraryDirectories” 和 “AdditionalDependencies” 这三个标签。这三个标签都是 “ItemDefinitionGroup” 的子标签,用来给项目配置提供条件,下面就重点介绍这三个标签。

3.2 AdditionalIncludeDirectories

“AdditionalIncludeDirectories” 用于给项目配置附加头文件目录,在 vcxproj 文件中也能看到它,它在配置层次关系上位于:

?<ItemDefinitionGroup>
? ? ?<ClCompile>
? ? ? ? ?<AdditionalIncludeDirectories> ? ?

一个典型的附加头文件目录设置是这样的:

<AdditionalIncludeDirectories>$(LIBICONV_ROOT)\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

这里面用到了 "%(AdditionalIncludeDirectories)",这个代表在这个属性表的附加头文件被引入前已经存在的附加头文件目录,它们是用 “;” 号隔开的一些列相对路径或绝对路径。带上 "%(AdditionalIncludeDirectories)" 非常重要,如果你忘了,会导致之前的项目或属性表文件引入的附加头文件目录被丢弃,只剩下这个属性表文件中新定义的附加头文件目录。当编译出现错误的时候,你会很迷惑,为什么属性表文件明明正确设置了附加头文件目录,还是会有编译错误?那是因为某个错误的属性表文件忘记了使用 "%(AdditionalIncludeDirectories)" 。

“AdditionalIncludeDirectories” 标签还可以使用条件,例如:

<AdditionalIncludeDirectories Condition="'$(Platform)'=='x64'">..\include\x64;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
 <AdditionalIncludeDirectories Condition="'$(Platform)'=='win32'">..\include\win32;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>

这样的配置会根据编译时选择的平台决定使用哪个 “AdditionalIncludeDirectories” 标签,头文件目录很少会用到条件,后面介绍的静态库文件目录和动态库文件的目录在很多情况下都会用到条件表达式。

3.3 AdditionalLibraryDirectories

“AdditionalLibraryDirectories” 用于给项目配置附加库文件目录,它在配置层次关系上位于:

?<ItemDefinitionGroup>
? ? ?<Link>
? ? ? ? <AdditionalDependencies>

一个典型的附加库文件目录设置是这样的:

<AdditionalLibraryDirectories >$(LIBICONV_ROOT)\bin\Debug\$(Platform);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

注意 “%(AdditionalLibraryDirectories)”,它的意义和 "%(AdditionalIncludeDirectories)" 一样,漏掉了它,会导致丢失之前配置的项目附加库文件目录。32 位的库和 64 位的库通常会分开放置,因为它们在二进制格式上有显著差异,如果有 Debug 版本和 Release 版本同时存在,就会构成四种交叉情况,所以 “AdditionalLibraryDirectories” 一般都会用到条件表达式。

使用你条件区分 Debug 版本和 Release 版本的例子如下:

<AdditionalLibraryDirectories Condition="'$(Configuration)' == 'Debug'">..\lib\check;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
 <AdditionalLibraryDirectories Condition="'$(Configuration)' == 'Release'">..\lib\free;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

如果要区分 32 位平台还是 64 位平台,可以这样用:

 <AdditionalLibraryDirectories Condition="'$(Platform)'=='x64'">..\lib\x64;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
 <AdditionalLibraryDirectories Condition="'$(Platform)'=='win32'">..\lib\x86;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

3.4 AdditionalDependencies

“AdditionalDependencies” 标签用于设置需要链接器链接的 lib 文件,对于静态库,这里就是库文件本身,对于动态库,这里需要指定与之对应的载入库。“AdditionalDependencies” 标签在配置层次关系上位于:

? <ItemDefinitionGroup>
? ? ?<Link>
? ? ? ?<AdditionalDependencies>

一个典型的依赖库设置是:

?<AdditionalDependencies>libiconv.lib;%(AdditionalDependencies)</AdditionalDependencies>

注意 “%(AdditionalDependencies)” 也是重要内容,不能漏了。如果库文件需要根据名称区分 Debug 版或 Release 版,或者是 32 位平台或 64 位平台,就需要用到条件表达式,比如下面的设置,根据目标平台指示链接器链接正确的库文件:

?<AdditionalDependencies Condition="'$(Platform)'=='x64'">xxx_x64.lib;%(AdditionalDependencies)</AdditionalDependencies>
?<AdditionalDependencies Condition="'$(Platform)'=='win32'">xxx_x86.lib;%(AdditionalDependencies)</AdditionalDependencies>

3.6 Condition 条件

前面已经展示过 Condition 的使用方法,实际上,Condition 表达式还支持 OR 或 AND 这样的逻辑表达,比如 Visual Studio 的较早版本中,针对 Unicode 还专门存在一个 UnicodeDebug 的配置,所以判断编译条件是否是 Debug 版本的严谨写法应该是:

?<AdditionalLibraryDirectories Condition="'$(Configuration)' == 'Debug' OR '$(Configuration)' == 'UnicodeDebug'">..\lib\check;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
?<AdditionalLibraryDirectories Condition="'$(Configuration)' == 'Release' OR '$(Configuration)' == 'UnicodeRelease'">..\lib\free;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

如果把环境变量看作字符串,Condition 表达式还支持拼接,比如:

?Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"

如果你对大小写有某种强迫症,总是纠结于到底该写 “X64” 还是 “x64”,那么还可以这样试试:

?Condition="'$(Platform.ToLower())'=='x64'"

“$(Configuration)” 表示当前编译时使用的配置名称,可能系统默认的 Debug、Release、UnicodeDebug 和 UnicodeRelease,也可能是自定义的配置,比如 static_dbg 。“$(Platform)” 是项目平台名称,可能是 “x64”,也可能是 “win32”,Visual Studio 2012 后还可能是 “ARM” 和 “ARM64”,不过这个也是支持自定义的,在配置管理器界面中可以创建自定义的平台,可用于一些交叉编译的场景。“$(PlatformTarget)” 是 CPU 的架构体系,可能是 “x64” 或 “x86”。有一些库会用工具链的版本标识自己的库的名称,比如 boost 库。变量 “$(PlatformToolsetVersion)” 就表示 VC 编译的工具链版本,如果 VC 的版本是 14.2,这个变量的值就是 “142”。 也可以用 “$(PlatformToolset)”,它的值是 “v142”

合理地使用 Visual Studio 提供的宏或环境变量,可以减少使用条件表达的次数。比如将库文件按照调试版本和平台的差异用以下目录结构存放:

?basepath\lib\debug\x86
?basepath\lib\debug\x64
?basepath\lib\release\x86
?basepath\lib\release\x64

则在指定附加库的目录的时候可以避免使用条件表达式,简单这样配置就可以了:

? <AdditionalLibraryDirectories>$(LIBICONV_ROOT)\lib\$(Configuration)\$(PlatformTarget);%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>

对于一些复杂的库名称,可以灵活地使用 Visual Studio 提供的宏环境变量。用过 boost 的朋友可能都知道,boost 的库命名一般是这样的:libboost_atomic-vc142-mt-gd-x64-1_70.lib,其后缀部分有固定的命名规则。巧妙地使用宏环境变量组合,用各种宏变量将后缀拼接出来,也可以有效地减少条件表达式的应用,比如定义一些自定义的宏:

?<BOOST_VER>1_70</BOOST_VER>
?<DBG_LIB_POSTFIX>vc$(PlatformToolsetVersion)-mt-gd-$(PlatformTarget)-$(BOOST_VER)</DBG_LIB_POSTFIX>
?<REL_LIB_POSTFIX>vc$(PlatformToolsetVersion)-mt-$(PlatformTarget)-$(BOOST_VER)</REL_LIB_POSTFIX>

3.7 其他

并不是只有 “<ItemDefinitionGroup>” 中的内容才能够在属性表中使用,也可以通过属性表修改当前项目的配置,比如输出目录和编译中间文件的缓存目录。它们在 “<PropertyGroup>” 中,标签名字分别是 “<OutDir>” 和 “<IntDir>”。我本人总是习惯将项目的输出位置和临时文件分开存放,遇到磁盘空间不足的时候可以方便地删除临时文件,所以我针对这两个标签做了一个属性表文件:

?<PropertyGroup>
? ? ?<OutDir>$(SolutionDir)$(Configuration)\$(PlatformTarget)\</OutDir>
? ? ?<IntDir>$(SolutionDir)$(Configuration)\obj\$(PlatformTarget)\$(ProjectName)\</IntDir>
? ?</PropertyGroup>

新建项目的时候,或者使用别人的项目的时候,导入这个属性表文件,就可以将这两个目录按照自己的喜好设定好。

有一些第三方的库的头文件会引入一些编译告警,如果在使用库的时候不想看到编译器出现这些恼人的编译告警,可以使用 “DisableSpecificWarnings” 屏蔽指定的告警:

?<ItemDefinitionGroup>
? ? ?<ClCompile>
? ? ? ? ?<DisableSpecificWarnings>4819;4267;4244;%(DisableSpecificWarnings)</DisableSpecificWarnings>
? ? ?</ClCompile>
?</ItemDefinitionGroup>

强大的 MSBuild 编译体系,还支持访问注册表。对于一些提供了安装界面的 SDK 来说,直接从注册表中提取库的位置是上佳选择。这是从 StackOverflow 上看到的一个例子,我没有验证过,你可以试试:

?<ClCompile>
? ? ? ?<AdditionalIncludeDirectories>$([MSBuild]::GetRegistryValue(`HKEY_LOCAL_MACHINE\Software\MyCompany\MySDK\v1`, `InstallDir`))\INCLUDE;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
?</ClCompile>

4 说说 C++ 的包管理和依赖管理

大部分在 linux 上做开发的 C++ 程序员,习惯于各种 linux 发行版提供的包管理器带来的便利,如果要使用一个开发库,只要安装库对应的 devel 版本即可。Windows 上就稍微麻烦一点,虽然有 Nuget,Vcpkg,cppan,但是它们之间貌似很难一起协作。由于工作关系,我大部分时间都是在 Windows 上做开发,对这个痛理解的更深。

这篇文章介绍的属性表文件,并不是最优的方法,只是我本人这些年积累的一些经验。我使用第三方库的方法就是先想办法本地编译库,如果不行就退而求其次,弄到这个库的 SDK,然后创建一个属性表文件,最后在项目中使用属性表文件。我本人积累了自己常用的几十个库,通过属性表引用它们,给开发工作带来了极大的方便。尽管如此,我任然很羡慕 Java 、 Python 这种在语言层面提供包和包的依赖管理的语言,一个 import 语句,能节省程序员多少时间啊。C++ 20 终于支持 module,貌似为统一的包管理器铺平了道路,但是到目前为止,并无太大进展,所以我依然在 Vcpkg 和 Nuget 之间兜兜转转,偶尔还要用一下 cppan,只是为了编译那个 tesseract,你懂的。

参考资料

.vcxproj 和 .props 文件结构 | Microsoft Docs

  开发工具 最新文章
Postman接口测试之Mock快速入门
ASCII码空格替换查表_最全ASCII码对照表0-2
如何使用 ssh 建立 socks 代理
Typora配合PicGo阿里云图床配置
SoapUI、Jmeter、Postman三种接口测试工具的
github用相对路径显示图片_GitHub 中 readm
Windows编译g2o及其g2o viewer
解决jupyter notebook无法连接/ jupyter连接
Git恢复到之前版本
VScode常用快捷键
上一篇文章      下一篇文章      查看所有文章
加:2021-10-28 12:34:37  更:2021-10-28 12:35:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 20:38:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码