Problems with loading AddIns

  • Thread starter Thread starter Nikola Novak
  • Start date Start date
N

Nikola Novak

Hello,

Here is the situation: I'm loading up various add-ins for my application
and I ran into trouble when one of my add-ins, which was placed in its own
subdirectory, failed to load its dependency (they were referenced under
"References").

The main program loads up two files of the add-in: AddIn.dll and
OtherAddIn.dll (both files are part of the same addin). OtherAddIn has -
among others - the AddInManager.dll in its references, but AddIn has both
AddInManager.dll and OtherAddIn.dll. I would like to have AddInManager
placed in the base application folder and all the addin files in their
respective subdirectories (\AddIns\Addin1, \AddIns\Addin2, etc.)

Moreover, AddIn is a user control, but it must also inherit the interface I
defined. OtherAddIn is a class library and inherits the other interface.

Searching the net for the solution I came across this article:

http://msdn.microsoft.com/en-us/magazine/cc163701.aspx

It is mainly concerned about the security of loading add-ins and not the
folder issue I have. However, I decided to implement it because it can also
help me with the folder issue and, of course, because security is
important.

The result was the following code:

In main.cs:

// ...

private IAddIn presenter1;

// ...

// this is called from constructor of the form with parameter value
// FileName = D:\MyAddInProject\AddIns\MyFirstAddIn\AddIn.DLL
private void LoadAddIn(string FileName)
{
Evidence LocalEvidence = new Evidence(new object[] { new Zone(SecurityZone.MyComputer) }, new object[] { });

PermissionSet LocalPermissions = SecurityManager.ResolvePolicy(LocalEvidence);

AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "\\AddIns\\MyFirstAddin";
AppDomain sandboxedDomain = AppDomain.CreateDomain("Add-In Domain", LocalEvidence, setup, LocalPermissions, new StrongName[] { });

Type addInManagerType = typeof(AddInManager);
AddInManager manager = (AddInManager)Activator.CreateInstanceFrom(sandboxedDomain, addInManagerType.Assembly.Location, addInManagerType.FullName).Unwrap();

// Program breaks on the following line with NullReferenceException
manager.SetParams(FileName, "AddIn.AddIn1");
presenter1 = manager.GetInterface();
Control ControlBoard = manager.GetControl();

//...
}

In AddInManager.cs:

using System;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Security;
using System.Security.Permissions;

namespace Interfaces
{
public class AddInManager : MarshalByRefObject
{
private string _Assembly = "";
private string _Type = "";
private object _Instance;

public void SetParams(string AssemblyName, string AssemblyType)
{
// writing to disk for debugging purposes
StreamWriter Test = new StreamWriter("abcd.txt");
Test.WriteLine(AssemblyName + " " + AssemblyType);
Test.Flush();
_Assembly = AssemblyName;
_Type = AssemblyType;
Assembly a = Assembly.LoadFile(AssemblyName);
Test.WriteLine(a.Location);
Test.Flush();
Type t = a.GetType(AssemblyType);
Test.WriteLine((t == null) ? "Type is null." : "Success!");
Test.Flush();
// Output stops here because t == null
_Instance = t.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);
Test.WriteLine("I'm out!");
Test.Close();
//Activator.CreateInstance(AssemblyName, AssemblyType);
}

// get the instance by casting it to the interface
// if this fails at this time, then there will be an exception
// that's good because everything's still in the tryout phase
public IAddIn GetInterface()
{
// one day this will be surrounded by a try/catch block
return (IAddIn)_Instance;
}

// get the instance by casting it to Control
// this I need because I need to access some of the methods and
// properties inherited from Control (so I don't need to force them
// all in the interface)
// if this fails at this time, then there will be an exception
// that's good because everything's still in the tryout phase
public Control GetControl()
{
// one day this will be surrounded by a try/catch block
return (Control)_Instance;
}
}

public interface IAddIn
{
System.Windows.Forms.BorderStyle Borderstyle { get; set; }
int FF { get; set; }
bool ShowMaxMoves { get; set; }
}

public interface IOtherAddIn
{
// ...
}
}

Contents of abcd.txt after execution:

