确保早期加载(某些)引用的程序集

时间:2012-01-11 22:54:45

标签: c# assemblies

我有一个包含多个项目的解决方案。

有一个项目引用了所有其他项目,这些引用项目之间也可能存在引用。

我们假设项目A为主要项目。 项目B有一个MyType类。 项目C具有MyType的扩展方法。

每个程序集都有一个属性。它允许我确定程序集是 mine 还是外部库(可能它不是最好的方法,但它可以工作)。

A的某个时刻,我有一种机制,它使用反射来查找MyType的所有扩展方法。为此,我处理事件AppDomain.AssemblyLoad以创建我加载的程序集的列表(标有属性)。然后我可以扫描组件以获得适当的扩展方法。

不幸的是,此时C未加载,因为C没有调用任何内容。因此,找不到扩展名。

我尝试使用Assembly.GetExecutingAssembly().GetReferencedAssemblies(),但我认为它只返回已加载的程序集,这里没有运气。

我在A的最终解决方案是:

public static void Main(string[] args)
{
    TypeFromProjectC temp;
    //further code...
}

它起作用,因为程序集在temp声明上加载,但是......

我可以尝试按路径/名称加载程序集,但我们假设它不是一个选项。我想创建一个通用的解决方案,每次添加另一个引用时都不需要更改。实际上,这就是我为程序集添加此属性的原因。

GetReferencedAssemblies()之类的东西会很棒。如果我可以在调用Load()之前检查程序集的属性,那将是最好的,但这不是必要的。 (它是否有意义?当我现在想到它时,我猜它必须在属性可用之前加载,但也许某些元信息可以做?)

这是我第一次玩具有相当复杂结构的多个装配体,所以我可能不知道它是如何工作的,我的问题显然是愚蠢的。

我希望不是;]

5 个答案:

答案 0 :(得分:3)

我回答之前的一些注释和评论:

1)您可以引用ClassLibrary1和ClassLibrary2,并从ClassLibrary1实例化Class1。在这种情况下,编译器会抛弃对ClassLibrary2的引用,Assembly.GetReferencedAssemblies()会返回实际优化的引用列表。当您使用ClassLibrary2中的任何类型时,编译器也会尊重该引用。制作强大参考的最佳方法是:

static class StrongReferencesLoader
{
    public static void Load()
    {
        var ref1 = typeof(Class1); // ClassLibrary1.dll
        var ref2 = typeof(Class2); // ClassLibrary2.dll
    }
}

这允许引用静态类,这允许通过调用此方法强制加载每个强引用库。

2)您确实使用了AppDomain.AssemblyLoad事件。考虑从您的入口点订阅。要小心,因为如果你的入口点产生其他东西,这可能会导致在订阅之前加载程序集。

class Program
{
    static void Main()
    {
        AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
        new Class1();
    }

    static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
    {
        Console.WriteLine("! " + args.LoadedAssembly.FullName);
    }
}

在此示例中,您将无法获得有关加载ClassLibrary1 的输出,因为在执行main方法之前已执行了库的加载。运行时加载执行指定方法所需的所有库。如果我们将这个调用提取到另一个方法 - 一切都会变好。

class Program
{
    static void Main()
    {
        AppDomain.CurrentDomain.AssemblyLoad += CurrentDomain_AssemblyLoad;
        RealMain();
    }

    static void RealMain()
    {
        new Class1();
    }

    static void CurrentDomain_AssemblyLoad(object sender, AssemblyLoadEventArgs args)
    {
        Console.WriteLine("! " + args.LoadedAssembly.FullName);
    }
}

在这种情况下,Main执行没有问题,并立即订阅。之后,由于调用RealMain而执行的程序集加载包含一个引用ClassLibrary1的字节代码,并且您的事件处理程序可以正常工作。

3)请注意,您可以随时按AppDomain.CurrentDomain.GetAssemblies()

列出所有已加载的程序集

我不完全明白你的问题。这只是解决方案范围的分析,或未来插件的通用想法,由其他任何人创建?解决方案范围的分析可以在编译时或运行时解决,但开放插件系统只是运行时。

运行时解决方案

首先,您应该明白,无法检测每个插件。比方说,Class1有扩展,由一些正文编写为另一个应用程序并安装在另一个目录中:

一些伪结构:

\GAC\ClassLibrary1.dll
\ProgramFiles\MyApp\ConsoleApplication1.exe // our entry point
\ProgramFiles\SomeApp\ClassLibrary3.dll // some library from another solution, that references shared ClassLibrary1

