2013-05-01 15 views
5

Używam ILMerge i Quartz.NET w aplikacji usługi Windows C# .NET 4.0. Aplikacja działa dobrze bez użycia ILMerge, ale teraz, gdy zbliżamy się do wydania wysyłki, chciałem połączyć wszystkie biblioteki DLL w jeden plik wykonywalny.Plik wykonywalny kończy się niepowodzeniem z dziwnym wyjątkiem

Problem w tym, że ILMerge wydaje się działać prawidłowo, ale gdy uruchamiam łączny wykonywalny, to zgłasza to wyjątek:

Unhandled Wyjątek: Quartz.SchedulerException: typ pula wątków 'Quartz.Simpl.SimpleThreadPool' może nie być tworzone. ---> System.InvalidCastException: nie można rzucić obiektu typu "Quartz.Simpl.SimpleThreadPool", aby wpisać "Quartz.Spi.IThreadPool".
w Quartz.Util.ObjectUtils.InstantiateType [T] (typu tekst): zgodnie 0
w Quartz.Impl.StdSchedulerFactory.Instantiate(): Linia 0
--- Koniec wewnętrznej śledzenia wyjątku stosu - -
na Quartz.Impl.StdSchedulerFactory.Instantiate() in: linia 0
w Quartz.Impl.StdSchedulerFactory.GetScheduler() w: linia 0

Czy ktoś ma jakiś pomysł, dlaczego tak jest? Marnowałem już ponad 4 godziny i nie mogę tego rozgryźć. Jeśli nie łączę się z ILMerge, wszystko działa poprawnie (z plikami Quartz.dll i Common.Logging.dll w tym samym katalogu).

Jestem pewna, że ​​ktoś wcześniej próbował wypróbować opakowanie Quartz.net, jakieś pomysły?

+0

Czy po raz pierwszy próbowałeś połączyć to z ILMerge? Czy to działało przed ostatnimi zmianami? –

+1

Po raz pierwszy próbowałem wykorzystać ILMerge, uruchomić go, nie działał już. Wyobraźmy sobie, że musiał to być ILMerge, próbował flagi internalizacji, niczego nie zmienił. Usuń ILMerge, skompilowany normalnie (jak kiedyś przed wypróbowaniem tego), wszystko działa (jeśli biblioteki DLL znajdują się w tym samym katalogu). –

+0

