PC Review


Reply
Thread Tools Rate Thread

Binary serialization + strongly named assemblies + GAC problem

 
 
Harold Howe
Guest
Posts: n/a
 
      24th Jul 2006

I am having a problem deserializing objects from a library when the
following conditions exist:

1- The library is strongly named
2- The serialized file was created with version 1.0 of the assembly
3- I am trying to deserialize from an EXE that references version 2.0 of
the assembly
4- Both version 1.0 and 2.0 of the assembly reside in the GAC (no policy
redirects exist).

Note that this is not the AssemblyFormat = FormatterAssemblyStyle.Simple
problem. It is a different issue that arises because the old DLL can
still be loaded from the GAC.

object o = null;
Foo foo = null; // Foo is in the strong named assembly

BinaryFormatter formatter = new BinaryFormatter();
formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
using(FileStream stream = File.OpenRead("foo.bin"))
{
o = formatter.Deserialize(stream);
}

foo = o as Foo;
// At this point, o is not null, but foo is null

Deserialization succeeds. The problem is that the deserialized object is
a version 1 object. During deserialization, .NET finds the old version
of the library in the GAC, even though I have compiled against version
2.0. The deserialized object is essentially unusable. The cast from
object to Foo returns a null object, since the returned object is not a
2.0 Foo. The modules view in the debugger shows that both versions of
the library are loaded.

If I remove the 1.0 version of the library from the GAC, deserialization
correctly returns a 2.0 Foo object. Is there a way to make this work
that doesn't involve removing the old library from the GAC? Having the
old version available solves a different set of problems for us.


Here is a complete set of steps + code to reproduce

1- Create a simple, serializable class

//---------- MyLib.cs-----------
using System;
using System.Collections.Generic;
using System.Reflection;

[assembly: AssemblyTitle("MyLib")]
[assembly: AssemblyVersion("1.0.0.0")]

namespace MyLib
{

[Serializable]
public class Foo
{
public Foo()
{
for(int i=0;i<10; ++i)
{
Values.Add(i);
}
}

public List<int> Values = new List<int>();
}
}


2- Compile MyLib.cs into a strongly named library, and add it to the GAC:

> sn -k key.snk
> csc /t:library /keyfile:key.snk MyLib.cs
> gacutil /i MyLib.dll



3- Create an app that serializes an instance of Foo from version 1.0 of
the library. Compile and execute

//-------Create.cs-------
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;

using MyLib;

static public class Program
{
[STAThread]
public static void Main()
{
Foo foo = new Foo();

BinaryFormatter formatter = new BinaryFormatter();
formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;

using(FileStream stream = File.Create("foo.bin"))
{
formatter.Serialize(stream, foo);
}
}
}

> csc /r:MyLib.dll Create.cs
> Create.exe



4- Add a new field to Foo, bump the version, rebuild, and install the
new version into the GAC

//---------- MyLib.cs-----------
....<snip>
[assembly: AssemblyVersion("2.0.0.0")]

....<snip>

public class Foo
{
.... <snip>
public string Text = "hello";
}

> csc /t:library /keyfile:key.snk MyLib.cs
> gacutil /i MyLib.dll


5- Create an app that tries to load the file that was serialized from
version 1.0 of the DLL. Compile and execute

//-------Load.cs-------
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters;
using System.Runtime.Serialization.Formatters.Binary;

using MyLib;

static public class Program
{
[STAThread]
public static void Main()
{
object o = null;
Foo foo = null;

BinaryFormatter formatter = new BinaryFormatter();
formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;

using(FileStream stream = File.OpenRead("foo.bin"))
{
o = formatter.Deserialize(stream);
}

Console.WriteLine(o.GetType().AssemblyQualifiedName);

foo = o as Foo;
if(foo == null)
Console.WriteLine("foo is null");
else
Console.WriteLine("foo is not null. Loaded correctly");
}
}

> csc /r:MyLib.dll Load.cs
> Load.exe


