Assembly.Load(byte[]) - how does it work? I get TargetInvocationException. Source code included...

  • Thread starter Thread starter Greg Patrick
  • Start date Start date
G

Greg Patrick

My problem:

I load an some assemblies (strong named) from a byte array using
Assembly.Load(byte[]). They load fine. But one one of them is actually
accessed, it's referenced assemblies can't be found on disk and I get a
TargetInvocationExeption.

For example, let's say I have a C# win forms application called Prog.

Prog.exe uses Assembly.Load(byte[]) to load two C# class library assemblies,
ClassLib1 and ClassLib2. Both are strong named. ClassLib1.dll and
ClassLib2.dll are NOT on disk. However, they were created in VS.NET in the
same solution, and ClassLib1 has a reference to ClassLib2 in VS.NET.

The problem is that when my Prog.exe calls a static method on a class in
ClassLib1, I get a TargetInvocationException with the details:
Unhandled Exception: System.Reflection.TargetInvocationException: Exception
has been thrown by the target of an invocation. --->
System.IO.FileNotFoundException: File or assembly name ClassLib2, or one of
its dependencies, was not found.

There are additional details that show that the framework is trying to load
ClassLib2.dll from disk, from the debug (or release) directory where
Prog.exe was started.

In fact, if I do put ClassLib2.dll on disk there, then there is no exception
because it finds it and loads it. Which means it has been loaded twice,
once by me with the Assembly.Load, and once due to the reference from
ClassLib1.

WHY? Why oh why? I've already loaded it!

So how do I make the framework "realize" that it is already loaded? I
thought strong naming would have do the trick, but it didn't make a
difference... it still wants to load it from disk because of the reference
from ClassLib1. Argghhh!

Any help would be GREATLY appreciated...

Thanks!

Greg Patrick

Source code is below if you want to try it out. You just make a VS.NET C#
Win Forms app solution and use Form1 from below. Then make two C# class
library projects ClassLibrary1, and ClassLibrary2. Don't reference either
class library from the main application project. Build. Copy
ClassLibrary1\bin\debug\ClassLibrary1.dll to <application>\bin\debug\C1.dll.
Similarly for ClassLibrary2.dll - copy to C2.dll.

----------------------------------------------------------------------------
-------------------------------

ClassLibrary2

using System;
namespace ClassLibrary2
{
public class Class2
{
public Class2()
{
}
}
}

ClassLibrary1 (Note, in VS.NET, has a reference to ClassLibrary2):

using System;
using System.Windows.Forms;
using ClassLibrary2;
namespace ClassLibrary1
{
public class Class1
{
public static Class2 class2 = new Class2();

public Class1()
{
}

public static void Hello()
{
MessageBox.Show("Hello!");
}
}
}

Prog.exe's Form1:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Reflection;
namespace WinApp
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

private Assembly LoadAss(string filename)
{
FileStream fin = new FileStream("c1.dll", FileMode.Open,
FileAccess.Read);
byte[] bin = new byte[16384];
long rdlen = 0;
long total= fin.Length;
int len;
MemoryStream memStream = new MemoryStream((int)total);
rdlen = 0;
while(rdlen < total)
{
len = fin.Read(bin, 0, 16384);
memStream.Write(bin, 0, len);
rdlen = rdlen + len;
}
// done with input file
fin.Close();
return Assembly.Load(memStream.ToArray());
}

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
Assembly c1 = this.LoadAss("c1.dll");
this.LoadAss("c2.dll");
Type t = c1.GetType("ClassLibrary1.Class1");
MethodInfo m = t.GetMethod("Hello");
m.Invoke(null, new object[]{});
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.Size = new System.Drawing.Size(300,300);
this.Text = "Form1";
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
}
}
 
You have to manage AssemblyResolve and make sure to return your dynamic
assembly whenever a request for your second strongly typed assembly is made.
What I would recommend is using Assembly.Load(byte[]), and then inserting
the Assembly references into a Hashtable that you can index later.