Jedną z rzeczy, których ILMerge nie obsługuje, jest ładowanie typu ze złożenia zewnętrznego (co może się zdarzyć w przypadku przeszukiwania stosu). Może zajrzyj także do jednej z znalezionych alternatyw [tutaj] (http://chrisghardwick.blogspot.nl/2012/01/ilmerge-getting-started-merging-and.html) – rene

Odpowiedz

1

Nota prawna: W ogóle nie znam Quartz.NET, chociaż spędziłem trochę czasu na walce z ILMerge. Kiedy w końcu zrozumiałem jego ograniczenia ... przestałem go używać.

Aplikacja ILMerge ma problemy ze wszystkim, co zawiera słowo "odbicie". Mogę zgadywać (nigdy nie używałem Quartz.NET), że niektóre klasy są rozwiązywane za pomocą odbicia i sterowane przez pliki konfiguracyjne.

Klasa jest identyfikowana nie tylko przez nazwę (z przestrzenią nazw), ale także przez zbiór, z którego pochodzi (niestety nie jest wyświetlany w komunikacie wyjątku). Załóżmy więc, że miałeś (przed ILMergingiem) dwa zestawy A (dla Ciebie) i Q (dla Quartz.NET). Zespół "A" odwoływał się do zespołu "Q" i korzystał z klasy "Q: QClass", która implementowała "Q: QIntf". Po połączeniu te klasy stały się "A: QClass" i "A: QIntf" (zostały przeniesione z zespołu Q do A), a wszystkie odniesienia w kodzie zostały zastąpione, aby użyć tych (całkowicie) nowych klas/interfejsów, więc " Odp .: QClass "implementuje teraz" A: QIntf ". Ale to nie zmieniło żadnych plików konfiguracyjnych/osadzonych ciągów, które mogą nadal odwoływać się do "Q: QClass".

Tak więc, gdy aplikacja odczytuje te niezaktualizowane pliki konfiguracyjne, wciąż ładuje "Q: QClass" (dlaczego MOŻE ZNALEŹĆ, że jest to inne pytanie, może zostawiłeś zestaw "Q" w bieżącym folderze lub może jest on w GAC - patrz 1). W każdym razie, "Q: QClass" NIE ZASTOSUJE "A: QIntf", nadal implementuje "Q: QIntf", nawet jeśli są one binarnie identyczne - więc nie można rzutować "Q: QClass" na "A: QIntf".

Rozwiązaniem nie idealnym, ale działającym jest "osadzanie" złożeń zamiast "scalania" z nimi. Napisałem narzędzie open-source, które to robi (osadzanie zamiast scalania), ale nie jest to związane z tym pytaniem. Więc jeśli zdecydujesz się na osadzenie, zapytaj mnie.

  1. Możesz to przetestować, usuwając (ukrywając, co działa dla ciebie) każde pojedyncze wystąpienie Q.dll na twoim komputerze. Jeśli mam rację, wyjątek powinien brzmieć teraz "FileNotFound".
+0

Skończyło się na tym, że nie używam ILMERGE, to po prostu nie działałoby i wydawało się, że wymaga dużo więcej pracy niż korzyści wynikające z posiadania tylko plików mniej podczas uruchamiania. Próbowałem również osadzania, ładowanie zespołów dynamicznie, co pomogło mi zrozumieć dużo więcej rzeczy zza kulis, ale to po prostu nie zadziałałoby do diabła. Załadowałem zespoły w najwcześniejszym punkcie, ale miałem dostęp do wielu klas statycznych i musiały one zostać ponownie załadowane, ale nawet statyczny konstruktor był zbyt późno, o dziwo. zawsze zawodzi. twoja odpowiedź miała najwięcej tła, więc dzięki! –

+0

Co rozumiesz przez "ładowanie złożeń w najwcześniejszym punkcie"? Z powodzeniem zastosowałem metodę opisaną przez Jeffreya Richtera na jego blogu (zobacz mój wcześniejszy komentarz). To było bardzo proste i jedyne, na co trzeba zwrócić uwagę, to upewnić się, że zdarzenie AssemblyResolve zostało zainicjowane przed cokolwiek innego, co w moim przypadku oznaczało utworzenie nowego punktu wejścia, który ustawił zdarzenie AssemblyResolve, a następnie wywołał stary punkt wejścia. – sgmoore

+0

@RomanMittermayr: Jestem także zaintrygowany "najwcześniejszym" punktem. Mimo że konstruktor statyczny jest dość wczesny, punktem końcowym jest inicjator modułu. Niestety inicjatora modułu nie można zmienić za pomocą C#, ale można go zmienić przy niewielkiej manipulacji IL (patrz: https://github.com/Fody/ModuleInit). Kiedy mówisz "przeładowany", mam wrażenie, że Quartz.NET tworzy oddzielne AppDomains. To może być problem, jeśli nie możesz uzyskać do nich dostępu i wstrzyknąć Ci AssemblyResolver. Możesz także wypróbować https://libz.codeplex.com/. –

1

Możesz spróbować stworzyć własny ISchedulerFactory i unikać używania odbicia, aby załadować wszystkie typy. The StdSchedulerFactory używa tego kodu do utworzenia pliku wątków. To gdzie błąd się dzieje i będzie miejscem do rozpoczęcia poszukiwań przy wprowadzaniu zmian:

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

Sposób ObjectUtils.InstantiateType że nazywa się to za jeden, a ostatnia linia to jeden wyrzucanie wyjątku:

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

Zaraz po tej sekcji w fabryce, źródła danych są ładowane przy użyciu tego samego wzoru, a następnie same zadania są również ładowane dynamicznie, co oznacza, że ​​trzeba również napisać własną aplikację JobFactory. Ponieważ Quartz.Net ładuje kilka bitów i elementów dynamicznie w czasie wykonywania tej drogi, oznacza to, że możesz przerobić sporo rzeczy.

Powiązane problemy