views:

400

answers:

1

There are two assemblies:
1) Assembly containing serializer. This is a place from where serialization and deserialization starts.
2) Assembly containing serialized types. This is a place which is calling serializer from 1st assembly.
The idea of serializer in assembly1 is simple. It has two methods which are used for conversions of objects from and to byte arrays. The client code for that serializer can look like this:

    ISerializer serializer = ...

    MyClass my = new MyClass();
    byte[] data = serializer.Serialize(my);
    Console.WriteLine(Encoding.ASCII.GetString(data)); // dump serialized form
    MyClass another = (MyClass)serializer.Deserialize(data);

MyClass is defined in assembly2, so assembly1 knows nothing about it. That scenario will work if serializer is implemented with standard .Net classes, like this:

public class DotNetSerializer : ISerializer
{
    public byte[] Serialize(object obj)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        using (MemoryStream stream = new MemoryStream())
        {
            formatter.Serialize(stream, obj);
            byte[] result = stream.GetBuffer();
            Array.Resize(ref result, (int)stream.Length);
            return result;
        }
    }

    public object Deserialize(byte[] data)
    {
        BinaryFormatter formatter = new BinaryFormatter();
        using (Stream stream = new MemoryStream(data))
        {
            return formatter.Deserialize(stream);
        }
    }
}

The serialized form of MyClass will contain information about assembly in which MyClass is defined. However, if serializer will be implemented with usage of Java's classes (converted with IKVM) then there will be ClassNotFound exception thrown during deserialization. This is a serializer implementation using Java classes:

public class JavaSerializer : ISerializer
{
    public object Deserialize(byte[] data)
    {
        ByteArrayInputStream stream = new ByteArrayInputStream(data);
        ObjectInputStream ois = new ObjectInputStream(stream);
        return ois.readObject();
    }

    public byte[] Serialize(object obj)
    {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(stream);
        oos.writeObject(obj);
        stream.flush();
        return stream.toByteArray();
    }
}

This will not work in .Net, but will work normally in Java if loaded from Eclipse with additional entries in plugin manifests such as BuddyPolicy and RegisterBuddy. I can't just switch from JavaSerializer to DotNetSerializer since in my application (which is mostly written in Java) there are lots of readObject,writeObject,readResolve,etc... But I need to fix that problem somehow, so I seeking for solution. Currently I see some hypothetical ways of solution:

  • Overloading some method of ObjectOutputStream, so serialized form of MyClass will contain assembly name too, like "MyClass, MyAssembly, ...".
  • Overloading some method in ObjectInputStream, so class will be loaded in some different way, maybe it should be searched in some different assembly, etc.
  • Adding some information to assemblies manifests so IKVM will know where to search for MyClass. Is anything of this is real? How this problem should be solved?
A: 

The guys from IKVM team gave me the answers:

1) You can subclass ObjectOutputStream and override annotateClass to write the assembly name and subclass ObjectInputStream and override resolveClass to read the assembly name.

2) Add the following custom attribute to the assembly that does the deserialization: [assembly: IKVM.Attributes.CustomAssemblyClassLoader(typeof(ikvm.runtime.AppDomainAssemblyClassLoader))]

I also found that I can override classloader for any assembly with explicit setting in application configuration file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <add key="ikvm-classloader:MyExampleAssembly" value="ikvm.runtime.AppDomainAssemblyClassLoader, IKVM.OpenJDK.ClassLibrary, Version=0.37.0.0, Culture=neutral, PublicKeyToken=null" />
  </appSettings>
</configuration>

And it also possibly can be set during convertation from java jar when -classloader command line parameter is used.

Dmitriy Matveev