2016-01-19 27 views
6

Istnieje biblioteka DLL, która jest dynamicznie ładowana przez aplikację główną (na komputerze) poprzez Windows.LoadLibrary. Dzieje się tak dlatego, że istnieje wiele podobnych bibliotek DLL i tylko kilka do pojedynczego z nich wymaga załadowania w środowisku wykonawczym. Więc łączenie statyczne nie jest opcją.Czy biblioteki DLL Delphi są predefiniowane pod kątem blokad ładujących?

Problem polega na tym, że co pewien czas zawiesza się główna aplikacja podczas ładowania jednej z tych bibliotek DLL. Zauważ, że problem może się zdarzyć w przypadku każdego z nich. Prawdopodobnie dlatego, że mają wiele wspólnego kodu.

Problemem wydaje się być blokada ładowarki (see this SO answer on what it is). Znalazłem kawałek wspólnego kodu, który jest używany przez wszystkie biblioteki DLL przy uruchamianiu w jednostce begin...end- jednostki library (to jest project.dpr), gdzie używane są GetModuleHandle i GetProcAddress.

I okazało się, że jest to zupełnie NIE z bibliotek DLL, jak begin...end -section pliku projektu DLL jest w rzeczywistości DllMain funkcja biblioteki i wywołanie tych funkcji może prowadzić do martwych zamki (nazwane ładowarka blokada) . Przeczytałem to w tym Microsoft Best Practice Guide.

Więc odbudowałem swój kod, że te połączenia są wywoływane później, gdy połączenie Windows.LoadLibrary zostało zakończone.

