2013-05-01 102 views
5

我在C#.NET 4.0 Windows Service应用程序中使用ILMerge和Quartz.NET。该应用运行良好,但没有使用ILMerge,但现在我们已经接近发货版本,我想将所有DLL合并成一个可执行文件。可执行文件失败,出现奇怪异常

问题是,ILMerge似乎工作正常,但是当我运行的综合可执行文件,它抛出此异常:

未处理的异常:Quartz.SchedulerException:线程池型“Quartz.Simpl.SimpleThreadPool”可能不被实例化。 ---> System.InvalidCastException:无法将类型为“Quartz.Simpl.SimpleThreadPool”的对象转换为键入“Quartz.Spi.IThreadPool”。
在Quartz.Util.ObjectUtils.InstantiateType [T](类型型):在Quartz.Impl.StdSchedulerFactory.Instantiate(行0
):0行
---内部异常堆栈跟踪结束 - -
在Quartz.Impl.StdSchedulerFactory.Instantiate()中:在0线
在Quartz.Impl.StdSchedulerFactory.GetScheduler():0行

有没有人有任何想法,这是为什么?我已经浪费了4个小时了,我无法弄清楚。如果我没有与ILMerge结合,那么一切运行良好(与Quartz.dll和Common.Logging.dll在同一目录中)。

我敢肯定有人必须尝试包装Quartz.net之前,像这样的任何想法?

+0

这是您第一次尝试将它与ILMerge结合吗?还是在最近的变化之前起作用? – 2013-05-01 18:13:36

+1

我第一次尝试使用ILMerge,运行它,不再工作。想象它一定是ILMerge,尝试了内部标志,没有改变任何东西。删除ILMerge,编译通常(像我以前试过这样),所有的作品(如果DLL在同一个目录中)。 – 2013-05-01 18:23:33

+0

ILMerge无法处理的其中一件事是从外部程序集中进行类型加载(这可能是基于对堆栈跟踪进行扫描的情况)。也许看看也可以找到其中一个替代方法[这里](http://chrisghardwick.blogspot.nl/2012/01/ilmerge-getting-started-merging-and.html) – rene 2013-05-01 18:24:18

回答

1

免责声明:我根本不知道Quartz.NET,尽管我花了一些时间在ILMerge上挣扎。当我终于明白其局限性时,我停止使用它。

ILMerge'd应用程序往往会遇到包含“反射”一词的所有问题。 我可以猜测(我从来没有使用过Quartz.NET)一些类是使用反射解析并由配置文件驱动的。

类不仅通过它的名称(带有名称空间),而且通过汇编它来自(不幸的是,它不会显示在异常消息中)。因此,假设您已经(在ILMerging之前)有两个程序集A(对于您的应用程序)和Q(对于Quartz.NET)。 组件'A'引用程序集'Q'并且正在使用实施'Q:QIntf'的'Q:QClass'类。 合并后,这些类变成'A:QClass'和'A:QIntf'(它们从程序集Q移到A),代码中的所有引用都被替换为使用那些(完全)新的类/接口,所以“答:QClass“现在正在实施”A:QIntf“。 但是,它没有改变任何可能仍然引用“Q:QClass”的配置文件/嵌入字符串。

因此,当应用程序正在读取那些未更新的配置文件时,它仍会加载“Q:QClass”(为什么它会发现它是一个不同的问题,也许你在当前文件夹中保留了“Q”组件或者它可能位于GAC - 见1)。 无论如何,“Q:QClass”并不实现“A:QIntf”,即使它们是二进制相同的,它仍然实现“Q:QIntf” - 所以你不能将Q:QClass转换为A:QIntf。

不理想但工作的解决方案是“嵌入”程序集,而不是“合并”它们。我写了一个开源工具(嵌入而不是合并),但它与这个问题没有关系。所以如果你决定嵌入只是问我。

  1. 您可以通过在您的PC上删除(隐藏,无论适用于您)Q.dll的每个实例来测试它。如果我是对的,现在例外应该说'FileNotFound'。
+0

我最终没有使用ILMERGE,它只是不起作用,似乎需要更多的工作,而不是仅仅在部署运行中减少文件文件的好处。我也试过嵌入,动态地加载程序集,这帮助我更多地理解幕后的东西,但它不适用于它。我在最早的时候加载了程序集,但是有很多静态类访问它们,并且需要它们重新加载,但即使是静态构造函数也太迟了,很奇怪。总是失败。你的答案有最多的背景,所以谢谢! – 2013-05-08 11:04:04

+0

你是什么意思的'在最早点加载程序集'?我已经成功地使用Jeffrey Richter在他的博客中描述的方法(请参阅我之前的评论)。这是非常直接的,唯一值得注意的是,你需要确保你的AssemblyResolve事件在其他任何事情之前被初始化,在我的情况下,这意味着创建一个新的入口点来设置AssemblyResolve事件,然后调用旧的入口点。 – sgmoore 2013-05-08 12:43:00

+0

@RomanMittermayr:我也很疑惑“最早”的一点。虽然静态构造函数相当早,但earlies点是模块初始化器。不幸的是,模块初始化器不能用C#修改,但可以通过一些较小的IL操作进行修改(请参阅:https://github.com/Fody/ModuleInit)。当你说“重新加载”时,我感觉Quartz.NET正在创建单独的AppDomain。事实上,这可能是一个问题,如果您无法访问它们并将AssemblyResolver注入到它们中。你也可以尝试https://libz.codeplex.com/。 – 2013-05-08 13:20:04

1

您可以尝试创建自己的ISchedulerFactory,并避免使用反射来加载所有类型。 StdSchedulerFactory使用此代码来创建线程池。这是您的错误发生,将开始寻找更改的地方:

 Type tpType = loadHelper.LoadType(cfg.GetStringProperty(PropertyThreadPoolType)) ?? typeof(SimpleThreadPool); 

     try 
     { 
      tp = ObjectUtils.InstantiateType<IThreadPool>(tpType); 
     } 
     catch (Exception e) 
     { 
      initException = new SchedulerException("ThreadPool type '{0}' could not be instantiated.".FormatInvariant(tpType), e); 
      throw initException; 
     } 

被调用的方法ObjectUtils.InstantiateType这是一个和最后一行是一个扔你的异常:

public static T InstantiateType<T>(Type type) 
    { 
     if (type == null) 
     { 
      throw new ArgumentNullException("type", "Cannot instantiate null"); 
     } 
     ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes); 
     if (ci == null) 
     { 
      throw new ArgumentException("Cannot instantiate type which has no empty constructor", type.Name); 
     } 
     return (T) ci.Invoke(new object[0]); 
    } 

在工厂的这一部分之后,数据源使用相同的模式加载,然后作业本身也被动态加载,这意味着您还必须编写自己的JobFactory。由于Quartz.Net会在运行时动态地加载一堆零碎的东西,这意味着您最终可能会重写相当多的东西。

相关问题