The output from Load.exe is

MyLib.Foo, MyLib, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=0b5ef2fdd6494a50

foo is null

If I remove the 1.0 version of MyLib from the GAC, deserialization
succeeds. The output is:

MyLib.Foo, MyLib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=0b5ef2fdd6494a50
foo is not null. Loaded correctly

H^2


 
Reply With Quote
 
 
 
 
Nicholas Paldino [.NET/C# MVP]
Guest
Posts: n/a
 
      24th Jul 2006
Harald,

Why not specify a redirection for your application? You can specify
that when the old version of the library is specified, you use the new
version.

If that doesn't work, I would set a reference to both dlls and then set
up aliases for them so that you can use both versions in your application.
Of course, this would require you to switch based on which version you are
working with, providing a shim to make it easier to use, most likely (so you
don't have to switch on every call).

Hope this helps.


--
- Nicholas Paldino [.NET/C# MVP]
- (E-Mail Removed)

"Harold Howe" <(E-Mail Removed)> wrote in message
news:(E-Mail Removed)...
>
> I am having a problem deserializing objects from a library when the
> following conditions exist:
>
> 1- The library is strongly named
> 2- The serialized file was created with version 1.0 of the assembly
> 3- I am trying to deserialize from an EXE that references version 2.0 of
> the assembly
> 4- Both version 1.0 and 2.0 of the assembly reside in the GAC (no policy
> redirects exist).
>
> Note that this is not the AssemblyFormat = FormatterAssemblyStyle.Simple
> problem. It is a different issue that arises because the old DLL can still
> be loaded from the GAC.
>
> object o = null;
> Foo foo = null; // Foo is in the strong named assembly
>
> BinaryFormatter formatter = new BinaryFormatter();
> formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
> using(FileStream stream = File.OpenRead("foo.bin"))
> {
> o = formatter.Deserialize(stream);
> }
>
> foo = o as Foo;
> // At this point, o is not null, but foo is null
>
> Deserialization succeeds. The problem is that the deserialized object is a
> version 1 object. During deserialization, .NET finds the old version of
> the library in the GAC, even though I have compiled against version 2.0.
> The deserialized object is essentially unusable. The cast from object to
> Foo returns a null object, since the returned object is not a 2.0 Foo. The
> modules view in the debugger shows that both versions of the library are
> loaded.
>
> If I remove the 1.0 version of the library from the GAC, deserialization
> correctly returns a 2.0 Foo object. Is there a way to make this work that
> doesn't involve removing the old library from the GAC? Having the old
> version available solves a different set of problems for us.
>
>
> Here is a complete set of steps + code to reproduce
>
> 1- Create a simple, serializable class
>
> //---------- MyLib.cs-----------
> using System;
> using System.Collections.Generic;
> using System.Reflection;
>
> [assembly: AssemblyTitle("MyLib")]
> [assembly: AssemblyVersion("1.0.0.0")]
>
> namespace MyLib
> {
>
> [Serializable]
> public class Foo
> {
> public Foo()
> {
> for(int i=0;i<10; ++i)
> {
> Values.Add(i);
> }
> }
>
> public List<int> Values = new List<int>();
> }
> }
>
>
> 2- Compile MyLib.cs into a strongly named library, and add it to the GAC:
>
> > sn -k key.snk
> > csc /t:library /keyfile:key.snk MyLib.cs
> > gacutil /i MyLib.dll

>
>
> 3- Create an app that serializes an instance of Foo from version 1.0 of
> the library. Compile and execute
>
> //-------Create.cs-------
> using System;
> using System.Collections.Generic;
> using System.IO;
> using System.Runtime.Serialization;
> using System.Runtime.Serialization.Formatters;
> using System.Runtime.Serialization.Formatters.Binary;
>
> using MyLib;
>
> static public class Program
> {
> [STAThread]
> public static void Main()
> {
> Foo foo = new Foo();
>
> BinaryFormatter formatter = new BinaryFormatter();
> formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
>
> using(FileStream stream = File.Create("foo.bin"))
> {
> formatter.Serialize(stream, foo);
> }
> }
> }
>
> > csc /r:MyLib.dll Create.cs
> > Create.exe

>
>
> 4- Add a new field to Foo, bump the version, rebuild, and install the new
> version into the GAC
>
> //---------- MyLib.cs-----------
> ...<snip>
> [assembly: AssemblyVersion("2.0.0.0")]
>
> ...<snip>
>
> public class Foo
> {
> ... <snip>
> public string Text = "hello";
> }
>
> > csc /t:library /keyfile:key.snk MyLib.cs
> > gacutil /i MyLib.dll

>
> 5- Create an app that tries to load the file that was serialized from
> version 1.0 of the DLL. Compile and execute
>
> //-------Load.cs-------
> using System;
> using System.Collections.Generic;
> using System.IO;
> using System.Runtime.Serialization;
> using System.Runtime.Serialization.Formatters;
> using System.Runtime.Serialization.Formatters.Binary;
>
> using MyLib;
>
> static public class Program
> {
> [STAThread]
> public static void Main()
> {
> object o = null;
> Foo foo = null;
>
> BinaryFormatter formatter = new BinaryFormatter();
> formatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
>
> using(FileStream stream = File.OpenRead("foo.bin"))
> {
> o = formatter.Deserialize(stream);
> }
>
> Console.WriteLine(o.GetType().AssemblyQualifiedName);
>
> foo = o as Foo;
> if(foo == null)
> Console.WriteLine("foo is null");
> else
> Console.WriteLine("foo is not null. Loaded correctly");
> }
> }
>
> > csc /r:MyLib.dll Load.cs
> > Load.exe

>
> The output from Load.exe is
>
> MyLib.Foo, MyLib, Version=1.0.0.0, Culture=neutral,
> PublicKeyToken=0b5ef2fdd6494a50
>
> foo is null
>
> If I remove the 1.0 version of MyLib from the GAC, deserialization
> succeeds. The output is:
>
> MyLib.Foo, MyLib, Version=2.0.0.0, Culture=neutral,
> PublicKeyToken=0b5ef2fdd6494a50
> foo is not null. Loaded correctly
>
> H^2
>
>



 
Reply With Quote
 
Thomas T. Veldhouse
Guest
Posts: n/a
 
      24th Jul 2006
Harold Howe <(E-Mail Removed)> wrote:
>
> I am having a problem deserializing objects from a library when the
> following conditions exist:
>
> 1- The library is strongly named
> 2- The serialized file was created with version 1.0 of the assembly
> 3- I am trying to deserialize from an EXE that references version 2.0 of
> the assembly
> 4- Both version 1.0 and 2.0 of the assembly reside in the GAC (no policy
> redirects exist).
>


Yeah, it's a PITA isn't it? A real Microsoft Faux Paux if you ask me.

Try using this binder when you deserialize (attach it to the binary
formatter). You should be able to set the AssemblyFormat to Simple but that
doesn't seem to do it across assembly boundries.

sealed class SimpleDeserializationBinder : SerializationBinder
{
private Regex _assemRegex
= new Regex("(?<assembly>^.*?),.*");
private Regex _typeRegex
= new Regex("(?<type>.*?),(?<assembly>.*?),.*(?<end>]])");

public override Type BindToType(string assemblyName, string typeName)
{
// remove strong name from assembly
Match match = _assemRegex.Match(assemblyName);
if (match.Success)
{
assemblyName = match.Groups["assembly"].Value;
}

// remove strong name from any generic collections
match = _typeRegex.Match(typeName);
if (match.Success)
{
typeName = string.Format("{0},{1}{2}",
match.Groups["type"].Value,
match.Groups["assembly"].Value,
match.Groups["end"].Value);
}

// replace assembly name with the simple assembly
// name - strip the strong name off of the name
string type = string.Format("{0}, {1}", typeName,
assemblyName);

// The following line of code returns the type.
return Type.GetType(type);
}
}

--
Thomas T. Veldhouse
Key Fingerprint: 2DB9 813F F510 82C2 E1AE 34D0 D69D 1EDC D5EC AED1

 
Reply With Quote
 
Thomas T. Veldhouse
Guest
Posts: n/a
 
      24th Jul 2006
Thomas T. Veldhouse <(E-Mail Removed)> wrote:
> Try using this binder when you deserialize (attach it to the binary
> formatter). You should be able to set the AssemblyFormat to Simple but that
> doesn't seem to do it across assembly boundries.
>


BTW ... I utilize it as follows:

BinaryFormatter binaryFormatter = new BinaryFormatter();
binaryFormatter.AssemblyFormat = FormatterAssemblyStyle.Simple;
binaryFormatter.Binder = new SimpleDeserializationBinder();

--
Thomas T. Veldhouse
Key Fingerprint: 2DB9 813F F510 82C2 E1AE 34D0 D69D 1EDC D5EC AED1

 
Reply With Quote
 
Harold Howe
Guest
Posts: n/a
 
      2nd Aug 2006

> Why not specify a redirection for your application? You can specify
> that when the old version of the library is specified, you use the new
> version.


Thanks for the suggestion. Initially we couldn't use a redirect for
technical reasons. Our application is a shell that loads various
plugins. The plugins all run in the same app domain, which means they
share a common app configuration. But the plugins have varying DLL
dependendencies. For example, plugin A that relies on version 1 of
MyLib, and plugin B that relies on version 2. One set of redirects for
all plugins would not be adequate.

However, after further testing, a redirect seems to be the only reliable
solution to my serialization problem. It also solves a couple of other
problems that I didn't post, namely, the deserialization of generics
where the type parameter is a user defined type. So we are working on
redesigning our system so that each plugin runs in its own appdomain.
That is something we have wanted to do for a long time anyway, but never
got around to working out all the kinks. Now we have no choice.

H^2
 
Reply With Quote
 
Harold Howe
Guest
Posts: n/a
 
      2nd Aug 2006
Thomas T. Veldhouse wrote:
>
> Yeah, it's a PITA isn't it? A real Microsoft Faux Paux if you ask me.
>
> Try using this binder when you deserialize (attach it to the binary
> formatter).

....

Thanks for the code. I tried using your binder. I was already using a
custom binder, so it was easy to incorporate your code into it.

At first, it seemed to work just fine. Then in started failing. What I
discovered is that calling

Type.GetType("MyLib.Foo, MyLib");

succeeds when MyLib.dll is in the same directory as the executable. It
doesn't matter that MyLib is strongly named and was already loaded from
the GAC. It isn't loaded again, and type resolution succeeds.

However, if I nuke the local DLL, then type resolution fails, and I am
back to square one. I also experimented with just nuking the version
information from the fully qualified assembly name. This resulted in the
same behavior.

H^2
 
Reply With Quote
 
 
 
Reply

Thread Tools
Rate This Thread
Rate This Thread:

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Debugging - Public key , Delay signed , Strongly Named assemblies inpuarg Microsoft C# .NET 0 21st Apr 2006 09:44 AM
debugging strongly named assemblies =?Utf-8?B?V2lsbGlhbSBTdWxsaXZhbg==?= Microsoft Dot NET Framework 2 10th Feb 2006 01:49 PM
Can strongly named assembly1 reference non-strongly named assembly2 ? Oleg Subachev Microsoft C# .NET 5 1st Jun 2005 05:15 PM
Strongly named dynamic assemblies Ostap Radkovskiy Microsoft Dot NET Framework 2 30th Apr 2004 08:48 AM
Strongly-named assemblies not in GAC Kyle Blaney Microsoft Dot NET 2 22nd Oct 2003 03:23 PM


Features
 

Advertising
 

Newsgroups
 


All times are GMT +1. The time now is 06:46 AM.