Prawidlowa obsluga WM_PAINT dla rysowania Bitmapy na Staticu.

0

Cześć.

Nadal męczę się z poprawieniem kodu źródłowego mojej wtyczki WLX dla Total Commandera, która jest pisana w WinAPI i jest to odtwarzacz modułów muzycznych z użyciem bass.dll. Specjalnie aby testy mogły trwać krócej, przekompilowałem wersję tak aby bass.dll był czytany z dysku w katalogu tam gdzie jest plugin, a nie z zasobów dllki. Niestety nadal mam problem.

Poprawiłem inne błedy powodujące prawdopodobnie memleaky, ale kiedy dodam jeden button na próbę to po odtworzeniu ponad 3200 modułów z zestawu liczącego 442 pliki, które zmienia symulując naciśnięcia klawiszy moj testowy program. Button ten znika. W konsekwencji powodwał by też inne problemy z rysowaniem. Wcześniej @kAzek przeglądal mój kod i narzędziem do badania zachowań wycieków GDI, którego ja nie ogarniam, ustalił że wedlug niego wycieków być niepowinno. Jednak mimo tego plugin w wersji kodu jaki mi odesłal, po odtwrozeniu okoló 6000 plików wykrzaczał się z kolorami i po zamnkięciu okna podglądu już się nie otwierał sypiąc wyjątkami av. Także kombinuję na nowo.

Button tworzę i subclassuje tak:

  PlayBtnHandle := CreateWindow('Static', '',
    WS_CHILD or WS_VISIBLE or SS_NOTIFY,
    GetControlLeft(ModulePositionSBHandle),
    GetControlTop(ModulePositionSBHandle) + GetControlHeight(ModulePositionSBHandle) + Controls_Vert_Distance,
    Buttons_Icon_Size, Buttons_Icon_Size, ControlsGBHandle, IDC_PLAYBTN, HInstance, nil);
//...
  POldStaticProc1 := Pointer(SetWindowLong(PlayBtnHandle, GWL_WNDPROC, Integer(MakeObjectInstance(NewStaticProc1))));

Użyte funkcje do ustalania rozmiarów i połóżeń kontrolki oraz subclassowania są na pewno ok. Bo wiadomo, żależy mi by móc odtwarzać bez wycieków i błedów wiele plików po sobie. Stąd odtworzenie tylko 3200 razy bez błędów nie spełnia założeń

Natomiast ikonki z zasobów ładuję tak jak poniżej i "przekonwertowuję" na typ HBITMAP w taki sposób:

//...
var
    BitmapsHandleArr : array[1..3] of HBITMAP;
//...
procedure TPluginObj.LoadImages;
var
  I : integer;
  IconH : HICON;
  IconInfo : TIconInfo;
begin
  for I := Low(BitmapsHandleArr) to High(BitmapsHandleArr) do
  begin
    IconH := LoadImage(HInstance, MAKEINTRESOURCE(GFX_BASE + (I mod 100)), IMAGE_ICON, Icon_Size, Icon_Size, LR_DEFAULTCOLOR);
    GetIconInfo(IconH, IconInfo);
    DestroyIcon(IconH);
    BitmapsHandleArr[I] := IconInfo.hbmColor;
  end;
end;

Powyższa procedura jest wywoływana tylko raz przy tworzeniu kontrolek pluginu. Do jej kodu zostało dodane DestroyIcon. Gdyż bez niego, kiedy była ona wywoływana nawet bez zastosowania buttonó, to plugin potrafił się wysypać po okolo 700 odtworzeniach plików. A oto kod na rysowanie grafiki, na konkretnie na razie buttonie do odtwarzania jest wykonane w taki sposób:

procedure TPluginObj.NewStaticProc1(var AMessage : TMessage);
var
  AHWnd : HWND;
  HBmp : HBITMAP;
  BMInfo : BITMAP;
  DC, DCMem : HDC;
  PS : TPaintStruct;
