Estoy aprendiendo C ++ y acabo de empezar a aprender sobre algunas de las capacidades de Qt para codificar programas GUI. Me hice la siguiente pregunta:
¿Cómo C ++, que anteriormente no tenía sintaxis capaz de pedirle al sistema operativo una ventana o una forma de comunicarse a través de redes (con API que tampoco entiendo completamente, lo admito) de repente obtiene tales capacidades a través de bibliotecas escritas en C ++? Todo me parece terriblemente circular. ¿Qué instrucciones de C ++ podrías encontrar en esas bibliotecas?
Me doy cuenta de que esta pregunta puede parecer trivial para un desarrollador de software experimentado, pero he estado investigando durante horas sin encontrar ninguna respuesta directa. Llegué al punto en que no puedo seguir el tutorial sobre Qt porque la existencia de bibliotecas es incomprensible para mí.
fuente
Respuestas:
Una computadora es como una cebolla, tiene muchas muchas capas, desde el núcleo interno de hardware puro para la capa de aplicación más externa. Cada capa expone partes de sí misma a la siguiente capa externa, de modo que la capa externa puede usar algunas de las funciones de las capas internas.
En el caso de, por ejemplo, Windows, el sistema operativo expone la llamada API WIN32 para aplicaciones que se ejecutan en Windows. La biblioteca Qt usa esa API para proporcionar aplicaciones que usan Qt a su propia API. Utiliza Qt, Qt usa WIN32, WIN32 usa niveles más bajos del sistema operativo Windows, y así sucesivamente hasta que haya señales eléctricas en el hardware.
fuente
Qt
aquí proporciona una abstracción de la capa debajo de ella, porque en LinuxQt
invoca la API de Linux y no la API WIN32.user32.dll
, o posiblementegdi32.dll
.Tienes razón en que, en general, las bibliotecas no pueden hacer nada posible que ya no sea posible.
Pero las bibliotecas no tienen que estar escritas en C ++ para que un programa de C ++ pueda utilizarlas. Incluso si están escritos en C ++, pueden usar internamente otras bibliotecas que no están escritas en C ++. Por lo tanto, el hecho de que C ++ no proporcionara ninguna forma de hacerlo no evita que se agregue, siempre que haya alguna forma de hacerlo fuera de C ++.
En un nivel bastante bajo, algunas funciones llamadas por C ++ (o por C) se escribirán en conjunto, y el conjunto contiene las instrucciones necesarias para hacer lo que no es posible (o no es fácil) en C ++, por ejemplo para llamar Una función del sistema. En ese punto, esa llamada al sistema puede hacer cualquier cosa que su computadora sea capaz de hacer, simplemente porque no hay nada que la detenga.
fuente
C y C ++ tienen 2 propiedades que permiten toda esta extensibilidad de la que habla el OP.
En el kernel o en una plataforma de modo básico no protegido, los periféricos como el puerto serie o la unidad de disco se asignan al mapa de memoria de la misma manera que la RAM. La memoria es una serie de conmutadores y activar los conmutadores del periférico (como un puerto serie o un controlador de disco) hace que su periférico haga cosas útiles.
En un sistema operativo en modo protegido, cuando se quiere acceder al núcleo desde el espacio de usuario (por ejemplo, cuando se escribe en el sistema de archivos o se dibuja un píxel en la pantalla) hay que hacer una llamada al sistema. C no tiene una instrucción para realizar llamadas a un sistema, pero C puede llamar al código del ensamblador que puede activar la llamada correcta al sistema. Esto es lo que permite que el código C se comunique con el núcleo.
Para facilitar la programación de una plataforma en particular, las llamadas al sistema se envuelven en funciones más complejas que pueden realizar alguna función útil dentro del propio programa. Uno es libre de llamar directamente al sistema (usando el ensamblador), pero probablemente sea más fácil hacer uso de una de las funciones de contenedor que proporciona la plataforma.
Hay otro nivel de API que es mucho más útil que una llamada al sistema. Tomemos por ejemplo malloc. Esto no solo llamará al sistema para obtener grandes bloques de memoria, sino que también administrará esta memoria haciendo todo el mantenimiento de libros sobre lo que está sucediendo.
Las API de Win32 envuelven algunas funciones gráficas con un conjunto de widgets de plataforma común. Qt lleva esto un poco más allá al envolver la API Win32 (o X Windows) de una manera multiplataforma.
Básicamente, aunque un compilador de C convierte el código de C en código de máquina y dado que la computadora está diseñada para usar código de máquina, debe esperar que C pueda lograr el recurso compartido de leones o lo que una computadora puede hacer. Todo lo que hacen las bibliotecas de contenedor es hacer el trabajo pesado por usted para que no tenga que hacerlo.
fuente
Los lenguajes (como C ++ 11 ) son especificaciones , en papel, generalmente escritas en inglés. Mire el último borrador de C ++ 11 (o compre la costosa especificación final de su proveedor de ISO).
Generalmente usa una computadora con alguna implementación de lenguaje (en principio, podría ejecutar un programa C ++ sin ninguna computadora, por ejemplo, usando un montón de esclavos humanos interpretándolo; eso sería poco ético e ineficiente)
Su implementación general de C ++ funciona por encima de algún sistema operativo y se comunica con él (usando algún código específico de implementación , a menudo en alguna biblioteca del sistema). En general, esa comunicación se realiza a través de llamadas del sistema . Busque, por ejemplo, en syscalls (2) para obtener una lista de las llamadas al sistema disponibles en el kernel de Linux .
Desde el punto de vista de la aplicación, un syscall es una instrucción de máquina elemental como
SYSENTER
en x86-64 con algunas convenciones ( ABI )En mi escritorio Linux, las bibliotecas Qt están por encima de las bibliotecas cliente X11 que se comunican con el servidor X11 Xorg a través de los protocolos X Windows .
En Linux, use
ldd
en su ejecutable para ver la lista (larga) de dependencias en las bibliotecas. Úselopmap
en su proceso de ejecución para ver cuáles están "cargados" en tiempo de ejecución. Por cierto, en Linux, su aplicación probablemente esté usando solo software libre, podría estudiar su código fuente (desde Qt, a Xlib, libc, ... el kernel) para comprender más lo que está sucediendofuente
Creo que el concepto que te estás perdiendo son las llamadas al sistema . Cada sistema operativo proporciona una enorme cantidad de recursos y funcionalidades que puede aprovechar para hacer cosas relacionadas con el sistema operativo de bajo nivel. Incluso cuando llama a una función de biblioteca normal, probablemente esté haciendo una llamada al sistema detrás de escena.
Las llamadas al sistema son una forma de bajo nivel de hacer uso de la potencia del sistema operativo, pero pueden ser complejas y engorrosas de usar, por lo que a menudo están "envueltas" en API para que no tenga que lidiar con ellas directamente. Pero debajo, casi cualquier cosa que haga que involucre recursos relacionados con O / S utilizará llamadas al sistema, incluida la impresión, la conexión en red y los sockets, etc.
En el caso de Windows, Microsoft Windows tiene su GUI realmente escrita en el kernel, por lo que hay llamadas del sistema para hacer ventanas, pintar gráficos, etc. En otros sistemas operativos, la GUI puede no ser parte del kernel, en cuyo caso por lo que sé, no habría ninguna llamada al sistema para cosas relacionadas con la GUI, y solo podría trabajar en un nivel aún más bajo con los gráficos de bajo nivel y las llamadas relacionadas con la entrada disponibles.
fuente
Buena pregunta. Todo desarrollador nuevo de C o C ++ tiene esto en mente. Asumo una máquina x86 estándar para el resto de esta publicación. Si está utilizando el compilador de Microsoft C ++, abra su bloc de notas y escriba esto (nombre el archivo Test.c)
Y ahora compile este archivo (usando el símbolo del sistema del desarrollador) cl Test.c /FaTest.asm
Ahora abra Test.asm en su bloc de notas. Lo que ves es el código traducido: C / C ++ se traduce al ensamblador. ¿Entiendes la pista?
Los programas C / C ++ están diseñados para ejecutarse en el metal. Lo que significa que tienen acceso a hardware de nivel inferior, lo que facilita la explotación de las capacidades del hardware. Digamos que voy a escribir una biblioteca C getch () en una máquina x86.
Dependiendo del ensamblador, escribiría algo de esta manera:
Lo ejecuto con un ensamblador y genero un .OBJ: nómbralo getch.obj.
Luego escribo un programa en C (no # incluyo nada)
Ahora nombre este archivo: GetChTest.c. Compile este archivo pasando getch.obj. (O compile individualmente en .obj y LINK GetChTest.Obj y getch.Obj juntos para producir GetChTest.exe).
Ejecute GetChTest.exe y encontrará que espera la entrada del teclado.
La programación C / C ++ no se trata solo del lenguaje. Para ser un buen programador de C / C ++, debe tener una buena comprensión del tipo de máquina que ejecuta. Necesitará saber cómo se maneja la administración de la memoria, cómo están estructurados los registros, etc. Es posible que no necesite toda esta información para la programación regular, pero lo ayudarían inmensamente. Además del conocimiento básico del hardware, ciertamente ayuda si comprende cómo funciona el compilador (es decir, cómo se traduce), lo que podría permitirle modificar su código según sea necesario. Es un paquete interesante!
Ambos idiomas admiten la palabra clave __asm, lo que significa que también puede mezclar su código de lenguaje ensamblador. Aprender C y C ++ te convertirá en un programador más completo en general.
No es necesario vincularse siempre con Assembler. Lo mencioné porque pensé que eso te ayudaría a entender mejor. En su mayoría, la mayoría de las llamadas a la biblioteca hacen uso de las llamadas al sistema / API proporcionadas por el sistema operativo (el sistema operativo a su vez hace las cosas de interacción de hardware).
fuente
No hay nada mágico en usar otras bibliotecas. Las bibliotecas son grandes bolsas simples de funciones a las que puede llamar.
Considérate escribiendo una función como esta
Ahora, si incluye ese archivo, puede escribir
addExclamation(myVeryOwnString);
. Ahora puede preguntarse, "¿cómo C ++ de repente obtuvo la capacidad de agregar signos de exclamación a una cadena?" La respuesta es fácil: escribiste una función para hacer eso y luego la llamaste.Entonces, para responder a su pregunta sobre cómo C ++ puede obtener capacidades para dibujar ventanas a través de bibliotecas escritas en C ++, la respuesta es la misma. Alguien más escribió funciones para hacer eso, y luego las compiló y se las dio en forma de biblioteca.
Las otras preguntas responden cómo funciona realmente el dibujo de la ventana, pero parecía confundido sobre cómo funcionan las bibliotecas, por lo que quería abordar la parte más fundamental de su pregunta.
fuente
La clave es la posibilidad del sistema operativo de exponer una API y una descripción detallada de cómo se utilizará esta API.
El sistema operativo ofrece un conjunto de API con convenciones de llamada. La convención de llamada define la forma en que se proporciona un parámetro en la API y cómo se devuelven los resultados y cómo ejecutar la llamada real.
Los sistemas operativos y los compiladores que crean código para ellos juegan muy bien juntos, por lo que generalmente no tiene que pensarlo, solo úselo.
fuente
No hay necesidad de una sintaxis especial para crear ventanas. Todo lo que se requiere es que el sistema operativo proporcione una API para crear ventanas. Dicha API consiste en llamadas a funciones simples para las que C ++ proporciona sintaxis.
Además, C y C ++ se denominan lenguajes de programación de sistemas y pueden acceder a punteros arbitrarios (que el hardware podría asignar a algún dispositivo). Además, también es bastante simple llamar a las funciones definidas en el ensamblaje, lo que permite la gama completa de operaciones que proporciona el procesador. Por lo tanto, es posible escribir un sistema operativo en sí mismo usando C o C ++ y una pequeña cantidad de ensamblaje.
También debería mencionarse que Qt es un mal ejemplo, ya que utiliza un llamado meta compilador para extender la sintaxis de C ++. Sin embargo, esto no está relacionado con su capacidad de llamar a las API proporcionadas por el sistema operativo para dibujar o crear ventanas.
fuente
Primero, creo que hay un pequeño malentendido
No hay sintaxis para realizar operaciones del sistema operativo. Es la cuestión de la semántica .
Bueno, el sistema operativo está escrito principalmente en C. Puede usar bibliotecas compartidas (dll) para llamar al código externo. Además, el código del sistema operativo puede registrar rutinas del sistema en syscalls * o interrupciones a las que puede llamar mediante ensamblaje . Las bibliotecas compartidas a menudo solo hacen que el sistema lo llame, por lo que se ahorra el uso del ensamblaje en línea.
Aquí está el buen tutorial sobre eso: http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
Es para Linux, pero los principios son los mismos.
¿Cómo está haciendo el sistema operativo operaciones en tarjetas gráficas, tarjetas de red, etc.? Es un tema muy amplio, pero principalmente necesita acceder a interrupciones, puertos o escribir algunos datos en una región de memoria especial. Como esas operaciones están protegidas, debe llamarlas a través del sistema operativo de todos modos.
fuente
En un intento de proporcionar una visión ligeramente diferente a otras respuestas, responderé así.
(Descargo de responsabilidad: estoy simplificando un poco las cosas, la situación que doy es puramente hipotética y está escrita como un medio para demostrar conceptos en lugar de ser 100% fiel a la vida).
Piense en las cosas desde la otra perspectiva, imagine que acaba de escribir un sistema operativo simple con capacidades básicas de enhebrado, ventanas y administración de memoria. Desea implementar una biblioteca C ++ para permitir que los usuarios programen en C ++ y hagan cosas como hacer ventanas, dibujar en ventanas, etc. La pregunta es cómo hacer esto.
En primer lugar, dado que C ++ compila el código de la máquina, debe definir una forma de utilizar el código de la máquina para interactuar con C ++. Aquí es donde entran las funciones, las funciones aceptan argumentos y dan valores de retorno, por lo que proporcionan una forma estándar de transferir datos entre diferentes secciones de código. Lo hacen estableciendo algo conocido como una convención de convocatoria .
Una convención de llamada establece dónde y cómo deben colocarse los argumentos en la memoria para que una función pueda encontrarlos cuando se ejecuta. Cuando se llama a una función, la función de llamada coloca los argumentos en la memoria y luego le pide a la CPU que salte a la otra función, donde hace lo que hace antes de volver a donde fue llamada. Esto significa que el código que se llama puede ser absolutamente cualquier cosa y no cambiará cómo se llama la función. Sin embargo, en este caso, el código detrás de la función sería relevante para el sistema operativo y funcionaría en el estado interno del sistema operativo.
Entonces, muchos meses después y tienes todas tus funciones del sistema operativo ordenadas. Su usuario puede llamar a funciones para crear ventanas y dibujar en ellas, puede hacer hilos y todo tipo de cosas maravillosas. Sin embargo, aquí está el problema, las funciones de su sistema operativo serán diferentes a las funciones de Linux o de Windows. Por lo tanto, decide que necesita darle al usuario una interfaz estándar para que pueda escribir código portátil. Aquí es donde entra QT.
Como es casi seguro que sabe, QT tiene muchas clases y funciones útiles para hacer las cosas que hacen los sistemas operativos, pero de una manera que parece independiente del sistema operativo subyacente. La forma en que esto funciona es que QT proporciona clases y funciones que son uniformes en la forma en que aparecen para el usuario, pero el código detrás de las funciones es diferente para cada sistema operativo. Por ejemplo, QApplication :: closeAllWindows () de QT realmente llamaría a la función de cierre de ventana especializada de cada sistema operativo, dependiendo de la versión utilizada. En Windows, lo más probable es que llame a CloseWindow (hwnd), mientras que en un sistema operativo que usa el sistema X Window, podría llamar a XDestroyWindow (pantalla, ventana).
Como es evidente, un sistema operativo tiene muchas capas, todas las cuales deben interactuar a través de interfaces de muchas variedades. Hay muchos aspectos que ni siquiera he mencionado, pero explicarlos llevaría mucho tiempo. Si está más interesado en el funcionamiento interno de los sistemas operativos, le recomiendo consultar el wiki de desarrollo del sistema operativo .
Sin embargo, tenga en cuenta que la razón por la que muchos sistemas operativos eligen exponer las interfaces a C / C ++ es que compilan el código de la máquina, permiten que las instrucciones de ensamblaje se mezclen con su propio código y brindan un gran grado de libertad al programador.
Una vez más, están pasando muchas cosas aquí. Me gustaría continuar explicando cómo las bibliotecas como .so y .dll no tienen que escribirse en C / C ++ y pueden escribirse en ensamblador u otros lenguajes, pero siento que si agrego más, también podría escribir un artículo completo, y por mucho que me gustaría hacer eso, no tengo un sitio para alojarlo.
fuente
Cuando intenta dibujar algo en la pantalla, su código llama a otro código que llama a otro código (etc.) hasta que finalmente hay una "llamada al sistema", que es una instrucción especial que la CPU puede ejecutar. Estas instrucciones se pueden escribir en ensamblador o en C ++ si el compilador admite sus "intrínsecos" (que son funciones que el compilador maneja "especialmente" al convertirlas en un código especial que la CPU puede entender). Su trabajo es decirle al sistema operativo que haga algo.
Cuando ocurre una llamada al sistema, se llama a una función que llama a otra función (etc.) hasta que finalmente se le indica al controlador de pantalla que dibuje algo en la pantalla. En ese punto, el controlador de pantalla observa una región particular en la memoria física que en realidad no es memoria, sino más bien un rango de direcciones en el que se puede escribir como si fuera memoria. Sin embargo, escribir en ese rango de direcciones hace que el hardware de gráficos intercepte la escritura de la memoria y dibuje algo en la pantalla.
Escribir en esta región de memoria es algo que podría codificarse en C ++, ya que en el lado del software es solo un acceso de memoria normal. Es solo que el hardware lo maneja de manera diferente.
una explicación realmente básica de cómo puede funcionar.
fuente
syscall
(y su primosysenter
) es de hecho una instrucción de CPU.sysenter
rutas de llamadas optimizadas, ya que el cambio de contexto utilizado por los manejadores de interrupciones no fue tan rápido como todos deseaban, pero fundamentalmente sigue siendo una interrupción generada por software mientras se maneja mediante vectorización a un manejador instalado por el núcleo del sistema operativo. Parte del proceso de cambio de contexto que IS realizasysenter
es cambiar los bits de modo en el procesador para establecer el anillo 0: acceso completo a todas las instrucciones privilegiadas, registros y áreas de memoria y E / S.Su programa C ++ está utilizando la biblioteca Qt (también codificada en C ++). La biblioteca Qt utilizará la función Windows CreateWindowEx (que se codificó en C dentro de kernel32.dll). O en Linux puede estar usando Xlib (también codificado en C), pero también podría estar enviando los bytes sin procesar que en el protocolo X significan " Por favor, cree una ventana para mí ".
Relacionado con su pregunta catch-22 está la nota histórica de que "el primer compilador de C ++ fue escrito en C ++", aunque en realidad era un compilador de C con algunas nociones de C ++, lo suficiente como para poder compilar la primera versión, que luego podría compilarse .
De manera similar, el compilador GCC usa extensiones GCC: primero se compila en una versión y luego se usa para recompilarse. (Instrucciones de compilación de GCC)
fuente
Como veo la pregunta, esta es en realidad una pregunta del compilador.
Míralo de esta manera, escribes un fragmento de código en ensamblador (puedes hacerlo en cualquier idioma) que traduce tu lenguaje recién escrito al que quieres llamar Z ++ en ensamblador, por simplicidad vamos a llamarlo compilador (es un compilador) .
Ahora le da a este compilador algunas funciones básicas, para que pueda escribir int, string, arrays, etc. en realidad le da suficientes habilidades para que pueda escribir el compilador en Z ++. y ahora tienes un compilador para Z ++ escrito en Z ++, bastante bien.
Lo que es aún mejor es que ahora puedes agregar habilidades a ese compilador usando las habilidades que ya tiene, expandiendo así el lenguaje Z ++ con nuevas funciones usando las funciones anteriores
Un ejemplo, si escribe suficiente código para dibujar un píxel en cualquier color, puede expandirlo usando Z ++ para dibujar lo que quiera.
fuente
El hardware es lo que permite que esto suceda. Puede pensar en la memoria de gráficos como una gran matriz (que consta de cada píxel en la pantalla). Para dibujar en la pantalla, puede escribir en esta memoria usando C ++ o cualquier lenguaje que permita el acceso directo a esa memoria. Esa memoria resulta accesible o ubicada en la tarjeta gráfica.
En los sistemas modernos, acceder a la memoria gráfica directamente requeriría escribir un controlador debido a varias restricciones, por lo que debe utilizar medios indirectos. Bibliotecas que crean una ventana (realmente solo una imagen como cualquier otra imagen) y luego escriben esa imagen en la memoria gráfica que la GPU muestra en la pantalla. No se debe agregar nada al idioma, excepto la capacidad de escribir en ubicaciones de memoria específicas, para lo que sirven los punteros.
fuente