D:\MyAddInProject\AddIns\MyFirstAddIn\AddIn.DLL AddIn.AddIn1
D:\MyAddInProject\AddIns\MyFirstAddIn\AddIn.DLL
Type is null.

Putting it short, what's going on in this code? I've never dealt with
security issues before and I basically have no clue what most of the
classes in LoadAddIn do.

Why does the variable t in AddInManager become null?

What am I doing wrong? (or better, what am I doing at all?)

Thanks,
Nikola
 
Does your code work if everything is placed in the same directory?
I know you don't want that but does it work?

You say your addins reference AddInManager.dll - why?

We implement a plugin model in our system, and the plugins all have to
implement an interface, and none of them know anything about the code
responsible for loading them in. Why would they need that?

I don't think you should be worrying about security. Just start with really
basic stuff - put all your dlls in the same directory and see if it works.
When that is OK, you can worry about using different directories for your
addins. But personally I'd recommend just installing everything to the same
directory; it can save a lot of hassle.

HTH,

Adam
=======
 
Does your code work if everything is placed in the same directory?
I know you don't want that but does it work?

You say your addins reference AddInManager.dll - why?

We implement a plugin model in our system, and the plugins all have to
implement an interface, and none of them know anything about the code
responsible for loading them in. Why would they need that?

I don't think you should be worrying about security. Just start with really
basic stuff - put all your dlls in the same directory and see if it works.
When that is OK, you can worry about using different directories for your
addins. But personally I'd recommend just installing everything to the same
directory; it can save a lot of hassle.

HTH,

Adam
=======

Hello Adam,

My old code which had no security worked when everything was in the same
directory. I haven't tested the secure version in this way, but I'll try.

The non-secure version also worked when AddIn.dll was in
AppRoot\Addins\Addin1 directory and OtherAddIn.dll was in AppRoot. It
didn't work, however, when both AddIn.dll and OtherAddIn.dll were in
AppRoot\Addins\Addin1, which is what I want (exception was thrown saying
that the file containing the OtherAddIn.dll was not found). That was when I
started looking on the Internet on how to solve the problem and when I
found the article I mentioned.

Thanks for pointing out that AddInManager doesn't need to be referenced by
the AddIns. The article I linked was a bit misleading on that one, because
both the interface(s) the addins must implement and the addin manager was
shown to be in the same file and that is what I did.

As for storing everything in the same directory, that won't do because I'm
expecting there will be a number of addins and they might end up
overwriting each other's files. The programmer I'm working with and myself
expect to develop at least three addins. We can, of course, make this work
when everything is in the same directory, but all it takes is one
additional addin to mess up that directory "structure". That's why we'd
prefer separate folders for each addin, regardless of the hassle we'd have
then.

At least, once the hassle is over, we no longer need to worry about it --
at least not that one particular hassle :)

I'll report my results as soon as I have them.

Nikola
 
I've separated the AddInManager and the Interfaces now and copied all addin
files to the same folder as the application (AppRoot). The code now works
and I can properly access (and modify where applicable) all public fields,
properties and methods in the Control. I also used a cast to UserControl
because of some problem I had (the problem wasn't solved by that although I
hoped it would be).

What happens is that when I try to add the control to the interface using

this.Controls.Add(ControlBoard); // ControlBoard is of type UserControl

and I get the RemotingException saying: "Remoting cannot find field
'parent' on type 'System.Windows.Forms.Control'."

I can also cast the object I get to IAddIn interface and access it through
that as well.

Any ideas?

Also, what changes do I need to make for it to work when the files are in
their proper folder?

Thanks,
Nikola

P.S. Here's the modified code:

In main.cs:

// ...

private IAddIn presenter1;

// ...

