This was originally a much more lengthy question, but now I have constructed a smaller usable example code, so the original text is no longer relevant.
I have two projects, one containing a single struct with no members, named TestType. This project is referenced by the main project, but the assembly is not included in the executable directory. The main project creates a new app-domain, where it registers the AssemblyResolve event with the name of the included assembly. In the main app-domain, the same event is handled, but it loads the assembly from the project resources, manually.
The new app-domain then constructs its own version of TestType, but with more fields than the original one. The main app-domain uses the dummy version, and the new app-domain uses the generated version.
When calling methods that have TestType in their signature (even simply returning it is sufficient), it appears that it simply destabilizes the runtime and corrupts the memory.
I am using .NET 4.5, running under x86.
DummyAssembly:
using System;
[Serializable]
public struct TestType
{
}
Main project:
using System;
using System.Reflection;
using System.Reflection.Emit;
internal sealed class Program
{
[STAThread]
private static void Main(string[] args)
{
Assembly assemblyCache = null;
AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs rargs)
{
var name = new AssemblyName(rargs.Name);
if(name.Name == "DummyAssembly")
{
return assemblyCache ?? (assemblyCache = TypeSupport.LoadDummyAssembly(name.Name));
}
return null;
};
Start();
}
private static void Start()
{
var server = ServerObject.Create();
//prints 155680
server.TestMethod1("Test");
//prints 0
server.TestMethod2("Test");
}
}
public class ServerObject : MarshalByRefObject
{
public static ServerObject Create()
{
var domain = AppDomain.CreateDomain("TestDomain");
var t = typeof(ServerObject);
return (ServerObject)domain.CreateInstanceAndUnwrap(t.Assembly.FullName, t.FullName);
}
public ServerObject()
{
Assembly genAsm = TypeSupport.GenerateDynamicAssembly("DummyAssembly");
AppDomain.CurrentDomain.AssemblyResolve += delegate(object sender, ResolveEventArgs rargs)
{
var name = new AssemblyName(rargs.Name);
if(name.Name == "DummyAssembly")
{
return genAsm;
}
return null;
};
}
public TestType TestMethod1(string v)
{
Console.WriteLine(v.Length);
return default(TestType);
}
public void TestMethod2(string v)
{
Console.WriteLine(v.Length);
}
}
public static class TypeSupport
{
public static Assembly LoadDummyAssembly(string name)
{
var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(name);
if(stream != null)
{
var data = new byte[stream.Length];
stream.Read(data, 0, data.Length);
return Assembly.Load(data);
}
return null;
}
public static Assembly GenerateDynamicAssembly(string name)
{
var ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
new AssemblyName(name), AssemblyBuilderAccess.Run
);
var mod = ab.DefineDynamicModule(name+".dll");
var tb = GenerateTestType(mod);
tb.CreateType();
return ab;
}
private static TypeBuilder GenerateTestType(ModuleBuilder mod)
{
var tb = mod.DefineType("TestType", TypeAttributes.Public | TypeAttributes.Serializable, typeof(ValueType));
for(int i = 0; i < 3; i++)
{
tb.DefineField("_"+i.ToString(), typeof(int), FieldAttributes.Public);
}
return tb;
}
}
While both TestMethod1 and TestMethod2 should print 4, the first one accesses some weird parts of the memory, and seems to corrupt the call stack well enough to influence the call to the second method. If I remove the call to the first method, everything is fine.
If I run the code under x64, the first method throws NullReferenceException.
The amount of fields of both structs seems to be important. If the second struct is larger in total than the first one (if I generate only one field or none), everything also works fine, same if the struct in DummyAssembly contains more fields. This leads me to believe that the JITter either incorrectly compiles the method (not using the generated assembly), or that the incorrect native version of the method gets called. I have checked that typeof(TestType) returns the correct (generated) version of the type.
All in all, I am not using any unsafe code, so this shouldn't happen.
I was able to reproduce this problem on my machine with newest framework.
I've added check for the version in default appdomain's assembly resolve:
if (name.Name == "DummyAssembly" && name.Version.Major == 1)
And I got following exception:
System.Runtime.Serialization.SerializationException: Cannot find assembly 'DummyAssembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
Server stack trace:
w System.Runtime.Serialization.Formatters.Binary.BinaryAssemblyInfo.GetAssembly()
w System.Runtime.Serialization.Formatters.Binary.ObjectReader.GetType(BinaryAssemblyInfo assemblyInfo, String name)
w 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)
w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryObjectWithMapTyped record)
w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.ReadObjectWithMapTyped(BinaryHeaderEnum binaryHeaderEnum)
w System.Runtime.Serialization.Formatters.Binary.__BinaryParser.Run()
w System.Runtime.Serialization.Formatters.Binary.ObjectReader.Deserialize(HeaderHandler handler, __BinaryParser serParser, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
w System.Runtime.Serialization.Formatters.Binary.BinaryFormatter.Deserialize(Stream serializationStream, HeaderHandler handler, Boolean fCheck, Boolean isCrossAppDomain, IMethodCallMessage methodCallMessage)
w System.Runtime.Remoting.Channels.CrossAppDomainSerializer.DeserializeObject(MemoryStream stm)
w System.Runtime.Remoting.Messaging.SmuggledMethodReturnMessage.FixupForNewAppDomain()
w System.Runtime.Remoting.Channels.CrossAppDomainSink.SyncProcessMessage(IMessage reqMsg)
Exception rethrown at [0]:
w System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
w System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
w ServerObject.TestMethod1(TestType& result, String v)
Binary formatter is used for Marshaling here, and it finds value types of different sizes from different AppDomains. Note that it tries to load your DummyAssembly with version 0.0.0.0 when you call TestMethod1, and you pass it the dummy version 1.0.0.0 you cached earliesr, where TestType has different size.
Because of different sizes of structs, when you return by value from your method, something goes wrong with marshaling between AppDomains and stack gets unbalanced (probably a bug in the runtime?). Returning by reference seems to work without issues (size of reference is always the same).
Making structs equal in size in both assemblies / returning by reference should work around this issue.
Related
Based on this Microsoft example, how can i Unload a loaded assembly by implementing it in the AssemblyLoadContext class? (Considering they are in a global List<Assembly>.
I have tried some ways, this example too but nothing seems to really release the dll and let me "delete it" or "overwrite it" with a new one without close the main app.
My goal would be to: load a dll (1.0) => execute the code => unload the dll => replace the *.dll file with a new version (v1.1) => load again the dll => execute the update code.
The actually loading code is this (in .Net 6):
static IEnumerable<ICommand> Plugs = Enumerable.Empty<ICommand>();
static readonly List<Assembly> PluginAssemblies = new();
static readonly List<string> PluginPath = new();
PluginPath.Add("C:\\...\\Plugin1.dll");
PluginPath.Add("C:\\...\\Plugin2.dll");
PluginPath.Add("C:\\...\\Plugin3.dll");
PluginPath.Add("C:\\...\\Plugin4.dll");
PluginPath.ForEach(P => { PluginAssemblies.Add(LoadPlugin(P)); });
Plugs = Plugs.Concat(PluginAssemblies.SelectMany(A => CreateCommands(A)));
And these are the functions from the example:
static Assembly LoadPlugin(string relativePath)
{
// Navigate up to the solution root
string root = Path.GetFullPath(Path.Combine(
Path.GetDirectoryName(
Path.GetDirectoryName(
Path.GetDirectoryName(
Path.GetDirectoryName(
Path.GetDirectoryName(typeof(Program).Assembly.Location)))))));
string pluginLocation = Path.GetFullPath(Path.Combine(root, relativePath.Replace('\\', Path.DirectorySeparatorChar)));
Console.WriteLine($"Loading commands from: {pluginLocation}");
PluginLoadContext loadContext = new PluginLoadContext(pluginLocation);
return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(pluginLocation)));
}
static IEnumerable<ICommand> CreateCommands(Assembly assembly)
{
int count = 0;
foreach (Type type in assembly.GetTypes())
{
if (typeof(ICommand).IsAssignableFrom(type))
{
ICommand result = Activator.CreateInstance(type) as ICommand;
if (result != null)
{
count++;
yield return result;
}
}
}
if (count == 0)
{
string availableTypes = string.Join(",", assembly.GetTypes().Select(t => t.FullName));
throw new ApplicationException(
$"Can't find any type which implements ICommand in {assembly} from {assembly.Location}.\n" +
$"Available types: {availableTypes}");
}
}
using System;
using System.Reflection;
using System.Runtime.Loader;
namespace AppWithPlugin
{
class PluginLoadContext : AssemblyLoadContext
{
private AssemblyDependencyResolver _resolver;
public PluginLoadContext(string pluginPath)
{
_resolver = new AssemblyDependencyResolver(pluginPath);
}
protected override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
{
string libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName);
if (libraryPath != null)
{
return LoadUnmanagedDllFromPath(libraryPath);
}
return IntPtr.Zero;
}
}
}
According to Microsoft, here, How to use and debug assembly unloadability in .NET
calling the AssemblyLoadContext.Unload method just initiates the
unloading
there are some conditions you need to meet to complete.
No threads have methods from the assemblies loaded into the AssemblyLoadContext on their call stacks.
None of the types from the assemblies loaded into the AssemblyLoadContext, instances of those types, and the assemblies themselves are referenced by:
References outside of the AssemblyLoadContext, except for weak references (WeakReference or WeakReference).
Strong garbage collector (GC) handles (GCHandleType.Normal or GCHandleType.Pinned) from both inside and outside of the AssemblyLoadContext.
In short, you need to be sure there are no references in memory to anything in the assemblies you loaded and wait for the GC to do its work.
It seems you're trying to load the assembly, keep the code and unload the assembly from memory keeping the 'command' assigned. That can not be done.
Besides, it doesn't seem you're removing the reference to the assembly from the PluginAssemblies list. You need to remove the 'command' from the plugs array too (or use weak references).
I can't see anywhere in your code where you're unloading the assembly. Instead of returning the Assembly object from the LoadPlugin method you can return your PluginLoadContext, so you can call it's Unload method, later when you're sure there are no references left to the assembly or inner classes (clear all assembly or command instances).
When debugging an application I always get the following error when break on exception is enabled in Visual Studio. This is really bugging me, since we work with break on exception. The funny thing is, that it still works when I continue (the StringCollection is loaded).
The Message is:
Could not load file or assembly 'System.XmlSerializers,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' or
one of its dependencies. The system cannot find the file specified.
Here is the code that is causing the exception (designer generated)
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.Specialized.StringCollection Mru {
get {
return ((global::System.Collections.Specialized.StringCollection)(this["Mru"]));
}
set {
this["Mru"] = value;
}
}
I tried to create an empty test application that shows the error, but the exception didn't occur. Our project is huge so it tough to find the cause. Maybe someone on this site has a clue how to solve this.
Just an explanation for why this exception is thrown. You can repro the exception with this sample Windows Forms app. Start by adding a setting named "Setting" of type StringCollection. Click the dots in the Value column and enter a couple of strings. Make the form class code look like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
protected override void OnFormClosing(FormClosingEventArgs e) {
Properties.Settings.Default.Setting[0] = DateTime.Now.ToString();
Properties.Settings.Default.Save();
base.OnFormClosing(e);
}
}
Debug + Exceptions, tick the Thrown checkbox for CLR exceptions. Run the form and close it, the debugger will stop when the exception is thrown. The top of the call stack looks like this:
mscorlib.dll!System.Reflection.Assembly.nLoad(System.Reflection.AssemblyName fileName, string codeBase, System.Security.Policy.Evidence assemblySecurity, System.Reflection.Assembly locationHint, ref System.Threading.StackCrawlMark stackMark, bool throwOnFileNotFound, bool forIntrospection) + 0x2c bytes
mscorlib.dll!System.Reflection.Assembly.InternalLoad(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, ref System.Threading.StackCrawlMark stackMark, bool forIntrospection) + 0x80 bytes
mscorlib.dll!System.Reflection.Assembly.Load(System.Reflection.AssemblyName assemblyRef) + 0x1d bytes
System.Xml.dll!System.Xml.Serialization.TempAssembly.LoadGeneratedAssembly(System.Type type = {Name = "StringCollection" FullName = "System.Collections.Specialized.StringCollection"}, string defaultNamespace = null, out System.Xml.Serialization.XmlSerializerImplementation contract = null) + 0xcd bytes
System.Xml.dll!System.Xml.Serialization.XmlSerializer.XmlSerializer(System.Type type = {Name = "StringCollection" FullName = "System.Collections.Specialized.StringCollection"}, string defaultNamespace = null) + 0x105 bytes
You can see the XmlSerializer class hunting for an assembly that contains the XML serializer for the StringCollection class. The LoadGeneratedAssembly method looks like this with the boring bits removed:
internal static Assembly LoadGeneratedAssembly(Type type, string defaultNamespace, out XmlSerializerImplementation contract)
{
...
AssemblyName parent = GetName(type.Assembly, true);
partialName = Compiler.GetTempAssemblyName(parent, defaultNamespace);
parent.Name = partialName;
parent.CodeBase = null;
parent.CultureInfo = CultureInfo.InvariantCulture;
try
{
serializer = Assembly.Load(parent); // <=== here
}
catch (Exception exception)
{
...
}
....
}
And Compiler.GetTempAssemblyName():
internal static string GetTempAssemblyName(AssemblyName parent, string ns)
{
return (parent.Name + ".XmlSerializers" + (((ns == null) || (ns.Length == 0)) ? "" : ("." + ns.GetHashCode())));
}
This GetTempAssemblyName is the evil-doer in this case. The StringCollection class lives in the System.dll assembly, the method generates the name "System.XmlSerializers". This method is designed to find the assembly for your own classes, the one generated by Sgen.exe. Like WindowsApplication1.XmlSerializers.dll for your sample program. But StringCollection is a class in the .NET Framework, the assembly name it generates just isn't valid. There isn't actually a "System.XmlSerializers.dll" assembly in the framework.
Feedback reports about this behavior at connect.microsoft.com have all been closed with "By Design". It was, the original designers considered the cost of preventing the exception too high and decided to just catch the exception. Which all works fine, the exception is indeed caught. You just happen to see it because you got the Thrown checkbox turned on in the Debug + Exceptions dialog.
Making the Xml serialization code behave differently here is not an option. It would have been easy enough for them to simply filter out types in the System.dll assembly, but that's a potentially never-ending battle, there are a lot more assemblies in the framework. A workaround is to use your own class to store the setting instead of using a StringCollection.
As this really seems to be part of the normal operation (see also:
XmlSerializer giving FileNotFoundException at constructor), I can only offer two workarounds:
Disable this specific exception: goto Debug/Exceptions, click Add, Type: C++ Exceptions, Name: EEFileLoadException (if this is the exception you're seeing), uncheck the Thrown checkbox for this exception.
Change the type of the setting to string and access it e.g. like so:
var mru = Settings.Default.Mru.Split('|');
Settings.Default.Mru = string.Join("|", mru.ToArray());
You are catching too many exceptions, the System.XmlSerializer will always throw this exception as part of it's normal operation, it is caught and handled by the class itself. Change your debugging options to only catch your exceptions, not exceptions that are caught and handled within the .net farmework classes.
I'm quite sure I'm missing some constraint or caveat somewhere, but here's my situation. Assume I have a class that I want to have a proxy for, like the following:
public class MyList : MarshalByRefObject, IList<string>
{
private List<string> innerList;
public MyList(IEnumerable<string> stringList)
{
this.innerList = new List<string>(stringList);
}
// IList<string> implementation omitted for brevity.
// For the sake of this exercise, assume each method
// implementation merely passes through to the associated
// method on the innerList member variable.
}
I want to create a proxy for that class, so that I can intercept method calls and perform some processing on the underlying object. Here is my implementation:
public class MyListProxy : RealProxy
{
private MyList actualList;
private MyListProxy(Type typeToProxy, IEnumerable<string> stringList)
: base(typeToProxy)
{
this.actualList = new MyList(stringList);
}
public static object CreateProxy(IEnumerable<string> stringList)
{
MyListProxy listProxy = new MyListProxy(typeof(MyList), stringList);
object foo = listProxy.GetTransparentProxy();
return foo;
}
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage callMsg = msg as IMethodCallMessage;
MethodInfo proxiedMethod = callMsg.MethodBase as MethodInfo;
return new ReturnMessage(proxiedMethod.Invoke(actualList, callMsg.Args), null, 0, callMsg.LogicalCallContext, callMsg);
}
}
Finally, I have a class that consumes the proxied class, and I set the value of the MyList member via reflection.
public class ListConsumer
{
public MyList MyList { get; protected set; }
public ListConsumer()
{
object listProxy = MyListProxy.CreateProxy(new List<string>() { "foo", "bar", "baz", "qux" });
PropertyInfo myListPropInfo = this.GetType().GetProperty("MyList");
myListPropInfo.SetValue(this, listProxy);
}
}
Now, if I try to use reflection to access the proxied object, I run into problems. Here is an example:
class Program
{
static void Main(string[] args)
{
ListConsumer listConsumer = new ListConsumer();
// These calls merely illustrate that the property can be
// properly accessed and methods called through the created
// proxy without issue.
Console.WriteLine("List contains {0} items", listConsumer.MyList.Count);
Console.WriteLine("List contents:");
foreach(string stringValue in listConsumer.MyList)
{
Console.WriteLine(stringValue);
}
Type listType = listConsumer.MyList.GetType();
foreach (Type interfaceType in listType.GetInterfaces())
{
if (interfaceType.IsGenericType && interfaceType.GetGenericTypeDefinition() == typeof(ICollection<>))
{
// Attempting to get the value of the Count property via
// reflection throws an exception.
Console.WriteLine("Checking interface {0}", interfaceType.Name);
System.Reflection.PropertyInfo propInfo = interfaceType.GetProperty("Count");
int count = (int)propInfo.GetValue(listConsumer.MyList, null);
}
else
{
Console.WriteLine("Skipping interface {0}", interfaceType.Name);
}
}
Console.ReadLine();
}
}
Attempting to call GetValue on the Count property via reflection throws the following exception:
An exception of type 'System.Reflection.TargetException' occurred in
mscorlib.dll but was not handled in user code
Additional information: Object does not match target type.
When attempting to get the value of the Count property, apparently the framework is calling down into System.Runtime.InteropServices.WindowsRuntime.IVector to call the get_Size method. I'm not understanding how this call fails on the underlying object of the proxy (the actual list) to make this happen. If I'm not using a proxy of the object, getting the property value works fine via reflection. What am I doing wrong? Can I even do what I'm trying to accomplish?
Edit: A bug has been opened regarding this issue at the Microsoft Connect site.
I think this may be a bug in the .Net framework. Somehow the RuntimePropertyInfo.GetValue method is picking the wrong implementation for the ICollection<>.Count property, and it appears to have to do with WindowsRuntime projections. Perhaps the remoting code was redone when they put the WindowsRuntime interop in the framework.
I switched the framework to target .Net 2.0 since I thought if this was a bug, it shouldn't be in that framework. When converting, Visual Studio removed the "Prefer 32 bit" check on my console exe project (since this doesn't exist in 2.0). It runs without exception when this is not present.
In summary, it runs on .Net 2.0 in both 32 and 64 bit. It runs on .Net 4.x in 64 bit. The exception is thrown on .Net 4.x 32 bit only. This sure looks like a bug. If you can run it 64-bit, that would be a workaround.
Note that I've installed .Net 4.6, and this replaces much of the .Net framework v4.x. It could be this is where the problem is introduced; I can't test until I get a machine that doesn't have .Net 4.6.
Update: 2015-09-08
It also happens on a machine with only .Net 4.5.2 installed (no 4.6).
Update: 2015-09-07
Here's a smaller repro, using your same classes:
static void Main(string[] args)
{
var myList = MyListProxy.CreateProxy(new[] {"foo", "bar", "baz", "quxx"});
var listType = myList.GetType();
var interfaceType = listType.GetInterface("System.Collections.Generic.ICollection`1");
var propInfo = interfaceType.GetProperty("Count");
// TargetException thrown on 32-bit .Net 4.5.2+ installed
int count = (int)propInfo.GetValue(myList, null);
}
I've also tried the IsReadOnly property, but it appears to work (no exception).
As to the source of the bug, there are two layers of indirection around properties, one being the remoting, and the other being a mapping of metadata structures called MethodDefs with the actual runtime method, known internally as a MethodDesc. This mapping is specialized for properties (as well as events), where additional MethodDescs to support the property's get/set PropertyInfo instances are known as Associates. By calling PropertyInfo.GetValue we go through one of these Associate MethodDesc pointers to the underlying method implementation, and remoting does some pointer math to get the correct MethodDesc on the other side of the channel. The CLR code is very convoluted here, and I don't have enough experience of the in-memory layout of the MethodTable which holds these MethodDesc records which remoting uses (or the mapping it uses to get to the MethodTable?), but I'd say it's a fair guess that remoting is grabbing the wrong MethodDesc via some bad pointer math. That's why we see a similar but unrelated (as far as your program) MethodDesc - UInt32 get_Size of IVector<T> being invoked on the call:
System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)
System.Reflection.RuntimeMethodInfo.InvokeArgumentsCheck(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
ConsoleApplication1.MyListProxy.Invoke(IMessage msg) Program.cs: line: 60
System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
System.Runtime.InteropServices.WindowsRuntime.IVector`1.get_Size()
System.Runtime.InteropServices.WindowsRuntime.VectorToCollectionAdapter.Count[T]()
This is a pretty interesting CLR bug, some of its guts are showing in the mishap. You can tell from the stack trace that it is trying to call the VectorToCollectionAdapter's Count property.
This class is rather special, no instance of it ever gets created. It is part of the language projection that was added in .NET 4.5 that makes WinRT interface types look like .NET Framework types. It is pretty similar to the SZArrayHelper class, an adapter class that helps implement the illusion that non-generic arrays implement generic interface types like IList<T>.
The interface mapping at work here is for the WinRT IVector<T> interface. As noted in the MSDN article, that interface type is mapped to IList<T>. The internal VectorToListAdapter class takes care of the IList<T> members, VectorToCollectionAdapter tackles the ICollection<T> members.
Your code forces the CLR to find the implementation of ICollection<>.Count and that could either be a .NET class implementing it as normal or it could be a WinRT object that exposes it as IVector<>.Size. Clearly the proxy you created gives it a headache, it incorrectly decided for the WinRT version.
How it is supposed to figure out which is the correct choice is pretty murky. After all, your proxy could be a proxy for an actual WinRT object and then the choice it made would be correct. This could well be a structural problem. That it acts so randomly, the code does work in 64-bit mode, is not exactly inspiring. VectorToCollectionAdapter is very dangerous, note the JitHelpers.UnsafeCast calls, this bug is potentially exploitable.
Well, alert the authorities, file a bug report at connect.microsoft.com. Let me know if you don't want to take the time and I'll take care of it. A workaround is hard to come by, using the WinRT-centric TypeInfo class to do the reflection did not make any difference. Removing the jitter forcing so it runs in 64-bit mode is a band-aid but hardly a guarantee.
we are currently hacking around this problem with this brittle intervention (apologies for code):
public class ProxyBase : RealProxy
{
// ... stuff ...
public static T Cast<T>(object o)
{
return (T)o;
}
public static object Create(Type interfaceType, object coreInstance,
IEnforce enforce, string parentNamingSequence)
{
var x = new ProxyBase(interfaceType, coreInstance, enforce,
parentNamingSequence);
MethodInfo castMethod = typeof(ProxyBase).GetMethod(
"Cast").MakeGenericMethod(interfaceType);
return castMethod.Invoke(null, new object[] { x.GetTransparentProxy() });
}
public override IMessage Invoke(IMessage msg)
{
IMethodCallMessage methodCall = (IMethodCallMessage)msg;
var method = (MethodInfo)methodCall.MethodBase;
if(method.DeclaringType.IsGenericType
&& method.DeclaringType.GetGenericTypeDefinition().FullName.Contains(
"System.Runtime.InteropServices.WindowsRuntime"))
{
Dictionary<string, string> methodMap = new Dictionary<string, string>
{ // add problematic methods here
{ "Append", "Add" },
{ "GetAt", "get_Item" }
};
if(methodMap.ContainsKey(method.Name) == false)
{
throw new Exception("Unable to resolve '" + method.Name + "'.");
}
// thanks microsoft
string correctMethod = methodMap[method.Name];
method = m_baseInterface.GetInterfaces().Select(
i => i.GetMethod(correctMethod)).Where(
mi => mi != null).FirstOrDefault();
if(method == null)
{
throw new Exception("Unable to resolve '" + method.Name +
"' to '" + correctMethod + "'.");
}
}
try
{
if(m_coreInstance == null)
{
var errorMessage = Resource.CoreInstanceIsNull;
WriteLogs(errorMessage, TraceEventType.Error);
throw new NullReferenceException(errorMessage);
}
var args = methodCall.Args.Select(a =>
{
object o;
if(RemotingServices.IsTransparentProxy(a))
{
o = (RemotingServices.GetRealProxy(a)
as ProxyBase).m_coreInstance;
}
else
{
o = a;
}
if(method.Name == "get_Item")
{ // perform parameter conversions here
if(a.GetType() == typeof(UInt32))
{
return Convert.ToInt32(a);
}
return a;
}
return o;
}).ToArray();
// this is where it barfed
var result = method.Invoke(m_coreInstance, args);
// special handling for GetType()
if(method.Name == "GetType")
{
result = m_baseInterface;
}
else
{
// special handling for interface return types
if(method.ReturnType.IsInterface)
{
result = ProxyBase.Create(method.ReturnType, result, m_enforce, m_namingSequence);
}
}
return new ReturnMessage(result, args, args.Length, methodCall.LogicalCallContext, methodCall);
}
catch(Exception e)
{
WriteLogs("Exception: " + e, TraceEventType.Error);
if(e is TargetInvocationException && e.InnerException != null)
{
return new ReturnMessage(e.InnerException, msg as IMethodCallMessage);
}
return new ReturnMessage(e, msg as IMethodCallMessage);
}
}
// ... stuff ...
}
m_coreInstance here is the object instance that the proxy is wrapping.
m_baseInterface is the interface the object is to be used as.
this code intercepts the call(s) made in VectorToListAdapter and VectorToCollectionAdapter and converts it back into the original via that methodMap dictionary.
the part of the conditional:
method.DeclaringType.GetGenericTypeDefinition().FullName.Contains(
"System.Runtime.InteropServices.WindowsRuntime")
makes sure it only intercepts calls that come from stuff in the System.Runtime.InteropServices.WindowsRuntime namespace - ideally we would target the types directly but they are inaccessible - this should probably be changed to target specific class names in the namespace.
the parameters are then cast into the appropriate types and the method is invoked. the parameter conversions appear to be necessary as the incoming parameter types are based on the parameter types of the method calls from the objects in the System.Runtime.InteropServices.WindowsRuntime namespace, and not the parameters of the method calls to the original object types; i.e. the original types before the objects in the System.Runtime.InteropServices.WindowsRuntime namespace hijacked the mechanism.
for example, the WindowsRuntime stuff intercepts the original call to get_Item, and converts it into a call to the Indexer_Get method: http://referencesource.microsoft.com/#mscorlib/system/runtime/interopservices/windowsruntime/vectortolistadapter.cs,de8c78a8f98213a0,references. this method then calls the GetAt member with a different parameter type, which then calls GetAt on our object (again with a different parameter type) - this is the call we hijack in our Invoke() and convert it back into the original method call with the original parameter types.
it would be nice to be able to reflect over VectorToListAdapter and VectorToCollectionAdapter to extract all their methods and the nested calls they make, but these classes are unfortunately marked as internal.
this works for us here, but i'm sure its full of holes - it is a case of trial and error, running it to see what fails and then adding in the required dictionary entries/parameter conversions. we are continuing the search for a better solution.
HTH
When debugging an application I always get the following error when break on exception is enabled in Visual Studio. This is really bugging me, since we work with break on exception. The funny thing is, that it still works when I continue (the StringCollection is loaded).
The Message is:
Could not load file or assembly 'System.XmlSerializers,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' or
one of its dependencies. The system cannot find the file specified.
Here is the code that is causing the exception (designer generated)
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.Specialized.StringCollection Mru {
get {
return ((global::System.Collections.Specialized.StringCollection)(this["Mru"]));
}
set {
this["Mru"] = value;
}
}
I tried to create an empty test application that shows the error, but the exception didn't occur. Our project is huge so it tough to find the cause. Maybe someone on this site has a clue how to solve this.
Just an explanation for why this exception is thrown. You can repro the exception with this sample Windows Forms app. Start by adding a setting named "Setting" of type StringCollection. Click the dots in the Value column and enter a couple of strings. Make the form class code look like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
protected override void OnFormClosing(FormClosingEventArgs e) {
Properties.Settings.Default.Setting[0] = DateTime.Now.ToString();
Properties.Settings.Default.Save();
base.OnFormClosing(e);
}
}
Debug + Exceptions, tick the Thrown checkbox for CLR exceptions. Run the form and close it, the debugger will stop when the exception is thrown. The top of the call stack looks like this:
mscorlib.dll!System.Reflection.Assembly.nLoad(System.Reflection.AssemblyName fileName, string codeBase, System.Security.Policy.Evidence assemblySecurity, System.Reflection.Assembly locationHint, ref System.Threading.StackCrawlMark stackMark, bool throwOnFileNotFound, bool forIntrospection) + 0x2c bytes
mscorlib.dll!System.Reflection.Assembly.InternalLoad(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, ref System.Threading.StackCrawlMark stackMark, bool forIntrospection) + 0x80 bytes
mscorlib.dll!System.Reflection.Assembly.Load(System.Reflection.AssemblyName assemblyRef) + 0x1d bytes
System.Xml.dll!System.Xml.Serialization.TempAssembly.LoadGeneratedAssembly(System.Type type = {Name = "StringCollection" FullName = "System.Collections.Specialized.StringCollection"}, string defaultNamespace = null, out System.Xml.Serialization.XmlSerializerImplementation contract = null) + 0xcd bytes
System.Xml.dll!System.Xml.Serialization.XmlSerializer.XmlSerializer(System.Type type = {Name = "StringCollection" FullName = "System.Collections.Specialized.StringCollection"}, string defaultNamespace = null) + 0x105 bytes
You can see the XmlSerializer class hunting for an assembly that contains the XML serializer for the StringCollection class. The LoadGeneratedAssembly method looks like this with the boring bits removed:
internal static Assembly LoadGeneratedAssembly(Type type, string defaultNamespace, out XmlSerializerImplementation contract)
{
...
AssemblyName parent = GetName(type.Assembly, true);
partialName = Compiler.GetTempAssemblyName(parent, defaultNamespace);
parent.Name = partialName;
parent.CodeBase = null;
parent.CultureInfo = CultureInfo.InvariantCulture;
try
{
serializer = Assembly.Load(parent); // <=== here
}
catch (Exception exception)
{
...
}
....
}
And Compiler.GetTempAssemblyName():
internal static string GetTempAssemblyName(AssemblyName parent, string ns)
{
return (parent.Name + ".XmlSerializers" + (((ns == null) || (ns.Length == 0)) ? "" : ("." + ns.GetHashCode())));
}
This GetTempAssemblyName is the evil-doer in this case. The StringCollection class lives in the System.dll assembly, the method generates the name "System.XmlSerializers". This method is designed to find the assembly for your own classes, the one generated by Sgen.exe. Like WindowsApplication1.XmlSerializers.dll for your sample program. But StringCollection is a class in the .NET Framework, the assembly name it generates just isn't valid. There isn't actually a "System.XmlSerializers.dll" assembly in the framework.
Feedback reports about this behavior at connect.microsoft.com have all been closed with "By Design". It was, the original designers considered the cost of preventing the exception too high and decided to just catch the exception. Which all works fine, the exception is indeed caught. You just happen to see it because you got the Thrown checkbox turned on in the Debug + Exceptions dialog.
Making the Xml serialization code behave differently here is not an option. It would have been easy enough for them to simply filter out types in the System.dll assembly, but that's a potentially never-ending battle, there are a lot more assemblies in the framework. A workaround is to use your own class to store the setting instead of using a StringCollection.
As this really seems to be part of the normal operation (see also:
XmlSerializer giving FileNotFoundException at constructor), I can only offer two workarounds:
Disable this specific exception: goto Debug/Exceptions, click Add, Type: C++ Exceptions, Name: EEFileLoadException (if this is the exception you're seeing), uncheck the Thrown checkbox for this exception.
Change the type of the setting to string and access it e.g. like so:
var mru = Settings.Default.Mru.Split('|');
Settings.Default.Mru = string.Join("|", mru.ToArray());
You are catching too many exceptions, the System.XmlSerializer will always throw this exception as part of it's normal operation, it is caught and handled by the class itself. Change your debugging options to only catch your exceptions, not exceptions that are caught and handled within the .net farmework classes.
When debugging an application I always get the following error when break on exception is enabled in Visual Studio. This is really bugging me, since we work with break on exception. The funny thing is, that it still works when I continue (the StringCollection is loaded).
The Message is:
Could not load file or assembly 'System.XmlSerializers,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089' or
one of its dependencies. The system cannot find the file specified.
Here is the code that is causing the exception (designer generated)
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public global::System.Collections.Specialized.StringCollection Mru {
get {
return ((global::System.Collections.Specialized.StringCollection)(this["Mru"]));
}
set {
this["Mru"] = value;
}
}
I tried to create an empty test application that shows the error, but the exception didn't occur. Our project is huge so it tough to find the cause. Maybe someone on this site has a clue how to solve this.
Just an explanation for why this exception is thrown. You can repro the exception with this sample Windows Forms app. Start by adding a setting named "Setting" of type StringCollection. Click the dots in the Value column and enter a couple of strings. Make the form class code look like this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
protected override void OnFormClosing(FormClosingEventArgs e) {
Properties.Settings.Default.Setting[0] = DateTime.Now.ToString();
Properties.Settings.Default.Save();
base.OnFormClosing(e);
}
}
Debug + Exceptions, tick the Thrown checkbox for CLR exceptions. Run the form and close it, the debugger will stop when the exception is thrown. The top of the call stack looks like this:
mscorlib.dll!System.Reflection.Assembly.nLoad(System.Reflection.AssemblyName fileName, string codeBase, System.Security.Policy.Evidence assemblySecurity, System.Reflection.Assembly locationHint, ref System.Threading.StackCrawlMark stackMark, bool throwOnFileNotFound, bool forIntrospection) + 0x2c bytes
mscorlib.dll!System.Reflection.Assembly.InternalLoad(System.Reflection.AssemblyName assemblyRef, System.Security.Policy.Evidence assemblySecurity, ref System.Threading.StackCrawlMark stackMark, bool forIntrospection) + 0x80 bytes
mscorlib.dll!System.Reflection.Assembly.Load(System.Reflection.AssemblyName assemblyRef) + 0x1d bytes
System.Xml.dll!System.Xml.Serialization.TempAssembly.LoadGeneratedAssembly(System.Type type = {Name = "StringCollection" FullName = "System.Collections.Specialized.StringCollection"}, string defaultNamespace = null, out System.Xml.Serialization.XmlSerializerImplementation contract = null) + 0xcd bytes
System.Xml.dll!System.Xml.Serialization.XmlSerializer.XmlSerializer(System.Type type = {Name = "StringCollection" FullName = "System.Collections.Specialized.StringCollection"}, string defaultNamespace = null) + 0x105 bytes
You can see the XmlSerializer class hunting for an assembly that contains the XML serializer for the StringCollection class. The LoadGeneratedAssembly method looks like this with the boring bits removed:
internal static Assembly LoadGeneratedAssembly(Type type, string defaultNamespace, out XmlSerializerImplementation contract)
{
...
AssemblyName parent = GetName(type.Assembly, true);
partialName = Compiler.GetTempAssemblyName(parent, defaultNamespace);
parent.Name = partialName;
parent.CodeBase = null;
parent.CultureInfo = CultureInfo.InvariantCulture;
try
{
serializer = Assembly.Load(parent); // <=== here
}
catch (Exception exception)
{
...
}
....
}
And Compiler.GetTempAssemblyName():
internal static string GetTempAssemblyName(AssemblyName parent, string ns)
{
return (parent.Name + ".XmlSerializers" + (((ns == null) || (ns.Length == 0)) ? "" : ("." + ns.GetHashCode())));
}
This GetTempAssemblyName is the evil-doer in this case. The StringCollection class lives in the System.dll assembly, the method generates the name "System.XmlSerializers". This method is designed to find the assembly for your own classes, the one generated by Sgen.exe. Like WindowsApplication1.XmlSerializers.dll for your sample program. But StringCollection is a class in the .NET Framework, the assembly name it generates just isn't valid. There isn't actually a "System.XmlSerializers.dll" assembly in the framework.
Feedback reports about this behavior at connect.microsoft.com have all been closed with "By Design". It was, the original designers considered the cost of preventing the exception too high and decided to just catch the exception. Which all works fine, the exception is indeed caught. You just happen to see it because you got the Thrown checkbox turned on in the Debug + Exceptions dialog.
Making the Xml serialization code behave differently here is not an option. It would have been easy enough for them to simply filter out types in the System.dll assembly, but that's a potentially never-ending battle, there are a lot more assemblies in the framework. A workaround is to use your own class to store the setting instead of using a StringCollection.
As this really seems to be part of the normal operation (see also:
XmlSerializer giving FileNotFoundException at constructor), I can only offer two workarounds:
Disable this specific exception: goto Debug/Exceptions, click Add, Type: C++ Exceptions, Name: EEFileLoadException (if this is the exception you're seeing), uncheck the Thrown checkbox for this exception.
Change the type of the setting to string and access it e.g. like so:
var mru = Settings.Default.Mru.Split('|');
Settings.Default.Mru = string.Join("|", mru.ToArray());
You are catching too many exceptions, the System.XmlSerializer will always throw this exception as part of it's normal operation, it is caught and handled by the class itself. Change your debugging options to only catch your exceptions, not exceptions that are caught and handled within the .net farmework classes.