begin
  with AMessage do
  begin
    case Msg of
      WM_PAINT :
        begin
          AHWnd := PlayBtnHandle;
          HBmp := BitmapsHandleArr[1];
          DC := BeginPaint(AHWnd, PS);
          DCMem := CreateCompatibleDC(DC);
          GetObject(HBmp, SizeOf(BMInfo), @BMInfo);
          SelectObject(DCMem, HBmp);
          TransparentBlt(DC, 0, 0, BMInfo.bmWidth, BMInfo.bmHeight, DCMem, 0, 0, BMInfo.bmWidth, BMInfo.bmHeight, ColorToRGB(clBlack));
          DeleteObject(HBmp);
          DeleteDC(DCMem);
          EndPaint(AHWnd, PS);
          Result := 0;
          Exit;
        end;
      wM_SETCURSOR :
        begin
          SetCursor(LoadCursor(0, IDC_HAND));
          Result := 0;
          Exit;
        end;
    end;
    Result := CallWindowProc(POldStaticProc1, PlayBtnHandle, Msg, WParam, LParam);
  end;
end;

Obecnie plugin został zmieniony tak, że okno podglądu jest tworzeone za każdym razem, gdy wczytujemy jakiś plik. Czyli eksportowane z dllki funkcje ListLoadNext oraz ListLoadNextW są zakomentowane i plugin ich nie udostępnia. No i jak pisałem po odtworzeniu 3200 plików z mojego zestawu testowego button znika. A kiedy testuje inne kontorlki bez jego rysowania wszystko jest raczej ok. Czy coś tutaj należy do kodu dodać, poprawić? Prosil bym o podpowiedzi i przykłady kodów źródlowych.

I dodam, że bitmapa raczej musi być rysowana tak, a nie ustawiana przez STM_SETIMAGE gdyż chcę aby miałą przezroczyste tło, gdyż domyślnie jest czarne, a docelowo user będze mógł zmieniać je checkboxem jeżeli zdefiniował własne kolory dla Listera w ustawieniach Total Commandera. Z mojego googlowania wynikało że raczej jest ok, a pewne przykłady jakie znalazłem na rysowanie bitmap w WM_PAINT nie rysowały mi nic tutaj, bo chyba oparte były na rysowaniu na głownycn oknach, a nie subclassowanych kontrolkach, jak tutaj. Sorry za rozpiskę. Z góry dziękuję za pomoc.

1

Przywróć w WM_DESTROY oryginalną procedurę okna, czyli SetWindowLong z POldStaticProc1.

0

@Azarien: dziękuję za odpowiedź. Niestety po zastosowaniu "odsubclassingu", tak jak napiusałeś - kontrolka nadal znika po odtworzeniu 3200 plików. Masz może jeszcze jakieś pomysły jak usprawnić kod by temu zapobiec? A i czy powinienem tak "odsubclassować" wszystkie kontrolki utworzone przez funkcję CreateWindow(Ex)? Nawet te, które aktualnie nie mają wykonywanego kodu przy obsłudze komunikatu WM_PAINT i są innego typu niż Static?

1

najprościej było by go zdiagnozować jakbyś tego dlla przerobił na exe. Swoją droga w menadżerze zadań możesz sobie pokazać kolumnę Obiekty GDI - zobacz jak zmienia się ta wartość dla TC przy ładowaniu kolejnych utworów. Teoretycznie powinny być na stałym poziomie

0

@abrakadaber: przerobienie na exek, troszkę odpada. Z tego względu, że za ładowanie plików odpowiadaja funkcje które są exportowane do wykorzystania przez Total Commander i to on nimi zarządza przekazując nazwy plików. Oczywiście można spróbować wczytywać jakoś listę plików "w kółko", ale nie wiem jak to nawet później debuggować. Wprawy w debuggowaniu, wielkiej nie posiadam, ot czasem jakiś skok zreversować w niespakowanym exeku, ale tak to ciężko.

A pod Windows 7 nie widzę takich wskazań GDI. Może źle szukam. Dodam też, że "odsubclassowanie" wszystkiego przy wyjściu i wyświetlenie okna pluginu bez tych przycisków, tylko podstawowe kontrolki bez WM_PAINT również po ponad 3200 otwarciach plików też chyba powoduje jakiś wyciek. Jednak jest to mniej widoczne, bo idzie to zawuażyć tylko po tym, iż ikonki typów przy niektórych plikach zmieniają u mnie wygląd z tej od WinAMP'a na chyba brak albo jakąś "niewidzialną". Ponieważ przy kilku plikach (ale nie następujących po sobie) pojawia się "plamka" wypełniona kolorem tła listy plików Total Commandera. I nie wiem czego szukać, bo według mnie to co trzeba zwalniam, a kolorować nie koloruje.

