C#에서 C++로: 프로젝트 변환을 자동화한 방법 - 1부

Aspose 제품은 인기 있는 형식의 프로토콜과 파일을 조작할 수 있도록 해주며, 고객들이 가치를 두고 있습니다. 이 제품들은 대부분 .NET으로 초기 개발되었습니다. 동시에 파일 형식의 비즈니스 응용 프로그램은 다양한 환경에서 실행됩니다. 이 기사에서는 Aspose 제품을 C++ 로 릴리스하는 데 성공한 방법을 설명하겠습니다. 이를 위해 C#에서 C++로 코드 변환을 위한 프레임워크를 구축했습니다. 이러한 제품들의 .NET 버전의 기능을 유지하는 것은 기술적으로 어려운 과제였습니다.

우리는 필요한 인프라를 직접 개발하여 언어 간 코드 변환과 .NET 라이브러리 함수의 에뮬레이션을 가능하게 했습니다. 이를 통해 일반적으로 학문적으로만 고려되는 문제를 해결했습니다. 이로써 매월 C# 코드 버전과 해당하는 C++ 코드 버전에서 릴리스용 코드를 얻어 매월 .NET 제품을 C++ 언어로 출시하기 시작했습니다. 또한 원래 C# 코드를 커버하는 테스트도 함께 번역하여 결과 솔루션의 기능을 C++에서 특별히 작성된 테스트와 동등하게 모니터링하고 있습니다.

배경

C#에서 C++ 코드 변환기의 성공은 CodePorting 팀이 자동 C#에서 Java 코드 변환을 설정하는 동안 얻은 성공적인 경험에 기반합니다. 생성된 프레임워크는 C# 클래스를 Java 클래스로 변환하면서 시스템 라이브러리 호출을 적절하게 대체했습니다.

프레임워크에 대해 다양한 접근 방식이 고려되었습니다. 순수한 Java 버전을 처음부터 개발하는 것은 너무 많은 리소스를 필요로 했습니다. 하나의 옵션은 Java 코드에서 .NET 환경으로 호출을 마샬링하는 것이었지만, 이는 향후 지원할 수 있는 프로그래밍 플랫폼 집합을 제한했습니다. 당시 .NET은 Windows에서만 사용되었습니다. 호출 마샬링은 드물게 발생하는 데이터 유형을 전달하는 경우 편리하지만, 많은 객체와 사용자 정의 데이터 유형을 처리하는 경우 압도적이었습니다.

대신, 기존 코드를 새 플랫폼으로 완전히 번역하는 방법을 고민했습니다. 이는 매월 코드 마이그레이션이 필요하며 모든 제품에 대해 동일한 기능을 갖춘 릴리스를 생성해야 했기 때문에 중요한 문제였습니다.

해결책은 두 부분으로 나뉘었습니다:

  • 번역기 — C# 구문을 Java로 변환하는 응용 프로그램으로, .NET 유형 및 메서드를 대상 언어 라이브러리에서 적절한 대체로 교체합니다.
  • 라이브러리 — Java에 적절하게 매핑되지 않은 .NET 라이브러리 부분을 에뮬레이트하는 구성 요소입니다. 작업을 간소화하기 위해 사용 가능한 타사 구성 요소를 활용할 수 있습니다.

다음 인수들이 기술적으로 계획이 타당하다는 것을 확인했습니다:

  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. .NET 패러다임에 익숙하지 않은 개발자들이 코드를 쉽게 사용할 수 있도록 번역된 C# 코드 스타일을 C++ 스타일로 변경합니다.

많은 독자들이 왜 Mono 프로젝트와 같은 기존 솔루션을 고려하지 않았는지 궁금해할 것입니다. 이에는 다음과 같은 이유가 있습니다:

  1. 이는 두 번째와 세 번째 요구 사항을 충족시키지 않았을 것입니다.
  2. Mono는 C#으로 구현되었으며 그것은 그것의 런타임에 의존합니다.
  3. 타사 코드를 우리의 요구 사항에 맞게 적응시키는 것은 우리의 솔루션을 만드는 데 필요한 시간과 비슷한 양의 시간이 필요했습니다.
  4. 우리 제품은 완전한 .NET 구현을 필요로하지 않습니다. 그러나 우리가 완전한 구현을 가지고 있다면 어떤 메서드와 클래스가 필요하고 어떤 것이 필요하지 않은지 구분하기 어려웠을 것입니다. 우리는 사용하지 않는 기능을 수정하는 데 많은 시간을 소비했을 것입니다.

이론적으로 우리는 기존 솔루션을 C++로 변환하기 위해 변환기를 사용할 수 있었습니다. 그러나 이는 시스템 라이브러리 없이는 어떤 번역된 코드도 디버그할 수 없기 때문에 처음부터 완전히 기능적인 변환기를 갖고 있어야 했습니다. 게다가 최적화 문제는 번역된 제품 코드보다 시스템 라이브러리 호출이 더 중요해질 것입니다.

코드 변환기의 요구 사항으로 돌아가보겠습니다. .NET 유형을 STL 유형에 매핑할 수 없는 불가능성 때문에 우리는 사용자 정의 라이브러리 유형을 대체로 사용하기로 결정했습니다. 이 라이브러리는 Java와 동일한 .NET 스타일 API를 통해 타사 라이브러리 기능을 사용할 수 있도록 개발되었습니다.

기존 API로 라이브러리를 번역하는 동안 번역된 코드에 대한 중요한 요구 사항은 고객의 응용 프로그램 내에서 실행되어야 한다는 것이었습니다. 따라서 번역된 코드에 가비지 컬렉션을 사용할 수 없었습니다. 왜냐하면 그렇게 하면 전체 응용 프로그램이 커버되기 때문입니다. 대신 C++ 개발자를 위해 메모리 관리 모델을 명확하게 해야 했습니다. 우리는 스마트 포인터를 선택했습니다. 메모리 모델을 어떻게 변경했는지에 대해서는 별도의 기사에서 설명하겠습니다.

CodePorting은 강력한 테스트 커버리지 문화를 가지고 있으며 C# 코드에 작성된 테스트를 C++ 제품에 적용할 수 있는 능력은 문제 해결을 크게 단순화했습니다. 코드 변환기는 테스트도 번역할 수 있어야 했습니다.

초기에 번역된 Java 코드를 수동으로 수정하는 것은 개발 및 제품 출시 속도를 높이는 데 도움이 되었습니다. 그러나 장기적으로는 각 버전을 출시하기 위해 준비하는 데 필요한 비용을 상당히 높였습니다. 왜냐하면 모든 번역 오류가 나타날 때마다 수정되어야 했기 때문입니다. 이는 결과적으로 Java 코드를 매번 처음부터 변환하는 대신 두 연속적인 C# 코드 리비전에 대해 변환기의 출력 차이로 계산된 패치로 결과적인 Java 코드를 공급함으로써 관리할 수 있었습니다. 그럼에도 불구하고 C++ 프레임워크 수정을 결과 코드 수정보다 우선시하도록 결정되었습니다. 따라서 각 번역 오류를 한 번만 수정했습니다.

관련 기사