I have some information on this on my blog. Note, that because your assemblies
are strong named, you might not want to use basic name resolving and instead use
a more complete resolving based on the full name. That is completely up to your
discretion.

http://weblogs.asp.net/justin_rogers/archive/2004/06/07/149964.aspx

--
Justin Rogers
DigiTec Web Consultants, LLC.
Blog: http://weblogs.asp.net/justin_rogers

Greg Patrick said:
My problem:

I load an some assemblies (strong named) from a byte array using
Assembly.Load(byte[]). They load fine. But one one of them is actually
accessed, it's referenced assemblies can't be found on disk and I get a
TargetInvocationExeption.

For example, let's say I have a C# win forms application called Prog.

Prog.exe uses Assembly.Load(byte[]) to load two C# class library assemblies,
ClassLib1 and ClassLib2. Both are strong named. ClassLib1.dll and
ClassLib2.dll are NOT on disk. However, they were created in VS.NET in the
same solution, and ClassLib1 has a reference to ClassLib2 in VS.NET.

The problem is that when my Prog.exe calls a static method on a class in
ClassLib1, I get a TargetInvocationException with the details:
Unhandled Exception: System.Reflection.TargetInvocationException: Exception
has been thrown by the target of an invocation. --->
System.IO.FileNotFoundException: File or assembly name ClassLib2, or one of
its dependencies, was not found.

There are additional details that show that the framework is trying to load
ClassLib2.dll from disk, from the debug (or release) directory where
Prog.exe was started.

In fact, if I do put ClassLib2.dll on disk there, then there is no exception
because it finds it and loads it. Which means it has been loaded twice,
once by me with the Assembly.Load, and once due to the reference from
ClassLib1.

WHY? Why oh why? I've already loaded it!

So how do I make the framework "realize" that it is already loaded? I
thought strong naming would have do the trick, but it didn't make a
difference... it still wants to load it from disk because of the reference
from ClassLib1. Argghhh!

Any help would be GREATLY appreciated...

Thanks!

Greg Patrick

Source code is below if you want to try it out. You just make a VS.NET C#
Win Forms app solution and use Form1 from below. Then make two C# class
library projects ClassLibrary1, and ClassLibrary2. Don't reference either
class library from the main application project. Build. Copy
ClassLibrary1\bin\debug\ClassLibrary1.dll to <application>\bin\debug\C1.dll.
Similarly for ClassLibrary2.dll - copy to C2.dll.

----------------------------------------------------------------------------
-------------------------------

ClassLibrary2

using System;
namespace ClassLibrary2
{
public class Class2
{
public Class2()
{
}
}
}

ClassLibrary1 (Note, in VS.NET, has a reference to ClassLibrary2):

using System;
using System.Windows.Forms;
using ClassLibrary2;
namespace ClassLibrary1
{
public class Class1
{
public static Class2 class2 = new Class2();

public Class1()
{
}

public static void Hello()
{
MessageBox.Show("Hello!");
}
}
}

Prog.exe's Form1:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.IO;
using System.Reflection;
namespace WinApp
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;

private Assembly LoadAss(string filename)
{
FileStream fin = new FileStream("c1.dll", FileMode.Open,
FileAccess.Read);
byte[] bin = new byte[16384];
long rdlen = 0;
long total= fin.Length;
int len;
MemoryStream memStream = new MemoryStream((int)total);
rdlen = 0;
while(rdlen < total)
{
len = fin.Read(bin, 0, 16384);
memStream.Write(bin, 0, len);
rdlen = rdlen + len;
}
// done with input file
fin.Close();
return Assembly.Load(memStream.ToArray());
}

public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
Assembly c1 = this.LoadAss("c1.dll");
this.LoadAss("c2.dll");
Type t = c1.GetType("ClassLibrary1.Class1");
MethodInfo m = t.GetMethod("Hello");
m.Invoke(null, new object[]{});
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.Size = new System.Drawing.Size(300,300);
this.Text = "Form1";
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
}
}
 
Back
Top