您可以检测GAC,AppDomainBase路径,RelativeSearchPath(由内置程序集解析程序完成的所有内容),任何自定义硬编码路径以及已加载程序集AppDomain.CurrentDomain.GetAssemblies().Select(x => Path.GetDirectoryName(x.Location)).Distinct();的路径中的插件 您可以递归地分析这些路径。 但除非你扫描整台计算机,否则你永远不会找到类似\ ProgramFiles \ SomeApp \ ClassLibrary3.dll的路径。

我希望,深入搜索已加载的装配位置可以解决您的问题。

请注意,您可以使用Mono.Cecil,但为此目的使用Assembly.ReflectionOnlyLoad很方便。您可以使用Assembly.ReflectionOnlyLoadFrom按文件名加载,并且可以通过AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve事件帮助解析其他位置的程序集。但您不能将其用于自定义属性分析。在这种情况下,需要一些hack,比如特殊的类名(static class MySuperMark{}),可以轻松检查存在。使用每个插件实现所需的特殊界面(但它不是那么接近你的情况)并不那么容易。

编译时间解决方案

仅适用于解决方案范围的分析。 既然你已经注意到了,

  

我想创建一个通用解决方案,每次添加其他引用时都不需要更改。

可能您正在寻找解决方案范围内的答案。我可以在这里建议一些元编程。通常我是通过metacreator http://code.google.com/p/metacreator/来解决这个问题的。您可以在编译ConsoleApplication1之前运行一些代码。例如,您可以通过元代码自动创建一个为每个引用的dll保存强引用的类,您可以运行此类来确保引用程序集的早期加载。这个类将自动实现每个编译。

请描述更多细节,我可以更深入地描述哪种情况。

答案 1 :(得分:2)

在我看来,Mono.Cecil的装配分析在这里很有用。例如,您可以枚举引用(当然没有通过编译器从项目中删除的引用),而无需通过以下方式加载给定的程序集及其任何引用:

var assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly("protobuf-net.dll");
foreach(var module in assembly.Modules)
{
    foreach(var reference in module.AssemblyReferences)
    {
    Console.WriteLine(reference.FullName);
    }
}

此类代码打印:

mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Runtime.Serialization, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089

然而,IMO更好的想法是扫描目录,可能包含插件并使用Cecil检查它们的属性,因此无需加载(因为您当时不知道是否要加载它们):

var assembly = Mono.Cecil.AssemblyDefinition.ReadAssembly("protobuf-net.dll");
foreach(var attribute in assembly.CustomAttributes)
{
    Console.WriteLine(attribute.AttributeType.FullName);
}

有几个选项用于比较属性(从加载的程序集中的类)到Cecil的属性描述,完整类型名称是其中之一。

答案 2 :(得分:1)

Assembly.GetReferencedAssemblies()为您提供了代码实际使用的非常可靠的程序集列表。你将不得不通过列表进行递归,这样你就可以找到所有这些,而不仅仅是你主要使用的那些。

所获得的内容必然与您使用Project + Add Reference添加的程序集相匹配。编译器确保仅在代码实际使用的元数据中包含引用的程序集。这可能是TypeFromProjectC temp;工作的原因。

如果周围有其他程序集应该加载但是通过GetReferencedAssemblies()递归找不到,那么你使用Assembly.Load / From()显式加载它们。实际上,使用此类程序集中的类型也必须手动完成,使用Activator.CreateInstance()创建一个对象,使用Reflection来调用方法等。

您的问题中没有证据表明您确实打算使用Reflection,尤其是因为您提到了扩展方法。实际在代码中使用的扩展方法将始终生成通过ReferencedAssemblies()发现的程序集。你也不清楚为什么要这样做。即时编译器已经非常擅长按需加载程序集,当它及时编译代码时这样做。这几乎总是令人满意的,因为它会通过强制CLR将所有组件找到用户不会注意到的一百针刺而变得很长的启动延迟。懒惰的程序集加载是一个非常重要的功能,而不是错误。

答案 3 :(得分:0)

如果没有对程序集的静态编译时引用(直接或间接),则这样的程序集不会被加载,因此对于枚举逻辑是不可见的。但是,可以在运行时动态加载所有程序集,例如特定目录。

另一方面,我不相信有一个系统全局的程序集字典可能有一个特定的自定义属性,这似乎是你要求的。

答案 4 :(得分:0)

您正在寻找您的ext方法,因此可以在以下位置找到引用的程序集:

  • 执行asm的子目录 - 简单实现
  • 被调用的asms的子目录 - 使用程序集加载的事件很容易实现
  • mainfest - 你必须弄明白这一点,但我不认为这很难。只需解析xml并读取元素
  • GAC - 如果您在GAC注册自己的权限。可以在此处找到解决方案:Extract assembly from GAC