Wyciek pamięci - co robię źle?

0

Zbadałem swoje "dzieło" pod kątem wycieków za pomocą FastMM4 (wersja 4.992) i okazuje się, że mam malutki wycieczek, którego nie rozumiem, więc pozwolę sobie dopytać.
Wyciek powstaje w momencie nadpisania Application.Handle w wywoływanej DLL żeby powiązać ją z aplikacją-matką, ale po kolei, przedstawię pacjentów.

DLL:

library Project1DLL;

uses
  FastMM4,  SysUtils,  Classes,  Windows,  Forms;

var LocalH :THandle; {wiem, że globalna zmienna to zło ale tu nie przeszkadza}
{$R *.res}

function LeakTest(AHandle:THandle):Integer; StdCall;
begin
  LocalH := Application.Handle;
  Application.Handle := AHandle; {<- to przypisanie generuje późniejszy wyciek, bez niego wszystko jest ok}
end;

procedure DLLDetach(reason: Integer);
begin
  if reason = DLL_PROCESS_DETACH then Application.Handle := LocalH;
end;

exports
 LeakTest;

begin
  DllProc := DLLDetach;
end. 

Aplikacja:
Daruję sobie wszystkie zbędne nagłówki, na starcie też jest FastMM:

function LeakTest(AHandle:THandle):Integer; StdCall; external 'Project1DLL.dll' name 'LeakTest';

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  LeakTest(Application.Handle);
end;

Czyli w zasadzie bez cudów.
Po kliknięciu buttona i zamknięciu aplikacji dostaję taką informację:

5-12 bajtów: Nieznany x 1

oraz w pliku z raportem:

Wyciekł blok pamięci. Rozmiar wynosi: 12

This block was allocated by thread 0x27A8, and the stack trace (return addresses) at the time was:
402D38 
451C1A 
451602 
41F8A6 
7745EEBB [Unknown function at AddClipboardFormatListener]
77455E7A [Unknown function at GetClassLongW]
77A807F6 [RtlRegisterSecureMemoryCacheCallback]
77455C8E [Unknown function at GetClassLongW]
779E3D46 [Unknown function at RtlGetCurrentServiceSessionId]
77455259 [Unknown function at GetSystemMetricsForDpi]
764A5ACC [NtUserReleaseDC]

Blok jest aktualnie używany w obiekcie klasy: Nieznany

Jest ilość alokacji : 395

Co robię źle? A może to FastMM4 wprowadza w błąd?

0
Rhode napisał(a):

Wyciek powstaje w momencie nadpisania Application.Handle w wywoływanej DLL żeby powiązać ją z aplikacją-matką, ale po kolei, przedstawię pacjentów.

Żeby co zrobić? Po co ten uchwyt modyfikujesz?

0

@furious programming: w bibliotece trzymam formularz, który muszę zsynchronizować z aplikacją. W przykładzie nie zawarłem formularza z dll ale efekt jest ten sam.
Już się przestraszyłem, że jakieś głupoty wymyślam ale sprawdziłem i nie. Marco Cantu zaleca takie rozwiązanie.

0

No właśnie dlatego pytam, bo nie widziałem jeszcze czegoś takiego, żeby uchwyt aplikacji zmieniać. Ale nadal nie wiem o jaką synchronizację chodzi i po co w ogóle ta zabawa z uchwytami. Dasz link do materiałów Marco, w których to widziałeś?

0

Tu jest wersja online prawie tego samego przykładu, który mam na papierze w "Praktyce Programowania". Konkretnie w rozdziale dotyczącym okien niemodalnych w dll
https://www.delphipower.xyz/guide_8/a_modeless_form_in_a_dll.html

Nie zmieniam uchwytu samej aplikacji tylko synchronizuję uchwyt do Application, która pochodzi z dll z Appilcation z aplikacji :)

1

No dobrze, a:

  • sprawdzałeś czy tak samo jest bez FastMM?
  • sprawdzałeś pod debuggerem, czy uchwyt zostaje przywrócony po stronie DLL?
Rhode napisał(a):

Nie zmieniam uchwytu samej aplikacji tylko synchronizuję uchwyt do Application, która pochodzi z dll z Appilcation z aplikacji :)

Przecież to jedno i to samo — przypisujesz nowy uchwyt do istniejącego, a to modyfikacja. ;)

0

Bez FastMM nie dostanę informacji od FastMM o wycieku :) a innych menagerów nie używałem.

Pod debuggerm wpadam do DLLDetach i obiekt Application należący do dll dostaje z powrotem swój uchwyt ale to bez znaczenia chyba jest. Z końcową podmianą lub bez niej efekt jest ten sam - cieknie.

Aplikacja ma swój obiekt Application z którego kopiuję Handle. Dll-ka ma swój (inny) obiekt Application, który w momencie inicjacji ma Handle = 0 i żeby właściwie był obsługiwany formularz w niej zaszyty to przypisuję do uchwytu Application należącego do dll uchwyt kopiowany z Application aplikacji wywołującej bibliotekę. Dżizzzz... ale to się czyta...
Jeśli nie zrobię tego przypisania to formularz schowany w dll zacznie się dziwnie zachowywać - pojawi się np jako nowa pozycja na pasku zadań.
Po zmianie LeakTest na wersję jak poniżej, przy braku przypisania AHandle w rzeczy samej pojawia się nowa pozycja na pasku zadań ale nie ma wycieku, po przypisaniu formularz jest częścią głównej aplikacji ale jest wyciek.

