J'apprends le C ++ et je viens de commencer à découvrir certaines des capacités de Qt pour coder les programmes GUI. Je me suis posé la question suivante:
Comment C ++, qui n'avait auparavant aucune syntaxe capable de demander au système d'exploitation une fenêtre ou un moyen de communiquer via des réseaux (avec des API que je ne comprends pas complètement non plus, j'en conviens), obtient-il soudainement de telles capacités via des bibliothèques écrites en C ++ elles-mêmes? Tout me semble terriblement circulaire. Quelles instructions C ++ pourriez-vous éventuellement trouver dans ces bibliothèques?
Je me rends compte que cette question peut sembler triviale à un développeur de logiciels expérimenté, mais je fais des recherches depuis des heures sans trouver de réponse directe. C'est arrivé au point où je ne peux pas suivre le tutoriel sur Qt car l'existence des bibliothèques est incompréhensible pour moi.
la source
Réponses:
Un ordinateur est comme un oignon, il comporte de nombreuses couches, depuis le noyau interne du matériel pur jusqu'à la couche d'application la plus externe. Chaque couche expose des parties d'elle-même à la couche externe suivante, de sorte que la couche externe peut utiliser certaines des fonctionnalités des couches internes.
Dans le cas de Windows par exemple, le système d'exploitation expose la soi-disant API WIN32 pour les applications fonctionnant sous Windows. La bibliothèque Qt utilise cette API pour fournir des applications utilisant Qt à sa propre API. Vous utilisez Qt, Qt utilise WIN32, WIN32 utilise des niveaux inférieurs du système d'exploitation Windows, et ainsi de suite jusqu'à ce que ce soit des signaux électriques dans le matériel.
la source
Qt
fournit ici une abstraction de la couche en dessous, car sous LinuxQt
appelle l'API Linux et non l'API WIN32.user32.dll
, ou peut-êtregdi32.dll
.Vous avez raison de dire qu'en général, les bibliothèques ne peuvent rien faire qui ne soit déjà possible.
Mais les bibliothèques n'ont pas besoin d'être écrites en C ++ pour être utilisables par un programme C ++. Même s'ils sont écrits en C ++, ils peuvent utiliser en interne d'autres bibliothèques non écrites en C ++. Ainsi, le fait que C ++ n'ait fourni aucun moyen de le faire ne l'empêche pas d'être ajouté, tant qu'il existe un moyen de le faire en dehors de C ++.
À un niveau assez bas, certaines fonctions appelées par C ++ (ou par C) seront écrites dans l'assembly, et l'assembly contient les instructions requises pour faire tout ce qui n'est pas possible (ou n'est pas facile) en C ++, par exemple pour appeler une fonction système. À ce stade, cet appel système peut faire tout ce dont votre ordinateur est capable, simplement parce que rien ne l'arrête.
la source
C et C ++ ont 2 propriétés qui permettent toute cette extensibilité dont parle l'OP.
Dans le noyau ou dans une plate-forme en mode non protégé de base, des périphériques comme le port série ou le lecteur de disque sont mappés dans la carte mémoire de la même manière que la RAM. La mémoire est une série de commutateurs et le basculement des commutateurs du périphérique (comme un port série ou un pilote de disque) permet à votre périphérique de faire des choses utiles.
Dans un système d'exploitation en mode protégé, lorsque l'on veut accéder au noyau depuis l'espace utilisateur (par exemple lors de l'écriture dans le système de fichiers ou pour dessiner un pixel sur l'écran), il faut effectuer un appel système. C n'a pas d'instructions pour effectuer un appel système mais C peut appeler du code assembleur qui peut déclencher l'appel système correct, c'est ce qui permet à son code C de parler au noyau.
Afin de faciliter la programmation d'une plate-forme particulière, les appels système sont enveloppés dans des fonctions plus complexes qui peuvent exécuter certaines fonctions utiles dans son propre programme. On est libre d'appeler directement les appels système (à l'aide de l'assembleur) mais il est probablement plus facile de simplement utiliser l'une des fonctions d'encapsuleur fournies par la plate-forme.
Il existe un autre niveau d'API qui est beaucoup plus utile qu'un appel système. Prenons par exemple le malloc. Non seulement cela appellera le système pour obtenir de gros blocs de mémoire, mais gérera cette mémoire en faisant tout le livre sur ce qui se passe.
Les API Win32 encapsulent certaines fonctionnalités graphiques avec un ensemble de widgets de plateforme commun. Qt va un peu plus loin en encapsulant l'API Win32 (ou X Windows) de manière multiplateforme.
Fondamentalement, bien qu'un compilateur C transforme le code C en code machine et puisque l'ordinateur est conçu pour utiliser le code machine, vous devez vous attendre à ce que C soit capable d'accomplir le partage lions ou ce qu'un ordinateur peut faire. Tout ce que les bibliothèques d'encapsuleurs font est de faire le gros du travail pour vous afin que vous n'ayez pas à le faire.
la source
Langues (comme C ++ 11 ) sont des spécifications , sur papier, généralement écrites en anglais. Regardez à l'intérieur de la dernière version de C ++ 11 (ou achetez la spécification finale coûteuse auprès de votre fournisseur ISO).
Vous utilisez généralement un ordinateur avec une implémentation de langage (vous pouvez en principe exécuter un programme C ++ sans ordinateur, par exemple en utilisant un tas d'esclaves humains qui l'interprètent; ce serait contraire à l'éthique et inefficace)
Votre implémentation C ++ générale fonctionne au-dessus d'un système d'exploitation et communique avec lui (en utilisant une implémentation spécifique code , souvent dans une bibliothèque système). Généralement, la communication se fait via les appels système . Recherchez par exemple dans syscalls (2) la liste des appels système disponibles sur le noyau Linux .
Du point de vue de l'application, un syscall est une instruction machine élémentaire comme
SYSENTER
sur x86-64 avec quelques conventions ( ABI )Sur mon bureau Linux, les bibliothèques Qt sont au dessus des bibliothèques client X11 communiquant avec les protocoles Xorg Xu X11 du serveur X11 .
Sous Linux, utilisez
ldd
sur votre exécutable pour voir la (longue) liste des dépendances sur les bibliothèques. Utilisezpmap
sur votre processus en cours d'exécution pour voir lesquels sont "chargés" au moment de l'exécution. BTW, sous Linux, votre application n'utilise probablement que des logiciels libres, vous pouvez étudier son code source (de Qt, à Xlib, libc, ... le noyau) pour mieux comprendre ce qui se passela source
Je pense que le concept qui vous manque, ce sont les appels système . Chaque système d'exploitation fournit une énorme quantité de ressources et de fonctionnalités que vous pouvez exploiter pour effectuer des tâches liées au système d'exploitation de bas niveau. Même lorsque vous appelez une fonction de bibliothèque standard, il effectue probablement un appel système en arrière-plan.
Les appels système sont un moyen de bas niveau d'utiliser la puissance du système d'exploitation, mais ils peuvent être complexes et lourds à utiliser, ils sont donc souvent "encapsulés" dans les API afin que vous n'ayez pas à les traiter directement. Mais en dessous, à peu près tout ce que vous faites qui implique des ressources liées à l'O / S utilisera des appels système, y compris l'impression, la mise en réseau et les sockets, etc.
Dans le cas de Windows, Microsoft Windows a son interface graphique réellement écrite dans le noyau, il y a donc des appels système pour créer des fenêtres, peindre des graphiques, etc. Dans d'autres systèmes d'exploitation, l'interface graphique peut ne pas faire partie du noyau, auquel cas pour autant que je sache, il n'y aurait pas d'appels système pour les choses liées à l'interface graphique, et vous ne pourriez travailler qu'à un niveau encore plus bas avec les graphiques de bas niveau et les appels liés aux entrées disponibles.
la source
Bonne question. Chaque nouveau développeur C ou C ++ a cela à l'esprit. Je suppose une machine x86 standard pour le reste de ce post. Si vous utilisez le compilateur Microsoft C ++, ouvrez votre bloc-notes et tapez ceci (nommez le fichier Test.c)
Et maintenant compilez ce fichier (en utilisant l'invite de commande du développeur) cl Test.c /FaTest.asm
Ouvrez maintenant Test.asm dans votre bloc-notes. Ce que vous voyez est le code traduit - C / C ++ est traduit en assembleur. Comprenez-vous l'indice?
Les programmes C / C ++ sont conçus pour fonctionner sur le métal. Ce qui signifie qu'ils ont accès à du matériel de niveau inférieur, ce qui facilite l'exploitation des capacités du matériel. Dis, je vais écrire une bibliothèque C getch () sur une machine x86.
Selon l'assembleur, je taperais quelque chose de cette façon:
Je l'écrase avec un assembleur et génère un .OBJ - Nommez-le getch.obj.
J'écris ensuite un programme C (je n'inclus rien)
Nommez maintenant ce fichier - GetChTest.c. Compilez ce fichier en passant getch.obj. (Ou compilez individuellement vers .obj et LINK GetChTest.Obj et getch.Obj ensemble pour produire GetChTest.exe).
Exécutez GetChTest.exe et vous constaterez qu'il attend l'entrée du clavier.
La programmation C / C ++ ne concerne pas seulement le langage. Pour être un bon programmeur C / C ++, vous devez avoir une bonne compréhension du type de machine qu'il exécute. Vous aurez besoin de savoir comment la gestion de la mémoire est gérée, comment les registres sont structurés, etc., Vous n'aurez peut-être pas besoin de toutes ces informations pour une programmation régulière - mais elles vous aideraient énormément. Outre les connaissances matérielles de base, cela aide certainement si vous comprenez comment fonctionne le compilateur (c'est-à-dire comment il se traduit) - ce qui pourrait vous permettre de modifier votre code si nécessaire. C'est un package intéressant!
Les deux langues prennent en charge le mot clé __asm, ce qui signifie que vous pouvez également mélanger le code de votre langage d'assembly. L'apprentissage du C et du C ++ fera de vous un programmeur mieux arrondi dans l'ensemble.
Il n'est pas nécessaire de toujours établir un lien avec l'assembleur. Je l'avais mentionné parce que je pensais que cela vous aiderait à mieux comprendre. La plupart du temps, la plupart de ces appels de bibliothèque utilisent des appels système / API fournis par le système d'exploitation (le système d'exploitation à son tour fait les choses d'interaction matérielle).
la source
Il n'y a rien de magique à utiliser d'autres bibliothèques. Les bibliothèques sont de simples gros sacs de fonctions que vous pouvez appeler.
Considérez que vous écrivez une fonction comme celle-ci
Maintenant, si vous incluez ce fichier, vous pouvez écrire
addExclamation(myVeryOwnString);
. Maintenant, vous pourriez vous demander: "Comment C ++ a-t-il soudainement pu ajouter des points d'exclamation à une chaîne?" La réponse est simple: vous avez écrit une fonction pour cela, puis vous l'avez appelée.Donc, pour répondre à votre question sur la façon dont C ++ peut obtenir des capacités pour dessiner des fenêtres à travers des bibliothèques écrites en C ++, la réponse est la même. Quelqu'un d'autre a écrit des fonctions pour le faire, puis les a compilées et vous les a données sous la forme d'une bibliothèque.
Les autres questions répondent au fonctionnement du dessin de la fenêtre, mais vous sembliez confus quant au fonctionnement des bibliothèques, je voulais donc aborder la partie la plus fondamentale de votre question.
la source
La clé est la possibilité pour le système d'exploitation d'exposer une API et une description détaillée de la façon dont cette API doit être utilisée.
Le système d'exploitation propose un ensemble d'API avec des conventions d'appel. La convention d'appel définit la façon dont un paramètre est donné dans l'API et comment les résultats sont renvoyés et comment exécuter l'appel réel.
Les systèmes d'exploitation et les compilateurs qui créent du code pour eux fonctionnent bien ensemble, vous n'avez donc généralement pas à y penser, il suffit de l'utiliser.
la source
Il n'est pas nécessaire d'avoir une syntaxe spéciale pour créer des fenêtres. Tout ce qui est requis, c'est que le système d'exploitation fournit une API pour créer des fenêtres. Une telle API se compose d'appels de fonction simples pour lesquels C ++ fournit la syntaxe.
De plus, le C et le C ++ sont des soi-disant langages de programmation système et peuvent accéder à des pointeurs arbitraires (qui peuvent être mappés à un périphérique par le matériel). De plus, il est également assez simple d'appeler des fonctions définies dans l'assembly, ce qui permet la gamme complète des opérations fournies par le processeur. Par conséquent, il est possible d'écrire un système d'exploitation lui-même en utilisant C ou C ++ et une petite quantité d'assemblage.
Il convient également de mentionner que Qt est un mauvais exemple, car il utilise un soi-disant méta-compilateur pour étendre la syntaxe C ++. Cela n'est cependant pas lié à sa capacité à appeler les API fournies par le système d'exploitation pour réellement dessiner ou créer des fenêtres.
la source
Tout d'abord, il y a un petit malentendu, je pense
Il n'y a pas de syntaxe pour effectuer des opérations OS. C'est la question de la sémantique .
Eh bien, le système d'exploitation est écrit principalement en C. Vous pouvez utiliser des bibliothèques partagées (donc, dll) pour appeler le code externe. De plus, le code du système d'exploitation peut enregistrer des routines système sur des appels système * ou des interruptions que vous pouvez appeler à l'aide de l' assembly . Les bibliothèques partagées font souvent que le système appelle pour vous, vous êtes donc épargné par l'assemblage en ligne.
Voici le joli tutoriel à ce sujet: http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
C'est pour Linux, mais les principes sont les mêmes.
Comment le système d'exploitation effectue-t-il les opérations sur les cartes graphiques, les cartes réseau, etc.? C'est un thème très large, mais la plupart du temps, vous devez accéder aux interruptions, aux ports ou écrire des données dans une région de mémoire spéciale. Comme ces opérations sont protégées, vous devez quand même les appeler via le système d'exploitation.
la source
Pour essayer de donner une vue légèrement différente aux autres réponses, je répondrai comme ceci.
(Avertissement: je simplifie légèrement les choses, la situation que je donne est purement hypothétique et est écrite comme un moyen de démontrer des concepts plutôt que d'être 100% fidèle à la vie).
Pensez aux choses d'un autre point de vue, imaginez que vous venez d'écrire un système d'exploitation simple avec des capacités de gestion de thread, de fenêtrage et de mémoire de base. Vous voulez implémenter une bibliothèque C ++ pour permettre aux utilisateurs de programmer en C ++ et faire des choses comme créer des fenêtres, dessiner sur des fenêtres, etc. La question est de savoir comment faire cela.
Tout d'abord, puisque C ++ compile en code machine, vous devez définir un moyen d'utiliser le code machine pour interfacer avec C ++. C'est là que les fonctions entrent en jeu, les fonctions acceptent des arguments et donnent des valeurs de retour, elles fournissent donc un moyen standard de transférer des données entre différentes sections de code. Ils le font en établissant quelque chose connu sous le nom de convention d'appel .
Une convention d'appel indique où et comment les arguments doivent être placés en mémoire afin qu'une fonction puisse les trouver lors de son exécution. Lorsqu'une fonction est appelée, la fonction appelante place les arguments en mémoire, puis demande au CPU de passer à l'autre fonction, d'où il fait ce qu'il fait avant de revenir à l'endroit d'où il a été appelé. Cela signifie que le code appelé peut être absolument n'importe quoi et cela ne changera pas la façon dont la fonction est appelée. Dans ce cas cependant, le code derrière la fonction serait pertinent pour le système d'exploitation et fonctionnerait sur l'état interne du système d'exploitation.
Ainsi, plusieurs mois plus tard, toutes vos fonctions du système d'exploitation sont triées. Votre utilisateur peut appeler des fonctions pour créer des fenêtres et dessiner dessus, il peut créer des threads et toutes sortes de choses merveilleuses. Voici le problème cependant, les fonctions de votre système d'exploitation seront différentes des fonctions de Linux ou des fonctions de Windows. Vous décidez donc que vous devez donner à l'utilisateur une interface standard afin qu'il puisse écrire du code portable. C'est là qu'intervient QT.
Comme vous le savez presque certainement, QT dispose de nombreuses classes et fonctions utiles pour faire le genre de choses que font les systèmes d'exploitation, mais d'une manière qui semble indépendante du système d'exploitation sous-jacent. La façon dont cela fonctionne est que QT fournit des classes et des fonctions qui sont uniformes dans leur apparence pour l'utilisateur, mais le code derrière les fonctions est différent pour chaque système d'exploitation. Par exemple, QApplication :: closeAllWindows () de QT appelle en fait la fonction de fermeture de fenêtre spécialisée de chaque système d'exploitation en fonction de la version utilisée. Dans Windows, il appellerait très probablement CloseWindow (hwnd) tandis que sur un système d'exploitation utilisant le système X Window, il pourrait potentiellement appeler XDestroyWindow (affichage, fenêtre).
Comme il est évident, un système d'exploitation comporte de nombreuses couches, qui doivent toutes interagir via des interfaces de nombreuses variétés. Il y a de nombreux aspects que je n'ai même pas abordés, mais les expliquer tous prendrait beaucoup de temps. Si vous êtes davantage intéressé par le fonctionnement interne des systèmes d'exploitation, je vous recommande de consulter le wiki de développement d'OS .
Gardez à l'esprit que la raison pour laquelle de nombreux systèmes d'exploitation choisissent d'exposer des interfaces à C / C ++ est qu'ils compilent en code machine, ils permettent de mélanger les instructions d'assemblage avec leur propre code et ils offrent une grande liberté au programmeur.
Encore une fois, il se passe beaucoup de choses ici. Je voudrais continuer à expliquer comment les bibliothèques comme les fichiers .so et .dll ne doivent pas être écrites en C / C ++ et peuvent être écrites en assembleur ou dans d'autres langages, mais je pense que si j'ajoute plus, je pourrais aussi bien écrire un article entier, et autant que j'aimerais faire, je n'ai pas de site pour l'héberger.
la source
Lorsque vous essayez de dessiner quelque chose à l'écran, votre code appelle un autre morceau de code qui appelle un autre code (etc.) jusqu'à ce qu'il y ait finalement un "appel système", qui est une instruction spéciale que le CPU peut exécuter. Ces instructions peuvent être écrites en assembleur ou écrites en C ++ si le compilateur prend en charge leurs "intrinsèques" (qui sont des fonctions que le compilateur gère "spécialement" en les convertissant en code spécial que le CPU peut comprendre). Leur travail consiste à dire au système d'exploitation de faire quelque chose.
Lorsqu'un appel système se produit, une fonction est appelée qui appelle une autre fonction (etc.) jusqu'à ce que le pilote d'affichage soit finalement invité à dessiner quelque chose sur l'écran. À ce stade, le pilote d'affichage examine une région particulière de la mémoire physique qui n'est en fait pas de la mémoire, mais plutôt une plage d'adresses qui peut être écrite comme s'il s'agissait de mémoire. Au lieu de cela, cependant, l'écriture dans cette plage d'adresses entraîne le matériel graphique pour intercepter l'écriture de la mémoire et dessiner quelque chose sur l'écran.
Écrire dans cette région de la mémoire est quelque chose qui pourrait être codé en C ++, car du côté logiciel, c'est juste un accès régulier à la mémoire. C'est juste que le matériel le gère différemment.
donc c'est une explication vraiment basique de la façon dont cela peut fonctionner.
la source
syscall
(et son cousinsysenter
) est en effet une instruction CPU.sysenter
chemins d'appel optimisés, car le changement de contexte utilisé par les gestionnaires d'interruptions n'était pas aussi rapide que tout le monde le souhaitait, mais fondamentalement, il s'agit toujours d'une interruption générée par logiciel tandis qu'elle est gérée par vectorisation vers un gestionnaire installé par le noyau du système d'exploitation. Une partie du processus de commutation de contexte qui est effectuée par lasysenter
modification des bits de mode dans le processeur pour définir l'anneau 0 - accès complet à toutes les instructions privilégiées, registres et zones de mémoire et d'E / S.Votre programme C ++ utilise la bibliothèque Qt (également codée en C ++). La bibliothèque Qt utilisera la fonction Windows CreateWindowEx (qui a été codée en C dans kernel32.dll). Ou sous Linux, il peut utiliser Xlib (également codé en C), mais il pourrait aussi bien envoyer les octets bruts qui dans le protocole X signifient " Veuillez créer une fenêtre pour moi ".
Liée à votre question de rattrapage est la note historique que «le premier compilateur C ++ a été écrit en C ++», bien qu'en réalité c'était un compilateur C avec quelques notions C ++, assez pour qu'il puisse compiler la première version, qui pourrait ensuite se compiler elle-même .
De même, le compilateur GCC utilise des extensions GCC: il est d'abord compilé dans une version puis utilisé pour se recompiler. (Instructions de construction GCC)
la source
Comment je vois la question, c'est en fait une question de compilation.
Regardez-le de cette façon, vous écrivez un morceau de code dans Assembly (vous pouvez le faire dans n'importe quelle langue) qui traduit votre langage nouvellement écrit que vous voulez appeler Z ++ en Assembly, pour plus de simplicité, appelons-le un compilateur (c'est un compilateur) .
Maintenant, vous donnez à ce compilateur quelques fonctions de base, de sorte que vous pouvez écrire int, chaîne, tableaux, etc. en fait, vous lui donnez suffisamment de capacités pour que vous puissiez écrire le compilateur lui-même en Z ++. et maintenant vous avez un compilateur pour Z ++ écrit en Z ++, très bien à droite.
Ce qui est encore plus cool, c'est que vous pouvez maintenant ajouter des capacités à ce compilateur en utilisant les capacités qu'il possède déjà, étendant ainsi le langage Z ++ avec de nouvelles fonctionnalités en utilisant les fonctionnalités précédentes
Par exemple, si vous écrivez suffisamment de code pour dessiner un pixel dans n'importe quelle couleur, vous pouvez le développer à l'aide du Z ++ pour dessiner tout ce que vous voulez.
la source
Le matériel est ce qui permet que cela se produise. Vous pouvez considérer la mémoire graphique comme un grand tableau (composé de chaque pixel de l'écran). Pour dessiner à l'écran, vous pouvez écrire dans cette mémoire à l'aide de C ++ ou de tout langage permettant un accès direct à cette mémoire. Cette mémoire se trouve être accessible par ou située sur la carte graphique.
Sur les systèmes modernes, l'accès direct à la mémoire graphique nécessiterait l'écriture d'un pilote en raison de diverses restrictions, vous utilisez donc des moyens indirects. Bibliothèques qui créent une fenêtre (vraiment juste une image comme n'importe quelle autre image) puis écrivent cette image dans la mémoire graphique que le GPU affiche ensuite à l'écran. Rien ne doit être ajouté au langage, sauf la possibilité d'écrire dans des emplacements de mémoire spécifiques, ce à quoi servent les pointeurs.
la source