1

widok->wybierz kolumny i na liście zaznacz obiekty GDI

0

Ok, dziękuję - podejrzałem. Niestety ta liczba cały czas dla procesu totalcmd.exe wzrasta. Co może być "winne"? Ewentualnie jeżeli podesłal bym Tobie @abrakadaber na priva linka do obecnie testowanego źródła z programem testującym (symuluje przejścia na kolejne utwory, ale okno Total Commandera musi być na pierwszym planie) plus spakowaną paczkę modułów na której to testuje, to czy miałbyś czas spróbować dojśc w czym może być problem, nie odkomentowując fragmentów które zakomentowałem? Może dojrzał byś coś, co powoduje takie "cyrki" z wyciekami. Jak ma to miejsce obecnie po tym kiedy zostanie wczytanie ponad 3200 utworów po kolei.

Co ciekawe kiedyś @Azarien testował na Windows 98, więc podejrzewam że pod jakąś VM. I tam kiedy odtworzył inną paczkę modułów do testów, to nic mu się nie "wykrzaczyło". A u mnie pod Windows 7 zanim "przeskoczyłem" całą paczkę do konca - to już były cyrki. Chyba tylko nowsze Windowsy od XP potrafią sprawiać problemy z GDI. Ale mogę się co do tego mylić.

0

Odświeżam. Bo jednak te graficzne Staticy nie są koniecznie jedynymi winowajcami "wykrzaczania się" pluginu. Obkomentowałem je i inne rzeczy też powodują kaszanienie się grafiki/kolorów tła pluginu lub ikonek typu plików po odtworzeniu ponad 3200 plików co 200 ms. Ważne jest ten odstęp, bo przy szybszym, moment skopania się działania pluginu widać dopiero po chwilowym zastopowaniu wciskania strzałki w dół, po wczytaniu ponad 3200 plików.

Dlatego zacząłem się zasatanawiać, co w tamtym kodzie nie gra. I tutaj kolejne pytanie. Czy kod poniższej funkcji jest prawidłowy? Bo często z niej w kodzie korzystam do prawidłowego ustalenia szerokości jednej z kontrolek i rozmieszczenia pozostałych wobec niej. Nie pamiętam skąd brałem ten kod. Albo taki wygooglowałem i pozmieniałem tylko nazwy elementów kodu albo tłumaczyłem to z wygooglowanego kodu w C++. Zwalnianie pamięci jest, ale właśnie czy coś jeszcze trzeba zrobić?

function GetTextHeight(TextToCheck : string; GivenTextFont : HFONT) : integer;
var
  DC : HDC;
  PStr : PChar;
  Size : TSize;
begin
  Result := 0;
  if TextToCheck <> '' then
  begin
    GetMem(PStr, Length(TextToCheck));
    CopyMemory(PStr, PChar(TextToCheck), Length(TextToCheck));
    DC := GetDC(0);
    SelectObject(DC, GivenTextFont);
    GetTextExtentPoint32(DC, PStr, Length(TextToCheck), Size);
    ReleaseDC(0, DC);
    FreeMem(PStr);
    Result := Size.cy;
  end;
end;

Z kodu przykłądowego użycie funkcji GetTextExtentPoint32 na: http://msdn.microsoft.com/en-us/library/windows/desktop/dd162491(v=vs.85).aspx - wynika, że jest jeszcze robione SelectObject dla czcionki domyślnej. Ale tutaj nie zmieniam czcionki tylko ją odczytuje. Ale może należy te funkcję poprawić, jeśli to ona mogła by być "winna". A kiedy @kAzek przeglądał mój kod mógł nie zwrócić na nią uwagę, bo jest w osobnym module. Jeżeli coś z nią jest nie tak, to podpowiedzcie jak to usprawnić, bo może obecny kod powoduje właśnie te cyrki z grafiką oraz jakieś wycieki.

1
function GetTextHeight(TextToCheck:String;GivenTextFont:HFONT):Integer;
var DC:HDC;
var SaveFont:HFONT;
var Size:TSize;
begin
  if TextToCheck='' then TextToCheck:='-';
  DC:=GetDC(0);
  SaveFont:=SelectObject(DC,GivenTextFont);
  GetTextExtentPoint32(DC,PChar(TextToCheck),Length(TextToCheck),Size);
  SelectObject(DC,SaveFont);
  ReleaseDC(0,DC);
  Result:=Size.cy;
