22 Kasım 2024
C# ve C++ gibi diller arasında etkili bir kod çevirici oluşturmak karmaşık bir görevdir. CodePorting.Translator Cs2Cpp aracının geliştirilmesi, bu iki dilin sözdizimi, anlambilim ve programlama paradigmalarındaki farklılıklar nedeniyle birçok sorunla karşılaştı. Bu makale, karşılaştığımız ana zorlukları ve bunları aşmanın olası yollarını tartışacaktır.
Bu, örneğin using
ve yield
operatörlerini ilgilendirir:
using (var resource = new Resource())
{
// Kaynağı kullanma
}
public IEnumerable<int> GetAllNumbers()
{
for (int i = 0; i < int.MaxValue; i++)
{
yield return i;
}
}
Bu gibi durumlarda, ya çeviricide ve kütüphanede kaynak kodun davranışını taklit etmek için oldukça karmaşık kod yazmanız ya da bu tür yapıları desteklemekten vazgeçmeniz gerekir.
Örneğin, kaynak kod sanal generik yöntemler veya sanal işlevler kullanan yapıcılar içerebilir:
public class A
{
public virtual T GenericMethod<T>(T param)
{
return param;
}
}
public class A
{
public A()
{
VirtualMethod();
}
public virtual void VirtualMethod()
{
}
}
public class B : A
{
public override void VirtualMethod()
{
}
}
Bu gibi durumlarda, sorunlu kodu C#'a çevrilebilecek terimlerle yeniden yazmaktan başka seçeneğimiz yoktur. Neyse ki, bu tür durumlar nadirdir ve genellikle küçük kod parçalarıyla ilgilidir.
Bu, kaynaklar, yansıma, dinamik derleme bağlantısı ve işlev içe aktarma işlemlerini içerir:
static void Main()
{
var rm = new ResourceManager("MyApp.Resources", typeof(Program).Assembly);
var value = rm.GetString("MyResource");
}
static void Main()
{
var type = typeof(MyClass);
var method = type.GetMethod("MyMethod");
var result = method.Invoke(null, null);
Console.WriteLine(result);
}
public class MyClass
{
public static string MyMethod()
{
return "Hello, World!";
}
}
static void Main()
{
var assembly = Assembly.Load("MyDynamicAssembly");
var type = assembly.GetType("MyDynamicAssembly.MyClass");
var instance = Activator.CreateInstance(type);
var method = type.GetMethod("MyMethod");
method.Invoke(instance, null);
}
Bu gibi durumlarda, ilgili mekanizmaları taklit etmemiz gerekir. Bu, kaynakların desteklenmesini (derlemede statik diziler olarak oluşturulan ve özel akış uygulamaları aracılığıyla okunan) ve yansımayı içerir. Elbette, .NET derlemelerini C++ koduna doğrudan bağlamak veya başka bir platformda çalışırken Windows dinamik kitaplıklarından işlevleri içe aktarmak mümkün olmadığından, bu tür kodlar kesilmek veya yeniden yazılmak zorundadır.
Bu durumda, genellikle ticari bir üründe kullanımını yasaklamayan üçüncü taraf kütüphanelerin uygulamalarını kullanarak ilgili davranışı uygularız.
Bazı durumlarda, bu, genellikle düzeltilmesi kolay olan basit uygulama hatalarıdır. Daha kötü olan, davranıştaki fark kütüphane kodu tarafından kullanılan alt sistemlerin düzeyinde olduğunda ortaya çıkar.
Örneğin, birçok kütüphanemiz, GDI+ üzerine inşa edilmiş System.Drawing
kütüphanesinden sınıfları yoğun olarak kullanır. C++ için geliştirdiğimiz bu sınıfların sürümleri grafik motoru olarak Skia kullanır. Skia'nın davranışı özellikle Linux'ta GDI+'dan farklıdır ve aynı görsel çıktıyı elde etmek için önemli kaynaklar harcamamız gerekir. Benzer şekilde, System::Xml
'in uygulandığı libxml2, diğer durumlarda farklı davranır ve bunu yamalamak veya sarmalayıcılarımızı karmaşıklaştırmak zorundayız.
C# programcıları, kodlarının çalıştığı koşullar için optimize ederler. Ancak, birçok yapı, tanıdık olmayan bir ortamda daha yavaş çalışmaya başlar.
Örneğin, C#'ta çok sayıda küçük nesne oluşturmak, farklı yığın yönetim şemaları nedeniyle (çöp toplama dâhil olsa bile) genellikle C++'tan daha hızlı çalışır. C++'ta dinamik tür dönüşümü de biraz daha yavaştır. İşaretçileri kopyalarken referans sayma, C#'ta olmayan bir başka ek yük kaynağıdır. Son olarak, C#'tan çevrilen kavramları (enumerators) yerleşik ve optimize edilmiş C++ kavramları (iterators) yerine kullanmak, kod performansını da yavaşlatır.
Darboğazları ortadan kaldırma yöntemi büyük ölçüde duruma bağlıdır. Kütüphane kodu nispeten kolay bir şekilde optimize edilebiliyorsa, çevrilen kavramların davranışını koruyarak, performanslarını tanıdık olmayan bir ortamda optimize etmek oldukça zorlayıcı olabilir.
Örneğin, ortak API'ler SharedPtr<Object>
kabul eden yöntemlere sahip olabilir, kaplarda iteratörler eksik olabilir ve akış işleme yöntemleri istream
, ostream
veya iostream
yerine System::IO::Stream
kabul edebilir.
Kodumuzu C++ programcıları için uygun hâle getirmek amacıyla çeviriciyi ve kütüphaneyi sürekli genişletiyoruz. Örneğin, çevirici, standart akışlarla çalışan begin
-end
yöntemleri ve overloadları zaten oluşturabiliyor.
C++ başlık dosyaları, özel alanların türlerini ve adlarını, ayrıca şablon yöntemlerin tam kodunu içerir. Bu bilgiler, .NET derlemeleri yayınlanırken genellikle gizlenir.
Gereksiz bilgileri üçüncü taraf araçlar ve çeviricinin özel modlarını kullanarak hariç tutmaya çalışıyoruz, ancak bu her zaman mümkün olmuyor. Örneğin, özel statik alanları ve sanal olmayan yöntemleri kaldırmak, istemci kodunun çalışmasını etkilemez; ancak, sanal yöntemleri kaldırmak veya yeniden adlandırmak işlevselliğin kaybedilmesine neden olur. Alanlar yeniden adlandırılabilir ve türleri aynı boyutta olan stublarla değiştirilebilir, tam başlık dosyalarıyla derlenmiş koddan oluşturucu ve yıkıcılar dışa aktarıldığı sürece. Aynı zamanda, ortak şablon yöntemlerinin kodunu gizlemek imkansızdır.
Çerçevemizi kullanarak oluşturulan C++ dili için ürünlerin yayınları, yıllardır başarıyla piyasaya sürülüyor. Başlangıçta ürünlerin küçültülmüş sürümlerini yayınladık, ancak şimdi çok daha eksiksiz işlevsellik sağlıyoruz.
Aynı zamanda, iyileştirme ve düzeltme için hâlâ çok fazla alan var. Bu, daha önce göz ardı edilen sözdizimi yapılarını ve kütüphane parçalarını desteklemeyi, ayrıca çeviricinin kullanım kolaylığını artırmayı içerir.
Mevcut sorunları çözmek ve planlanan iyileştirmelerin yanı sıra, çeviriciyi modern Roslyn sözdizimi analizörüne geçirme üzerinde çalışıyoruz. Kısa bir süre öncesine kadar NRefactory analizörünü kullanıyorduk, bu analizör C# sürümlerini 5.0'a kadar destekliyordu. Roslyn'e geçiş, modern C# dil yapıları gibi şunları desteklememizi sağlayacak:
Son olarak, desteklenen dil sayısını artırmayı planlıyoruz—hem hedef hem de kaynak. Roslyn tabanlı çözümleri VB kodunu okumak için uyarlamak nispeten kolay olacak, özellikle C++ ve Java kütüphanelerinin zaten hazır olduğunu düşünürsek. Öte yandan, Python'u desteklemek için kullandığımız yaklaşım çok daha basit ve benzer şekilde, PHP gibi diğer betik dilleri de desteklenebilir.