I'm currently at a point where I really need some advice. In our company we mix many languages that are unmanaged like PowerBuilder or pure C++. For now we need a lot of code out of .NET. So my first purpose was why not just make a plugin system via COM.
This is what I'm trying to achieve at the moment. Everything works fine, the plugin system can load plugins. But as soon as I expose my plugin system to COM and try to load a plugin via, as an example VBS, I always get the the following error:
Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information
From my research I found that this problem occurs when, during Assembly.GetTypes(), the type couldn't be loaded. Which is really strange as I have put my interface into it's own assembly and reference it by my plugin system and the plugin, so that the interface is always the same.
But as I debugged a bit more I found that actually the problem seems to not be my interface. The load problem actually happens when he tries to load the type of my class of the plugin which inherits of the interface. Maybe it's the interface or something else.
But to point out for now, as long as I use the plugin system via managed code directly there is no problem. As soon as I do it via COM I receive this error. So I assume that I'm missing or messing something up with COM.
At the moment I really need a solution but not just the solution an explanation of the solution would also be really nice because I actually want to understand what I messed up.
Here is the download link to the solution. Maybe you find someting.
Solution
[ComVisible(true)]
public bool Initialize(string dllPlugin)
{
try
{
string dllPluginPath = Assembly.GetExecutingAssembly().Location.Replace("IncoPluginSystem.dll", "") + "plugins\\";
string completePluginPath = dllPluginPath + dllPlugin + ".dll";
if (!File.Exists(completePluginPath))
{
pluginError = "The plugin could not be found in the plugins directory.";
return false;
}
plugin = Assembly.LoadFile(completePluginPath);
if (plugin == null)
{
pluginError = "No plugin loaded. Pls initialize first";
return false;
}
foreach (Type t in plugin.GetTypes())
{
if (t.GetInterface("IPlugin") != null)
{
pluginInstance = Activator.CreateInstance(t) as IPlugin;
}
}
return true;
}
catch (Exception e)
{
pluginError = e.Message;
return false;
}
}
The Problem happens in the foreach loop where u access the Types of the assembly.
I added a more detailed Exception handling. This is the Error when i expose it to COM
Could not load file or assembly 'IncoPluginSystemInterface, Version-1.0.0.0, Culture=neutral, PuplicKeyToken=497bca4abf979e3e' or one of its dependencies. The system cannot finde the file specified.
Fusion Log:
WRN: Assembly binding logging is turned OFF.
Note: There is some performance penalty associated with assembly bind failure logging.
The references of the DLL are just the standard ones of creating a .net library. Nothing added.
The structure of the plugin system is the following
IncoPluginSystem.dll
IncoPluginSystemInterface.dll
plugins
->IncoPluginSystemInterface.dll
->TestPlugin.dll
So the missing dll is actually at the 2 spots where needed. Maybe it's because the IncoPluginSystem.dll is loaded as a COM object so maybe the path is incorrect but I'm not sure. I tested and changed all I can imagine. So it definitly has to be something I messed up with COM.
I was able to locate the problem. It's actually really that my DLL is in the wrong place. As soon as i put the DLL into the GAC it works. So for now my question is - how can i determine where to put the dll where it was missing. In my exception i only see what dll is missing but not the path. Does anyone know how to determine this?
Ok I was able to answer it myself. To Explain what was wrong. What I did to may help\improve others work in future.
I turned on the FusionLog in the Registry and on the LoaderException I checked for the FusionLog
catch (ReflectionTypeLoadException ex)
{
StringBuilder sb = new StringBuilder();
foreach (Exception exSub in ex.LoaderExceptions)
{
sb.AppendLine(exSub.Message);
FileNotFoundException exFileNotFound = exSub as FileNotFoundException;
if (exFileNotFound != null)
{
if (!string.IsNullOrEmpty(exFileNotFound.FusionLog))
{
sb.AppendLine("Fusion Log:");
sb.AppendLine(exFileNotFound.FusionLog);
}
}
sb.AppendLine();
}
pluginError = sb.ToString();
}
After enabling the FisonLog on the Registry (HKLM\SOFTWARE\Microsoft\Fusion!EnableLog (DWORD 1) I was able to see that the missing DLL is actually needed at the location of the executing program that call's the OLEObject to create an instance of the system via COM.
I wasn't aware of this, but yeah in some way it's rather logic. Luckily i didn't messed up anything on the code it was just in the wrong place.
Related
I'm a bit confused here and haven't gotten much help from google. Here's what I'm trying to do:
public Boolean LoadModule(String moduleHandle)//name of module MUST match its .dll name. Name of AppDomain is the same as the Handle.
{
try
{
AppDomain moduleDomain = AppDomain.CreateDomain(moduleHandle);
String pathToDll = #"C:\IModules.dll"; //Full path to dll you want to load
Type moduleType = typeof(IModule);
IModule loadedModule = (IModule)moduleDomain.CreateInstanceFromAndUnwrap(pathToDll, moduleType.FullName);
ModuleList.Add(loadedModule, moduleDomain);
Broadcast("Module loaded: " + moduleHandle, ModuleManagerHandle);
return true;
}
catch (Exception e)
{
//console writeline the error? probably cant
OutputBox.AppendText(e.ToString() + Environment.NewLine);
return false;
}
}
I thought I finally had this figured out but when I try to instantiate the IModule (ConsoleModule, in this case), I get the following error:
System.MissingMethodException: Constructor on type 'IModules.IModule' not found.
I take this to mean that I need to have a constructor, as if this were a class object instantiating itself on this function call, but I cannot make an interface have a constructor.
I have seen other threads suggesting ways to solve this problem, but they use assembly instead of appdomain, which will mess up the ability to unload modules. I'm concerned that without the ability to unload modules the application will suffer memory bloating over time.
The end goal is to be able to write a module, leave the program running and load/unload the modules during runtime without any changes to the core program, and add functionality on the go.
Anyone know of a workaround or maybe a better way to deal with dynamic module loading and unloading?
This is fixed with .NET 5.0 AssemblyLoadContext:
var basePath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
AssemblyLoadContext moduleAssemblyLoadContext = new AssemblyLoadContext(moduleHandle, true);
Assembly moduleAssembly = moduleAssemblyLoadContext.LoadFromAssemblyPath($"{basePath}\\{moduleHandle}.dll");
Type[] types = moduleAssembly.GetTypes();
foreach (Type type in types)
{
// Does this class support the transport interface?
Type typeModule = type.GetInterface("IModule");
if (typeModule == null)
{
// Not supported.
continue;
}
// This class supports the interface. Instantiate it.
IModule loadedModule = moduleAssembly.CreateInstance(type.FullName) as IModule;
if (loadedModule != null)
{
loadedModule.LoadedModule(this);
ModuleList.Add(loadedModule, moduleAssemblyLoadContext);
Broadcast("Module loaded: " + moduleHandle, ModuleManagerHandle);
OutputTextBox.AppendText(moduleHandle + " was loaded." + Environment.NewLine);
// Successfully created the interface. We are done.
return true;
}
}
return false;
Can't find the source anymore but found it looking for a related problem (you can find it on MSDN anyways). This successfully loads and unloads assemblies into their context. User must set the isCollectible value to TRUE to enable full unloading.
Only issue I had is that .NET 5.0 is not compatible -with itself- yet and libraries loaded as .NET 5.0 into .NET 5.0 programs will give a BadImageFormatException when trying to load the assembly. To fix, set the LIBRARY to the next most recent target framework (in my case, .NET Core 3.1) and move the newly compiled dll to wherever it goes and the application should run using the new dll.
The error tells you that there is no default (empty) constructor found for the type IModule. Since IModule is an interface, the message seems to make some sense.
Resulotion: Instantiate a class that implements IModule. An interface can never be intantiated on its own.
To instantiate the class, just one line neds to be changed:
Type moduleType = typeof(ClassThatImplementIModule);
You can still cast the instance to IModule
I have a c# library which provides some functionallity to upload data onto connected (android) devices. The dll itself gets exported via UnmangedExports to be used by an delphi application.
Here is the function which gets called by the delphi application:
[DllExport]
[return: MarshalAs(UnmanagedType.LPStr)]
public static string getDevices()
{
try
{
var devices = string.Empty;
var collection = new PortableDeviceCollection();
collection.Refresh();
foreach (var device in collection)
{
device.Connect();
if (devices != string.Empty)
{
devices += ";";
}
devices += device.FriendlyName;
device.Disconnect();
}
return devices;
}
catch (Exception e)
{
SomeClass.WriteErrorToLogFile(e);
return "ERROR";
}
}
Here is the class PortableDeviceCollection:
public class PortableDeviceCollection : Collection<PortableDevice>
{
private readonly PortableDeviceApiLib.PortableDeviceManagerClass _deviceManager;
public PortableDeviceCollection()
{
this._deviceManager = new PortableDeviceApiLib.PortableDeviceManagerClass();
}
public bool Refresh()
{
this._deviceManager.RefreshDeviceList();
// Determine how many WPD devices are connected
var deviceIds = new string[1];
uint count = 1;
this._deviceManager.GetDevices(null, ref count);
if (count > 0)
{
// Retrieve the device id for each connected device
deviceIds = new string[count];
this._deviceManager.GetDevices(deviceIds, ref count);
foreach (var deviceId in deviceIds)
{
Add(new PortableDevice(deviceId));
}
return true;
}
else
return false;
}
}
I can create the dll with visual studio and use this inside of the delphi application. When the delphi application calls the getDevices() function, i get an error on the instantiation of the PortableDeviceCollection class:
The file or assembly "Interop.PortableDeviceApiLib, Version = 1.0.0.0,
Culture = neutral, PublicKeyToken = null" or a dependency of it was
not found. The assembly is created by a runtime that is more recent
than the currently loaded runtime and can not be loaded.
ProductXY.PortableDeviceCollection..ctor()
ProductXY.ProductXYMain.getDevices()
The targetframework for the c# project is set to .Net Framework 4. Using any lower version i get an error when i try to compile the project:
The primary reference "Interop.PortableDeviceApiLib" could not be
resolved because it has an indirect dependency on the .NET Framework
assembly "mscorlib, version = 4.0.0.0, Culture = neutral,
PublicKeyToken = b77a5c561934e089", which is a higher version 4.0.0.0
than version 2.0.0.0 in the current target framework.
Please note. I have neither written the c# library nor the delphi application. Both have worked together for years. Now i have to add a functionallity to the c# library. I have not added any code to the project. I just tried to compile it again and use the dll. The only thing i did was updating the RGiesecke.DLLExport.Metadata via NuGet Packetmanager. Without updating i got an error
"Microsoft.Build.Utilities.ToolLocationHelper could not find
ildasm.exe"
I am aware of this Enumerating Windows Portable Devices in C# question. But my error is thrown before the code which is treaded by the question is reached. I still tried the solution to the question, but the action (deassamble, find and replace in the dll) which is described in the answere has already been done (otherwise my code would not have compiled).
The error message doesn't make sense to me. Interop.PortableDeviceApiLib is a COM-Lib which is not available for download in different framework-versions. I think I am missing something here.
Can anyone help me?
I was finally able to solve this problem. To be honest I don't know what finally solved this. For every one who stumbles up on this, here are the things i tried to fix this problem. They are in no specific order (since i tried everything multiple times):
Updating the RGiesecke.DLLExport packet
Changing the plattform in the Konfigurations-Manager to x86
Disassamble, edit and reassable the Interop.PortableDeviceApiLib like in this question (answeres of Christophe Geers and Bruno Klein)
Delete the reference to the Interop.PortableDeviceApiLib
Delete the reference to the Interop.PortableDeviceTypesLib
Readding the reference to the Interop.PortableDeviceApiLib
Readding the reference to the Interop.PortableDeviceTypesLib
Rebuild the project
Setting the Interoptyp embeddet on both to false (I found various statements to NOT do this, but the project was set up like this when i got it and it worked (be carefull)) on both Interop-Libs.
At least this helped me.
I have a C# dll that references a 3rd party dll. There are different versions of the 3rd party dll.
As you might expect if the latest 3rd Party dll is present I want to use the new functionality if not I want to execute the old functionality.
I wasn't sure how to achieve this but I thought the first thing to try would be a simple if statement that decides which function to call.
So I find the assembly, get its location and hence its version info. (I need the file version as the product versions are the same).
Then a simple
if (version >= 3) do x() else do y()
When I execute the code on a machine with version 2 installed I get a MissingMethodException regarding x(). I thought I had made a stupid mistake but the logic was correct. The version is 2 so x(); should not be executed. I decided to remove the offending method and replace it with a throw new Exception(). The exception is not thrown and the code completes successfully.
Here is the danger - I am thinking that this is due to branch prediction. This is dangerous because it is not an area I have any knowledge of and therefore making assumptions is a dangerous thing.
So my questions are:
Am I tacking this problem the wrong way - is there a more obvious solution that I am missing?
or
Is there a way to disable branch prediction (if that is the cause) or to somehow enforce/flag the if condition as a point that must be executed before continuing.
Here is the code being executed:
On a machine with version 3 installed then it is fine.
On a machine with version 2 installed I get a MissingMethodException regarding method x().
It I removed the call to x(); and uncomment the throwing of the exception - no exception is thrown.
Relevant code:
Assembly assembly = System.Reflection.Assembly.GetAssembly(typeof(3rdPartyClass));
FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
if (fileVersionInfo.FileMajorPart >= 3)
{
// throw new Exception("aagghh");
x();
}
else
{
y();
}
Using reflection, it's possible to get a list of Methods available for a particular DLL (more specifically: Type).
You could use this methodinfo to dynamically invoke the method as specified in Vlad's solution.
In fact, you could leave out the version check and just try to find the intended method directly.
var methodX = assembly.GetType("sometype").GetMethod("X");
if (methodX != null)
{
methodX.Invoke(params);
}
else
{
assembly.GetType("sometype").GetMethod("Y").Invoke(otherParams);
}
Edit: This is not exactly what you want, but with this kind of reflection you can find the correct methods, also for your own assembly.
There is no "branch prediction": the runtime binding seems to happen as the method is executed.
So the workaround would be like this:
if (fileVersionInfo.FileMajorPart >= 3)
{
CallX();
}
else
{
CallY();
}
void CallX()
{
DependentClass.X();
}
void CallY()
{
DependentClass.Y();
}
However, anyway this seems to be a hack: you need to execute with the version of DLL you were linking against.
This is actually a more accurate answer :
Assembly assembly = System.Reflection.Assembly.GetAssembly(typeof(String));
FileVersionInfo fileVersionInfo = FileVersionInfo.GetVersionInfo(assembly.Location);
ObjectHandle oh = Activator.CreateInstanceFrom("AssemblyName.dll", "namespace.class");
object o = oh.Unwrap();
Type to = o.GetType();
if (fileVersionInfo.FileMajorPart >= 3)
{
to.InvokeMember("Method X", BindingFlags.InvokeMethod, null, o, null);
}
else
{
to.InvokeMember("Method Y", BindingFlags.InvokeMethod, null, o, null);
}
I am working on some software that will dynamically build menu items for certain dlls so that we can load components in dynamically based on what dlls are available on the users machine. Any dlls that I want to load have been flagged with an Assembly Attribute in the AssemblyInfo.cs file and how I determine whether or not I want to build a menu item for that dll. Here is my method so far:
private void GetReportModules() {
foreach (string fileName in Directory.GetFiles(Directory.GetCurrentDirectory())) {
if (Path.GetExtension(fileName) == ".dll" || Path.GetExtension(fileName) == ".exe") {
System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFrom(fileName);
object[] attributes = assembly.GetCustomAttributes(typeof(ReportNameAttribute), false);
if (attributes.Count() > 0) {
ReportNameAttribute reportNameAttribute = attributes[0] as ReportNameAttribute;
Type type = assembly.GetType(reportNameAttribute.BaseType);
MenuItem customReportsMenuItem = new MenuItem();
customReportsMenuItem.Header = reportNameAttribute.ReportName;
ReportsMenuItem.Items.Add(customReportsMenuItem);
customReportsMenuItem.Click += (s, ev) => {
var obj = Activator.CreateInstance(type);
type.InvokeMember("Show", System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.InvokeMethod, null, obj, null);
};
}
}
}
}
For the most part its working fine, I am getting the dlls that I am expecting back out and am creating my menu items fine. The problem is that in order to check for the attribute I first need to load the assembly using Reflection. Some of the other local dlls are throwing errors when I try to load them about missing dependencies or he module was expected to contain an assembly manifest. Is there a way I can safely check to see if an assembly CAN be loaded before I actually do so? (sounds stupid as I write it out). Any thoughts on the problem I'm running into or better suggestions for how to accomplish what I'm trying here? Feeling a little bit in over my head.
You can create a separate AppDomain, try to load the assemblies there, send the results back, and unload the AppDomain. This way you do not change your current AppDomain with 'garbage' of any loaded assemblies.
One way would be to make use of a try catch block. If it throw's an exception, you're not interested...
EDIT:
MSDN explains clearly the type of exceptions LoadFrom can throw. FileLoadException looks likely in your case.
I'm sure there is code out there that carried on after a catch. For example a logging framework. I would not want my framework to catch an exception and make my executable stop etc, i'd want it to smother the exception. My application should not fail just because a line of log miss fired.
You can try the Unmanaged Metadata API (http://msdn.microsoft.com/en-us/library/ms404384.aspx) or the Common Compiler Infrastructure Metadata API (http://ccimetadata.codeplex.com/) as alternatives to plain reflection.
Aloha
Given a plug-in architecture (C# / .NET 3.5) with the plug-ins stored in the GAC, how can I list/load all dll's that contain types that implement my specific interface? In other words, I'd like to investigate which plug-ins for my application are installed on a machine, by looking in the GAC.
-Edoode
To add to BFree's answer, I suggest that you could load the assemblies for reflection only. This gives you enhanced security (the assemblies aren't able to execute at all) without appdomains, and flexibility (you can load assemblies that are for a different architecture).
First a little clarification: a DLL cannot implement an interface. The DLL contains types that could implement a specific interface. Here's a .NET wrapper around fusion.dll that allows you to enumerate all the assemblies in the GAC. Once you have loaded the assembly with Assembly.Load you can try to find all the types that implement the specific interface:
foreach (var type in assembly.GetTypes())
{
var myInterfaceType = typeof(IMyInterface);
if (type != myInterfaceType && myInterfaceType.IsAssignableFrom(type))
{
Console.WriteLine("{0} implements IMyInterface", type);
}
}
The Gac is really just a directory on your machine like any other. Here's the typical breakdown:
c:\windows\assembly\GAC
\GAC_32
\GAC_MSIL
And maybe some others..
Within each of those folders, you'll find subfolders where the actual dll's are stored. You'll first need to write a recursive method to get you all the dll's found under \assembly\, (you can find these online easily if you're not comfortable doing it yourself). Once you have that list of dll's you can proceed to use Assembly.LoadFile to load up the assembly and check all the types to see if they implement a given interface.
My only suggestion would be to load up these dll's in a seperate appdomain so that you're not allowing any potential harmful dll's to get loaded into your app.
Some links:
Searching Directories.
Loading Assemblies and checking for a given interface.
Creating new AppDomain.
You should look at the Type Selector Tool in Enterprise Library. It's probably not what you want directly, but it does what you are describing and you might be able to borrow some implementation from it.
First off, I'd recommend not doing this. To do this, you have to load all the assemblies from the GAC. I'd recommend you have your user (or an admin, or whatever) tell you what assemblies to try to load from (though for that, you might want a list of all the options, which might be why you're asking this...)
That said, this might work, but it's throwing errors for several assemblies it should work for, and I'm not sure why.... Also, I'm not sure how to detect where the GAC is -- c:\windows\assembly is the default, but I don't know where the real value is stored (registry?)
var targetType = typeof(IComparable);
var errors = new List<Exception>();
var c = Directory.GetFiles(#"c:\windows\assembly", "*.dll", SearchOption.AllDirectories).ToList()
.ConvertAll(f => Path.GetFileNameWithoutExtension(f))
.Where(f => !f.EndsWith(".ni"))
.Distinct().ToList()
.ConvertAll(f => { try { return Assembly.ReflectionOnlyLoad(f); } catch (Exception ex) { errors.Add(ex); return null; } })
.Where(a => a != null)
.SelectMany(a => { try { return a.GetTypes(); } catch (Exception ex) { errors.Add(ex); return new Type[] { }; } })
.Where(t => targetType.IsAssignableFrom(t));
Good luck with that.