Again the topic of MEF not loading plugins correctly..
First of all: I took the last 7 hours searching similar questions and trying to apply the solutions given there, but I couldn't find any solution that worked for me.
Here is the Situation: Main application using 3 classes
Main class with GUI ( Windows forms )
Export class with Interface IMain for the PLugIns being able to give messages to the main window
Import class that loads all available Plugins from a given Directory
All this worked like a charm for one directory, where all the Plugins were stored. Now my superiors wanted me to rework it, so that every Plugin gets its own subdirectory within the main plugin Directory. And that's were the trouble began. I tried modifying my Code to get this Task done but now all the Plugins are loaded into the Catalogs but are missing in the list, from where I can call them through the applications GUI.
Here the code snippet from my Import class:
public class ServiceTool
{
CompositionContainer ComContainer;
[ImportMany(typeof(Interfaces.IPlugIn))]
public List<Interfaces.IPlugIn> Liste = new List<Interfaces.IPlugIn>();
public ServiceTool()
{
ModuleSuchen();
MessageBox.Show(Liste.Count.ToString());
}
void ModuleSuchen()
{
var AggKatalog = new AggregateCatalog();
var Dlls = Directory.GetFiles("D:\\Automation\\TIA\\FER-ServiceTool\\PlugIns", "*.dll", SearchOption.AllDirectories);
foreach(var DllDatei in Dlls)
{
try
{
AggKatalog.Catalogs.Add(new AssemblyCatalog(Assembly.LoadFile(DllDatei)));
}
catch(Exception e)
{
MessageBox.Show(e.ToString());
}
}
ComContainer = new CompositionContainer(AggKatalog);
try
{
this.ComContainer.ComposeParts(this);
}
catch (CompositionException ComEx)
{
var ComExceptions = ComEx.Errors;
foreach (var ComException in ComExceptions)
{
MessageBox.Show(ComException.ToString());
}
}
catch (ReflectionTypeLoadException RTLEx)
{
Exception[] Exceptions = RTLEx.LoaderExceptions;
foreach (Exception Ex in Exceptions)
{
MessageBox.Show(Ex.ToString());
}
}
}
}
Where is the problem loading the plugins from the subdirectories? I'm just a beginner in programming and I have absolutely no clue why it isn't working anymore..
Related
I Have a c# application that include various esternal DLL. When the application start, dlls are exctracted in the .exe folder to grant the correct execution.
here the code:
var executingAssembly = Assembly.GetExecutingAssembly();
string folderName = string.Format("{0}.Resources.DLLs", executingAssembly.GetName().Name);
var list = executingAssembly
.GetManifestResourceNames()
.ToArray();
foreach (var item in list)
{
File.WriteAllBytes(item.Replace("myapp.DLLs.", ""),
ReadAllBytes(executingAssembly.GetManifestResourceStream(item)));
}
when i close the form, i want to delete those files with this code, associated to the form closing event:
private void CleanFiles(Object sender, FormClosingEventArgs e)
{
var executingAssembly = Assembly.GetExecutingAssembly();
string folderName = string.Format("{0}.Resources.DLLs", executingAssembly.GetName().Name);
string folder = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetEntryAssembly().Location);
var list = executingAssembly
.GetManifestResourceNames()
.ToArray();
foreach (var item in list)
{
File.Delete(folder + #"\" + item.Replace("myapp.DLLs.", ""));
}
}
If I open and then close the form, it works perfectly. But, if I open the form, do some operations, it throw an exception during the closing operations because access to dlls is denied.
How can I release all dlls/resources?
I assume that if you copy these DLLs, you load and use them afterwards.
There's a slight issue with dynamically loading DLLs, being that you can't just unload them. Once loaded into your application domain, they're here to stay.
The solution is thus to simply create a new application domain, load the DLL inside of it, and when you're done, unload this new application domain.
Something like:
var dynamicDomain = AppDomain.CreateDomain("YourDomainName");
var dynamicallyLoadedAssembly = dynamicDomain.Load("YourAssembly");
// do stuff with your dynamically loaded assembly
AppDomain.Unload(dynamicDomain);
For more information on the topic: MSDN "How to: Load and unload assemblies".
You can (and should) implement exception handling around the parts where you manipulate files on the system:
try
{
// Manipulate your files here.
}
catch (Exception ex)
{
// Handle exceptions here. you can also delete the dlls here if you wish.
// Remember to check in CleanFiles if the files are already deleted.
}
finally
{
// You could also get rid of the files here.
// Finally-block is executed regardless if an exception was thrown or not.
}
Usefull links: Microsoft: Exception Handling, Microsoft: Best Ractices for exceptions
I'm currently working on a project where I want to automatically build a showcase of all the styles that are defined in the styling project of another Visual Studio solution.
To do this, I select the Styling.dll from the at runtime via OpenFileDialog from the other solutions bin/Debug folder, add its resources to my applications resources and then build the views. That works pretty well, but now I've got a problem when selecting an assembly with references to several other assemblies.
This is how I load the assemblies right now:
public void OpenFile()
{
var openFileDialog = new OpenFileDialog { Filter = "DLL Dateien (*.dll)|*.dll" };
if (openFileDialog.ShowDialog() == true)
{
var path = Path.GetDirectoryName(openFileDialog.FileName);
foreach (var dll in Directory.GetFiles(path, "*.dll").Where(dll => dll != openFileDialog.FileName && !dll.ToString().ToLower().Contains("datagrid")))
{
this.LoadResources(dll, false);
}
this.LoadResources(openFileDialog.FileName);
}
}
I put in the foreach to first load a Utils.dll that always comes with the Styling.dll, but that obviously doesnt work if there are multiple other assemblies referencing each other.
Then in LoadResources :
public void LoadResources(string assemblypath)
{
var assembly = Assembly.LoadFile(assemblypath);
var stream = assembly.GetManifestResourceStream(assembly.GetName().Name + ".g.resources");
if (stream != null)
{
var resourceReader = new ResourceReader(stream);
foreach (DictionaryEntry resource in resourceReader)
{
// Get all the .baml files from the .dll and create URI for corresponding .xaml files
if (new FileInfo(resource.Key.ToString()).Extension.Equals(".baml"))
{
var uri = new Uri("/" + assembly.GetName().Name + ";component/" + resource.Key.ToString().Replace(".baml", ".xaml"), UriKind.Relative);
// Add ResourceDictionary to Application Resources
var rd = Application.LoadComponent(uri) as ResourceDictionary;
if (rd != null)
{
Application.Current.Resources.MergedDictionaries.Add(rd);
}
}
}
}
}
Note: The exception always throws at the var rd = Application.LoadComponent(uri) as ResourceDictionary; point.
Now, how do I get my application to correctly load all the other assemblies in the directory? Right now, I always get an error telling me that the other assemblies could not be found, even though they are in the same directory as the referencing assembly.
After looking for a solution for a while now, I understood that the application automatically tries to find the missing assemblies, but doesn't search in the directory where they actually are.
Since i want to load the assemblies dynamically, I also cant just add additional search paths to my app.config beforehand, can I do it at runtime somehow?
I hope you understand my problem, any help is appreciated, thank you!
Edit:
I have already read through this question, but it didnt help me any further.
When I implement the AssemblyResolve Event, I still get FileNotFound / XamlParse exceptions, even when the file in the assemblypath exists.
Also, the handler always tries to find xxx.resources.dll files, which on the other hand do not exist in the directory.
I use Assembly.LoadFile(string path) to load assembly to C# program. It works perfectly on my PC and two notebooks but... when I tried to send this app to my friend it crashed just after this call without any exceptions. We use same versions of .NET Framework, everything must be fine. I cant understand what happens. No exceptions, no errors, just "silent" return.
I also tried to use LoadFrom but nothing changed.
I use absolute path for dll files
public LoadedType[] LoadFrom(string path)
{
Assembly assembly = Assembly.LoadFile(path);
}
and calling method is
Loader loader = new Loader();
string[] paths = Directory.GetFiles(Directory.GetCurrentDirectory(), "*.dll", SearchOption.TopDirectoryOnly);
List<string> corrupted = new List<string>();
foreach (string path in paths)
{
try
{
LoadedType[] loadedTypes = loader.LoadFrom(path);
MessageBox.Show("loaded");
if (loadedTypes.Length == 0)
{
continue;
}
foreach (LoadedType loadedT in loadedTypes)
{
AvailableTypes.Add(loadedT);
}
}
catch (ReflectionTypeLoadException)
{
corrupted.Add(Path.GetFileName(path));
}
}
MessageBox does not appear.
Could somebody explain me whats wrong and why this code works on three PCs and does not work on another two PCs with the same Framework version?
using the code above you can not know if is there an exception or not, because you catch just exception of type ReflectionTypeLoadException, add another catch(Exception ex).
Check that string[] paths is not empty.
check that these assemblies are not used by another process.
check that you have access to read these assemblies.
You have to consider other types of possible exceptions as well:
try
{
// Ignore assemblies we can't load. They could be native, etc...
Assembly.LoadFrom(assemblyFile);
}
catch (Win32Exception) { }
catch (ArgumentException) { }
catch (FileNotFoundException) { }
catch (PathTooLongException) { }
catch (BadImageFormatException) { }
catch (SecurityException) { }
I am using visual studio 2010 and I am having a .DWG file which I want to open in autocad. Till now I have used this.
Process p = new Process();
ProcessStartInfo s = new ProcessStartInfo("D:/Test File/" + fileName);
p.StartInfo = s;
p.Start();
But what I want is to close the file inside the Autocad but not the autocad itself. (Means atocad.exe should be kept running).
Till now I hve used this but its closing the acad.exe not the file.
foreach (Process Proc in Process.GetProcesses())
{
if (Proc.ProcessName.Equals("acad"))
{
Proc.CloseMainWindow();
Proc.Kill();
}
}
Take the Autocad .NET libraries from Autodesk Sites (http://usa.autodesk.com/adsk/servlet/index?id=773204&siteID=123112)
Then you will be able to use Application and Document classes.
They will give you full control over opening and closing documents within the application.
You can find many articles on that, and can ask further questions.
AutoCAD does have an api. there are 4 assemblys. Two for in-process and two for COM.
inprocess :
acdbmgd.dll
acmgd.dll
COMInterop :
Autodesk.Autocad.Interop.dll
Autodesk.Autocad.Interop.Common.dll
this is a method that will open a new instance of AutoCAD or it will connect to an existing running instance of AutoCAD.
you will need to load these .dlls into your project references.
using Autodesk.AutoCAD.Interop;
using Autodesk.AutoCAD.Interop.Common;
namespace YourNameSpace {
public class YourClass {
AcadApplication AcApp;
private const string progID = "AutoCAD.Application.18.2";// this is AutoCAD 2012 program id
private string profileName = "<<Unnamed Profile>>";
private const string acadPath = #"C:\Program Files\Autodesk\AutoCAD 2012 - English\acad.exe";
public void GetAcApp()
{
try
{
AcApp = (AcadApplication)Marshal.GetActiveObject(progID);
} catch {
try {
var acadProcess = new Process();
acadProcess.StartInfo.Arguments = string.Format("/nologo /p \"{0}\"", profileName);
acadProcess.StartInfo.FileName = (#acadPath);
acadProcess.Start();
while(AcApp == null)
{
try { AcApp = (AcadApplication)Marshal.GetActiveObject(progID); }
catch { }
}
} catch(COMException) {
MessageBox.Show(String.Format("Cannot create object of type \"{0}\"",progID));
}
}
try {
int i = 0;
var appState = AcApp.GetAcadState();
while (!appState.IsQuiescent)
{
if(i == 120)
{
Application.Exit();
}
// Wait .25s
Thread.Sleep(250);
i++;
}
if(AcApp != null){
// set visibility
AcApp.Visible = true;
}
} catch (COMException err) {
if(err.ErrorCode.ToString() == "-2147417846"){
Thread.Sleep(5000);
}
}
}
}
}
closeing it is as simple as
Application.Exit();
and forgive the code. its atrocious, this was one of my first methods when i just started developing...
I doubt you will be able to do this unless AutoCAD has an API that you can hook into and ask it to close the file for you.
Your c# app can only do things to the process (acad.exe) , it doesn't have access to the internal operations of that process.
Also, you shouldn't use Kill unless the process has become unresponsive and certainly not immediately after CloseMainWindow.
CloseMainWindow is the polite way to ask an application to close itself. Kill is like pulling the power lead from the socket. You aren't giving it the chance to clean up after itself and exit cleanly.
There is one other possibility - this will only work if your C# code is running on the same machine as the AutoCAD process and it is not really recommended, but, if you are really stuck and are prepared to put up with the hassle of window switching you can send key strokes to an application using the SendKeys command.
MSDN articles here:
http://msdn.microsoft.com/EN-US/library/ms171548(v=VS.110,d=hv.2).aspx
http://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.send.aspx
Using this you could send the key strokes to simulate the user using the menu commands to close the file.
To perform the closing of file, best way out is to follow the steps at this ObjectARX SDK for c# and change the following code with the below code.
[CommandMethod("CD", CommandFlags.Session)]
static public void CloseDocuments()
{
DocumentCollection docs = Application.DocumentManager;
foreach (Document doc in docs)
{
// First cancel any running command
if (doc.CommandInProgress != "" &&
doc.CommandInProgress != "CD")
{
AcadDocument oDoc =
(AcadDocument)doc.AcadDocument;
oDoc.SendCommand("\x03\x03");
}
if (doc.IsReadOnly)
{
doc.CloseAndDiscard();
}
else
{
// Activate the document, so we can check DBMOD
if (docs.MdiActiveDocument != doc)
{
docs.MdiActiveDocument = doc;
}
int isModified =
System.Convert.ToInt32(
Application.GetSystemVariable("DBMOD")
);
// No need to save if not modified
if (isModified == 0)
{
doc.CloseAndDiscard();
}
else
{
// This may create documents in strange places
doc.CloseAndSave(doc.Name);
}
}
}
I wrote a WiX custom MBA that I have been using which embeds all of the setup packages (msis, cabs and exes) I need for my installation. However I would now like to make a lightweight web bootstrapper which will download the packages that need to be installed. I thought you would get that for free with the underlying WiX bootstrapper engine, but I guess I was wrong.
I tried subscribing to the ResolveSource event to get a package's download url and download it to the local source location, but it seems like at that point it's too late in the process as my installation fails with an error "Failed to resolve source for file: " (even though the download is successful).
Sample of what I tried:
private void OnResolveSource(object sender, ResolveSourceEventArgs e)
{
string localSource = e.LocalSource;
string downloadSource = e.DownloadSource;
if (!File.Exists(localSource) && !string.IsNullOrEmpty(downloadSource))
{
try
{
using (WebClient webClient = new WebClient())
{
webClient.DownloadFile(e.DownloadSource, e.LocalSource);
}
}
catch (ArgumentNullException ex)
{
e.Result = Result.Error;
}
catch (WebException ex)
{
e.Result = Result.Error;
}
}
}
Thanks to Rob Mensching answering this on the wix-users mailing list:
Make sure your packages of URLs provided (authored is easiest but you can
programmatically set them all) then return IDDOWNLOAD from the
ResolveSource call.
I edited my code as follows:
private void OnResolveSource(object sender, ResolveSourceEventArgs e)
{
if (!File.Exists(e.LocalSource) && !string.IsNullOrEmpty(e.DownloadSource))
e.Result = Result.Download;
}
Setting the result to Result.Download instructs the bootstrapper engine to download the package. No need to try to download the file myself.