C# deserialization fails in Automation Addin but not in NUnit tests
I have an automation addin (implementing Extensibility.IDTExtensibility2), written in C# 4, in which I'm trying to load some (binary) serialized data. It works perfectly in unit tests, but fails when running from within Excel:
Unable to find assembly 'XXX, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
at System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name)
at System.Runtime.Serialization.Formatters.Binary.ObjectMap..ctor(String objectName, String[] memberNames, BinaryTypeEnum[] binaryTypeEnumA, Object[] typeInformationA, Int32[] memberAssemIds, ObjectReader objectReader, Int32 objectId, BinaryAssemblyInfo assemblyInfo, SizedArray assemIdToAssemblyTable)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum)
at System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
at System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
at System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream)
at
On the BinaryFormatter I'm setting the AssemblyFormat (for both serialization and deserialization) as follows:
serializationCodec.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
Which I thought would ignore the开发者_如何学Python version as per here.
Then thought it may be due to Excel's notion of "Trusted Location" so I added the project's directory and checked all subdirectories, but the error remained.
In vain I tried adding the System.Runtime.Serialization.OptionalFieldAttribute attribute, but it didn't help.
The unit tests can load the serialized files generated by itself or the same code executed in Excel, but Excel cannot load the serialized data regardless of whether it did the actual serialization.
The fact that Excel cannot deserialize what it has serialized itself hints that this is a redherring as it would obviously have access to the assembly used.
So the question is why does Excel deserialize differently compared to my unit test? (Or perhaps more importantly; How can I get deserialization working in Excel?)
thanks.
I chased the red herring(s) and got no where with the assumption that it was a versioning issue (I'd looked at shims and all sorts).
It was in fact due to weird assembly binding; although the automation addin's DLL could see and load classes from the DLLs it depended on, when it can to deserialization it failed to find the necessary assemblies - even though the deserialization call was from a class belonging to a DLL that it claimed it couldn't find!
Solution is certainly a hack but it's got me past this, so in case anyone else is stuck as i was, here's the hack(s):
Inside a class X, that depends on classes Y and Z each from their respective project DLLs (named ProjectForX, ProjectForY and ProjectForZ)
private static Assembly MyResolveEventHandler(object sender, ResolveEventArgs args)
{
if(args.Name.StartsWith("ProjectForX,")) {
return typeof(X).Assembly;
} else if(args.Name.StartsWith("ProjectForY,")) {
return typeof(Y).Assembly;
} else if(args.Name.StartsWith("ProjectForZ,")) {
return typeof(Z).Assembly;
}
return null;
}
public static X LoadX(string filename)
{
AppDomain currentDomain = AppDomain.CurrentDomain;
ResolveEventHandler handler = new ResolveEventHandler(MyResolveEventHandler);
currentDomain.AssemblyResolve += handler;
try {
Stream stream = new FileStream(@filename, FileMode.Open);
try {
BinaryFormatter deserializer = createBinaryFormatter();
X model = (X)deserializer.Deserialize(stream);
return model;
} finally {
stream.Close();
}
} finally {
currentDomain.AssemblyResolve -= handler;
}
}
Basically it just hooks into the resolve events and provides the correct assembly based on the requested type name - this isn't particularly safe (I'm using the comma in the assembly descriptor to try to minimize any clashes).
There's almost certainly a clear way to do this, and the issue is probably just a result of poor configuration and my limited experience with MS products and C# in particular.
This wouldn't have been an issue if I'd just built a monolithic DLL =)
精彩评论