2013-05-01 20 views
5

Sto utilizzando ILMerge e Quartz.NET in un'applicazione di servizio Windows .NET 4.0 C#. L'app funziona bene senza usare ILMerge, ma ora che ci stiamo avvicinando alla versione di spedizione, volevo combinare tutte le DLL in un singolo eseguibile.Eseguibile non riuscito con l'eccezione strana

Il problema è, che ILMerge sembra funzionare bene, ma quando ho eseguito il file eseguibile combinato, si genera questa eccezione:

Eccezione non gestita: Quartz.SchedulerException: Tipo ThreadPool 'Quartz.Simpl.SimpleThreadPool' possibile non essere istanziato. ---> System.InvalidCastException: impossibile eseguire il cast dell'oggetto di tipo 'Quartz.Simpl.SimpleThreadPool' per digitare 'Quartz.Spi.IThreadPool'.
a Quartz.Util.ObjectUtils.InstantiateType [T] (tipo Type) in: Linea 0
a Quartz.Impl.StdSchedulerFactory.Instantiate() in: Linea 0
--- Fine dell'analisi dello stack eccezione interna - -
a Quartz.Impl.StdSchedulerFactory.Instantiate() in: linea 0
a Quartz.Impl.StdSchedulerFactory.GetScheduler() in: linea 0

qualcuno ha qualche idea del perché questo è? Ho già perso più di 4 ore e non riesco a capirlo. Se non combino con ILMerge, allora tutto funziona correttamente (con Quartz.dll e Common.Logging.dll nella stessa directory).

Sono sicuro che qualcuno deve aver provato a confezionare Quartz.net in questo modo prima, qualche idea?

+0

È la prima volta che hai provato a combinarlo con ILMerge? O ha funzionato prima delle recenti modifiche? –

+1

La prima volta che ho provato a utilizzare ILMerge, l'ho eseguito, non funzionava più. Pensato che doveva essere ILMerge, provato la bandiera internalizzata, non ha cambiato nulla. Rimuovi ILMerge, compilato normalmente (come facevo prima di provare), tutto funziona (se le DLL sono nella stessa directory). –

+0

Una delle cose che ILMerge non gestisce è il caricamento di tipo da un assieme esterno (che potrebbe essere il caso basato su uno sguardo sullo stacktrace). Forse guarda anche a una delle alternative trovate [qui] (http://chrisghardwick.blogspot.nl/2012/01/ilmerge-getting-started-merging-and.html) – rene

risposta

1

Disclaimer: Non conosco affatto Quartz.NET, anche se ho trascorso un po 'di tempo a lottare con ILMerge. Quando ho finalmente capito i suoi limiti ... ho smesso di usarlo.

L'applicazione di ILMerge tende ad avere problemi con tutto ciò che contiene la parola "riflessione". Posso indovinare (non ho mai usato Quartz.NET) che alcune classi siano state risolte usando il reflection e guidate dai file di configurazione.

La classe non viene identificata solo dal nome (con namespace) ma anche dall'assembly da cui proviene (sfortunatamente non viene visualizzato nel messaggio di eccezione). Quindi, supponiamo di avere (prima di ILMerging) due assembly A (per l'applicazione) e Q (per Quartz.NET). L'assembly 'A' stava facendo riferimento all'assembly 'Q' e stava usando una classe 'Q: QClass' che stava implementando 'Q: QIntf'. Dopo la fusione, tali classi diventano "A: QClass" e "A: QIntf" (sono state spostate dall'assembly da Q ad A) e tutti i riferimenti nel codice sono stati sostituiti per utilizzare queste (completamente) nuove classi/interfacce, quindi " A: QClass "sta implementando" A: QIntf "ora. Tuttavia, non ha modificato alcun file di configurazione/stringhe incorporate che potrebbero ancora fare riferimento a "Q: QClass".

Così quando l'applicazione legge quei file di configurazione non aggiornati carica ancora "Q: QClass" (perché può trovare una domanda diversa, forse hai lasciato l'assembly 'Q' nella cartella corrente o forse è in GAC - vedi 1). In ogni caso, "Q: QClass" NON implementa "A: QIntf", implementa ancora "Q: QIntf" anche se sono binari identici - quindi non puoi lanciare "Q: QClass" su "A: QIntf".

La soluzione non ideale, ma di lavoro, è di "incorporare" gli assembly invece di "unirli". Ho scritto uno strumento open source che lo fa (incorporare invece di fondere) ma non è correlato a questa domanda. Quindi se decidi di incorporare basta chiedere a me.

  1. Puoi testarlo rimuovendo (nascondendo, qualunque cosa funzioni per te) ogni singola istanza di Q.dll sul tuo PC. Se ho ragione, l'eccezione dovrebbe dire ora 'FileNotFound'.
+0

Ho finito per non utilizzare ILMERGE, semplicemente non funzionava e sembrava che richiedesse molto più lavoro rispetto al vantaggio di avere solo un file di file in meno nelle esecuzioni di distribuzione. Ho anche provato a incorporare, caricando gli assembly in modo dinamico, il che mi ha aiutato a capire molto di più sul dietro le quinte, ma non avrebbe funzionato per niente. Ho caricato gli assembly al più presto, ma avevo molte classi statiche che li accedevano e loro avevano bisogno di essere ricaricati, ma anche il costruttore statico era troppo tardi, stranamente. sempre fallito. la tua risposta aveva più background, quindi grazie! –

+0

Cosa intendi con "caricamento degli assiemi al primo punto"? Ho usato con successo il metodo descritto da Jeffrey Richter nel suo blog (vedi il mio precedente commento). Era molto semplice e l'unica cosa da tenere a mente è che devi assicurarti che l'evento AssemblyResolve sia inizializzato prima di qualsiasi altra cosa, che nel mio caso significava creare un nuovo punto di ingresso che configurasse l'evento AssemblyResolve e poi chiamasse il vecchio punto di ingresso. – sgmoore

+0

@RomanMittermayr: Sono anche perplesso "primo punto". Sebbene il costruttore statico sia piuttosto precoce, il punto di partenza è l'inizializzatore del modulo. Sfortunatamente, l'inizializzatore del modulo non può essere modificato con C# ma può essere modificato con alcune manipolazioni IL minori (vedere: https://github.com/Fody/ModuleInit). Quando si dice "ricaricato", ho la sensazione che Quartz.NET stia creando AppDomain separati. Questo potrebbe essere, in effetti, un problema se non puoi accedervi e inserirti AssemblyResolver in essi. Puoi provare anche https://libz.codeplex.com/. –

1

Si potrebbe provare a creare il proprio ISchedulerFactory ed evitare l'uso di reflection per caricare tutti i tipi. StdSchedulerFactory utilizza questo codice per creare un threadpool. È il luogo dove l'errore sta accadendo e sarebbe il posto per iniziare la ricerca a fare cambiamenti:

 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; 
     } 

Il metodo ObjectUtils.InstantiateType che viene chiamato è questa, e l'ultima riga è quella buttare i vostri eccezione:

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]); 
    } 

Subito dopo questa sezione in fabbrica, le origini dati vengono caricate utilizzando lo stesso modello e quindi anche i lavori stessi vengono caricati dinamicamente, il che significa che è anche necessario scrivere il proprio JobFactory. Poiché Quartz.Net carica dinamicamente un sacco di bit e parti in fase di esecuzione su questa strada significa che potresti finire per riscrivere una buona quantità di cose.