从 C# 到 C++:我们如何自动化项目转换 - 第 2 部分

开发

C#到C++ 代码翻译器的设计和开发完全由CodePorting执行。这需要进行许多调查,应用多种方法和测试,涉及内存模型和其他方面的不同。最终,选择了两种解决方案之一,其中一种目前用于Aspose产品的C++版本。

技术

现在是时候解释一下我们在代码翻译器中使用的技术了。该翻译器是一个用C#编写的控制台应用程序,因此可以轻松嵌入到执行典型序列(如翻译-编译-测试)的脚本中。还有一个GUI组件,您可以通过单击按钮来执行相同的操作。

语法分析由NRefactory库在过时的翻译器版本中执行,而在新版本中由Roslyn执行。

该翻译器使用多个AST tree遍历来收集信息并生成输出的C++ 代码。对于C++ 代码,不会创建AST表示,而是以纯文本形式处理输出代码。

在微调翻译器时,有许多情况需要额外的信息。这些信息通过选项和属性传递。选项适用于整个项目。通常,它们用于指定类导出宏名称或在解析代码时使用的C#条件符号。属性适用于类型和实体,并为它们提供一些特定的信息,例如:标记哪些类成员在翻译后的代码中需要constmutable限定符,或者哪些实体应该被排除在翻译之外。

C#类和结构被转换为C++ 类。它们的成员和源代码被转换为最接近的模拟。泛型类型和方法被映射到C++ 模板。C#引用被翻译成智能指针(共享或弱引用)。引用类在Library中定义。代码翻译器的其他内部细节将在单独的文章中描述。

因此,从C#翻译到C++的项目依赖于我们的Library,而不是.NET库:

C# to C++

为了构建代码翻译器库和翻译后的项目,我们使用了Cmake。目前,我们支持VS 2017和2019(Windows)、GCC和Clang(Linux)编译器。

正如已经提到的,我们大部分的.NET实现都是对第三方库的薄适配层,包括:

  • Skia — 图形支持。
  • Botan — 加密功能。
  • ICU — 字符串、代码页和文化支持。
  • Libxml2 — XML 操作。
  • PCRE2 — 正则表达式支持。
  • zlib — 压缩功能。
  • Boost — 不同的用途。
  • 还有其他一些库。

翻译器和库都经过了许多测试。库的测试使用了GoogleTest框架。翻译器的测试主要使用NUnit/xUnit,并分为几个类别,以确保:

  • 翻译器的输出在特定输入数据上与目标一致。
  • 翻译后的程序输出与目标一致。
  • 输入项目中的NUnit/xUnit测试被翻译成GoogleTest测试并通过。
  • 翻译后的项目API在C++中正常工作。
  • 翻译器的选项和属性按预期工作。

我们使用GitLab作为版本控制系统。在持续集成方面,我们使用Jenkins。翻译后的产品以NuGet包和可下载的存档形式提供。

问题

在开发这个项目时,我们遇到了许多不同的问题。其中一些是预料之中的,而其他一些则是在过程中逐步揭示出来的:

  1. .NET 和 C++ 之间的类型系统差异。
    C++ 没有任何替代 Object 类型的机制,而大多数库类也没有运行时类型信息(RTTI)。这使得将 .NET 类型映射到 STL 类型变得不可能。
  2. 翻译算法复杂。
    在翻译后的代码中需要揭示许多不平凡的细微差别。例如,C# 中计算方法参数的顺序是明确定义的,而在 C++ 中却存在未定义行为。
  3. 故障排除困难。
    调试翻译后的代码需要特定的技能。上述描述的细微差别可能对程序的运行产生重大影响,导致难以解释的错误。另一方面,它们也可能轻易变成隐藏的错误并长时间存在。
  4. 内存管理系统不同。
    C++ 没有垃圾回收机制。因此,为了使翻译后的代码与原始代码的行为相似,需要更多的资源。
  5. C# 开发人员需要遵守规定。
    C# 开发人员必须习惯于代码翻译过程中的限制。这些限制的原因包括:
    • 翻译器语法分析器必须支持语言版本。
    • 翻译器不支持的代码结构是禁止的(例如 yield)。
    • 代码风格受到翻译后代码结构的限制(例如,每个引用字段必须明确地是弱引用还是共享引用,而对于任意的 C# 代码,不一定是这样的)。
    • C++ 语言施加了其自身的限制(例如,在 C# 中,静态变量在所有前台线程完成之前不会被删除,而在 C++ 中不是这样)。
  6. 大量的工作。
    我们产品使用的 .NET 库子集足够大,实现所有类和方法需要很长时间。
  7. 开发人员的特殊要求。
    深入研究复杂的平台内部,并使用两种或更多的编程语言,限制了可用候选人的数量。另一方面,对编译器理论或其他奇特学科感兴趣的开发人员很容易找到自己的位置。
  8. 系统的脆弱性。
    尽管我们有成千上万的测试和数百万行代码来测试翻译器,但有时我们会遇到问题,因为为修复一个项目的编译而进行的更改会导致另一个项目的编译失败。例如,在项目中的罕见语法结构和特定代码风格中可能会发生这种情况。
  9. 高门槛。
    代码翻译器项目中的大多数任务都需要深入分析。由于子系统和场景众多,每个新任务都需要长时间熟悉项目的不同方面。
  10. 知识产权保护问题。
    虽然有很多有效地混淆 C# 代码的现成解决方案,但在 C++ 中,许多信息都保存在类头文件中。此外,某些定义无法从公共头文件中删除而不产生后果。将泛型类和方法映射到模板会产生另一种漏洞,因为它会暴露算法。

尽管如此,代码翻译器项目从技术角度来看非常有趣,其学术复杂性迫使我们不断学习新知识。

结论

在开发代码翻译器项目时,我们成功地实现了一个解决有趣学术问题的系统,即代码翻译。我们已经按计划发布了 Aspose 库的月度版本,以适用于原本不应该使用的语言。

我们计划发布更多关于代码翻译器的文章。下一篇文章将详细介绍转换过程,包括如何将具体的 C# 构造映射到 C++ 构造。另一篇文章将讨论内存管理模型。

我们将尽力回答提出的问题。如果读者对代码翻译器开发的其他方面感兴趣,我们可以考虑撰写更多相关文章。

相关文章