InvalidCastException, wrong context? - c#

I have an exe that does data dumps. The exe will dynamically pick up DLL's based on configuration and pass a class object into it. The DLL has a copy of this class compiled with it and can see the data, under debug, without a problem as an object. However, when I try to cast that to the class, it tells me it can't because of the context. I'm sure I've overlooked something as I do that at times.
Error:
[A]MyClass cannot be cast to [B]MyClass. Type A originates from
'MyExe, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the
context 'Default' at location 'C:\MyPath\MyExe.exe'. Type B originates
from 'MyDLL, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in
the context 'LoadNeither' at location 'C:\MyPath\MyDLL.dll'.
EXE Code:
Object[] param = new Object[] { MyClass };
MethodInfo m = type.GetMethod("MyMethod");
reader = (SqlDataReader)m.Invoke(obj, param);
DLL Code:
public SqlDataReader MyMethod(Object param)
{
SqlDataReader reader = new SqlDataReader();
Type t = param.GetType(); //Returns MyClass
if (param is MyClass) //Returns false
reportItem = (MyClass)param; //Never executes
MyClass reportItem = (MyClass)param; //InvalidCastException
//other code here, pulling data
return reader;
}

The DLL has a copy of this class compiled with it
Don't do that, basically. You should have the type in one assembly, and only one assembly. As far as the CLR is concerned, these are entirely different types.
You probably want to have a common library which both the plugins and your application can refer to. Or you could make your plugins refer to the application executable and keep the type within there.

Related

AppDomain.TypeResolve not called for dynamic type

Demorepo:
https://github.com/gabbersepp/csharp-dynamic-replace-class
How to use:
Checkout
Compile
Delete TestLib.dll & TestLib.pdb from console/bin/Debug
Execute console.exe through cmd
Old SO post:
Replace existing class definition at runtime with newly created type
Given:
A class in a lib:
namespace Test.TestLib
{
public class Class1
{
}
}
And a second class which creates an instance of it:
namespace console
{
public class AnotherClass
{
public void Create()
{
new Class1();
}
}
}
And a console app that calls create:
static void Main(string[] args)
{
//...
new AnotherClass().Create();
}
Please keep in mind that only Class1 is in an extra lib. The other two classes are in the same.
What I want to do:
Replace the Assembly at runtime:
AssemblyName dynamicAssemblyName = new AssemblyName("TestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
dynamicAssembly =
AssemblyBuilder.DefineDynamicAssembly(dynamicAssemblyName, AssemblyBuilderAccess.Run);
var dynamicModule = dynamicAssembly.DefineDynamicModule("TestLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null");
But I want not provide the type at this time. Instead I use:
AppDomain.CurrentDomain.TypeResolve += CurrentDomain_TypeResolve;
private static Assembly CurrentDomain_TypeResolve(object sender, ResolveEventArgs args)
{
Console.WriteLine("resolve type");
if (args.Name.Contains("TestLib"))
{
dynamicModule.DefineType("Test.TestLib.Class1", TypeAttributes.Class | TypeAttributes.Public).CreateType();
return dynamicAssembly;
}
return null;
}
Problem:
The event is not called when the line new AnotherClass().Create(); is executed. Instead an exception is thrown:
System.TypeLoadException: Der Typ "Test.TestLib.Class1" in der
Assembly "TestLib, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=null" konnte nicht geladen werden
something like:
System.TypeLoadException: The type "Test.TestLib.Class1" in the
assembly "TestLib, Version = 1.0.0.0, Culture = neutral,
PublicKeyToken = null" could not be loaded
Please have a look into the repo for a full example.
//Edit:
The demo project is written with VS2019 für .net461. I think the main concepts are the same for dotnet core. Let me know if you'd rather work with dotnet core so I can provide the project for both platforms.
//Edit2:
I debugged into the IL Code and saw, that everything runs fine until the constructor of Class1 is called:
So personally I don't think that the event handler is plugged into too late as stated by Bruno.
The official documentation states, that this event is called if the assembly is unknown:
https://learn.microsoft.com/de-de/dotnet/api/system.appdomain.typeresolve?view=netframework-4.8
The TypeResolve event occurs when the common language runtime is
unable to determine the assembly that can create the requested type
I did not read that before. Hopefully someone can help me :-)
//Edit3 - possible solution:
A workaround could be, to create the types based on a list of class names. To not loose compile safety, I can use nameof which produces no IL code. An example can be found in the repo in the branch resolveType-solution1 . But of course, not the solution I am looking for :-(
I am not 100% sure but I think I figured out what is going on.
When running your code, your main is evaluated by the JIT before being executed. This results in an attempt to load AnotherClass and it's dependecies (as everything is very likely to be inlined in the end because it's very small).
This means that you won't have the opportunity to run this code before .net tries to find your type:
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
AppDomain.CurrentDomain.TypeResolve += CurrentDomain_TypeResolve;
I managed to bypass this limitation by not mentionning the class in your main by replacing:
new AnotherClass().Create();
by:
var type = Type.GetType("AnotherClass");
var method = type.GetMethod("Create");
var instance = type.GetConstructor(new Type[] { }).Invoke(new object[0]);
method.Invoke(instance, new object[0]);
The trick is deferring the load of the type after your callbacks have been plugged in the AppDomain. By using reflection I mask the Types really used to the JIT until this code is actually called.
Take away: you don't really know when your type will be loaded (in fact as soon as JIT reads code mentioning your type). This means you have to plug your callback as soon as possible and defer as much as possible the inclusion of code mentioning your type.
calling Type.GetType("Test.TestLib.Class1"); would invoke AppDomain.TypeResolve but not AppDomain.AssemblyResolve, not sure why though