Niestety, problem z wiszącym pozostaje. :-(

Uruchomiłem debuggera, przechodząc przez każdą inicjalizację, która jest wywoływana, nawet przed wykonaniem pojedynczej linii mojego kodu. Stwierdziłem, że wiele kodu strony trzeciej jest sprzeczne z instrukcją co zrobić i czego nie robić w kodzie inicjującym DLL:

  • TMS Komponent Pakiet
  • JEDI Biblioteka komponentów
  • OmniThreadLibrary
  • Indy Komponenty

Wszystkie powyższe dynamicznie ładują inne biblioteki DLL w kodzie initialization lub wysyłają zapytania do procedur za pośrednictwem GetProcAddress. Myślę, że te wywołania powodują zawieszanie się podczas ładowania bibliotek DLL.

Czy tylko nieliczni programiści Delphi wiedzą o niebezpieczeństwach związanych z initialization? Co mogę z tym zrobić?

+3

'GetModuleHandle()' i 'GetProcAddress()' są bezpieczne w użyciu podczas inicjowania biblioteki DLL, w dokumencie, z którym się łączyłeś. Indy dynamicznie ładuje kilka bibliotek DLL w środowisku wykonawczym, ale żadna z nich nie powinna była zostać wczytana w żadnych sekcjach "inicjalizujących", są one ładowane dynamicznie w razie potrzeby podczas wywoływania funkcji DLL. Ale właśnie znalazłem 1 DLL (zlib), który jest ładowany w sekcji 'inicjalizacji', więc naprawiłem to teraz. –

+0

Zobacz sekcję "biblioteka" w dokumentacji funkcji API. GetModuleHandle i GetProcAddress są funkcjami jądra. –

+0

Więc ta strona internetowa (https://blog.barthe.ph/2009/07/30/no-stdlib-in-dllmai/) jest błędna w odniesieniu do 'GetModuleHandle'? Jest napisane: "Ta" blokada programu ładującego "jest pobierana za każdym razem, gdy biblioteka jest ładowana, ale także wtedy, gdy używane są funkcje takie jak GetModuleHandle() lub GetModuleFileName()." * –

Odpowiedz

6

Jest to typowy problem, który moim zdaniem nie jest szczególnie specyficzny dla programistów Delphi. Jeśli masz kod, który wywołuje LoadLibrary w sekcji inicjalizacji, lub w rzeczywistości FreeLibrary w sekcji finalizacji, ten kod nie jest bezpieczny do użycia w bibliotece.

Należy pamiętać, że nie znam wszystkich wspomnianych bibliotek i nie potwierdzam, że wszystkie mają kod sekcji initialization, który nie jest bezpieczny w użyciu w bibliotece. Sądzę, że to jest coś, co należy potwierdzić - wolę trzymać się konceptów zawartych w tej odpowiedzi niż komentować konkretne biblioteki Delphi.

Powiedziałbym jednak, że połączenia z GetModuleHandle i GetProcAddress są w porządku od DllMain. Mówię to, ponieważ wspomniałeś konkretnie o GetProcAddress.Uzyskanie uchwytu modułu jest absolutnie w porządku, na przykład poprzez wywołanie GetModuleHandle, a następnie uzyskanie adresu funkcji przez wywołanie GetProcAddress. Jeśli więc niektóre z podejrzanych bibliotek to robią i nie dzwonią pod numer LoadLibrary, mogą być w porządku.

W każdym razie, z zastrzeżeniem powyższych warunków, musisz ustalić, że kod, który będzie wywoływany z DllMain, który jest sprzeczny z zasadami określonymi przez firmę Microsoft, jest wywoływany w bezpiecznym czasie, a nie od DllMain. Niestety zasady te są w najlepszym razie mgliste. Microsoft say the following in the DllMain documentation:

Funkcja punktu wejścia powinny wykonywać tylko proste inicjalizacji lub zadania wypowiedzenia. Nie może wywoływać funkcji LoadLibrary lub LoadLibraryEx (lub funkcji, która wywołuje te funkcje), ponieważ może to spowodować utworzenie pętli zależności w kolejności ładowania biblioteki DLL. Może to spowodować użycie biblioteki DLL zanim system wykona swój kod inicjujący. Podobnie, funkcja punktu wejścia nie może wywoływać funkcji FreeLibrary (lub funkcji, która wywołuje FreeLibrary) podczas kończenia procesu , ponieważ może to spowodować użycie biblioteki DLL po tym, jak system wykonał swój kod zakończenia.

Ponieważ Kernel32.dll gwarantowana ma być ładowany w adresie procesowego miejsca, gdy funkcja punktu wejścia wywołaniu funkcji połączeń w Kernel32.dll nie powoduje DLL używanego przed jego kod inicjalizacji został wykonany. Dlatego funkcja entry-point może wywoływać funkcje w Kernel32.dll, które nie ładują innych bibliotek DLL . Na przykład DllMain może tworzyć obiekty synchronizacji, takie jak krytyczne sekcje i muteksy, i używać TLS. Niestety istnieje nie pełna lista bezpiecznych funkcji w Kernel32.dll.

Ostatni akapit pozostawia niewiele wskazówek. Aby mieć pewność, że biblioteka będzie solidna trzeba będzie coś zrobić wzdłuż następujących linii:

  1. Umów że każda sekcja inicjalizacji każdej jednostki, którego kod źródłowy można kontrolować rejestrów z centralnym rejestrze inicjalizacji i procedury finalizacji.
  2. W projekcie wykonywalnym wywołuje się procedury inicjalizacji po ich zarejestrowaniu i wywołuje procedury finalizacji w kolejności odwrotnej po zakończeniu programu.
  3. W projekcie bibliotecznym odkładasz wywoływanie procedur inicjalizacji i finalizacji. Wyeksportuj parę funkcji z biblioteki DLL, którą może wywołać użytkownik biblioteki DLL, aby wywoływać procedury inicjowania i finalizowania.

Takie podejście podjąłem z moimi bibliotekami i dobrze mi służyło przez wiele lat.

Podejście to wymaga dużej ilości pracy i ma wadę, że modyfikujesz biblioteki stron trzecich. Jeśli jednak te biblioteki nie będą działały poprawnie, gdy są używane jako dostarczone, jaką alternatywę masz?

Być może w wolniejszym czasie możesz skontaktować się z twórcami bibliotek, które Twoim zdaniem nie są kompatybilne z biblioteką. Spróbuj przekonać ich, aby zmienili swój kod, tak aby był zgodny z używaniem w bibliotece.Jak widać z komentarza Remy'ego do twojego pytania, jest całkiem możliwe, że twórcy bibliotek mogą być nieświadomi problemu i bardzo chętni do wprowadzania zmian.

0

W przypadku dynamicznego ładowania wewnątrz programu, biblioteki DLL systemu Windows nie mogą powodować blokad ładujących, ponieważ są one już ładowane, gdy pierwszy kod w programie ma szansę na wykonanie. Więc blokady ładujące mogą być powodowane tylko między twoimi bibliotekami. W takim przypadku będziesz musiał określić prawidłową kolejność ładowania. Jeśli masz jakąś dokumentację, wyszukaj ją.

Jeśli wszystkie twoje biblioteki są bibliotekami Delphi/C++ Builder i są kompilowane w tym samym wydaniu RAD Studio, proponuję włączenie dla nich pakietów uruchomieniowych. Spowoduje to ograniczenie powielania kodu i szansę na zablokowanie, ponieważ dwa wystąpienia obiektu singelton, takiego jak aplikacja, próbują działać jednocześnie. Lub jeszcze lepiej - przekonwertuj swoje biblioteki do pakietów. To wyeliminuje szansę na blokady.

+0

Nie jest to dokładne. Niektóre systemowe biblioteki DLL są ładowane podczas uruchamiania procesu. Ale w żadnym wypadku nie wszystkie. Inne można załadować później. Kolejność ładowania nie jest istotna. Nie masz kontroli nad kolejnością ładowania niejawnie załadowanych bibliotek. Konwersja do pakietów runtime prawie nie pomaga. Są to również pliki DLL. A kto mówi, że możesz to zrobić. Co zrobić, jeśli nie kontrolujesz wszystkich bibliotek DLL? –

+0

Jeśli nie wierzysz, że konwersja bibliotek DLL Delphi do pakietów w 100% wyeliminuje blokady między nimi, lepiej sprawdź, w jaki sposób zostały one zaimplementowane. – Torbins

+0

Co do przypadków, gdy masz mieszankę bibliotek Delphi i innych niż Delphi, możesz statycznie zaimportować wymagane biblioteki non-Delphi do głównego exe. Lub jeszcze lepiej: przepisać cały kod Delphi na C++ i statycznie połączyć wszystko. – Torbins

1

pomysł z rosyjskim blogu: http://www.gunsmoker.ru/

Można utworzyć DLL w Delphi, który nic nie robi w DllMain. W tym celu należy utworzyć nowy pakiet jak następujące:

package Plugin1; 

//... 
{$E dll} 

contains 
    InitHook, 
    //... 
    ; 

end. 

I InitHook:

unit InitHook; 

interface 

implementation 

function GetProcAddress(hModule: HMODULE; lpProcName: PAnsiChar): Pointer; stdcall; external 'kernel32.dll' name 'GetProcAddress'; 

procedure Done; stdcall; 
type 
    TPackageUnload = procedure; 
var 
    PackageUnload: TPackageUnload; 
begin 
    @PackageUnload := GetProcAddress(HInstance, 'Finalize'); //Do not localize 
    PackageUnload; 
end; 

procedure Init; stdcall; 
type 
    TPackageLoad = procedure; 
var 
    PackageLoad: TPackageLoad; 
begin 
    @PackageLoad := GetProcAddress(HInstance, 'Initialize'); //Do not localize 
    PackageLoad; 
end; 

exports 
    Init, 
    Done; 
end. 

Teraz można umieścić wewnątrz tego pakietu dowolny kod chciał umieścić wewnątrz DLL. Ale będziesz musiał wywołać Init przed wywołaniem jakiejkolwiek innej funkcji z tej biblioteki DLL i wywołać Gotowe przed rozładowaniem.

Inicjalizacja i finalizacja to procedury, które kompilator automatycznie tworzy w pakietach. Procedury te wykonują wszystkie sekcje inicjalizacji i finalizacji we wszystkich jednostkach w pakiecie.

Powiązane problemy