Uczę się C ++ i właśnie zacząłem uczyć się o niektórych możliwościach Qt do kodowania programów GUI. Zadałem sobie następujące pytanie:
W jaki sposób C ++, który wcześniej nie miał składni, która mogłaby prosić system operacyjny o okno lub sposób komunikowania się przez sieci (z interfejsami API, których do końca nie rozumiem, przyznaję) nagle zyskuje takie możliwości dzięki bibliotekom napisanym w C ++? To wszystko wydaje mi się okropnie okrągłe. Jakie instrukcje w C ++ można wymyślić w tych bibliotekach?
Zdaję sobie sprawę, że to pytanie może wydawać się trywialne doświadczonemu twórcy oprogramowania, ale badam od wielu godzin, nie znajdując żadnej bezpośredniej odpowiedzi. Doszło do tego, że nie mogę wykonać samouczka o Qt, ponieważ istnienie bibliotek jest dla mnie niezrozumiałe.
źródło
Odpowiedzi:
Komputer jest jak cebula, ma wiele wielu warstw, od wewnętrznego rdzenia czystego sprzętu po zewnętrzną warstwę aplikacji. Każda warstwa wystawia części siebie na następną warstwę zewnętrzną, dzięki czemu warstwa zewnętrzna może wykorzystywać niektóre funkcje warstw wewnętrznych.
W przypadku np. Systemu Windows system operacyjny udostępnia tak zwany interfejs API WIN32 dla aplikacji działających w systemie Windows. Biblioteka Qt używa tego interfejsu API do dostarczania aplikacji korzystających z Qt do własnego interfejsu API. Używasz Qt, Qt używa WIN32, WIN32 używa niższych poziomów systemu operacyjnego Windows i tak dalej, dopóki nie będzie sygnałów elektrycznych w sprzęcie.
źródło
Qt
tutaj zapewnia abstrakcję warstwy poniżej, ponieważ w systemie LinuxQt
wywołuje interfejs API systemu Linux, a nie interfejs API WIN32.user32.dll
, a możegdi32.dll
.Masz rację, że ogólnie biblioteki nie mogą umożliwić niczego, co nie jest już możliwe.
Ale biblioteki nie muszą być napisane w C ++, aby mogły być używane przez program C ++. Nawet jeśli są napisane w C ++, mogą wewnętrznie używać innych bibliotek, które nie zostały napisane w C ++. Tak więc fakt, że C ++ nie podała żadnych sposób to zrobić nie zapobiec jej dodał, tak długo, jak istnieje jakiś sposób, aby to zrobić poza C ++.
Na dość niskim poziomie niektóre funkcje wywoływane przez C ++ (lub przez C) będą pisane w asemblerze, a asembler zawiera wymagane instrukcje do robienia wszystkiego, co nie jest możliwe (lub nie jest łatwe) w C ++, na przykład do wywołania funkcja systemu. W tym momencie to wywołanie systemowe może zrobić wszystko , co potrafi komputer, po prostu dlatego, że nic go nie powstrzymuje.
źródło
C i C ++ mają 2 właściwości, które pozwalają na całą tę rozszerzalność, o której mówi OP.
W jądrze lub na podstawowej platformie trybu niechronionego urządzenia peryferyjne, takie jak port szeregowy lub dysk, są odwzorowywane na mapę pamięci w taki sam sposób, jak pamięć RAM. Pamięć to seria przełączników, a przestawienie przełączników urządzeń peryferyjnych (takich jak port szeregowy lub sterownik dysku) zmusza urządzenie peryferyjne do robienia użytecznych rzeczy.
W systemie operacyjnym trybu chronionego, gdy chce się uzyskać dostęp do jądra z przestrzeni użytkownika (np. Podczas pisania do systemu plików lub narysowania piksela na ekranie), należy wykonać wywołanie systemowe. C nie ma instrukcji do wywoływania wywołań systemowych, ale C może wywoływać kod asemblera, który może wywołać prawidłowe wywołanie systemowe. To pozwala kodowi C komunikować się z jądrem.
Aby ułatwić programowanie konkretnej platformy, wywołania systemowe są pakowane w bardziej złożone funkcje, które mogą wykonywać przydatne funkcje w ramach własnego programu. Można swobodnie wywoływać wywołania systemowe bezpośrednio (za pomocą asemblera), ale prawdopodobnie łatwiej jest po prostu skorzystać z jednej z funkcji otoki, które zapewnia platforma.
Istnieje inny poziom API, który jest o wiele bardziej przydatny niż wywołanie systemowe. Weźmy na przykład Malloc. Nie tylko wywoła to system w celu uzyskania dużych bloków pamięci, ale będzie zarządzać tą pamięcią, wykonując całą księgowość na temat tego, co się dzieje.
Interfejsy API Win32 obejmują niektóre funkcje graficzne wspólnym zestawem widżetów platformy. Qt idzie o krok dalej, pakując API Win32 (lub X Windows) na wiele platform.
Zasadniczo chociaż kompilator C zamienia kod C w kod maszynowy, a ponieważ komputer jest zaprojektowany do korzystania z kodu maszynowego, należy oczekiwać, że C będzie w stanie osiągnąć udział lwów lub to, co może zrobić komputer. Wszystko, co robią biblioteki opakowań, to ciężkie podnoszenie dla ciebie, abyś nie musiał.
źródło
Języki (jak C ++ 11 ) to specyfikacje na papierze, zwykle napisane w języku angielskim. Zajrzyj do najnowszej wersji roboczej C ++ 11 (lub kup kosztowną ostateczną specyfikację od dostawcy ISO).
Zwykle używasz komputera z pewną implementacją języka (w zasadzie możesz uruchomić program C ++ bez żadnego komputera, np. Używając tłumu ludzkich niewolników interpretujących go; byłoby to nieetyczne i nieefektywne)
Ogólna implementacja C ++ działa powyżej pewnego systemu operacyjnego i komunikuje się z nim (przy użyciu kodu specyficznego dla implementacji , często w bibliotece systemowej). Zasadniczo komunikacja odbywa się za pośrednictwem wywołań systemowych . Wyszukaj na przykład w syscalls (2) listę wywołań systemowych dostępnych w jądrze systemu Linux .
Z punktu widzenia aplikacji syscall jest podstawową instrukcją maszynową, jak
SYSENTER
na x86-64 z pewnymi konwencjami ( ABI )Na moim pulpicie z systemem Linux biblioteki Qt znajdują się powyżej bibliotek klienta X11 komunikujących się z serwerem X11 Xorg przez X protokołów Windows .
W systemie Linux użyj
ldd
w swoim pliku wykonywalnym, aby zobaczyć (długą) listę zależności od bibliotek. Użyjpmap
w uruchomionym procesie, aby zobaczyć, które z nich są „ładowane” w czasie wykonywania. BTW, w Linuksie Twoja aplikacja prawdopodobnie używa tylko wolnego oprogramowania, możesz przestudiować jego kod źródłowy (od Qt, do Xlib, libc, ... jądra), aby lepiej zrozumieć, co się dziejeźródło
Myślę, że koncepcja, której brakuje, to wywołania systemowe . Każdy system operacyjny zapewnia ogromną ilość zasobów i funkcji, z których można korzystać, aby wykonywać czynności związane z niskim poziomem systemu operacyjnego. Nawet jeśli wywołasz zwykłą funkcję biblioteczną, prawdopodobnie wykonuje ona wywołanie systemowe za sceną.
Wywołania systemowe są niskopoziomowym sposobem korzystania z mocy systemu operacyjnego, ale mogą być skomplikowane i uciążliwe w użyciu, dlatego często są „zawinięte” w interfejsy API, dzięki czemu nie trzeba bezpośrednio się z nimi obchodzić. Ale poniżej wszystko, co robisz, co wiąże się z zasobami związanymi z O / S, będzie korzystać z wywołań systemowych, w tym drukowania, sieci i gniazd itp.
W przypadku Windows, Microsoft Windows ma GUI faktycznie zapisane w jądrze, więc są wywołania systemowe do tworzenia okien, malowania grafiki itp. W innych systemach operacyjnych GUI może nie być częścią jądra, w którym to przypadku o ile wiem, nie byłoby żadnych wywołań systemowych dla rzeczy związanych z GUI, a ty mógłbyś pracować tylko na jeszcze niższym poziomie z dostępnymi wywołaniami graficznymi i wejściowymi niskiego poziomu.
źródło
Dobre pytanie. Każdy nowy programista C lub C ++ ma to na uwadze. Zakładam standardową maszynę x86 do końca tego postu. Jeśli używasz kompilatora Microsoft C ++, otwórz notatnik i wpisz go (nazwij plik Test.c)
A teraz skompiluj ten plik (używając wiersza polecenia programisty) cl Test.c /FaTest.asm
Teraz otwórz Test.asm w swoim notatniku. To, co widzisz, to przetłumaczony kod - C / C ++ jest tłumaczony na asembler. Dostajesz podpowiedź?
Programy C / C ++ są zaprojektowane do działania na metalu. Co oznacza, że mają dostęp do sprzętu niższego poziomu, co ułatwia wykorzystanie jego możliwości. Powiedzmy, że zamierzam napisać getch () biblioteki C na maszynie x86.
W zależności od asemblera napisałbym coś w ten sposób:
Uruchomię go za pomocą asemblera i wygeneruję plik .OBJ - Nazwij go getch.obj.
Następnie piszę program w języku C (nie włączam niczego)
Teraz nazwij ten plik - GetChTest.c. Skompiluj ten plik, przekazując getch.obj. (Lub skompiluj indywidualnie do .obj i LINK GetChTest.Obj i getch.Obj razem, aby utworzyć GetChTest.exe).
Uruchom GetChTest.exe, a zobaczysz, że czeka on na klawiaturę.
Programowanie w C / C ++ to nie tylko język. Aby być dobrym programistą C / C ++, musisz dobrze rozumieć rodzaj obsługiwanego komputera. Będziesz musiał wiedzieć, jak obsługiwane jest zarządzanie pamięcią, jak są zbudowane rejestry itp. Może nie potrzebujesz wszystkich tych informacji do regularnego programowania - ale one bardzo by ci pomogły. Oprócz podstawowej wiedzy o sprzęcie, z pewnością pomaga, jeśli rozumiesz, jak działa kompilator (tj. Jak to tłumaczy) - co może umożliwić ci poprawienie kodu w razie potrzeby. To ciekawy pakiet!
Oba języki obsługują słowo kluczowe __asm, co oznacza, że możesz również mieszać kod języka asemblera. Nauka języków C i C ++ sprawi, że będziesz ogólnie lepszym programistą.
Nie zawsze jest konieczne łączenie się z asemblerem. Wspomniałem o tym, ponieważ myślałem, że to pomoże ci lepiej zrozumieć. Przeważnie większość takich wywołań biblioteki korzysta z wywołań systemowych / interfejsów API dostarczanych przez system operacyjny (system operacyjny z kolei robi interakcje sprzętowe).
źródło
Nie ma nic magicznego w korzystaniu z innych bibliotek. Biblioteki to proste duże pakiety funkcji, które można wywoływać.
Zastanów się, czy nie napisać takiej funkcji
Teraz, jeśli dodasz ten plik, możesz napisać
addExclamation(myVeryOwnString);
. Teraz możesz zapytać: „w jaki sposób C ++ nagle uzyskał możliwość dodawania wykrzykników do ciągu?” Odpowiedź jest prosta: napisałeś funkcję, aby to zrobić, a następnie ją nazwałeś.Aby odpowiedzieć na pytanie o to, jak C ++ może uzyskać możliwości rysowania okien za pomocą bibliotek napisanych w C ++, odpowiedź jest taka sama. Ktoś inny napisał (e) funkcje, aby to zrobić, a następnie skompilował je i dał ci w formie biblioteki.
Inne pytania odpowiadają, jak działa rysunek okna, ale wydawało się, że jesteś zdezorientowany, jak działają biblioteki, więc chciałem odpowiedzieć na najbardziej podstawową część twojego pytania.
źródło
Kluczem jest możliwość ujawnienia przez system operacyjny interfejsu API oraz szczegółowy opis sposobu użycia tego interfejsu API.
System operacyjny oferuje zestaw interfejsów API z konwencjami wywoływania. Konwencja wywoływania określa sposób podawania parametru do interfejsu API, sposób zwracania wyników i sposób wykonania rzeczywistego wywołania.
Systemy operacyjne i kompilatory, które tworzą dla nich kod, grają dobrze razem, więc zazwyczaj nie musisz o tym myśleć, po prostu go używaj.
źródło
Nie ma potrzeby specjalnej składni do tworzenia okien. Wystarczy, że system operacyjny zapewni interfejs API do tworzenia okien. Taki interfejs API składa się z prostych wywołań funkcji, dla których C ++ zapewnia składnię.
Ponadto C i C ++ są tak zwanymi językami programowania systemowego i mogą uzyskiwać dostęp do dowolnych wskaźników (które mogą być mapowane na niektóre urządzenia przez sprzęt). Ponadto dość łatwo jest wywoływać funkcje zdefiniowane w asemblerze, co pozwala na pełny zakres operacji zapewnianych przez procesor. Dlatego możliwe jest napisanie samego systemu operacyjnego przy użyciu C lub C ++ i niewielkiej ilości asemblera.
Należy również wspomnieć, że Qt jest złym przykładem, ponieważ wykorzystuje tak zwany meta kompilator do rozszerzenia składni C ++. Nie jest to jednak związane z jego zdolnością do wywoływania interfejsów API udostępnianych przez system operacyjny w celu rysowania lub tworzenia okien.
źródło
Po pierwsze, myślę, że jest to trochę nieporozumienie
Nie ma składni do wykonywania operacji systemu operacyjnego. To kwestia semantyki .
Cóż, system operacyjny jest napisany głównie w C. Możesz użyć bibliotek współdzielonych (więc dll) do wywołania kodu zewnętrznego. Ponadto kod systemu operacyjnego może rejestrować procedury systemowe w wywołaniach systemowych * lub przerwać, które można wywołać za pomocą asemblera . Te współdzielone biblioteki często po prostu wywołują dla ciebie wywołania systemowe, więc oszczędzasz sobie korzystania z wbudowanego zestawu.
Oto fajny samouczek na ten temat: http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
To jest dla Linuksa, ale zasady są takie same.
Jak system operacyjny wykonuje operacje na kartach graficznych, kartach sieciowych itp.? Jest to bardzo szeroki temat, ale przede wszystkim potrzebujesz dostępu do przerwań, portów lub zapisu danych w specjalnym regionie pamięci. Ponieważ te operacje są chronione, i tak musisz wywoływać je za pośrednictwem systemu operacyjnego.
źródło
Próbując przedstawić nieco inne spojrzenie na inne odpowiedzi, odpowiem w ten sposób.
(Oświadczenie: Upraszczam nieco, sytuacja, którą podaję, jest czysto hipotetyczna i została napisana jako środek demonstracji koncepcji, a nie bycia w 100% wiernym życiu).
Pomyśl o tym z innej perspektywy, wyobraź sobie, że właśnie napisałeś prosty system operacyjny z podstawowymi funkcjami wątkowania, okienkowania i zarządzania pamięcią. Chcesz zaimplementować bibliotekę C ++, aby umożliwić użytkownikom programowanie w C ++ i wykonywanie takich czynności, jak tworzenie okien, rysowanie na oknach itp. Pytanie brzmi, jak to zrobić.
Po pierwsze, ponieważ C ++ kompiluje się do kodu maszynowego, musisz zdefiniować sposób użycia kodu maszynowego do interfejsu z C ++. To tutaj przychodzą funkcje, funkcje akceptują argumenty i zwracają wartości, dzięki czemu zapewniają standardowy sposób przesyłania danych między różnymi sekcjami kodu. Robią to, ustanawiając coś, co nazywa się konwencją powołania .
A konwencja wywoływania stanów, gdzie i jak argumenty powinny być umieszczane w pamięci tak, że funkcja może ich znaleźć, gdy zostanie wykonany. Kiedy funkcja zostaje wywołana, funkcja wywołująca umieszcza argumenty w pamięci, a następnie prosi CPU, aby przeskoczył do innej funkcji, gdzie robi to, co robi, zanim wróci do miejsca, z którego został wywołany. Oznacza to, że wywoływany kod może być absolutnie dowolny i nie zmienia sposobu wywoływania funkcji. W takim przypadku kod stojący za funkcją byłby odpowiedni dla systemu operacyjnego i działałby w stanie wewnętrznym systemu operacyjnego.
Wiele miesięcy później masz już uporządkowane wszystkie funkcje systemu operacyjnego. Użytkownik może wywoływać funkcje w celu tworzenia okien i rysowania na nich, może tworzyć wątki i wszelkiego rodzaju wspaniałe rzeczy. Oto problem: funkcje twojego systemu będą się różnić od funkcji Linuksa lub Windowsa. Więc decydujesz, że musisz dać użytkownikowi standardowy interfejs, aby mógł pisać przenośny kod. Tutaj pojawia się QT.
Jak prawie na pewno wiesz, QT ma mnóstwo przydatnych klas i funkcji do robienia tego, co robią systemy operacyjne, ale w sposób, który wydaje się niezależny od bazowego systemu operacyjnego. Działa to w ten sposób, że QT zapewnia klasy i funkcje, które są jednolite pod względem wyglądu dla użytkownika, ale kod stojący za funkcjami jest inny dla każdego systemu operacyjnego. Na przykład QApplication :: closeAllWindows () QT faktycznie wywoływałby specjalną funkcję zamykania okien każdego systemu operacyjnego w zależności od używanej wersji. W Windows najprawdopodobniej wywołałby CloseWindow (hwnd), podczas gdy w systemie operacyjnym korzystającym z X Window System potencjalnie wywołałby XDestroyWindow (display, window).
Jak widać, system operacyjny ma wiele warstw, z których wszystkie muszą oddziaływać poprzez interfejsy wielu odmian. Jest wiele aspektów, których nawet nie dotknąłem, ale ich wyjaśnienie zajęłoby bardzo dużo czasu. Jeśli jesteś zainteresowany wewnętrznymi działaniami systemów operacyjnych, polecam sprawdzenie wiki dla programistów systemu operacyjnego .
Należy jednak pamiętać, że powodem, dla którego wiele systemów operacyjnych decyduje się na udostępnienie interfejsów C / C ++, jest to, że kompilują się one do kodu maszynowego, pozwalają na mieszanie instrukcji asemblera z własnym kodem i zapewniają programistom dużą swobodę.
Znowu wiele się tu dzieje. Chciałbym wyjaśnić, w jaki sposób biblioteki takie jak .so i .dll nie muszą być pisane w C / C ++ i mogą być napisane w asemblerze lub w innych językach, ale wydaje mi się, że jeśli coś jeszcze dodam, równie dobrze napisz cały artykuł i tak bardzo, jak chciałbym to zrobić, nie mam witryny, w której można by go hostować.
źródło
Kiedy próbujesz narysować coś na ekranie, twój kod wywołuje inny fragment kodu, który wywołuje inny kod (itp.), Aż w końcu pojawia się „wywołanie systemowe”, które jest specjalną instrukcją, którą CPU może wykonać. Instrukcje te mogą być napisane w asemblerze lub mogą być napisane w C ++, jeśli kompilator obsługuje ich „elementy wewnętrzne” (które są funkcjami, które kompilator obsługuje „specjalnie” poprzez konwersję ich na specjalny kod, który procesor może zrozumieć). Ich zadaniem jest powiedzieć systemowi operacyjnemu, aby coś zrobił.
Kiedy nastąpi wywołanie systemowe, wywoływana jest funkcja, która wywołuje inną funkcję (itp.), Aż w końcu sterownik ekranu wyświetli polecenie narysowania czegoś na ekranie. W tym momencie sterownik ekranu patrzy na konkretny region w pamięci fizycznej, który w rzeczywistości nie jest pamięcią, ale raczej zakresem adresów, do którego można zapisać tak, jakby była pamięcią. Zamiast tego zapisywanie w tym zakresie adresów powoduje, że sprzęt graficzny przechwytuje zapis w pamięci i rysuje coś na ekranie.
Zapis w tym obszarze pamięci jest czymś, co można by kodować w C ++, ponieważ po stronie oprogramowania jest to zwykły dostęp do pamięci. Po prostu sprzęt obsługuje to inaczej.
To naprawdę podstawowe wyjaśnienie, w jaki sposób może działać.
źródło
syscall
(i jego kuzynsysenter
) jest rzeczywiście instrukcją procesora.sysenter
są zoptymalizowanymi ścieżkami wywołań, ponieważ przełączanie kontekstu używane przez procedury obsługi przerwań nie było tak szybkie, jak wszyscy tego pragnęli, ale zasadniczo jest to nadal przerwanie generowane programowo, podczas gdy jest obsługiwane przez wektoryzację do procedury obsługi zainstalowanej przez jądro systemu operacyjnego. Część procesu przełączania kontekstu wykonywanego przez JESTsysenter
polega na zmianie bitów trybu w procesorze, aby ustawić pierścień 0 - pełny dostęp do wszystkich uprzywilejowanych instrukcji, rejestrów oraz obszarów pamięci i I / O.Twój program C ++ korzysta z biblioteki Qt (również zakodowanej w C ++). Biblioteka Qt będzie korzystać z funkcji Windows CreateWindowEx (która została zakodowana w C wewnątrz kernel32.dll). Lub pod Linuksem może używać Xlib (również zakodowany w C), ale równie dobrze może wysyłać surowe bajty, które w protokole X oznaczają „ Proszę o utworzenie okna dla mnie ”.
Powiązane z pytaniem catch-22 jest historyczna notatka, że „pierwszy kompilator C ++ został napisany w C ++”, chociaż tak naprawdę był to kompilator C z kilkoma pojęciami w C ++, wystarczająco, aby mógł skompilować pierwszą wersję, która mogłaby się następnie skompilować .
Podobnie kompilator GCC korzysta z rozszerzeń GCC: najpierw jest kompilowany do wersji, a następnie sam się rekompiluje. (Instrukcje kompilacji GCC)
źródło
Jak widzę pytanie, tak naprawdę jest to pytanie kompilatora.
Spójrz na to w ten sposób, piszesz fragment kodu w asemblerze (możesz to zrobić w dowolnym języku), który tłumaczy twój nowo napisany język, który chcesz nazwać Z ++ w asemblerze, dla uproszczenia pozwala nazwać go kompilatorem (jest to kompilator) .
Teraz dajesz temu kompilatorowi kilka podstawowych funkcji, dzięki czemu możesz pisać int, string, tablice itp., W rzeczywistości dajesz mu wystarczające możliwości, abyś mógł pisać sam kompilator w Z ++. a teraz masz kompilator dla Z ++ napisany w Z ++, całkiem nieźle, prawda.
Jeszcze fajniejsze jest to, że teraz możesz dodawać umiejętności do tego kompilatora za pomocą już posiadanych umiejętności, rozszerzając w ten sposób język Z ++ o nowe funkcje, korzystając z poprzednich funkcji
Przykład: jeśli napiszesz wystarczającą ilość kodu, aby narysować piksel w dowolnym kolorze, możesz go rozwinąć za pomocą Z ++, aby narysować cokolwiek chcesz.
źródło
Sprzęt jest na to pozwala. Pamięć graficzną można traktować jako dużą tablicę (składającą się z każdego piksela na ekranie). Aby narysować na ekranie, możesz pisać w tej pamięci za pomocą C ++ lub dowolnego języka, który umożliwia bezpośredni dostęp do tej pamięci. Ta pamięć jest po prostu dostępna lub znajduje się na karcie graficznej.
W nowoczesnych systemach bezpośredni dostęp do pamięci graficznej wymagałby napisania sterownika z powodu różnych ograniczeń, dlatego używasz środków pośrednich. Biblioteki, które tworzą okno (tak naprawdę obraz jak każdy inny obraz), a następnie zapisują ten obraz do pamięci graficznej, którą GPU wyświetla na ekranie. Do języka nie trzeba dodawać nic oprócz możliwości pisania w określonych lokalizacjach pamięci, do czego służą wskaźniki.
źródło