操作文件会阻止它们

本文关键字:文件 操作 | 更新日期: 2023-09-27 18:14:02

我正在编写一个使用MEF加载程序集的WinForms程序。这些程序集与可执行文件不在同一个文件夹中。由于我需要执行一些文件维护,在加载实际的WinForm之前,我在文件program. cs中实现了一些代码,因此这些文件(即使是程序集)不会被程序加载(或者不应该被程序加载)。

我正在执行两个操作:—将文件夹从一个位置移动到另一个位置-从存档中解压缩文件并覆盖移动文件夹中的dll文件(如果存档中的文件比移动的文件更新)

问题是移动文件夹后,其中的文件被锁定,不能被覆盖。我还尝试过一个一个地移动文件,在移动完成后处理它们。

谁能告诉我为什么文件被阻止了,我怎么才能避免呢?

感谢
private static void InitializePluginsFolder()
    {
        if (!Directory.Exists(Paths.PluginsPath))
        {
            Directory.CreateDirectory(Paths.PluginsPath);
        }
        // Find archive that contains plugins to deploy
        var assembly = Assembly.GetExecutingAssembly();
        if (assembly.Location == null)
        {
            throw new NullReferenceException("Executing assembly is null!");
        }
        var currentDirectory = new FileInfo(assembly.Location).DirectoryName;
        if (currentDirectory == null)
        {
            throw new NullReferenceException("Current folder is null!");
        }
        // Check if previous installation contains a "Plugins" folder
        var currentPluginsPath = Path.Combine(currentDirectory, "Plugins");
        if (Directory.Exists(currentPluginsPath))
        {
            foreach (FileInfo fi in new DirectoryInfo(currentPluginsPath).GetFiles())
            {
                using (FileStream sourceStream = new FileStream(fi.FullName, FileMode.Open))
                {
                    using (FileStream destStream = new FileStream(Path.Combine(Paths.PluginsPath, fi.Name), FileMode.Create))
                    {
                        destStream.Lock(0, sourceStream.Length);
                        sourceStream.CopyTo(destStream);
                    }
                }
            }
            Directory.Delete(currentPluginsPath, true);
        }
        // Then updates plugins with latest version of plugins (zipped)
        var pluginsZipFilePath = Path.Combine(currentDirectory, "Plugins.zip");
        // Extract content of plugins archive to a temporary folder
        var tempPath = string.Format("{0}_Temp", Paths.PluginsPath);
        if (Directory.Exists(tempPath))
        {
            Directory.Delete(tempPath, true);
        }
        ZipFile.ExtractToDirectory(pluginsZipFilePath, tempPath);
        // Moves all plugins to appropriate folder if version is greater
        // to the version in place
        foreach (var fi in new DirectoryInfo(tempPath).GetFiles())
        {
            if (fi.Extension.ToLower() != ".dll")
            {
                continue;
            }
            var targetFile = Path.Combine(Paths.PluginsPath, fi.Name);
            if (File.Exists(targetFile))
            {
                if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
                {
                    // If version to deploy is newer than current version
                    // Delete current version and copy the new one
                    // FAILS HERE
                    File.Copy(fi.FullName, targetFile, true);
                }
            }
            else
            {
                File.Move(fi.FullName, targetFile);
            }
        }
        // Delete temporary folder
        Directory.Delete(tempPath, true);
    }

操作文件会阻止它们

检查这部分代码中使用的GetAssemblyVersion()方法的实现:

if (File.Exists(targetFile))
{
  if (fi.GetAssemblyVersion() > new FileInfo(targetFile).GetAssemblyVersion())
  {
    // If version to deploy is newer than current version
    // Delete current version and copy the new one
    // FAILS HERE
    File.Copy(fi.FullName, targetFile, true);
  }
}

fi变量具有FileInfo类型,GetAssemblyVersion()看起来像一个扩展方法。您应该检查如何从文件检索程序集版本。如果此方法加载程序集,则还应卸载该程序集以释放文件。

单独的AppDomain是有帮助的,如果你需要加载程序集,完成工作,然后卸载它。下面是GetAssemblyVersion方法的实现:

public static Version GetAssemblyVersion(this FileInfo fi)
{
  AppDomain checkFileDomain = AppDomain.CreateDomain("DomainToCheckFileVersion");
  Assembly assembly = checkFileDomain.Load(new AssemblyName {CodeBase = fi.FullName});
  Version fileVersion = assembly.GetName().Version;
  AppDomain.Unload(checkFileDomain);
  return fileVersion;
}

以下GetAssemblyVersion()的实现可以在不将程序集加载到AppDomain的情况下检索程序集版本。感谢@usterdev的提示。它还允许您获取没有程序集引用的版本resolve:

public static Version GetAssemblyVersion(this FileInfo fi)
{
  return AssemblyName.GetAssemblyName(fi.FullName).Version;
}

您必须确保您没有将Assembly加载到您的域以从中获取版本,否则文件将被锁定。

通过使用AssemblyName.GetAssemblyName()静态方法(参见MSDN),程序集文件被加载,版本被读取,然后卸载,但不添加到您的域

这里是FileInfo的扩展:

public static Version GetAssemblyVersion(this FileInfo fi)
{
    AssemblyName an = AssemblyName.GetAssemblyName(fi.FullName);
    return an.Version;
}

下面的语句锁定文件

destStream.Lock(0, sourceStream.Length);

但是在那之后你还没有解锁文件。

我会开始检查你的程序是否实际上已经加载了程序集。

两个建议:

1 -在调用InitializePluginsFolder

之前调用这样的方法
static void DumpLoadedAssemblies()
{
    var ads = AppDomain.CurrentDomain.GetAssemblies();
    Console.WriteLine(ads.Length);
    foreach (var ad in ads)
    {
        Console.WriteLine(ad.FullName);
        // maybe this can be helpful as well
        foreach (var f in ad.GetFiles())
            Console.WriteLine(f.Name);
        Console.WriteLine("*******");
    }
}

2 -在Main的第一行,注册AssemblyLoad事件并在事件处理程序

中转储加载的程序集
public static void Main()
{
    AppDomain.CurrentDomain.AssemblyLoad += OnAssemlyLoad;
    ...
}
static void OnAssemlyLoad(object sender, AssemblyLoadEventArgs args) 
{
    Console.WriteLine("Assembly Loaded: " + args.LoadedAssembly.FullName);
}

您肯定使用AssemblyName加载程序集。不幸的是,. net没有常规的方法在不加载程序集的情况下检查程序集元数据。要避免这种情况,可以:

  1. 加载组件在分离的AppDomain尼基塔建议,我可以添加:加载它与ReflectionOnlyLoad
  2. 或使用Mono获取汇编版本。Cecil库作为Reflector做
  3. 只是为了完整:实际上你可以在不锁定汇编文件的情况下将汇编加载到同一个AppDomain中,分为两个阶段:将文件内容读入byte[]和使用assembly。加载(byte[] rawAssembly),但这种方式有严重的"加载上下文"问题,你将如何处理几个加载的程序集:)