VS2015配置NTL库实战:C++大整数运算与密码学开发环境搭建 1. 项目概述为什么是VS2015与NTL库的组合如果你是一名C开发者尤其是在密码学、算法竞赛或者需要处理大整数、多项式运算的领域摸爬滚打过那你大概率听说过或者被“大数计算”折磨过。标准C的int、long long在真正的数论问题面前比如RSA加密解密、椭圆曲线计算、或者仅仅是求一个几百位质数都显得力不从心。这时候一个强大、高效且专门为这类任务设计的库就成了必需品。NTLNumber Theory Library正是这样一个在学术界和工业界都享有盛誉的C数论函数库。那么为什么我们今天要特别讨论在Visual Studio 2015VS2015环境下配置和使用NTL呢这背后有几个非常实际的原因。首先VS2015是一个相当经典且稳定的Windows开发环境它自带的MSVC编译器对C11标准的支持已经比较完善而NTL库的许多现代特性正好依赖于此。其次很多遗留项目、高校课程实验甚至是某些工业软件其开发环境依然锁定在VS2015因为它平衡了功能、稳定性和对旧项目的兼容性。最后对于初学者而言在Windows上用IDE配置一个像NTL这样需要编译的库VS2015的图形化界面比命令行更友好调试也更直观。简单来说这个组合解决的核心问题是在Windows平台上为C开发者提供一个稳定、可调试的高性能数论计算环境。无论你是要写一个密码学作业还是要为一个需要大整数运算的后台服务添加功能搞定了VS2015下的NTL就等于手握了一把解决复杂数学计算问题的瑞士军刀。接下来我会带你从零开始一步步拆解环境搭建、库编译、项目配置中的每一个细节和坑点分享我这些年用下来的实战心得。2. 环境准备构建稳固的开发地基在开始敲代码之前把环境搭建扎实是避免后续无数诡异错误的关键。这一步我们分三块安装VS2015、获取NTL源码、以及准备必要的依赖。2.1 Visual Studio 2015的安装与组件选择很多人觉得安装VS就是一路“下一步”但对于我们要用的NTL库有几个组件是必须勾选的否则编译时会找不到头文件或者链接库。首先你需要获取VS2015的安装镜像或在线安装器。安装时在选择工作负载的界面务必勾选“使用C的桌面开发”。这看起来是废话但关键在后面的“可选组件”。点击这个工作负载右侧的“修改”展开组件列表。这里有几个容易被忽略但至关重要的项目Windows 10 SDK即使你的系统不是Win10也建议安装一个版本比如10.0.14393或10.0.15063。NTL编译过程中可能会用到一些Windows SDK的头文件。Visual C MFC for x86 and x64虽然NTL本身不依赖MFC但某些编译脚本或测试用例可能会间接用到MFC的运行时库。装上它可以避免一些“找不到afxres.h”之类的编译错误。用于 Windows 的 C CMake 工具这不是必须的但如果你未来想用更现代的CMake来管理项目有这个会方便很多。注意安装路径尽量不要包含中文或空格。虽然VS2015对中文路径的容忍度比老版本高但第三方库的编译脚本Makefile很可能无法正确处理导致编译失败。我个人的习惯是安装在D:\VS2015\这样的路径下。安装完成后建议打开一次VS2015创建一个空的控制台项目编译运行一个“Hello World”确保开发环境本身没有问题。2.2 获取NTL库源码与依赖项NTL的官方网站在国外有时候访问可能不太稳定。获取源码最直接的方式是访问它的官方GitHub仓库搜索libntl/ntl。我们通常需要下载最新的稳定版Stable Release比如ntl-11.5.1.tar.gz。下载下来的是一个压缩包解压到一个你喜欢的目录例如D:\Libraries\ntl-11.5.1\。这个目录我们称之为NTL_SRC_DIR。NTL库有一个可选的、但强烈推荐的依赖GMPGNU Multiple Precision Arithmetic Library。GMP是一个用C写的高精度算术库速度极快。NTL可以通过链接GMP库来进一步提升大整数运算的性能。对于VS2015环境我们不需要从源码编译GMP可以直接使用预编译好的Windows版本。你可以搜索“GMP Windows precompiled”找到下载。通常是一个包含include、lib和dll文件夹的zip包。下载后解压比如放到D:\Libraries\gmp-6.2.1\。记住这个路径我们称它为GMP_DIR。里面应该会有include\gmp.h和lib\目录下诸如libgmp-10.lib这样的文件。2.3 配置系统环境变量可选但推荐为了后续编译和项目配置更方便可以添加两个系统环境变量在“此电脑”-“属性”-“高级系统设置”-“环境变量”中NTL_ROOT:D:\Libraries\ntl-11.5.1GMP_ROOT:D:\Libraries\gmp-6.2.1这样做的好处是在VS的项目属性里你可以直接用$(NTL_ROOT)来引用路径避免硬编码。如果不想设置后续就用绝对路径代替。3. 编译NTL库从源码到静态链接库NTL默认的构建系统是Unix风格的configure和make这在Windows上不能直接使用。我们需要使用VS2015提供的开发者命令提示符来手动编译。3.1 使用VS2015开发者命令提示符不要用普通的CMD或PowerShell。在开始菜单里找到Visual Studio 2015文件夹里面有一个Visual Studio 2015 的开发者命令提示。以管理员身份运行它。这个环境会自动设置好cl编译器、link链接器、nmake构建工具等命令的路径。首先通过cd命令切换到你的NTL源码目录下的src文件夹cd D:\Libraries\ntl-11.5.1\src3.2 配置与编译步骤详解在src目录下有一个名为DoConfig的脚本但它也是为Unix设计的。我们需要手动创建一个编译配置。核心是使用nmake工具和一个Makefile。幸运的是NTL的src目录里有一个现成的Makefile模板我们需要根据Windows环境修改它但更简单的方法是直接使用nmake命令并传入参数。第一步运行配置脚本模拟实际上我们是通过命令行参数直接告诉nmake该怎么做。但首先我们可以复制一份现有的配置文件作为参考。不过对于VS2015一个比较可靠的命令如下nmake /f Makefile setup这个命令可能会失败因为Makefile可能没有setup目标。更通用的方法是直接指定编译器和选项。第二步手动编译核心打开src目录下的Makefile文件用记事本或VS Code我们需要关注几个关键变量但我们可以通过命令行覆盖它们。关闭文件在命令提示符中输入以下命令进行编译nmake /f Makefile CXXcl CXXFLAGS/O2 /EHsc /MD /DNDEBUG /DNTL_STD_CXX NTL_GMP_LIPon GMP_PREFIXD:\Libraries\gmp-6.2.1让我拆解一下这个命令nmake /f Makefile: 指定使用当前目录的Makefile。CXXcl: 指定编译器为VS的cl.exe。CXXFLAGS...: 设置编译选项。/O2: 优化速度。/EHsc: 指定C异常处理模型。/MD: 使用动态链接运行时库多线程DLL这通常是与第三方库链接时的推荐选择。/DNDEBUG: 定义NDEBUG宏关闭断言提升发布版性能。/DNTL_STD_CXX: 强制使用标准C模式避免一些平台特定的扩展。NTL_GMP_LIPon: 启用GMP支持LIP是NTL的内部接口层之一GMP_LIP表示使用GMP作为底层大整数实现。GMP_PREFIX...: 指定GMP库的安装路径。执行这个命令后nmake会开始编译。整个过程可能会持续几分钟。如果一切顺利你会在src目录下看到生成的文件最重要的是ntl.lib静态库和一系列.h头文件。第三步处理常见编译错误错误无法打开包括文件: “windows.h”这通常是因为Windows SDK路径没有正确设置。确保你安装了Windows 10 SDK并且VS2015开发者命令提示符的环境是正确的。可以尝试在开始菜单中寻找不同版本的开发者命令提示如带“x64”的。错误找不到 gmp.h检查GMP_PREFIX路径是否正确并且该路径下的include文件夹里确实有gmp.h。路径中不要有中文或空格。错误LNK2001: 无法解析的外部符号 __imp_xxxx这通常是链接GMP动态库时的问题。确保GMP_PREFIX\lib目录下有对应的.lib文件如libgmp-10.lib并且你后续在VS项目中正确添加了附加库目录和这个库文件名。如果编译成功恭喜你最困难的一步已经过去了。将生成的ntl.lib可能在src目录也可能在一个以编译器命名的子目录下如ntl.lib本身和include目录即NTL_SRC_DIR\include里面有很多.h文件整理好。我习惯在NTL_SRC_DIR下创建一个lib文件夹把ntl.lib放进去这样结构清晰NTL_SRC_DIR\include和NTL_SRC_DIR\lib。4. 在VS2015项目中集成与使用NTL库编译好了现在要在我们自己的VS2015 C项目中使用它。4.1 创建新项目与属性配置打开VS2015创建一个新的“Win32控制台应用程序”项目在向导中取消“预编译头”的勾选避免不必要的复杂性。项目创建好后右键点击项目名称选择“属性”。我们需要配置的是“调试”模式下的“x64”平台如果你需要32位就配置“Win32”。配置管理器里确保活动解决方案平台是x64。在属性页中找到“C/C” - “常规” - “附加包含目录”。在这里添加NTL和GMP的头文件路径$(NTL_ROOT)\include;$(GMP_ROOT)\include如果你没设置环境变量就填写绝对路径如D:\Libraries\ntl-11.5.1\include;D:\Libraries\gmp-6.2.1\include。接着找到“链接器” - “常规” - “附加库目录”。添加NTL和GMP的库文件路径$(NTL_ROOT)\lib;$(GMP_ROOT)\lib同样可以用绝对路径。然后找到“链接器” - “输入” - “附加依赖项”。在这里添加需要链接的库文件名ntl.lib;libgmp-10.lib注意libgmp-10.lib的名字可能因GMP版本不同而略有差异请根据你GMP_DIR\lib下的实际文件名修改。4.2 第一个测试程序大整数运算配置完成后我们来写一个简单的测试程序验证NTL是否工作正常。在main.cpp中写入以下代码#include iostream #include NTL/ZZ.h // NTL的大整数类ZZ int main() { // 使用NTL的命名空间 using namespace std; using namespace NTL; // 声明两个大整数 ZZ a, b, c; // 从字符串初始化非常大的整数 a to_ZZ(123456789012345678901234567890); b to_ZZ(987654321098765432109876543210); // 加法 c a b; cout a b c endl; // 乘法 c a * b; cout a * b c endl; // 求最大公约数 (GCD) c GCD(a, b); cout GCD(a, b) c endl; // 判断素数概率性测试 if (ProbPrime(a, 10)) { // 10是测试次数可信度已非常高 cout a is probably prime. endl; } else { cout a is composite. endl; } return 0; }编译并运行这个程序。如果一切配置正确你将看到控制台正确输出了巨大的加法、乘法结果以及GCD。这个简单的测试涵盖了NTL最基本也是最重要的大整数ZZ操作。4.3 项目属性中的关键细节与陷阱运行时库一致性这是最经典的坑。我们编译NTL时使用了/MD选项多线程DLL。因此你的项目属性中“C/C” - “代码生成” - “运行时库”也必须设置为“多线程DLL (/MD)”或“多线程调试DLL (/MDd)”。如果设置为静态链接/MT或/MTd会导致链接错误因为库和程序使用了不同的运行时库。平台工具集确保项目属性中“常规”-“平台工具集”是“Visual Studio 2015 (v140)”。如果你混合了不同版本的VS编译的库很容易出问题。字符集NTL库本身通常使用“未设置”或多字节字符集。如果你的项目使用了“Unicode 字符集”一般不影响NTL的核心功能但如果你在代码中混合了字符串操作需要注意转换。调试与发布配置我们上面编译的NTL库是发布版/DNDEBUG。如果你需要在调试模式下使用并且需要NTL内部的调试断言你需要用类似的命令但去掉/DNDEBUG并可能使用/MDd选项重新编译一个调试版的ntl.lib并在项目的调试配置中链接这个调试版库。通常对于学习和小项目直接使用发布版库在调试模式下链接也是可以运行的只是没有NTL内部的调试信息。5. NTL核心功能实战解析通过了环境测试我们来深入看看NTL几个核心模块的实战用法。NTL的功能非常庞大这里挑几个最常用的领域。5.1 大整数ZZ与模运算ZZ_pZZ类是NTL的基石可以表示任意大小的整数。除了基本的四则运算它更强大的地方在于数论相关函数。#include NTL/ZZ.h #include NTL/ZZ_p.h // 模运算域 void ZZ_Demo() { NTL::ZZ n; // 随机生成一个512位的素数 GenPrime(n, 512); std::cout 随机512位素数: n std::endl; NTL::ZZ a NTL::to_ZZ(123456789); NTL::ZZ b NTL::to_ZZ(1000000007); // 一个模数 // 模幂运算: a^e mod n 这是RSA等加密算法的核心 NTL::ZZ e NTL::to_ZZ(65537); NTL::ZZ mod_result PowerMod(a, e, b); // 计算 a^e mod b std::cout a^e mod b mod_result std::endl; // 扩展欧几里得算法求逆元 NTL::ZZ d, x, y; d NTL::GCD(a, b, x, y); // d a*x b*y if (d 1) { // x 是 a 模 b 下的逆元 std::cout a的逆元模b是: x (模b下) std::endl; } }ZZ_p类则是在一个固定的素数模数p下工作的整数域它的运算会自动取模效率比手动ZZ取模高很多。void ZZ_p_Demo() { NTL::ZZ p; NTL::GenPrime(p, 100); // 生成一个100位的素数作为模数 NTL::ZZ_p::init(p); // 初始化ZZ_p域模数为p NTL::ZZ_p a, b, c; a NTL::to_ZZ_p(12345); b NTL::to_ZZ_p(67890); c a * b; // 乘法结果自动模p std::cout a * b in GF(p) c std::endl; c NTL::inv(a); // 求乘法逆元因为p是素数非零元必有逆元 std::cout 逆元 a^-1 c std::endl; std::cout 验证 a * a^-1 a * c std::endl; // 应该输出1 }5.2 多项式运算ZZ_pX与有限域在编码理论和一些密码学方案中多项式运算是核心。NTL提供了在ZZ_p域上的多项式类ZZ_pX。#include NTL/ZZ_pX.h void Polynomial_Demo() { NTL::ZZ p NTL::to_ZZ(17); // 一个小素数模数方便演示 NTL::ZZ_p::init(p); NTL::ZZ_pX f, g, q, r; // 设置多项式系数 f(x) 3x^3 2x^2 x 5 NTL::SetCoeff(f, 3, NTL::to_ZZ_p(3)); NTL::SetCoeff(f, 2, NTL::to_ZZ_p(2)); NTL::SetCoeff(f, 1, NTL::to_ZZ_p(1)); NTL::SetCoeff(f, 0, NTL::to_ZZ_p(5)); // g(x) x^2 1 NTL::SetCoeff(g, 2, NTL::to_ZZ_p(1)); NTL::SetCoeff(g, 0, NTL::to_ZZ_p(1)); std::cout f(x) f std::endl; std::cout g(x) g std::endl; // 多项式乘法 NTL::ZZ_pX h f * g; std::cout f(x) * g(x) h std::endl; // 多项式带余除法 NTL::DivRem(q, r, f, g); // f q*g r std::cout 商 q(x) q std::endl; std::cout 余数 r(x) r std::endl; // 求多项式在某个点的值 NTL::ZZ_p x_val NTL::to_ZZ_p(2); NTL::ZZ_p y_val NTL::eval(f, x_val); // 计算 f(2) std::cout f(2) y_val std::endl; }5.3 向量与矩阵Vec MatNTL也提供了模板化的向量VecT和矩阵MatT类可以与ZZ、ZZ_p等类型结合进行线性代数运算。#include NTL/vector.h #include NTL/matrix.h void VectorMatrix_Demo() { NTL::VecNTL::ZZ vec1, vec2; vec1.SetLength(3); vec2.SetLength(3); vec1[0] 1; vec1[1] 2; vec1[2] 3; vec2[0] 4; vec2[1] 5; vec2[2] 6; NTL::ZZ dot_product NTL::InnerProduct(vec1, vec2); std::cout 向量点积: dot_product std::endl; NTL::MatNTL::ZZ mat; mat.SetDims(2, 3); // 2行3列矩阵 mat[0][0] 1; mat[0][1] 2; mat[0][2] 3; mat[1][0] 4; mat[1][1] 5; mat[1][2] 6; std::cout 矩阵 mat: std::endl; std::cout mat std::endl; // 矩阵乘法 (需要维度匹配) NTL::MatNTL::ZZ mat2; mat2.SetDims(3, 1); mat2[0][0] 7; mat2[1][0] 8; mat2[2][0] 9; NTL::MatNTL::ZZ result mat * mat2; // 结果应为 2x1 矩阵 std::cout 矩阵乘法结果: std::endl; std::cout result std::endl; }6. 高级应用与性能调优当你熟悉了基本操作后可能会关心如何写出更高效、更健壮的代码。6.1 随机数生成与密码学安全NTL提供了随机数生成器RandomStream但对于密码学应用默认的随机数生成器可能不具备密码学安全性。在需要生成密钥或随机质数用于加密时务必使用NTL的RandomBnd函数配合一个安全的随机源或者使用操作系统提供的密码学安全随机数生成器如Windows的BCryptGenRandom来为NTL的随机状态播种。#include NTL/ZZ.h #include windows.h // 用于 BCryptGenRandom #include bcrypt.h #pragma comment(lib, Bcrypt.lib) NTL::ZZ GenerateCryptoSafePrime(long bit_length) { NTL::ZZ seed; // 使用Windows Crypto API获取安全随机字节来设置种子 HCRYPTPROV hProv 0; if (!CryptAcquireContext(hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { throw std::runtime_error(无法获取加密上下文); } const size_t seed_size sizeof(NTL::_ntl_uint32) * NTL::NTL_NBITS; // 估算种子大小 unsigned char* rand_buf new unsigned char[seed_size]; if (!CryptGenRandom(hProv, seed_size, rand_buf)) { CryptReleaseContext(hProv, 0); delete[] rand_buf; throw std::runtime_error(生成随机数失败); } CryptReleaseContext(hProv, 0); // 将随机字节转换为ZZ作为种子这里简化处理实际应更严谨 NTL::ZZFromBytes(seed, rand_buf, seed_size); delete[] rand_buf; // 设置NTL的随机种子 NTL::SetSeed(seed); // 生成一个密码学安全的随机素数使用更多轮的素性测试 NTL::ZZ prime; // GenPrime的最后一个参数是搜索间隔对于密码学应用通常设为1。 // 我们使用更可靠的 ProbPrime 循环 do { NTL::RandomBits(prime, bit_length); // 生成随机比特长度的数 prime | 1; // 确保是奇数 // 使用更多轮的Miller-Rabin测试例如50轮来增加可信度 } while (!NTL::ProbPrime(prime, 50)); // 50轮测试错误概率极低 return prime; }重要提示上述代码仅为演示思路实际密码学应用中的随机数生成需要极其谨慎建议使用经过严格审计的密码学库如OpenSSL的接口来生成质数或者深入研究NTL文档中关于密码学安全随机数的部分。6.2 内存管理与性能瓶颈排查NTL对象如巨大的ZZ数的内存管理是自动的但不当使用仍会导致性能问题。避免不必要的拷贝NTL的大对象拷贝成本很高。尽量使用引用const ZZ作为函数参数使用移动语义如果编译器支持C11及以上或者就地修改。预分配空间对于已知会增长到很大规模的Vec或Mat使用SetLength或SetDims预先分配空间避免多次重新分配和拷贝。使用ZZ_pContext保存模运算环境如果你需要在同一个模数下进行大量ZZ_p运算创建并保存一个ZZ_pContext对象然后使用ZZ_pContext::restore()来切换这比反复调用ZZ_p::init()高效得多。利用NTL的线程局部存储NTL内部有一些全局状态如当前模数ZZ_p::modulus。在多线程环境下直接使用ZZ_p等类是不安全的因为一个线程修改模数会影响其他线程。NTL提供了ZZ_pPush和ZZ_pPop等机制来管理上下文但在复杂多线程场景下最安全的方式是为每个线程创建独立的计算环境或者使用锁来同步对NTL全局状态的访问。性能剖析如果觉得程序慢首先确定瓶颈是否在NTL计算上。可以尝试使用更高效的算法例如NTL的PowerMod比先Power再Mod快无数倍。检查是否启用了GMP。链接GMP后大整数运算性能会有数量级提升。在VS2015中使用性能探测器Profiler来定位热点函数。6.3 与STL及其他库的协作NTL的容器如VecMat与C标准模板库STL的容器如std::vector是独立的。它们之间需要数据转换。// 将 std::vectorlong 转换为 NTL::VecZZ std::vectorlong stl_vec {1, 2, 3, 4, 5}; NTL::VecNTL::ZZ ntl_vec; ntl_vec.SetLength(stl_vec.size()); for (long i 0; i stl_vec.size(); i) { ntl_vec[i] NTL::to_ZZ(stl_vec[i]); } // 将 NTL::VecZZ 转换为 std::vectorstd::string NTL::VecNTL::ZZ ntl_vec2; // ... 假设已有数据 std::vectorstd::string stl_str_vec; stl_str_vec.reserve(ntl_vec2.length()); for (long i 0; i ntl_vec2.length(); i) { std::stringstream ss; ss ntl_vec2[i]; // 利用NTL的流输出操作符 stl_str_vec.push_back(ss.str()); }对于文件I/ONTL对象通常支持C流操作符和可以方便地读写到文件。#include fstream NTL::ZZ big_num NTL::to_ZZ(12345678901234567890); // 写入文件 std::ofstream outfile(number.txt); if (outfile) { outfile big_num std::endl; } outfile.close(); // 从文件读取 NTL::ZZ loaded_num; std::ifstream infile(number.txt); if (infile) { infile loaded_num; } std::cout 从文件读取的数: loaded_num std::endl;7. 疑难杂症与解决方案速查表在VS2015下使用NTL你可能会遇到一些典型的错误。这里列出一个速查表方便你快速定位问题。问题现象可能原因解决方案编译错误无法打开源文件 “NTL/ZZ.h”1. 附加包含目录未设置或路径错误。2. 环境变量NTL_ROOT未设置且项目中使用的是$(NTL_ROOT)。1. 在项目属性 - C/C - 常规 - 附加包含目录中检查并修正NTL头文件路径绝对路径。2. 或者直接在附加包含目录中使用绝对路径。链接错误LNK2001: 无法解析的外部符号 “class NTL::ZZ …”1. 附加依赖项中没有添加ntl.lib。2. 附加库目录路径错误找不到ntl.lib。3. 编译的NTL库平台x86/x64与项目配置不符。4. 运行时库不匹配/MT vs /MD。1. 在链接器 - 输入 - 附加依赖项中添加ntl.lib。2. 检查链接器 - 常规 - 附加库目录路径。3. 确保项目平台x86或x64与编译的NTL库平台一致。4. 将项目属性 - C/C - 代码生成 - 运行时库改为/MD或/MDd与编译NTL时的选项一致。链接错误LNK2001: 无法解析的外部符号 __imp_mpz_…1. 启用了NTL_GMP_LIPon但未链接GMP库。2. GMP库的路径或库文件名不正确。1. 在附加依赖项中添加GMP的lib文件如libgmp-10.lib。2. 检查附加库目录是否包含GMP的lib路径。运行时错误程序崩溃或结果不正确1. 未正确初始化ZZ_p域就使用了ZZ_p对象。2. 多线程环境下并发修改了NTL全局状态如模数。3. 内存访问越界如访问Vec未分配的元素。1. 在使用ZZ_p前务必调用ZZ_p::init(p)设置模数。2. 使用锁保护对ZZ_p::init、SetSeed等的调用或为每个线程使用独立的上下文。3. 使用SetLength或SetDims预分配空间并通过.length()检查边界。编译NTL时nmake命令报错提示语法错误1. 路径中包含空格或中文。2. 使用了不支持的Makefile目标或变量。3. VS开发者命令提示符版本不对如用了x86的编译x64。1. 将NTL和GMP解压到无空格无中文的路径。2. 严格按照本文第3.2节的nmake命令格式。3. 尝试使用“VS2015 x64 本机工具命令提示符”。“GenPrime”或“RandomBits”生成的数看起来不随机未设置随机种子或种子固定。NTL默认可能有固定的启动状态。在程序开始时使用当前时间或系统随机源调用SetSeed函数初始化随机数生成器。对于密码学应用必须使用密码学安全的随机源。最后再分享一个我个人的小技巧对于复杂的、包含NTL的项目我通常会创建一个单独的“Property Sheet”属性表.props文件里面写好所有的包含目录、库目录和依赖项。这样新建项目时只需要在项目属性中“添加现有属性表”所有配置一次性导入非常干净也便于团队共享和版本管理。在VS2015里可以通过“视图”-“其他窗口”-“属性管理器”来创建和管理属性表。