'object' does not contain a definition for '...' within same assembly

Per my boss' recommendations, we are using Dapper to access our database. As this is just a quick, throwaway project, he would like to forego creating POCO objects for the results of these calls to the database, so we're just having Dapper return dynamic objects.
So I have several Web API controllers, all deriving from the same BaseController. In the base class, we have a method like this:
protected dynamic ExecuteSingle(string sql, object parameters = null, bool useStoredProcedure = true) {
using (var db = ObjectFactory.GetInstance<IDbManager>())
{
var cmd = useStoredProcedure ? db.SetSpCommand(sql) : db.SetCommand(sql);
if (parameters != null)
{
cmd = cmd.SetParameters(parameters);
}
return cmd.ExecuteObject<dynamic>();
}
}
We are able to use that successfully when taking that result and passing it as the parameter to a Ok() return value. All of the properties of the object get successfully parsed into the JSON object.
I'm now working on another section of code where instead of immediately spitting it back out, I need to use the data coming back to hit another set of functionality.
var sql = " SELECT Id FROM Table WHERE ReviewId = :ReviewId ";
dynamic dbSurvey = ExecuteSingle(sql, new {ReviewId = reviewId}, false);
var survey = sg.GetSurvey(new GetSurveyRequest(dbSurvey.Id));
It then fails on that last line where calling dbSurvey.Id with an exception saying
'object' does not contain a definition for 'Id'
Checking the object, there is a property on there named "Id".
I have checked several questions already, and even though they are all dealing with Anonymous objects (which I could understand dynamic being under that heading), I am staying within the same assembly, so those points about Anonymous objects being declared as "internal" wouldn't apply in this case.
I also tried changing the return type of ExecuteSingle to an ExpandoObject, but am getting the same results.
EDIT
Here is a screenshot of how it is being called, versus the object and properties that it contains. Perhaps the format of the properties could help determine why it's not being found.
I also used the Watch menu to try various ways to access the property, and none of the following worked:
dbSurvey["Id"]
(dbSurvey as IDictionary<string, long>)["Id"]
((IDictionary<string, int>)dbSurvey)["Id"]
Here is the result of dbSurvey.GetType() from the immediate window while running:
{<>f__AnonymousType2`1[System.Int64]}
base: {Name = "<>f__AnonymousType2`1" FullName = "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}
Assembly: {***.Web.Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null}
AssemblyQualifiedName: "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], ***.Web.Test, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"
BaseType: {Name = "Object" FullName = "System.Object"}
ContainsGenericParameters: false
DeclaringMethod: 'dbSurvey.GetType().DeclaringMethod' threw an exception of type 'System.InvalidOperationException'
DeclaringType: null
FullName: "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"
GenericParameterAttributes: 'dbSurvey.GetType().GenericParameterAttributes' threw an exception of type 'System.InvalidOperationException'
GenericParameterPosition: 'dbSurvey.GetType().GenericParameterPosition' threw an exception of type 'System.InvalidOperationException'
GUID: {382c0269-d631-3c89-a105-38a1be8a3db7}
IsConstructedGenericType: true
IsEnum: false
IsGenericParameter: false
IsGenericType: true
IsGenericTypeDefinition: false
IsSecurityCritical: true
IsSecuritySafeCritical: false
IsSecurityTransparent: false
MemberType: TypeInfo
MetadataToken: 33554480
Module: {***.Web.Test.dll}
Name: "<>f__AnonymousType2`1"
Namespace: null
ReflectedType: null
StructLayoutAttribute: {System.Runtime.InteropServices.StructLayoutAttribute}
TypeHandle: {System.RuntimeTypeHandle}
UnderlyingSystemType: {Name = "<>f__AnonymousType2`1" FullName = "<>f__AnonymousType2`1[[System.Int64, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]"}
OK, so I resolved this issue. I just figured out what the problem was. Thanks to xanatos' help in the comments, we noticed that the Module property of the type was coming from the Test harness. That didn't make any sense to me at the time, because the dynamic object was being created in the same assembly.
However, what I didn't think about until I came back to the issue this morning was that the source of the object being "created" in the base API Controller was an anonymous object that I was creating in the Test harness. So that Module was correct.
If I went and created a separate POCO object for the Mock object in the Test DLL, then it was no longer creating a dynamic off of an anonymous object created in another assembly.

