.NET/Mono в Java? Легко!

    Здравствуйте. Хочу представить свой проект – компилятор .NET/Mono в Java. Целью проекта является создание компилятора, и набора стандартных библиотек позволяющих переносить написанные приложения и библиотеки на платформу Java, версии 1.6 и выше. Из аналогичных проектов мне известен лишь проект dot42. Но он заточен под Android и имеет собственную стандартную библиотеку не совсем совместимую с .NET/Mono.

    Пока есть только альфа версия, и поэтому для реального использования компилятор пока не годится, однако уже частично работоспособен, генерирует валидный код Java и поддерживает часть стандарта ECMA-335.

    Исходные коды на github.com: https://github.com/zebraxxl/CIL2Java


    Компилятор представляет собой консольное приложение, которое при запуске без параметров выводит справку. Так что с использованием проблем возникнуть не должно.

    Ещё хочу отметить, что при работе компилятор исходит из предположения, что весь код, поданный ему на вход, является валидным.

    Что не поддерживается


    Сразу хочу оговорить, что на данный момент не поддерживается:
    • Неуправляемые указатели
    • Математика над указателями (как управляемыми, так и неуправляемыми)
    • P/Invoke
    • Не векторные массивы (массивы с нижней границей не равной 0)
    • Оператор switch на типе long
    • Опкод calli
    • Фильтры исключений

    Последние три пункта планируется реализовать в ближайшее время, на счёт остальных – это долгосрочные планы, если их вообще реально будет реализовать.

    Как это работает


    Компиляция разделена на три больших этапа. Первым этапом компилятор загружает и преобразует во внутреннее представление все используемые типы. Вторым производит подготовку к третьему этапу. Третьим этапом преобразует метаинформацию и компилирует код.

    Преобразование типов во внутреннее представление происходит на лету во время их загрузки. То есть берётся тип, преобразуется во внутреннее представление, затем добавляется в список компиляции. Затем из оригинального типа берутся все поля и методы и так же преобразуются во внутреннее представление. Таким образом, в список компиляции добавляются все типы, их поля и методы из явно указанных в качестве входных, сборок.

    Но так же, во время преобразования любого типа, поля или метода (далее члена) так же подгружаются, преобразуются во внутреннее представление и добавляются в список компиляции, все члены от которых исходный член зависит. При этом добавляются только действительно используемые члены. Таким образом, после первого этапа в списке компиляции будут лишь те типы, которые есть в исходных сборках, плюс те типы и их члены, которые необходимы. Благодаря этому на выходе мы получаем откомпилированными исходные сборки и необходимые куски остальных. Для примера возьмём код:
    using System;
    
    namespace TestConsole
    {
        public class Program
        {
            public static void  Main(string[] args)
            {
                Console.WriteLine("Hello world");
            }
    
            // Точка входа для Java
            public static void main(string[] args)
            {
                Main(args);
            }
        }
    }
    


    После первого этапа, список компиляции будет выглядеть вот так:
    0:  type Foo.Program
        methods:
            Main
            main
    1:  type System.Console
        methods:
            WriteLine
    2:  type System.String
    


    Здесь так же необходимо отметить о механизме подмены сборок. При обнаружении ссылки на тип, который находится во внешней сборке (не указанной в качестве входной), эта сборка будет автоматически подгружена. Однако что делать, если подгружаемая сборка имеет реализацию не совместимую с Java? Например, стандартный mscorlib? Для этого и нужен механизм подмены сборок. По умолчанию, на данный момент, так подменяется mscorlib на специальную реализацию, которая использует для работы механизмы Java. Можно так же указать и другие сборки, для подмены используя ключ компиляции –r. Вкратце работает это следующим образом: когда Mono.Cecil начинает искать откуда загрузить сборку, он обращается с этим вопросом к AssemblyResolver, который ему был передан в качестве параметра при чтении изначальной сборки. AssemblyResolver компилятора сначала ищет сборку с таким именем в ранее загруженных, если там не находит, то смотрит нет ли её в списках на подмену. Если есть, то загружает и возвращает сборку, указанную в списке на подмену. Если же и в списках на подмену её нет, то загружается стандартная сборка, стандартными средствами.

    Перед вторым этапом компиляции происходит этап предкомпиляции, в котором компилятор производит дополнительную обработку типов для подготовки их к непосредственной компиляции. К примеру именно на этом этапе добавляются методы которые явно нигде не вызываются, но являются перегруженными виртуальными методами явно используемых методов.

    И собственно третий этап — самый основной. Преобразование метаинформации процесс достаточно простой и понятный. Единственное что хотелось бы отметить – это то, что все типы объявляются в результате с публичным доступом из-за несовместимости уровней видимости Java и CIL. Глобальные типы в Java могут иметь либо публичный доступ, либо доступ только из пакета (пространства имён). А вложенные типы в Java, имеющие например закрытый уровень видимости вообще не могут использоваться за пределами типа, в котором объявлены. Если обратится к любому члену такого типа из внешнего класса будет сгенерировано исключение. Так что все типы автоматически становятся открытыми.

    А вот компиляция кода более сложный и объёмный процесс, который так же делится на несколько этапов. Первым этапом строится граф кода. Этим занимается библиотека ICSharpCode.Decompiler из состава ILSpy. В целом получается почти готовый для компиляции граф, но все же несколько дополнительных преобразований выполняется. Для примера обратно преобразуются псевдо инструкции CompoundAssignment, которые генерирует ICSharpCode.Decompiler. Ну и после этого собственно происходит компиляция в байт-код Java.

    Вот так работает сам компилятор. Теперь я более подробно расскажу о некоторых моментах работы и как реализована поддержка тех или иных вещей.

    Дженерики


    С точки зрения JVM – дженериков не существует. Дженерики в Java – это лишь расширение компилятора и дженерик с точки зрения JVM – это обычный объект типа java.lang.Object. Дженерики в CLI являются компилируемыми во время исполнения. Это значит, что когда компилятор встречает дженерик, он подставляет вместо него реальный тип и, по сути, создаёт новый тип или метод на основе исходного. CIL2Java действует так же, пропуская методы и типы имеющие джерик параметры и создаёт их только когда встречает ссылку с указанием какие на какие типы заменять эти параметры.

    Значимые типы


    Это наверно один из главных доводов почему .NET/Mono лучше Java в спорах, что же лучше. Да, в Javа поддержки таких типов нет. Поэтому все значимые типы компилируются как указательные. Но что бы не было проблем из-за разницы в поведении значимых и указательных типов, поведение значимых типов эмулируется. Во первых генерируется конструктор без параметров, и добавляются три внутренних метода:
    • c2j__$__ZeroFill() – заполняет нулями содержимое типа
    • c2j__$__CopyTo(ValueType) – копирует содержимое исходного типа в указанный
    • c2j__$__GetCopy() – создаёт новый экземпляр типа, и копирует в него данные из исходного


    Используя эти три метода поведение значимых типов полностью эмулируется. Для примера код «Foo(valType);» будет преобразован в «Foo(valType.c2j__$__GetCopy());» и в метод Foo будет передана копия valType.

    Так же для корректной работы все значимые типы автоматически инициализируются конструктором по умолчанию в конструкторах и в самом начале методов (прологе).

    Таким образом, главное преимущество таких типов – при правильном использовании они увеличивают скорость работы приложения, не просто теряется, а наоборот их использование будет замедлять работу приложения.

    Перечисления


    В .NET/Mono перечисления по своей сути являются значимыми типами, но с дополнительными ограничениями. У них не может быть никаких методов, всего одно не статическое поле примитивного типа (int, short и т.д.) имеющее имя «value__» и статические поля имеющие тип самого перечисления.

    При компиляции вместо типа перечисления, подставляется его базовый тип. То есть метод «void Foo(EnumType val);» после компиляции станет «void Foo(int val);».

    Упаковка


    Упаковка значимых типов делится на три категории: упаковка примитивных типов, упаковка значимых типов и упаковка перечислений.

    Упаковка примитивных типов реализована двумя способами: упаковка в типы CIL или упаковка в типы Java. В первом случае в качестве типов для упаковки используются стандартные для CIL типы из пространства имён System (System.Int32, System.Single и т.д.). Во втором – стандартные типы для Java (java.lang.Integer, java.lang.Float и т.д.)

    В случае упаковки в типы CIL мы сохраняем информацию о беззнаковых типах и код вида «uintType.ToString()» будет иметь правильный результат. Однако при передаче таких параметров в Java, в методы где требуется передать упакованный примитивный тип (например java.lang.reflect.Method.invoke) компилятору будет необходимо генерировать код переупаковки (правда пока этой функции в компиляторе нет), и, таким образом, будет падать производительность.

    В случае упаковки в типы Java все соответственно наоборот. Код «uintType.ToString()» даст неправильный результат, если значение uintType будет больше 2 147 483 647, но не будет лишних переупаковок из CIL в Java и обратно. Какой метод применять – решать вам. За это отвечает параметр компиляции – box. По умолчанию происходит упаковка в типы CIL.

    С упаковкой значимых типов все проще. Берём копию типа и просто передаём её. Он и так по факту, после компиляции, становится указательным типом.

    А вот перечисления упаковываются в их настоящий тип. То есть, если имеется перечисление типа EnumType, имеющее базовый тип int, то, как было сказано выше, при компиляции вместо EnumType будет подставляться тип int. А вот в случае упаковки, будет создан объект именно типа EnumType, а в его поле value__ будет положено значение этого перечисления. Таким образом, будет сохранена информация о типе.

    Указатели


    Как уже было сказано, компилятор не поддерживает небезопасные указатели. А вот передача по ссылке вполне успешно работает. Если в метод передаётся значение по ссылке, то типом этого параметра станет тип CIL2Java.VES.ByRef[type], где [type] – это тип на который создаётся ссылка (возможные значения: Byte, Short, Int, Long, Float, Double, Bool, Char, Ref). Отдельные типы для примитивных типов необходимы для того, что бы не упаковывать/распаковывать их при каждом обращении. Сам тип ссылки является абстрактным классом с двумя абстрактными методами: get_Value и set_Value для получения и установки значения по ссылке соответственно. Вот так это выглядит:
    public abstract class ByRef[type]
    {
        public abstract [type] get_Value();
        public abstract void set_Value([type] newValue);
    }
    


    При создании ссылки на значение, создаётся объект который реализует соответственный абстрактный класс. И реализует в зависимости от того, где хранится значение на которое мы создаём ссылку:

    LocalByRef[type] – ссылка на локальную переменную или параметр метода. Просто хранит в себе значение до выхода из вызываемого места, после чего происходит восстановление значения переменной или параметра.
    Возьмём вот такой код:
    public class Program
    {
        public static void Foo(ref int refValue)
        {
            refValue = 10;
        }
    
        public static void  Main(string[] args)
        {
            int localVar = 0;
            Foo(ref localVar);
        }
    
        // Точка входа для Java
        public static void main(string[] args)
        {
            Main(args);
        }
    }
    


    После компиляции код будет выглядеть вот так:
    public class LocalByRefInt : ByRefInt
    {
        private int value;
    
        public LocalByRefInt(int initialValue) { value = initialValue; }
    
        public override int get_Value() { return value; }
        public override void set_Value(int newValue) { value = newValue; }
    }
    
    public class Program
    {
        public static void Foo(ByRefInt refValue)
        {
            refValue.set_Value(10);
        }
    
        public static void  Main(string[] args)
        {
            int localVar = 0;
            LocalByRefInt tmpByRef = new LocalByRefInt(localVar);
            Foo(tmpByRef);
            localVar = tmpByRef.get_Value();
        }
    
        // Точка входа для Java
        public static void main(string[] args)
        {
            Main(args);
        }
    }
    


    FieldByRef[type] – ссылка на поле объекта. Реализуется силами рефлексии. Вот так выглядит этот тип после компиляции:
    public class FieldByRef[type] : ByRef[type]
    {
        private object target;
        private java.lang.reflect.Field field;
        private [type] value;
    
        public FieldByRefInt(object target, Field targetField)
        {
            this.target = target;
            this.field = targetField;
            paramField.setAccessible(true);
            this.value = targetField.get[type](target);
        }
    
        public [type] get_Value()
        {
            return this.value;
        }
    
        public void set_Value([type] newValue)
        {
            this.field.set[type](this.target, newValue);
            this.value = newValue;
        }
    }
    


    ArrayByRef[type] – ссылка на элемент массива. Тут всё просто – сохраняем сам массив (который является указательным типом) и индекс в этом массиве. Вот так это выглядит после компиляции:
    public class ArrayByRef[type] : ByRef[type]
    {
        private [type][] array;
        private int index;
        private int value;
    
        public ArrayByRefInt([type][] paramArray, int index)
        {
            this.array = paramArray;
            this.index = index;
            this.value = paramArray[index];
        }
    
        public int get_Value()
        {
            return this.value;
        }
    
        public void set_Value(int newValue)
        {
            this.array[this.index] = newValue;
            this.value = newValue;
        }
    }
    


    Указатели на методы и делегаты


    Это то, чего мне сильнее всего не хватает в Java. Одним из способов реализации указателей на методы является рефлексия. Но мне этот вариант не понравился тем, что требует упаковки параметров, что снижает производительность. Таким образом был использован второй способ.

    В дальнейшем описании я буду использовать вот такой пример:
    using System;
    
    namespace TestConsole
    {
        public delegate void Deleg(int f);
    
        public class Program
        {
            public void Foo(int f)
            {
                Console.WriteLine(f);
            }
    
            public static void  Main(string[] args)
            {
                Program p = new Program();
    
                Deleg d = new Deleg(p.Foo);
                d(10);
            }
    
            // Точка входа для Java
            public static void main(string[] args)
            {
                Main(args);
            }
        }
    }
    


    Способ заключается в том, что если мы встречаем инструкцию ldftn или ldvirtftn то сначала генерируется интерфейс в пространстве имён CIL2Java.VES.MethodPointers с именем, зависящим от сигнатуры метода и с единственным методом invoke, имеющим почти такую же сигнатуру что и метод, на который мы получаем указатель, добавив первым параметром ссылку на объект в котором необходимо вызвать метод. В нашем примере такой интерфейс будет выглядеть вот так:

    public interface __void_int
    {
        void invoke(object target, int param);
    }
    


    Затем, каждая инструкция ldftn или ldvirtftn генерирует вложенный тип реализующий интерфейс указателя на метод. Метод invoke просто вызывает метод, на который инструкция получает указатель. В приведённом примере это выглядит так:

    public class C2J_anon_0 : __void_int
    {
        public void invoke(object target, int paramInt)
        {
            ((Program)target).Foo(paramInt);
        }
    }
    


    И уже в конструктор делегата, в качестве указателя на метод, передаётся экземпляр данного класса.

    Сам делегат после компиляции принимает такой вид:

    public sealed class Deleg : MulticastDelegate
    {
        public Deleg(object target, __void_int method_pointer)
            : super(paramObject, method_pointer)
        {
        }
    
        public sealed void Invoke(int paramInt)
        {
            ((__void_int)this.method).invoke(this.target, paramInt);
            if (this.next != null)
                ((Deleg)this.next).Invoke(paramInt);
        }
    }
    


    Таково поведение компилятора по умолчанию. Как вы можете заметить, сигнатура конструктора делегата изменена – последний параметр имеет тип интерфейса указателя метода, а не native int как это необходимо по стандарту. Сделано это опять же для оптимизации. Однако вы можете указать компилятору что необходимо компилировать указатели на метод согласно стандарту используя параметр "-method_pointers standart". В таком случае создание делегата в нашем примере принимает вид:
    Deleg d = new Deleg(p, Global.AddMethodPointer("TestConsole.Program$C2J_anon_0"));


    А сам делегат становится вот таким:
    public sealed class Deleg : MulticastDelegate
    {
        public Deleg(object target, int paramInt)
            : base(target, Integer.valueOf(paramInt));
        {
        }
    
        public sealed void Invoke(int paramInt)
        {
            ((__void_int)Global.GetMethodPointer(((Integer)this.method).intValue())).invoke(this.target, paramInt);
            if (this.next != null)
                ((Deleg)this.next).Invoke(paramInt);
        }
    }
    


    Как вы можете видеть, в таком случае, указатель на метод имеет тип int, но на самом деле, это всего лишь индекс в глобальном списке указателей на методы. Таким способом, мы соблюдаем стандарт, но теряем в производительности.

    yield return/break


    Здесь если честно рассказывать нечего. Просто работает.

    Async/await


    Здесь тоже особо рассказывать нечего. Код использующий async/await компилируется, но не работает. Не работает потому что нет реализации необходимых для работы типов (System.Threading.Tasks.Task, System.Runtime.CompilerServices.AsyncTaskMethodBuilder и так далее)

    Беззнаковые числа


    Поддержка беззнаковых чисел в компиляторе имеется, но включается отдельно параметром "-unsigned". В реализации очень помогла статья http://habrahabr.ru/post/225901/ за авторством elw00d. В целом в этой статье всё описано и все операции с беззнаковыми числами были сделаны по этой статье.

    Исключения


    В целом исключения в Java и в CIL очень похожи. Пока правда не поддерживаются фильтры исключений (их не поддерживает ICSharpCode.Decompiler).

    Дополнительно, был добавлен механизм связки типов исключений Java и CIL. К примеру в CIL имеется исключение System.ArithmeticException. В Java имеется свой тип java.lang.ArithmeticException. Как сделать так, что бы перехватывая System.ArithmeticException перехватывался так же и java.lang.ArithmeticException? Для этого был введёт атрибут JavaExceptionMapAttribute который указывает компилятору аналогичное исключение в Java. И когда компилятор встречает перехват System.ArithmeticException, он так же добавляет перехват и аналогичного Java исключения. Единственное что добавляется условие что в System.ArithmeticException должен быть введён дополнительный конструктор, принимающий только один параметр типа java.lang.ArithmeticException для того, что бы перехватчику был передан экземпляр исключения одного типа.

    Отладка


    Компилятор поддерживает генерацию отладочной информации (если она есть в исходных сборках) при указании ключа компиляции "-debug". Вот пример того, как тестовое приложение отлаживается в Eclipse:


    Подмена типов


    Данный механизм был создан для того, что бы типы, имеющие аналогичные в Java, можно было при компиляции превращать в эти самые аналоги. Пример такого типа – System.String. В реализации mscorlib этот тип помечен атрибутом TypeMapAttribute, а при компиляции превращается в java.lang.String. Так же возможна и подмена отдельных методов. Для этого их необходимо помечать атрибутом MethodMapAttribute.

    Заключение


    Вот в целом и всё. Это лишь альфа версия проекта, и пока стабильность работы оставляет желать лучшего. Так что дальнейший вектор работы – улучшение стабильности работы и реализация стандартной библиотеки. Спасибо что дочитали до конца.
    Поделиться публикацией

    Комментарии 36

      +6
      Спасибо за проект
        0
        Зачем нужен отдельный клас LocalByRef[type] со всякми геттерами-сеттерами, почему бы не использовать вместо него массив из одного элемента?

        Для чего в примерах нужен main(String[]) в дополнение к Main(string[]), что мешает генерировать его автоматически при трансляции?
          +1
          Зачем нужен отдельный клас LocalByRef[type] со всякми геттерами-сеттерами, почему бы не использовать вместо него массив из одного элемента?

          Класс LocalByRef[type] является наследником абстрактного класс ByRef[type] который в свою очередь представляет единый интерфейс для всех типов ссылок (на локальную переменную, на поле объекта или элемент массива). А массив из одного элемента подходит только для передачи по ссылке локальных переменных.

          Для чего в примерах нужен main(String[]) в дополнение к Main(string[]), что мешает генерировать его автоматически при трансляции?

          Ничего не мешает. В будущем будет реализовано.
            –2
            Класс LocalByRef[type] является наследником абстрактного класс ByRef[type] который в свою очередь представляет единый интерфейс для всех типов ссылок (на локальную переменную, на поле объекта или элемент массива).
            А что нам даст единый интерфейс для случая ref-параметра метода, чего не может дать массив? Из приведённого примера кода это не ясно.
              +2
              Попробую объяснить примером:
              Исходный код на C#
              public class Program
              {
              	public int field;
              
              	public void Foo(ref int value)
              	{
              		value = 10;
              	}
              
              	public void Main(string[] args)
              	{
              		int localVar = 10;
              		Program p = new Program();
              
              		Foo(ref localVar);
              		Foo(ref p.field);
              	}
              }
              


              Превратится в:
              Результат
              public abstract class ByRefInt
              {
              	public abstract int get_Value();
              	public abstract void set_Value(int newValue);
              }
              
              public sealed class LocalByRefInt
              {
              	private int value;
              
              	public LocalByRefInt(int value) { this.value = value; }
              
              	public int get_Value() { return this.value; }
              	public void set_Value(int newValue) { this.value = newValue; }
              }
              
              public sealed class FieldByRefInt
              {
              	private object target;
                  private java.lang.reflect.Field field;
                  private int value;
               
                  public FieldByRefInt(object target, Field targetField)
                  {
                      this.target = target;
                      this.field = targetField;
                      paramField.setAccessible(true);
                      this.value = targetField.getInt(target);
                  }
               
                  public int get_Value()
                  {
                      return this.value;
                  }
               
                  public void set_Value(int newValue)
                  {
                      this.field.set[type](this.target, newValue);
                      this.value = newValue;
                  }
              }
              
              public class Program
              {
              	public int field;
              
              	public void Foo(ByRefInt value)
              	{
              		value.set_Value(value);
              	}
              
              	public static void Main(string[] args)
              	{
              		int localVar = 10;
              		Program p = new Program();
              
              		LocalByRefInt tempLocal = new LocalByRefInt(localVar);
              		Foo(tempLocal);
              		localVar = tempLocal.get_Value();
              
              		Foo(new FieldByRefInt(p, Program.class.getDeclaredField("field")));
              	}
              }
              
          +3
          на платформу Java, версии не выше 1.6


          Два вопроса:
          1. Как вам удалось сломать совместимость с более свежими JVM?
          2. Почему не Java 8? В ней есть указатели на методы и лямбды, на которые хорошо ложатся делегаты из C#.

          Но в целом, конечно, круто.
            +3
            Видимо не правильно сформулировал с топике. Имелось в виду что результирующий код должен запускаться на Java версии не ниже 1.6. Поэтому 1.7 и 1.8 соответственно отпадают. Почему 1.6? Что бы охватить большее количество платформ.
              0
              тогда поправьте на «версии 1.6 и выше»
                0
                Поправил. Спасибо.
            +1
            Для чего используете или будете использовать ваш проект?
              +4
              Пока ни для чего не используется. Изначально задумывался как аналог dot42 — для написания приложений под Android на C#.
              0
              Проект хорош. А какой overhead всего этого генерируемого кода?
                0
                Всё зависит от конкретных ситуаций и кода. Если в коде не используется вещей которых нет в Java (делегаты, значения по ссылке, значимые типы и т.д.) то оверхед минимальный, а то и вообще не будет, код получается идентичный если писать его сразу на Java. Конкретных цифр к сожалению назвать не могу — тесты пока не проводил.
                  0
                  Было бы отлично увидеть бенчмарки простых кусков кода.
                +5
                Ваш компилятор написан на C#. Можно ли его скомпилировать вашим компилятором в Java? Какой будет провал в производительности?

                Генерируете ли вы StackMapTable? Говорят, без неё на новых JVM никак. Или вы именно поэтому остановились на Java 1.6?

                Вообще, конечно, невероятный пласт работы. Мне пока вряд ли это пригодится, но очень внушает. Поставил плюсики, где мог.
                  +2
                  Ваш компилятор написан на C#. Можно ли его скомпилировать вашим компилятором в Java? Какой будет провал в производительности?

                  Пока нельзя. Нет реализации стандартных библиотек. Для mscorlib есть только заглушки (весь код усеян throw new NotImplementedException();), а остальных библиотек нет даже заглушек. И получается ситуация что компилятор подхватывает стандартные библиотеки от .NET, которые мало того, что не подходят, так ещё и собраны так, что mscorlib от .NET даёт им доступ к internal типам, которых нет в моей реализации mscorlib. Но на то это и альфа версия. План минимум — это что бы компилятор мог скомпилировать самого себя.

                  Генерируете ли вы StackMapTable? Говорят, без неё на новых JVM никак. Или вы именно поэтому остановились на Java 1.6?

                  Нет не генерирую. Если честно раньше думал что она не обязательна и не стал заморачиваться.

                  Вообще, конечно, невероятный пласт работы.

                  Впереди работы не меньше =)

                  Поставил плюсики, где мог.

                  Спасибо
                    0
                    Нет не генерирую. Если честно раньше думал что она не обязательна и не стал заморачиваться.

                    Ну если я правильно понимаю, пока вы используете версию .class-файлов 50, любая JVM простит вам отсутствие StackMapTable. Иначе сломается обратная совместимость. Вот если захотите перейти на более новую версию .class-файлов, придётся заморачиваться :-)
                      0
                      Версия class файлов — 49. Почему именно её — уже не помню. Код для работы с Java файлами писался довольно давно.
                      +1
                      Глянул одним глазом в corlib. Как я понимаю, System.Array.Resize проще написать через
                      array = java.util.Arrays.copyOf(array, newSize);

                      Да, тут работы непаханый край. Вроде всё в одну строчку реализуется, но надо кидать именно C#-исключения, поэтому все границы диапазонов придётся проверять вручную… Ищите помощников, а то рутина заест :-)
                        +1
                        К сожалению не получится. java.util.Arrays.copyOf(array, newSize); имеет перегруженные версии для примитивных типов, которые нельзя привести к Object[] что бы использовать единый T[] copyOf(T[] original, int newLength). Да и смысл если под капотом у них все тот же System.arraycopy.
                          0
                          Ваша правда.
                        0
                        Нет реализации стандартных библиотек.


                        А стандартные библиотеки планируется тоже перекомпилировать в код на java вашим компилятором или вы хотите сделать заглушки-аналоги стандартных библиотек в Net через стандартные библиотеки java? В смысле чтобы вместо метода X класса Y, вызывался некий java аналог?
                          0
                          По возможности будет напрямую вызываться java аналог. Но там где аналогов нет, либо он по какой то причине не подходит, будет своя реализация впоследствии компилируемая в java.
                      0
                      А с помощью какой библиотеки C# вы формируете байткод JVM?
                        +1
                        Судя по сорцам, всё самописное.
                          +1
                          Да, весь код по работе с байт кодом и class файлами писал сам по «Java Virtual Machine Specification». Вся работа с class файлами собрана в пространстве имён CIL2Java.Java, плюс есть класс CIL2Java.JavaBytecodeWriter для упрощения генерации байткода и класс CIL2Java.StackSimulator для трассировки получившегося кода и заполнения значений max_stack и max_locals в Code_attribute.
                        0
                        Если есть overload метода для типа int и enum-а (void Foo(int) и void Foo(MyEnum)), то код после трансляции перестанет работать.
                          0
                          Такая ситуация предусмотрена. После трансляции void Foo(MyEnum) превратится в void Foo__0MyEnum(int). То же самое касается и беззнаковых типов.
                            0
                            Забыл правда упомянуть что такое не работает с конструкторами (их переименовывать нельзя) и там действительно код перестанет работать (верификатор не пропустит).
                              0
                              Можно добавлять фиктивный параметр произвольного типа, который не конфликтует с имеющимися сигнатурами :-)
                                0
                                Вы прямо прочитали мои мысли =)
                          0
                          Респект за проделанную работу!
                          Вы пробовали портировать какое-либо приложение полностью?
                            0
                            Вряд ли автору это удалось, судя по практически полному отсутствию стандартной библиотеки.
                              0
                              Жаль. Например, для PHP5 есть quercus на jvm.
                              Quercus implements PHP 5 and a growing list of PHP extensions including APC, iconv, GD, gettext, JSON, MySQL, Oracle, PDF, and Postgres. Many popular PHP application will run as well as, if not better, than the standard PHP interpreter straight out of the box. The growing list of PHP software certified running on Quercus includes DokuWiki, Drupal, Gallery2, Joomla, Mambo, Mantis, MediaWiki, Phorum, phpBB, phpMyAdmin, PHP-Nuke, Wordpress and XOOPS.


                              В случае запуска .NET на jvm, интересно кто кого засудит за нарушение патентов Oracle Microsoft или Microsoft Oracle? Повторится ли давняя история!?)
                            +1
                            Очень интересно!

                            В списке нереализованых фич у вас нет stackalloc/localloc — или это подпадает под раздел «указатели»?

                            В CLR намного более гибкая система с переопределением методов в классах-наследниках — newslot, явное указание переопределяемого метода etc — как именно вы это реализовываете? Генерацией имен при необходимости?

                            (кстати, вот на этом месте сразу возникает вопрос, как будет работать рефлекшн, если там много где нужно делать name mangling...)

                            Как вы обрабатываете (или планируете обрабатывать) виртуальные дженерик-методы?

                            Как насчет vararg-методов, ArgIterator и TypedReference?

                            При перегрузке методов, в сигнатуре помимо типов параметров учитываются также модификаторы modopt и modreq. Причем эти штуки могут быть еще и вложенными, что в принципе позволяет пихать в сигнатуры практически произвольную информацию. Вы это как-то транслируете, или код на C++/CLI, где, например, перегружен метод на int и long (они там различаются как раз через modopt), сломается?

                            Дотнет (в отличие от спеки CLI) позволяет в верифицируемом коде методам возвращать managed pointer. Правда, вернуть таким образом они могут только результат ld[s]flda — но это вам может потенциально усложнить жизнь…
                              +1
                              В списке нереализованых фич у вас нет stackalloc/localloc — или это подпадает под раздел «указатели»?

                              В принципе да, пока нет полной поддержки указателей, эти вещи смотреть бесполезно. Для localloc есть заглушка, просто создающая массив байт указанного размера — просто на время сделал так.

                              В CLR намного более гибкая система с переопределением методов в классах-наследниках — newslot, явное указание переопределяемого метода etc — как именно вы это реализовываете? Генерацией имен при необходимости?

                              И тут вы правы — такие методы просто переименовываются что бы эмулировать поведение CLR.

                              (кстати, вот на этом месте сразу возникает вопрос, как будет работать рефлекшн, если там много где нужно делать name mangling...)

                              В будущем планирую просто добавлять к переименованным методам аннотацию с его настоящим именем. Да и вообще в аннотациях держать всю недостающую информацию для рефлексии (проперти, события и т.д.)

                              Как вы обрабатываете (или планируете обрабатывать) виртуальные дженерик-методы?

                              Пока никак =) Забыл про такую ситуацию. Так что спасибо что напомнили =)
                              Ну а обрабатывать их я вижу только одним способом — если метод был инстансирован, то и все его перегруженные варианты так же должны инстансироваться с тем же списком параметров.

                              Как насчет vararg-методов, ArgIterator и TypedReference?

                              Только что выкатил на github поддержку vararg, ArgIterator и частичную поддержку TypedReference

                              При перегрузке методов, в сигнатуре помимо типов параметров учитываются также модификаторы modopt и modreq. Причем эти штуки могут быть еще и вложенными, что в принципе позволяет пихать в сигнатуры практически произвольную информацию. Вы это как-то транслируете, или код на C++/CLI, где, например, перегружен метод на int и long (они там различаются как раз через modopt), сломается?

                              Вот за это спасибо. Не думал что modopt и modreq несут ещё и такую функцию. Пока они вообще никак не учитываются (не считая volatile у полей), но я обращу на них внимания.

                              Дотнет (в отличие от спеки CLI) позволяет в верифицируемом коде методам возвращать managed pointer. Правда, вернуть таким образом они могут только результат ld[s]flda — но это вам может потенциально усложнить жизнь…

                              Manged pointer у меня подменятся на ByRef[type]. Так что пускай возвращает =) По идее не должно ничего сломаться.

                            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                            Самое читаемое