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

客户青睐Aspose产品,这些产品允许操作流行格式的协议和文件。其中大部分最初是为.NET开发的。与此同时,用于文件格式的业务应用在不同环境中运行。本文将描述我们如何通过构建一个从C#到C++ 的代码翻译框架,成功地为C++ 设置了Aspose产品的发布。保持这些产品的.NET版本的功能在技术上具有挑战性。

我们自己开发了必要的基础设施,实现了语言之间的代码翻译和.NET库函数的仿真。通过这样做,我们解决了通常被视为学术问题的难题。这使我们能够开始每月发布.NET产品的C++ 版本,从相应的C#代码版本中获取代码。此外,覆盖原始C#代码的测试也会与之一起翻译,确保所得解决方案的功能性与C++中专门编写的测试一致。

前史

C#到C++代码转换器的成功基于CodePorting团队在设置自动C#到Java代码转换时的成功经验。创建的框架将C#类转换为Java类,同时适当地替换系统库调用。

对于这个框架,我们考虑了不同的方法。从头开始开发纯Java版本需要太多资源。一种选择是将Java代码中的调用传递到.NET环境,但这将限制我们未来可以支持的编程平台集。当涉及到很少发生的调用和广泛使用的数据类型时,调用传递是方便的。然而,在处理大量对象和自定义数据类型时,这变得很繁琐。

相反,我们思考如何完全将现有代码翻译到新平台上。这是一个当时的热门问题,因为每月都必须进行代码迁移,而且对所有产品都要进行,以产生具有相似功能的发布的同步流。

解决方案分为两部分:

  • 翻译器 — 将C#语法转换为Java语法的应用程序,用目标语言库中适当的替代品替换.NET类型和方法。
  • — 用于仿真.NET库的部分,这些库无法正确映射到Java。为了简化任务,可以使用现有的第三方组件。

以下的论点证实了这一计划在技术上是可行的:

  1. C# 和 Java 语言在思想上有相似之处。至少在类型结构和内存管理模型方面是如此。
  2. 我们只需要翻译库,因此不需要将 GUI 移动到不同的平台上。
  3. 翻译后的库主要包含业务逻辑和低级文件操作,其中最复杂的依赖关系是 System.NetSystem.Drawing
  4. 从一开始,这些库就被开发成可以在各种 .NET 版本上工作(包括 Framework、Standard,甚至 Xamarin)。因此,可以忽略较小的平台差异。

我们不会详细介绍 C# 到 Java 翻译器的更多细节,这需要专门的文章。总之,由于创建的代码翻译器,将 C# 产品转换为 Java 已经成为公司的常规做法。这个翻译器已经从一个简单的基于规则的文本转换器发展成了一个与源代码的 AST 表示一起工作的复杂代码生成器。

C# 到 Java 翻译器的成功帮助我们进入了 Java 市场,因此我们提出了使用相同方案开始发布 C++ 版本的问题。

需求

为了能够发布我们产品的 C++ 版本,我们需要创建一个框架,允许我们将 C# 代码翻译成 C++,编译、测试并发送给客户。这些代码是一组库,每个库都有几百万行代码。代码翻译器的库组件必须涵盖以下内容:

  1. 为翻译后的代码模拟 .NET 环境。
  2. 适应 C++ 的翻译后的代码:类型结构、内存管理等。
  3. 将翻译后的 C# 代码风格转换为 C++ 风格,以便开发人员不熟悉 .NET 范例的情况下轻松使用代码。

许多读者可能会问,为什么我们没有考虑使用现有的解决方案,比如 Mono 项目。有几个原因:

  1. 这不会涵盖第二和第三个需求。
  2. Mono 是基于 C# 实现的,依赖于其运行时。
  3. 将第三方代码适应我们的需求(API、类型系统、内存管理模型、优化等)需要与创建我们自己的解决方案相当的时间。
  4. 我们的产品不需要完整的 .NET 实现。但是,如果我们有一个完整的实现,很难区分哪些方法和类是我们需要的,哪些不是。我们将花费很多时间修复我们从未使用过的功能。

从理论上讲,我们可以使用我们的翻译器将现有解决方案转换为 C++。然而,这需要在一开始就拥有一个完全功能的翻译器,因为没有系统库,无法调试任何翻译后的代码。此外,优化问题对于翻译后的产品代码来说比原来更加重要,因为系统库调用往往成为瓶颈。

让我们回到代码转换器的需求上。由于无法将 .NET 类型映射到 STL 类型,我们决定使用自定义的 Library 类型作为替代。该库被开发为一组适配器,允许通过类似于 .NET 的 API(与 Java 中相同)使用第三方库的功能。

在使用现有 API 翻译库时,翻译后的代码的一个重要要求是它应该在任何客户应用程序内运行。因此,我们不能在翻译后的代码中使用垃圾回收,因为它会覆盖整个应用程序。相反,我们的内存管理模型必须对 C++ 开发人员清晰可见。我们选择使用智能指针作为折衷方案。我们将在另一篇文章中描述我们如何成功地改变了内存模型。

CodePorting 具有强大的测试覆盖文化,将 C# 代码编写的测试应用于 C++ 产品将大大简化故障排除。代码翻译器必须能够翻译这些测试。

最初,手动修复翻译后的 Java 代码加速了开发和产品发布。然而,从长远来看,这显著增加了为每个版本准备发布所需的费用,因为每次出现翻译错误时都必须进行修复。通过将生成的 Java 代码与为两个连续的 C# 代码修订版本生成的翻译器输出之间的差异作为补丁提供给结果代码,可以管理这一问题,而不是每次都从零开始转换。尽管如此,我们决定优先考虑 C++ 框架修复,而不是结果代码修复,从而只修复每个翻译错误一次。

相关文章