How to reproduce InvalidCastException when binding to an Assembly in the LoadFrom Context

In Suzanne Cook's .NET CLR Notes she talks about the dangers of the "LoadFrom" context. Specifically,
If a Load context assembly tries to load this assembly by display name, it will fail to be found by default (e.g., when mscorlib.dll deserializes this assembly)
Worse, an assembly with the same identity but at a different path could be found on the probing path, causing an InvalidCastException, MissingMethodException, or unexpected method behavior later on.
How do you reproduce this behavior with deserialization, but without explicitly loading two different versions of the assembly?
I've created a Console application, A.exe, which indirectly loads (via `Assembly.LoadFrom) and calls (via reflection) code from a class library, B.dll.
A.exe does not (necessarily) have a reference to B.dll, but B.dll should exist in the same directory as A.exe
A copy of of B.dll should be placed into another directory (here I've used the subdirectory called LoadFrom), this is the location we will use Assembly.LoadFrom on.
A.exe
class Program
{
static void Main(string[] args)
{
// I have a post build step that copies the B.dll to this sub directory.
// but the B.dll also lives in the main directory alongside the exe:
// mkdir LoadFrom
// copy B.dll LoadFrom
//
var loadFromAssembly = Assembly.LoadFrom(#".\LoadFrom\B.dll");
var mySerializableType = loadFromAssembly.GetType("B.MySerializable");
object mySerializableObject = Activator.CreateInstance(mySerializableType);
var copyMeBySerializationMethodInfo = mySerializableType.GetMethod("CopyMeBySerialization");
try
{
copyMeBySerializationMethodInfo.Invoke(mySerializableObject, null);
}
catch (TargetInvocationException tie)
{
Console.WriteLine(tie.InnerException.ToString());
}
Console.ReadKey();
}
}
B.dll
namespace B
{
[Serializable]
public class MySerializable
{
public MySerializable CopyMeBySerialization()
{
return DeepClone(this);
}
private static T DeepClone<T>(T obj)
{
using (var ms = new MemoryStream())
{
var formatter = new BinaryFormatter();
formatter.Serialize(ms, obj);
ms.Position = 0;
return (T)formatter.Deserialize(ms);
}
}
}
}
Output
System.InvalidCastException:
[A]B.MySerializable cannot be cast to
[B]B.MySerializable.
Type A originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'Default' at location 'c:\Dev\bin\Debug\B.dll'.
Type B originates from 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'LoadFrom' at location 'c:\Dev\bin\Debug\LoadFrom\B.dll'.
at B.MySerializable.DeepClone[T](T obj)
at B.MySerializable.CopyMeBySerialization()
Here's what is happening:
When the call to formatter.Deserialize(ms) is made, it uses the information stored in the MemoryStream to determine what type of object it needs to create (and which assembly it needs in order to create that object).
It finds that it needs B.dll and attempts to Load it (from the default "Load" context).
The currently loaded B.dll is not found (because it was loaded in the "LoadFrom" context).
Thus, an attempt is made to find B.dll in the usual locations--it is found in the ApplicationBase directory and is loaded.
All types in this B.dll are considered different types that those from the other B.dll. Thus the cast in the expression (T)formatter.Deserialize(ms) fails.
Additional notes:
If the B.dll had not existed somewhere where A.exe could find it using Assembly.Load, then instead of an InvalidCastException, there would be a SerializationException with the message Unable to find assembly 'B, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'.
The same problem occurs even with signed assemblies, but what is more alarming with signed assemblies is that it can load a different version of the signed assembly. That is, if B.dll in the "LoadFrom" context is 1.0.0.0, but the B.dll found in the main directory is 2.0.0.0, the serialization code will still load the wrong version B.dll to do deserialization.
The DeepClone code I've shown seems to be one of the more popular ways to do a deep clone on an object. See: Deep cloning objects in C#.
So, from any code that was loaded into the "LoadFrom" context, you cannot use deserialization successfully (without jumping through additional hoops to allow the assembly to successfully load in the default "Load" context).

Send Object as parameter to Dll function

I have a Class Library (dll) that holds operations with reports. My dll needs a object to fill the desired report.
The problem is that I can't convert the object from my main .exe to the same object in the dll.
[A]MyMainEXE.Model.MyObject can't be converted to [B]MyClassLibrary.Model.MyObject
The type A cames from 'MyMainEXE', Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'Default' at
'C:\fakepath\DummyName.exe'.
The type B cames from 'MyClassLibrary', Version=1.0.0.0, Culture=neutral, PublicKeyToken=null'
in the context 'Default' at
'C:\fakepath\DummyName.dll'
I'm just trying to pass like this:
doWorks(myObjectname);
and receive like this:
public void doWorks(object myobject)
{
MyObject thing = (MyObject) myobject;
//Do something
}
I already know how to pass using a array or List but
Why can't I do with objects?/What am I doing Wrong?
Since both objects are of the same name but probably different namespace, I think you have to serialize/deserialize the object from MyMainEXE.Model.MyObject to XML/Binary to MyClassLibrary.Model.MyObject

InvalidCastException for two Objects of the same type

I have this weird problem that I cannot handle myself. A class in the model of my mvp-project designed as singleton causes an InvalidCastException.
The source of error is found in this code line where the deserialised object is assigned to the instance variable of the class: engineObject = (ENGINE)xSerializer.Deserialize(str);. It occurs whenever I try to add one of my UserControls to a Form or to a different UC. All of my UCs have a special presenter that accesses the above mentioned instance variable of the singleton class.
This is what I get when trying to add a UC somewhere:
'System.TypeInitializationException: The type initializer for 'MVP.Model.EngineData' threw an exception. ---->
System.InvalidCastException: [A]Engine cannot be cast to [B]Engine. Type A originates from 'MVP.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither'
at location '[...]\AppData\Roaming\Microsoft\VisualStudio\9.0\ProjectAssemblies\uankw1hh01\MVP.Model.dll'.
Type B originates from 'MVP.Model, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadNeither'
at location '[...]\AppData\Roaming\Microsoft\VisualStudio\9.0\ProjectAssemblies\u_hge2de01\MVP.Model.dll'
...
So I somehow have two assemblies and they are not accessed from my project folder, but from a VS temp folder? I googled a lot and only found this: IronPython Exception: [A]Person cannot be cast to [B]Person. There is a solution offered, but it concerns IronPhyton and I don't know where to use it within my project.
Types are per-assembly; if you have "the same" assembly loaded twice, then types in each "copy" of the assembly are not considered to be the same type.
These issues normally crop up when the two assemblies are in the Load and LoadFrom contexts. See
Difference between LoadFile and LoadFrom with .NET Assemblies?
and the link to suzcook's blog for details on that issue.
Also, consider using the fusion log viewer to help diagnose the problem.
http://msdn.microsoft.com/en-us/library/e74a18c4%28VS.71%29.aspx
Judging by the context in which the assembly is getting loaded (the context is "LoadNeither"), some developers may be doing something like loading an assembly that has been internally packaged as a resource with your application. If you do this, you will be using the AppDomain.CurrentDomain.AssemblyResolve event handler, so that your application can specify where .NET should get any particular assembly that it needs.
My answer will not explain the machinations of how to do that - but I'm mentioning it because this process led directly to the same exact error encountered by the original poster. As Eric Lippert mentions, types are per-assembly. So if you load an individual assembly more than once, the same defined class will appear as different classes - even though visual inspection shows that they appear to be the same.
We've seen instances in which .NET will call the ResolveEventHandler more than once for the same DLL. I'm not sure why .NET sometimes does this (it happened on some machines, but not all machines). But to resolve the problem, we needed keep a global list of handles to loaded assemblies, so that if .NET wanted to load the assembly again, we returned a handle to the same Assembly that was originally loaded, instead of loading another copy into memory.
I have included the code that caused the issue for us, and notes about how to handle it properly.
public void AppStartup (object sender, StartupEventArgs e)
{
AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
}
public System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
string dllName = args.Name.Contains(',') ? args.Name.Substring(0, args.Name.IndexOf(',')) : args.Name.Replace(".dll", "");
dllName = dllName.Replace(".", "_");
if (dllName.EndsWith("_resources")) return null;
System.Resources.ResourceManager rm = new System.Resources.ResourceManager(GetType().Namespace + ".Properties.Resources", System.Reflection.Assembly.GetExecutingAssembly());
byte[] bytes = null;
try
{
bytes = (byte[])rm.GetObject(dllName);
}
catch (Exception ex)
{
}
if (bytes != null)
{
// the following call will return a newly loaded assembly
// every time it is called
// if this function is called more than once for the same
// assembly, you'll load more than one copy into memory
// this can cause the InvalidCastException
// instead of doing this, you keep a global list of loaded
// assemblies, and return the previously loaded assembly
// handle, instead of loading it again
return System.Reflection.Assembly.Load(bytes);
}
return null;
}
My situation involved two copies of the same dll. One was in the bin folder and one was in a sub-folder of the same bin folder. Both were loaded, amazingly some things worked fine, but some things didn't and that's when this error message appeared:
System.InvalidOperationException; There was an error generating the XML document.; Source: System.Xml; TargetSite: Void Serialize(System.Xml.XmlWriter, System.Object, System.Xml.Serialization.XmlSerializerNamespaces, System.String, System.String);
Hidden in this was the following inner exception (this was to do with Microsoft Dynamics CRM 4.0, but could relate to anything)
System.InvalidCastException; [A]XXX.CRMCustomCode.YYY.CreateCompanyRequest cannot be cast to [B]XXX.CRMCustomCode.YYY.CreateCompanyRequest. Type A originates from 'XXX.CRMCustomCode, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'LoadFrom' at location 'C:\Program Files\Microsoft CRM\Server\bin\assembly\XXX.CRMCustomCode.dll'. Type B originates from 'XXX.CRMCustomCode, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null' in the context 'Default' at location 'C:\Program Files\Microsoft CRM\Server\bin\XXX.CRMCustomCode.dll'.;
I simply deleted the duplicate dll (in C:\Program Files\Microsoft CRM\Server\bin) and the error went away.
My particular case - class library referenced in web application was renamed and rebuilt. Older version of library was still in bin folder under old name. The framework loads whatever in bin folder (both libraries) and emits this error. So it happens not only when assemblies are loaded explicitly.
The obvious solution in my case is to clean bin folder.
I got it working when I tried changing this cast:
var t = (TeacherWebPages)Session["TeachersAD"];
To this:
var t = Session["TeachersAD"] as TeacherWebPages;
However the assembly/session/memcache was different and no data got back but no error occurred. So later I still had to delete specific temporary files every time source page was changed from the folder it was complaining about which would require me to kill IIS process from task manager. Or in my case I can just logout and it clears the session state.

Categories

Resources