Zabrałem się więc do roboty. Muszę się przyznać, że poprzednią rzecz z GUI dla Windowsów i z COM robiłem gdzieś w okolicach 2001, to jeszcze było C++ i MFC (albo coś krótko po MFC, jakieś AFX czy coś takiego, zdążyłem tymczasem zapomnieć). Potem robiłem na Windowsy już tylko w Javie i bez GUI, a jak w Javie to przecież nie z COM. Szczerze mówiąc, to nie lubię robić GUI, to jest cała masa żmudnej, nudnej roboty która mnie nie bawi. No ale co zrobić - samo się nie napisze.
Po tylu latach musiałem się oczywiście na nowo zapoznać z dostępnymi technologiami. Pamiętałem, że kiedyś istniała Java od Microsofta, zwana bodajże Visual J++, ale po sprawdzeniu wyszło, że już to już dawno nieaktualne - teraz microsoftowy zamiennik Javy to C#. I w sumie to jedyny sensowny wybór z języków .NET. Więc trzeba było się z nim zapoznać. A, po drodze był jeszcze J#, ale też już umarł.
No i co do C# moje uczucia są mieszane. Owszem, ma to parę nowych, sensownych konstrukcji, na przykład partial class albo tuple. Z drugiej strony nie podoba mi się na przykład konwencja żeby zmienne mogły się nazywać tak samo jak typy, nawet bez różnicy w case. I w ogóle ta koncepcja żeby wszystko było obiektem COM z propertiesami też mi się średnio podoba. No ale podoba się, czy nie podoba, wielkiego wyboru nie ma. W sumie język jak język, C-pochodny pod względem syntaksu, Java-pochodny pod względem użycia maszyn wirtualnych i reflection, da się z tym żyć.
Dalej nastąpił wybór technologii do okienkowania. Owszem, zauważyłem WPF, jednak wolałem się w to nie wpuszczać. Zostało przy normalnych Windows Forms. W Visual Studio okienka designuje się graficznie, a IDE robi z tego kod w C#. Spróbowałem, ale większość moich okien musi być tworzona dynamicznie, zależnie od klasy obiektu który mam pokazać, więc w ten sposób się nie dało - większość okien muszę tworzyć z kodu.
I tu wkrótce wlazłem na problem: Zawsze było, że w takim C czy C++ to pamięcią trzeba zarządzać ręcznie i pilnować żeby zwalniać to co zaallokowane, a już niepotrzebne. Nawet w jednym projekcie zostałem najęty, bo sobie z tym nie poradzili i musieli restartować system co noc, inaczej im się zwieszał z braku pamięci (a sprzedawali tym systemem bilety na Expo 2000, tak po 100.000 na dobę). Wtedy posprzątałem, ale potem zawsze było że nowoczesne języki programowania, jak Java albo C#, to mają garbage collector i nie mają takiego problemu.
Tyle że guzik prawda. Po pewnym czasie mój program też się zaczął wieszać z braku pamięci. Po zbadaniu sprawy okazało się, że to ręczne zarządzanie w C++ to było łatwe, a w nowoczesnym C# trzeba się nakombinować. Problem polega na tym, że:
- Garbage collector i owszem, jest, tyle że on z definicji zwalnia obiekty do których nie ma żadnych referencji.
- Tymczasem wszystkie obiekty dialogowe również z definicji są referowane przez jakieś tam kawałki systemu.
- Ja wkoło muszę tworzyć obiekty dialogowe na nowo (według struktury obiektu AUTOSARa, który akurat wyświetlam)
- Znaczy - muszę je jawnie zwalniać. Całkiem tak samo jak w tych starych, marnych językach.
- A nawet nie tak samo - klasy których obiekty zwalniamy muszą implementować interface IDisposable, i jest do tego dość złożony pattern, trochę inny dla klas bazowych, a inny dla dziedziczących.
- Niektóre klasy są managed (przez garbage collector), inne unmanaged, trzeba zawsze patrzeć które są jakie, i jak i kiedy je zwalniać.
I w sumie to jest o wiele bardziej skomplikowane niż kiedyś. Postęp, tak ich mać.
A, jeszcze zanim wyczerpała się pamięć (a muszę działać na 32 bity, więc max to 2GB), to wcześniej zaczęły mi się wyczerpywać handle windowsowe. Wyszło że każdy obiekt dialogowy bierze parę do kilku takich handli, limit na proces jest 10.000, a w systemie jest ich tylko 32K. I jeszcze jest jakiś podział na system handles i user handles, i to wszystko działa w niezbyt jasny sposób. Ostatecznie pomogło dopiero porządne zwalnianie niepotrzebnych obiektów, ale potencjalnie to nadal jest problem.
O problemach z Windowsami mogę jeszcze długo. Na przykład:
- Tworzę dynamicznie menu kontekstowe, Add new AUTOSAR object, może być duże (przez ToolStripMenuItem). Robione to jest w dwóch różnych kontekstach, przy testach mi wyszło, że w jednym z nich idzie to błysk, a w drugim trwa kilkanaście sekund. No WTF? Pierwotnie były do tego dwie różne funkcje (tak jakoś wyszło), były tam drobne różnice, ale zintegrowałem je w jedną. Nie pomogło. Ta sama funkcja - a zależnie od kontekstu różnica w czasie wykonania jest pięćdziesięciokrotna. Serio - mierzyłem. Znalazłem różnicę między kontekstami - jeżeli miało być to menu pierwszego poziomu, szło bardzo wolno, a na niższych poziomach szybko. Przemieściłem na niższy poziom i sprawa załatwiona, ale w kodzie .NET albo windowsów musi być w tych okolicach jakiś poważny problem.
- Chciałem dopasować kolor moich okienek do schematu ustawionego w EA. No i te po pierwsze to ten cały framework nie ma supportu do takich rzeczy, znaczy żeby powiedzieć że wszystko ma mieć kolory według zdefiniowanego (przez siebie, nie systemowo) schematu, wszystko trzeba ustawiać ręcznie w kodzie dla każdego elementu z osobna, a po drugie jest straszny problem z ustawieniem swojego koloru dla captiona okna. No nie da się i już, chyba żeby wszystko rysować samemu. (Chętnych do podrzucenia linka do rozwiązania problemu proszę najpierw o sprawdzenie, czy to rozwiązanie jeszcze działa w aktualnych windowsach, bo takich już nie działających to znalazłem na pęczki).
- W combo boxach chciałem mieć oprócz tekstu zawsze ikonkę. Teoretycznie żaden problem - ustawić owner draw, narysować i już. Przy większości kombinacji nawet działa, ale akurat przy takiej którą potrzebuję robi się coś dziwnego - przy przejściu do edycji w edit boxie combo boxa przestaje być wołany mój OnPaint, a okienko rysuje się takie, jak było około Windowsów 3.1. Serio, używany font jest taki jak wtedy – żeby wszystko działało jak chcę musiałbym całkowicie zimplementować całego combo boxa na piechotę (albo znaleźć jakiś gotowy).
No ale to nic szczególnego, każdy kto pisze coś na Windowsy ma takich historyjek na pęczki. Dalej będzie o bardziej specyficznych problemach.
@C#
Jedną bardzo chwaloną rzeczą jest pełna specyfikacja. Tj. nie ma takich sytuacji jak w C, że „to co się stanie jest implementation-dependent”.
@Skończyły się HANDLE
Miałem wiele lat temu podobny problem. W moim przypadku okazało się, że kontrolki COM/ActiveX których tworzyliśmy takie ilości mają tryb pracy „windowless”. W tym trybie tylko kontener miał okno (HWND) i w jakiś bezpośredni sposób wołał kod kontrolki podając jej prostokąt, który ma sobie narysować, routował zdarzenia i tak dalej – zamiast zwyczajowego okna, które ma podookna, które mają podookna…działało to pięknie, ale co sobie nawyrywałem włosów z głowy pisząc windowless ActiveX container, to moje.
Może zobacz czy Twoje kontrolki/kontener nie mają takiej opcji do włączenia po prostu?
@coś nagle muli
Takie rzeczy się często dzieją w aplikacjach Win32/MFC jak coś jest popieprzone z dispatchowaniem eventów (MSG). Zwykle dlatego, że te wszystkie warstwy (MFC) często mają jakiś hack oparty o MSG do celów np mapowania między obiektami C++ a Win32 (CWnd vs HWND albo AfxWindow vs HWND, etc). To często działa tak, że aplikacja okresowo wysyła komunikat do wszystkich swoich okien, a one do podobien, etc. No i normalnie działa dobrze, CHYBA że masz dużo okien (a z tego co mówisz, masz), albo że coś się pokręci z obsługą tych MSG, albo MSG przez nie triggerowanych. Ogólnie takie rzeczy debuguje się Spy++, kiedyś był dołączany do Visuala, nie wiem jak teraz.
@rysowanie w stylu Win3.1
To z kolei śmierdzi używaniem niewłaściwego/nieskonfigurowanego DC. Nie wiem jak to dokładnie robisz, ale kod tego typu (owner draw albo własne implementacje WM_PAINT/WM_ERASEBKGND i rodziny) często robi się tak, że się pobiera jakiś DC z okolicy (GetDC, czy tam GetWndDC, etc). No i w teorii powinieneś dostawać DC skonfigurowany do stylu kontrolki – np z właściwym fontem, ale to właśnie nie do końca działa w tych specjalnych sytuacjach.
Dobrym rozwiązaniem może być pobranie „permanentnie” tego właściwego DC, zapamiętanie go sobie gdzieś, i używanie go zawsze. Bo to co opisujesz – „jak Win3.1” oznacza najprawdopodobniej że dostałeś goły DC jak go matka stworzyła, a z defaultu one mają załadowany jakiś antyczny font bitmapowy…
@coś nagle muli
Nie, to nie ta sytuacja, to jest całkowicie powtarzalne i systematyczne. Coś jest spieprzone w implementacji.
@rysowanie w stylu Win3.1
Też nie, sprawdzałem, to znany problem, też spieprzona implementacja, jedynym znanym rozwiązaniem jest zrobienie takiego controla samemu.
Ale dzięki za wskazówkę, zobaczę, może coś się da zrobić.