// this is called from constructor of the form with parameter value
// FileName = D:\MyAddInProject\AddIn.DLL
private void LoadAddIn(string FileName)
{
Evidence LocalEvidence = new Evidence(new object[] { new Zone(SecurityZone.MyComputer) }, new object[] { });

PermissionSet LocalPermissions = SecurityManager.ResolvePolicy(LocalEvidence);

AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase; // + "\\AddIns\\MyFirstAddin";
AppDomain sandboxedDomain = AppDomain.CreateDomain("Add-In Domain", LocalEvidence, setup, LocalPermissions, new StrongName[] { });

Type addInManagerType = typeof(BoardManager);
BoardManager manager = (BoardManager)Activator.CreateInstanceFrom(sandboxedDomain, addInManagerType.Assembly.Location, addInManagerType.FullName).Unwrap();

manager.SetParams(FileName, "AddIn.AddIn1");
presenter1 = manager.GetInterface();
UserControl ControlBoard = manager.GetControl();

ControlBoard.BackColor = System.Drawing.Color.LightSteelBlue;
ControlBoard.Location = new System.Drawing.Point(12, 12);
ControlBoard.Name = "presenter1";
ControlBoard.Size = new System.Drawing.Size(650, 430);
ControlBoard.TabIndex = 0;

presenter1.Borderstyle = System.Windows.Forms.BorderStyle.Fixed3D;
presenter1.FF = 2;
presenter1.ShowMaxMoves = true;

// Program breaks on the following line with RemotingException
this.Controls.Add(ControlBoard);
}

In AddInManager.cs:

using System;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using System.Security;
using System.Security.Permissions;

namespace AddInManager
{
public class BoardManager : MarshalByRefObject
{
private string _Assembly = "";
private string _Type = "";
private object _Instance;

public void SetParams(string AssemblyName, string AssemblyType)
{
// writing to disk for debugging purposes
StreamWriter Test = new StreamWriter("abcd.txt");
Test.WriteLine(AssemblyName + " " + AssemblyType);
Test.Flush();
_Assembly = AssemblyName;
_Type = AssemblyType;
Assembly a = Assembly.LoadFile(AssemblyName);
Test.WriteLine(a.Location);
Test.Flush();
Type t = a.GetType(AssemblyType);
Test.WriteLine((t == null) ? "Type is null." : "Success!");
Test.Flush();
_Instance = t.InvokeMember(null, BindingFlags.CreateInstance, null, null, null);
Test.WriteLine("I'm out!");
Test.Close();
}

// get the instance by casting it to the interface
// if this fails at this time, then there will be an exception
// that's good because everything's still in the tryout phase
public IAddIn GetInterface()
{
// one day this will be surrounded by a try/catch block
return (IAddIn)_Instance;
}

// get the instance by casting it to Control
// this I need because I need to access some of the methods and
// properties inherited from Control (so I don't need to force them
// all in the interface)
// if this fails at this time, then there will be an exception
// that's good because everything's still in the tryout phase
public UserControl GetControl()
{
// one day this will be surrounded by a try/catch block
return (UserControl)_Instance;
}
}
}

In Inderfaces.cs:

// using directives

namespace Interfaces
{
public interface IAddIn
{
System.Windows.Forms.BorderStyle Borderstyle { get; set; }
int FF { get; set; }
bool ShowMaxMoves { get; set; }
}

public interface IOtherAddIn
{
// ...
}
}

Contents of abcd.txt after execution:

D:\GamesSolutions\Checkers\Checkers\bin\Debug\\Board.DLL Board.Board
D:\GamesSolutions\Checkers\Checkers\bin\Debug\\Board.DLL
Success!
I'm out!
 
Whoops:

Contents of abcd.txt after execution:

D:\MyAddInProject\AddIn.DLL AddIn.AddIn1
D:\MyAddInProject\AddIn.DLL
Success!
I'm out!

:)
 
If you're using different directories, one more thing has just occurred to
me.

Beware of what we in our company call the "evil twin" problem. This usually
occurs because of install problems. The symptoms are that you get a
InvalidCastException but the object you are trying to cast definitely is of
the type you're trying to cast it to.

i.e. Suppose DerivedClass inherits from BaseClass

void MyFunc(BaseClase b)
{
DerivedClass d = (DerivedClass) b; // <- exceptions here even when b
definitely is a DerivedClass
}

This is caused when the dll that defines DerivedClass has been loaded in
twice. And that happens when your executable can get at more than one copy
of the dll. So your exe runs up and loads the dll, then one of your addins
gets loaded. If one of those addins has the same dll in its directory the
dll will get loaded again. An object created by one copy of the dll cannot
be used by a method in the other copy of the dll.

