I asked this on the MEF Codeplex forum already, but I haven't gotten a response yet, so I figured I'd try StackOverflow. Here's the original post if anyone's interested (this is just a copy from it):
MEF Codeplex
"Let me first say that I'm completely new to MEF (just discovered it today) and am very happy with it so far. However, I've ran in to a problem that is very frustrating. I'm creating an app that will have a plugin architecture and the plugins will only be stored in a single DLL file (or coded into the main app). The DLL file needs to be able to be recompiled during run-time and the app should recognize this and re-load the plugins (I know this is difficult, but it's a requirement). To accomplish this I took the approach covered http://blog.maartenballiauw.be/category/MEF.aspx there (look for WebServerDirectoryCatalog). Basically the idea is to "monitor the plugins folder, copy the new/modified assemblies to the web application’s /bin folder and instruct MEF to load its exports from there." This is my code, which is probably not the correct way to do it but it's what I found in some samples around the net:
main()...
string myExecName = Assembly.GetExecutingAssembly().Location;
string myPath = System.IO.Path.GetDirectoryName(myExecName);
catalog = new AggregateCatalog();
pluginCatalog = new MyDirectoryCatalog(myPath + #"/Plugins");
catalog.Catalogs.Add(pluginCatalog);
exportContainer = new CompositionContainer(catalog);
CompositionBatch compBatch = new CompositionBatch();
compBatch.AddPart(this);
compBatch.AddPart(catalog);
exportContainer.Compose(compBatch);
and
private FileSystemWatcher fileSystemWatcher;
public DirectoryCatalog directoryCatalog;
private string path;
private string extension;
public MyDirectoryCatalog(string path)
{
Initialize(path, "*.dll", "*.dll");
}
private void Initialize(string path, string extension, string modulePattern)
{
this.path = path;
this.extension = extension;
fileSystemWatcher = new FileSystemWatcher(path, modulePattern);
fileSystemWatcher.Changed += new FileSystemEventHandler(fileSystemWatcher_Changed);
fileSystemWatcher.Created += new FileSystemEventHandler(fileSystemWatcher_Created);
fileSystemWatcher.Deleted += new FileSystemEventHandler(fileSystemWatcher_Deleted);
fileSystemWatcher.Renamed += new RenamedEventHandler(fileSystemWatcher_Renamed);
fileSystemWatcher.IncludeSubdirectories = false;
fileSystemWatcher.EnableRaisingEvents = true;
Refresh();
}
void fileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
{
RemoveFromBin(e.OldName);
Refresh();
}
void fileSystemWatcher_Deleted(object sender, FileSystemEventArgs e)
{
RemoveFromBin(e.Name);
Refresh();
}
void fileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
Refresh();
}
void fileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
Refresh();
}
private void Refresh()
{
// Determine /bin path
string binPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Plugins");
string newPath = "";
// Copy files to /bin
foreach (string file in Directory.GetFiles(path, extension, SearchOption.TopDirectoryOnly))
{
try
{
DirectoryInfo dInfo = new DirectoryInfo(binPath);
DirectoryInfo[] dirs = dInfo.GetDirectories();
int count = dirs.Count() + 1;
newPath = binPath + "/" + count;
DirectoryInfo dInfo2 = new DirectoryInfo(newPath);
if (!dInfo2.Exists)
dInfo2.Create();
File.Copy(file, System.IO.Path.Combine(newPath, System.IO.Path.GetFileName(file)), true);
}
catch
{
// Not that big deal... Blog readers will probably kill me for this bit of code :-)
}
}
// Create new directory catalog
directoryCatalog = new DirectoryCatalog(newPath, extension);
directoryCatalog.Refresh();
}
public override IQueryable<ComposablePartDefinition> Parts
{
get { return directoryCatalog.Parts; }
}
private void RemoveFromBin(string name)
{
string binPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "");
File.Delete(Path.Combine(binPath, name));
}
So all this actually works, and after the end of the code in main my IEnumerable variable is actually filled with all the plugins in the DLL (which if you follow the code is located in Plugins/1 so that I can modify the dll in the plugins folder).
So now at this point I should be able to re-compile the plugins DLL, drop it in to the Plugins folder, my FileWatcher detect that it's changed, and then copy it into folder "2" and directoryCatalog should point to the new folder. All this
actually works! The problem is, even though it seems like every thing is pointed to the right place, my IEnumerable variable is never updated with the new plugins. So close, but yet so far! Any suggestions?
I know the downsides of doing it this way, that no dll is actually getting unloaded and causing a memory leak, but it's a Windows App and will probably be started at least once a day, and the plugins are un-likely to change
that often, but it's still a requirement from the client that it does this without re-loading the app. Thanks!
Thanks for any help you all can provide, it's driving me crazy not being able to figure this out."
There is no trigger for recomposition, because your catalog implementation doesn't provide notifications. Implement INotifyComposablePartCatalogChanged to fix this.
I believe MEF can only load one version of the same assembly (I was trying on Silverlight though)
I was having a similar issue- after copying discovered Plugins to the application's directory, a DirectoryCatalog wouldn't see them, even after calling .refresh() on the DirectoryCatalog.
I found that stepping through the code resolved the issue- my best guess is that the filesystem still needs a moment after the FileSystemWatcher kicks off it's notification before MEF can scan the new assembly (perhaps to finish some obscure copy operation) and see the parts inside.
System.Threading.Thread.Sleep(1000), lame as it is, solved the issue.
Related
Using the VLC library provided by Vlc.DotNet, I have tried to implement it in a simple WPF.
I copied exactly the code from the repository, and got the NuGet online, but can't seem to make it work. I get a Directory Not Found exception straight from the load of the file on the disk.
Here is my code:
public MainWindow()
{
InitializeComponent();
VLCControl.MediaPlayer.VlcLibDirectoryNeeded += OnVlcControlNeedsLibDirectory;
}
private void OnVlcControlNeedsLibDirectory(object sender, Vlc.DotNet.Forms.VlcLibDirectoryNeededEventArgs e)
{
var currentAssembly = Assembly.GetEntryAssembly();
var currentDirectory = new FileInfo(currentAssembly.Location).DirectoryName;
if (currentDirectory == null)
return;
if (AssemblyName.GetAssemblyName(currentAssembly.Location).ProcessorArchitecture == ProcessorArchitecture.X86)
e.VlcLibDirectory = new DirectoryInfo(System.IO.Path.Combine(currentDirectory, #"..\..\..\lib\x86\"));
else
e.VlcLibDirectory = new DirectoryInfo(System.IO.Path.Combine(currentDirectory, #"..\..\..\lib\x64\"));
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var d = new Microsoft.Win32.OpenFileDialog();
d.Multiselect = false;
if (d.ShowDialog() == true)
{
Uri src = new Uri(d.FileName);
VLCControl.MediaPlayer.Play(src); //Exception here
}
}
VLCControl being the VLC control in the xaml.
By changing the VlcLibDirectory with another path where I put the libraries (for example the root of application), I get this StackTrace :
at Vlc.DotNet.Core.Interops.VlcInteropsManager..ctor(DirectoryInfo dynamicLinkLibrariesPath)
at Vlc.DotNet.Core.Interops.VlcManager..ctor(DirectoryInfo dynamicLinkLibrariesPath)
at Vlc.DotNet.Core.Interops.VlcManager.GetInstance(DirectoryInfo dynamicLinkLibrariesPath)
at Vlc.DotNet.Core.VlcMediaPlayer..ctor(DirectoryInfo vlcLibDirectory)
at Vlc.DotNet.Forms.VlcControl.EndInit()
at Vlc.DotNet.Forms.VlcControl.Play(Uri uri, String[] options)
at VLCTest.MainWindow.Button_Click(Object sender, RoutedEventArgs e) in c:\Users\ME\Documents\Visual Studio 2013\Projects\VLCTest\VLCTest\MainWindow.xaml.cs:ligne 56
The code becomes :
if(AssemblyName.GetAssemblyName(currentAssembly.Location).ProcessorArchitecture == ProcessorArchitecture.X86)
e.VlcLibDirectory = new DirectoryInfo(currentDirectory);
else
e.VlcLibDirectory = new DirectoryInfo(currentDirectory);
Thank you for your help.
The problem is definitely with your library path, though you have to debug the problem yourself in order to find the exact discrepancy between provided path and actual path.
The misunderstanding may be, which libraries are missing. You do have the Vlc.DotNet.Core.Interops.dll but your are missing the nativ libraries behind. This is the reason, why the exception occurs inside Vlc.DotNet.Core.Interops.dll when it tries to load the actual libraries.
The OnVlcControlNeedsLibDirectory function is called inside VLCControl.MediaPlayer.Play(src);, so the Path from OpenFileDialog has nothing to do with the problem.
Steps I taken to reproduce / fix:
Downloaded your project
Tested / Debugged
Exception occurred as you describe
Downloaded the libraries from Vlc.DotNet repository
Changed the paths to absolute values
Tested / Debugged again
Successfully played a music file
Another exception occured on closing (different story alltogether)
My folder layout:
Solution path:
D:\Programmierung\VLCTest-VAlphaTesting\VLCTest-VAlphaTesting\
Actual Assembly location on execute
D:\Programmierung\VLCTest-VAlphaTesting\VLCTest-VAlphaTesting\VLCTest\bin\Debug
ProcessorArchitecture: x86
Library Path:
D:\Programmierung\Vlc.DotNet-master\Vlc.DotNet-master\lib\x86
Contents of library path:
plugins (folder)
.keep (file)
libvlc.dll (file)
libvlccore.dll (file)
For testing purposes I hardcoded the library path - you may want to do that as well
if (AssemblyName.GetAssemblyName(currentAssembly.Location).ProcessorArchitecture == ProcessorArchitecture.X86)
e.VlcLibDirectory = new DirectoryInfo(#"D:\Programmierung\Vlc.DotNet-master\Vlc.DotNet-master\lib\x86");
else
e.VlcLibDirectory = new DirectoryInfo(#"D:\Programmierung\Vlc.DotNet-master\Vlc.DotNet-master\lib\x64");
I know that Directory.CreateDirectory actually creates parents, so how can I STOP this from happening? i.e. is there a mode that I can utilise like a stricter way of doing so, the reason is that I have a watch program watching the parent top tree dir and it goes beserk if Directory.CreateDirectory makes more than one dir at a time.
Is there an equivalent to Directory.CreateDirectory which will NOT make parents?
Do you understand what for you need such method? It seems like you don't want to create all folders needed to create your target folder, like: C:\this\is\your\path\TargetFolder
In this case you can just do the following:
const string path = #"C:\this\is\your\path";
if (Directory.Exists(path))
{
Directory.CreateDirectory(Path.Combine(path, "TargetDirectory"));
}
If you have other purpose for that method, please help us to understand which one
List<string> missingDirectories = null;
private void MakeParents(string path)
{
missingDirectories = new List<string>();
missingDirectories.Add(path);
parentDir(path);
missingDirectories = missingDirectories.OrderBy(x => x.Length).ToList<string>();
foreach (string directory in missingDirectories)
{
Directory.CreateDirectory(directory);
}
}
private void parentDir(string path)
{
string newPath = path.Substring(0, path.LastIndexOf(Path.DirectorySeparatorChar));
if (!Directory.Exists(newPath))
{
missingDirectories.Add(newPath);
parentDir(newPath);
}
}
this does it, the issue is that if you want to "gently" roll up the paths one dir at a time making them, something like this is the only way you can do it :/
In the following sample app I create a new AppDomain and I execute it with shadow copy enabled. From the new AppDomain I then try to delete (replace) the original main exe. However I get an "access is denied error". Interestingly, after launching the program, from Windows Explorer it is possible to rename the main exe (but not to delete it).
Can shadow copy work for runtime overwriting of the main exe?
static void Main(string[] args)
{
// enable comments if you wanna try to overwrite the original exe (with a
// copy of itself made in the default AppDomain) instead of deleting it
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
{
Console.WriteLine("I'm the default domain");
System.Reflection.Assembly currentAssembly = System.Reflection.Assembly.GetExecutingAssembly();
string startupPath = currentAssembly.Location;
//if (!File.Exists(startupPath + ".copy"))
// File.Copy(startupPath, startupPath + ".copy");
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = Path.GetFileName(startupPath);
setup.ShadowCopyFiles = "true";
AppDomain domain = AppDomain.CreateDomain(setup.ApplicationName, AppDomain.CurrentDomain.Evidence, setup);
domain.SetData("APPPATH", startupPath);
domain.ExecuteAssembly(setup.ApplicationName, args);
return;
}
Console.WriteLine("I'm the created domain");
Console.WriteLine("Replacing main exe. Press any key to continue");
Console.ReadLine();
string mainExePath = (string)AppDomain.CurrentDomain.GetData("APPPATH");
//string copyPath = mainExePath + ".copy";
try
{
File.Delete(mainExePath );
//File.Copy(copyPath, mainExePath );
}
catch (Exception ex)
{
Console.WriteLine("Error! " + ex.Message);
Console.ReadLine();
return;
}
Console.WriteLine("Succesfull!");
Console.ReadLine();
}
You can achive self updating application within a single application with multiple AppDomains. The trick is move application executable to a temporary directory and copy back to your directory, then load the copied executable in a new AppDomain.
static class Program
{
private const string DELETED_FILES_SUBFOLDER = "__delete";
/// <summary>
/// The main entry point for the application.
/// </summary>
[LoaderOptimization(LoaderOptimization.MultiDomainHost)]
[STAThread]
static int Main()
{
// Check if shadow copying is already enabled
if (AppDomain.CurrentDomain.IsDefaultAppDomain())
{
// Get the startup path.
string assemblyPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string assemblyDirectory = Path.GetDirectoryName(assemblyPath);
string assemblyFile = Path.GetFileName(assemblyPath);
// Check deleted files folders existance
string deletionDirectory = Path.Combine(assemblyDirectory, DELETED_FILES_SUBFOLDER);
if (Directory.Exists(deletionDirectory))
{
// Delete old files from this folder
foreach (var oldFile in Directory.EnumerateFiles(deletionDirectory, String.Format("{0}_*{1}", Path.GetFileNameWithoutExtension(assemblyFile), Path.GetExtension(assemblyFile))))
{
File.Delete(Path.Combine(deletionDirectory, oldFile));
}
}
else
{
Directory.CreateDirectory(deletionDirectory);
}
// Move the current assembly to the deletion folder.
string movedFileName = String.Format("{0}_{1:yyyyMMddHHmmss}{2}", Path.GetFileNameWithoutExtension(assemblyFile), DateTime.Now, Path.GetExtension(assemblyFile));
string movedFilePath = Path.Combine(assemblyDirectory, DELETED_FILES_SUBFOLDER, movedFileName);
File.Move(assemblyPath, movedFilePath);
// Copy the file back
File.Copy(movedFilePath, assemblyPath);
bool reload = true;
while (reload)
{
// Create the setup for the new domain
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationName = assemblyFile;
setup.ShadowCopyFiles = true.ToString().ToLowerInvariant();
// Create an application domain. Run
AppDomain domain = AppDomain.CreateDomain(setup.ApplicationName, AppDomain.CurrentDomain.Evidence, setup);
// Start application by executing the assembly.
int exitCode = domain.ExecuteAssembly(setup.ApplicationName);
reload = !(exitCode == 0);
AppDomain.Unload(domain);
}
return 2;
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
MainForm mainForm = new MainForm();
Application.Run(mainForm);
return mainForm.ExitCode;
}
}
}
As it's an interesting use case of MEF, I've bashed out a quick demo of how to hot-swap running code in C#. This is very simple and leaves out a lot of edge cases.
https://github.com/i-e-b/MefExperiments
Notable classes:
src/PluginWatcher/PluginWatcher.cs -- watches a folder for new implementations of a contract
src/HotSwap.Contracts/IHotSwap.cs -- minimal base contract for a hot-swap
src/HotSwapDemo.App/Program.cs -- Does the live code swap
This doesn't lock the task .dlls in the Plugins folder, so you can delete old versions once new ones are deployed.
Hope that helps.
You asked specifically to use ShadowCopy for the update process. If that (and why would it be?) not a fixed requirement, these ones were real eye openers for me:
https://visualstudiomagazine.com/articles/2017/12/15/replace-running-app.aspx
https://www.codeproject.com/Articles/731954/Simple-Auto-Update-Let-your-application-update-i
It comes down to you renaming the target file (which is allowed, even when it is locked since it is running) and then moving/copying the desired file to the now freed destination.
The vs-magazine article is very detailed, including some nifty tricks like finding out if a file is in use by current application (though only for exe, for .dlls and others one has to come up with a solution).
Please could a C# expert help with a simple problem which for some strange reason I just can't seem to work out? I'm trying to move multiple sub folders in the current directory to a new directory and keep the subfolder name, see below:
public string currentDirectory = System.Environment.GetEnvironmentVariable("LOCALAPPDATA") + #"\Test\CurrentFolder\";
public string newDirectory = System.Environment.GetEnvironmentVariable("LOCALAPPDATA") + #"\Test\NewFolder\";
private void btnMoveFolder_Click(object sender, RoutedEventArgs e)
{
string[] subdirectoryEntries = Directory.GetDirectories(currentDirectory);
try
{
foreach (string subCurrentDirectory in subdirectoryEntries)
{
Directory.Move(subCurrentDirectory, newDirectory);
}
}
catch (System.Exception)
{
Log("Problem with moving the directory.");
}
}
At the moment, I only seem to be able to move one folder instead of all of the them.
Any help would be greatly appreciated!
I suppose you want this:
Directory.Move(subCurrentDirectory,
Path.Combine(
newDirectory,
Path.GetFileName(subCurrentDirectory)));
Try this out:
DirectoryInfo subfolder = new DirectoryInfo(#"OLDPATH\DirectoryToMove");
subfolder.MoveTo(#"NEWPATH\DirectoryToMove");
Just make sure you include the name of the directory to move in both the old AND new filepaths.
In general DirectoryInfo and FileInfo are much more useful than Directory and File in most situations.
Let's say that I have a few applications in a folder (each application has subfolders where plugins can be located):
Clients
Application A
...
Application B
...
Application C
...
...
Some files in these applications have an Export-attribute applied, others don't. Now, I want to be able to load these plugins in some of these applications. Is there a proper way to let MEF search recursively in every subfolder of a specified folder?
No, you will need to recurse through the directories yourself creating a DirectoryCatalog for each. Then, combine all of the DirectoryCatalogs with an AggregateCatalog to create the container.
Another way is to get all the DLL files under a specified directory (recursively) and load them one by one using the assembly catalog.`
var catalog = new AggregateCatalog();
var files = Directory.GetFiles("Parent Directory", "*.dll", SearchOption.AllDirectories);
foreach (var dllFile in files)
{
try
{
var assembly = Assembly.LoadFile(dllFile);
var assemblyCatalog = new AssemblyCatalog(assembly);
catalog.Catalogs.Add(assemblyCatalog);
}
catch (Exception e)
{
// this happens if the given dll file is not a .NET framework file or corrupted.
}
}
There's a MEFContrib project available that has a RecursiveDirectoryCatalog for just this purpose...
https://www.nuget.org/packages/MefContrib/
I created an implementation based in Nicholas Blumhardt answer, I hope that code helps others in future.
private void RecursivedMefPluginLoader(AggregateCatalog catalog, string path)
{
Queue<string> directories = new Queue<string>();
directories.Enqueue(path);
while (directories.Count > 0)
{
var directory = directories.Dequeue();
//Load plugins in this folder
var directoryCatalog = new DirectoryCatalog(directory);
catalog.Catalogs.Add(directoryCatalog);
//Add subDirectories to the queue
var subDirectories = Directory.GetDirectories(directory);
foreach (string subDirectory in subDirectories)
{
directories.Enqueue(subDirectory);
}
}
}