function LeakTest(AHandle:THandle):Integer; StdCall;
var F :TForm;
begin
  Application.Handle := AHandle;
  F := TForm.Create(Application);
  F.Show;
end;

Mógłbym machnąć ręką bo to "tylko" kilka bajtów no ale... jednak cieknie i może się przy okazji czegoś jeszcze nauczę :)

1

Artykuł czytałem i ogarniam o co w tym chodzi. Jesteś pewien, że uchwyt faktycznie zostaje przywrócony? Bo masz w jednej linii napisany i warunek, i linijkę modyfikującą Application.Handle, a czegoś takiego nie da się normalnie debugować. Sprawdź czy ten warunek zostaje w ogóle spełniony i jeśli tak, to gdzieś indziej jest problem.

W razie czego napisz sobie drugą procedurę w DLL, której zadaniem będzie przywrócenie uchwytu i ją wywołaj w głównej aplikacji, zanim ta zdąży się zamknąć. Być może problem leży gdzieś w finalizacji obiektu Application po stronie DLL, stąd wyciek. A jeśli nawet wyciek istnieje, to wina leży po stronie VCL, bo Twój kod zdaje się wyglądać i działać dokładnie tak samo, jak w linkowanym artykule. Sam nie widzę żadnych błędów, ale dla pewności spróbuj przetestować powyższe. W razie czego zrób też test z dynamicznym ładowaniem procedur z DLL.

Wyciekiem bym się na Twoim miejscu nie przejmował, jeśli nie piszesz aplikacji, która ma działać miesiącami bez przerwy. Wyciek jest tak mały, że dla zwykłego programu narzędziowego wręcz nieistotny, a pamięć która wyciekła i tak zostanie zwolniona przez system, po tym jak proces Twojej apki zakończy działanie.

1

TL;DR Prawdopodobnie odlaczasz sie od aplikacji w DLL przed zwolnieniem wszystkich zasobow (w tym form). Nie pamietam czy sa dwie czy jedna sterta w tym wypadku?
Jesli dwie to DLLka zwalniajac swoja sterte zaraportuje wyciek.
A potem Application w aplikacji moze i probuje cos zwolnic ake jest za pozno.

Rozwiazanie:

  • rejestruj wszystkie obiekty "korzeniowe" ktore sam tworzysz z DLLki (np formy, drukarki, moduly) w osobnej liscie X
  • w kazdym z tych obiektow przypisz onDestroy w ktorym usuwasz go z listy X
  • na DetachDll usun obiekty z listy X tak zeby sie nie zapetlic (od konca listy, usuwasz element z listy a potem z pamieci)

To tak wymyslone na kolanie, mozna by to pewnie zrobic lepiej.

0

@vpiotr: W podanym na początku przykładzie nie mam żadnych zasobów poza tym co widać na ekranie. Surowy szkielet biblioteki, żadnych dodatkowych form czy innych obiektów. Formę dorzuciłem później by przyjrzeć się niewłaściwemu zachowaniu na pasku zadań.

Podczas zwalniania na pewno przechodzi przez DLLDetach i na pewno Application dostaje z powrotem 0, które trzymałem w LocalH.

Czy ktoś jest w stanie zreplikować opisane przeze mnie zachowanie u siebie i potwierdzić lub obalić moją obserwację o wycieku?
Może to wina VCL w D7?

1
Rhode napisał(a):

Może to wina VCL w D7?

Artykuł, z którego czerpiesz wiedzę, najpewniej dotyczy świeżego Delphi (nie wiem czy napisany w tym roku, ale aktualizowany niedawno), więc to może być wina starego VCL. Czemu używasz Delphi 7? To środowisko nie jest od 15 lat wspierane, więc nawet jeśli jest w nim gdzieś błąd, to nigdy nie zostanie naprawiony i nigdy się tego błędu nie pozbędziesz ze swojej aplikacji.

Spróbuj to co sugerowałem — dopisz sobie drugą procedurkę w DLL, która zaktualizuje uchwyt i wywołaj ją dla testów w OnDestroy formularza po stronie głównej aplikacji. Jeśli to nic nie da, spróbuj z dynamicznym ładowaniem z DLL i wywołaj ją tuż przed odłączeniem biblioteki. Jeśli i to nic nie da, to już raczej niczego nie da się zrobić.

0

FastMM ma to do siebie nie jest dokładny. Wywala nawet zmienne typu stringi nieużywane jajko wycieki pamięci.

3

BTW fajne zestawienie narzędzi do wyłapywania wycieków pamięci i nie tylko:
https://landgraf.dev/en/catching-memory-leaks-in-delphi/

1 użytkowników online, w tym zalogowanych: 0, gości: 1