The only solution is to make absolutely sure that shared dlls only exist
once in the whole of your installation.

You might not ever come across this but I thought I'd mention it because it
can cause no end of grief - and banging of heads on walls!

Cheers,

Adam.
 
Glad to hear it :-)

Umm, I hope you do realize that that was part of the output from
addinmanager.dll as it was used -- when all files were in the same
directory.

Which means -- I still have the problem when I have files in separate
directories... so, umm...

Help, please?
 
OK so I managed to set up my directories properly when using AppDomain, but
now I have a different problem.

The AddIns I load are controls which I need to display on a form in the
main application. This means that at some point I need to add that control
to the form. I do this with:

this.Controls.Add(LoadedControlAddIn);

This results in a Remoting exception with message:

Remoting cannot find field 'parent' on type 'System.Windows.Forms.Control'.

Note that 'parent' is not capitalized, therefore I think it's a private
field in Control. So, my question is how do I make it possible for my
application to access private fields in assemblies I loaded?

Is it even possible to operate a control loaded from an assembly through
AppDomain?

Here's my code at the moment:

// ...

private IBoard presenter1;

// ...

// the following method is called with
// FileName = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "\\AddIns\\AddIn1";
private void LoadBoard(string FileName)
{
// get the set of permissions granted to code from the Internet.
Evidence LocalEvidence = new Evidence(new object[] { new Zone(SecurityZone.MyComputer) }, new object[] { });

PermissionSet LocalPermissions = SecurityManager.ResolvePolicy(LocalEvidence);

// create a sandboxed domain
AppDomainSetup setup = new AppDomainSetup();
setup.DisallowApplicationBaseProbing = false;
setup.DisallowBindingRedirects = false;
setup.DisallowCodeDownload = true;
setup.PrivateBinPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + "\\AddIns\\AddIn1";
setup.ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
AppDomain sandboxedDomain = AppDomain.CreateDomain("Add-In Domain", LocalEvidence, setup, LocalPermissions, new StrongName[] { });

Type addInManagerType = typeof(BoardManager);
BoardManager manager = (BoardManager)Activator.CreateInstanceFrom(sandboxedDomain, addInManagerType.Assembly.Location, addInManagerType.FullName).Unwrap();

// the following loads up the control in the manager
manager.SetParams(FileName, "Board.Board");
// this returns it as interface
presenter1 = manager.GetInterface();
// this returns it as Control
UserControl ControlBoard = manager.GetControl();

// ...

this.Controls.Add(uc);
}

Thanks,
Nikola
 
Umm, I hope you do realize that that was part of the output from
addinmanager.dll as it was used -- when all files were in the same
directory.

Which means -- I still have the problem when I have files in separate
directories... so, umm...

Help, please?

OK, I managed to solve the problem with directories now. I have further
problems, however. I started a new thread called Controls and AppDomain.

Adam, thanks for your help. Hope you'll stay with me until this is
resolved. :)

Nikola
 
If you're using different directories, one more thing has just occurred to
me.

Beware of what we in our company call the "evil twin" problem. This usually
occurs because of install problems. The symptoms are that you get a
InvalidCastException but the object you are trying to cast definitely is of
the type you're trying to cast it to.

i.e. Suppose DerivedClass inherits from BaseClass

void MyFunc(BaseClase b)
{
DerivedClass d = (DerivedClass) b; // <- exceptions here even when b
definitely is a DerivedClass
}

This is caused when the dll that defines DerivedClass has been loaded in
twice. And that happens when your executable can get at more than one copy
of the dll. So your exe runs up and loads the dll, then one of your addins
gets loaded. If one of those addins has the same dll in its directory the
dll will get loaded again. An object created by one copy of the dll cannot
be used by a method in the other copy of the dll.

The only solution is to make absolutely sure that shared dlls only exist
once in the whole of your installation.

You might not ever come across this but I thought I'd mention it because it
can cause no end of grief - and banging of heads on walls!

Cheers,

Adam.

Thanks for the warning, I'll keep it in mind.
 
Back
Top