end;
0

@_13th_Dragon: dzięki. Zaraz przetestuje, bo to na 99% winnym wycieków i błędów była ta funkcja. Ponieważ kiedy zastąpiłem inną ze swojego modułu i początkowo bez RealeseDC na końcu - to po równo 3200 otwarciach plików, otrzymałem "elegancką" zwiechę pluginu. A jak widzicie jej kod jest mało optymalny tymbardziej, że wywoływana jest sporo razy. Ale teraz jak zastosowałem ją do testów w obecnym kształcie to bez problemów odtworzyłem ponad 6320 plików i żadnych problemów. Chyba w końcu uda się mi to ogarnąć. Ech, a to @kAzek męczył się i mi w kodzie robił poprawki. A tutaj jedna użyta funkcja najprawdopodobniej i tak wszystko niweczyła złym działaniem. Sorry :/

function TextWidth(AHandle : HWND; Text : string) : integer;
var
  DC : HDC;
  I : integer;
  ChrW : integer;
begin
  Result := 0;
  DC := GetDC(AHandle);
  for I := 1 to Length(Text) do
  begin
    ChrW := 0;
    GetCharWidth(DC, Ord(Text[I]), Ord(Text[I]), ChrW);
    Result := Result + ChrW;
  end;
  ReleaseDC(AHandle, DC);
end;

EDIT: oczywiście gapa ze mnie, bo wkleiłem funkcję do okeślania wysokości, a nie szerokości. Jednak tutaj różnica jest, z tego co się orientuje w cx i cy.

EDIT 2: wygląda na to że główny problem zażegnany. Teraz uwzględniając inne naprawy potencjalnych wycieków GDI, które usprawniał mi swego czasu w kodzie @kAzek - będę testował całość. Jeżeli wszystko oprze się testowi odtworzenia po sobie kilkunastu tysięcy plików po sobie, to wrzucę plugin w wersji 0.4 na totalcmd.net i dam Wam credits w readme :) Jeszcze raz dziękuję wszystkim za odzew w tym temacie. Ech, a swoją drogą strasznie trzeba pamiętać o tym zwalnianiu wszystkiego co dotyczy obiektów do rysowania, fontów i kombinacji z HDC. Do tej pory nie musiałem przywiązywać do tego aż takiej wagi, bo aplikacja uruchomiona przez jakiś czas bez uruchomiania z wieloma plikami wydaje się pracować ok. Dopiero taki plugin i konieczności obsługi otwarcia bardzo wielu plików w nieniszczonych lub nawet niszczonych kontrolkach, pokazuje że możemy mieć problemy z wyciekami itp.

0

@Azarien : tak, użyłem tego kodu, który podał Dragon. Przy i tak zakomentowaniu pewnych rzeczy - części kodu tworzenia kontrolek następuje wyciek po załadowaniu ponad 3200 plików po sobie.

@abrakadaber: teraz jestem w pracy, także source z instrukcjami podeślę Tobie jutro. Najgorzej jednak wykonywac testy tego pluginu. Trzeba cierpliwie poczekać, aż odtworzy około 3300 plików. Zestaw testowy też Ci wrzuce na www i podam linka na PW. Jak również program symulujący przejście po liście plików. Może uda się Tobie ustalić co powoduje wycieki. Bo wartość w kolumni obiektów GDI co chwile rośnie.

I na koniec pytanie, bo nie doczytałem tego na MSDNie. Czy należy przywracać jakoś czcionki po WM_SETFONT? Bo przy niszczeniu robię DeleteObject dla użych zmiennych typu HFONT z CreateFont. Ale może należy zrobić coś jeszcze?

0

Najbardziej istotne jest zdanie:

MSDN napisał(a)

The application should call the DeleteObject function to delete the font when it is no longer needed; for example, after it destroys the control.

Czyli robisz CreateFont†, potem wysyłasz WM_SETFONT do kontrolek (możesz użyć jednego HFONT do wielu kontrolek) i DeleteObject na końcu.

† moim zdaniem wygodniejsze jest CreateFontIndirect, mniej bzdurnych parametrów które nikogo nie interesują.

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