Na tentativa de fornecer uma visão ligeiramente diferente para outras respostas, responderei dessa maneira.
(Isenção de responsabilidade: estou simplificando um pouco as coisas, a situação apresentada é puramente hipotética e foi escrita como um meio de demonstrar conceitos, em vez de ser 100% fiel à vida).
Pense nas coisas de outra perspectiva, imagine que você acabou de escrever um sistema operacional simples com recursos básicos de segmentação, janelas e gerenciamento de memória. Você deseja implementar uma biblioteca C ++ para permitir que os usuários programem em C ++ e façam coisas como criar janelas, atrair janelas etc. A questão é: como fazer isso.
Primeiro, como o C ++ é compilado no código da máquina, você precisa definir uma maneira de usar o código da máquina para fazer interface com o C ++. É aqui que as funções entram, as funções aceitam argumentos e fornecem valores de retorno, portanto, elas fornecem uma maneira padrão de transferir dados entre diferentes seções do código. Eles fazem isso estabelecendo algo conhecido como convenção de chamada .
Uma convenção de chamada indica onde e como os argumentos devem ser colocados na memória para que uma função possa encontrá-los quando for executada. Quando uma função é chamada, a função de chamada coloca os argumentos na memória e solicita à CPU que pule para a outra função, onde faz o que faz antes de voltar para onde foi chamada. Isso significa que o código que está sendo chamado pode ser absolutamente qualquer coisa e não muda como a função é chamada. Nesse caso, no entanto, o código por trás da função seria relevante para o sistema operacional e operaria no estado interno do sistema operacional.
Portanto, muitos meses depois, você terá todas as suas funções de sistema operacional resolvidas. Seu usuário pode chamar funções para criar janelas e desenhar nelas, pode criar threads e todo tipo de coisas maravilhosas. Mas aqui está o problema: as funções do seu sistema operacional serão diferentes das funções do Linux ou do Windows. Portanto, você decide fornecer ao usuário uma interface padrão para que ele possa escrever um código portátil. Aqui é onde entra o QT.
Como você quase certamente sabe, o QT possui muitas classes e funções úteis para realizar as tarefas que os sistemas operacionais fazem, mas de uma maneira que parece independente do sistema operacional subjacente. A maneira como isso funciona é que o QT fornece classes e funções uniformes na aparência para o usuário, mas o código por trás das funções é diferente para cada sistema operacional. Por exemplo, QApplication :: closeAllWindows () do QT estaria chamando a função de fechamento de janela especializada de cada sistema operacional, dependendo da versão usada. No Windows, provavelmente chamaria CloseWindow (hwnd), enquanto em um sistema operacional usando o X Window System, potencialmente chamaria XDestroyWindow (exibição, janela).
Como é evidente, um sistema operacional possui muitas camadas, todas as quais precisam interagir através de interfaces de várias variedades. Há muitos aspectos em que eu nem mencionei, mas explicar todos eles levaria muito tempo. Se você ainda estiver interessado no funcionamento interno dos sistemas operacionais, recomendo consultar o wiki do desenvolvedor do OS .
Lembre-se, porém, que a razão pela qual muitos sistemas operacionais optam por expor interfaces ao C / C ++ é que eles compilam com código de máquina, permitem que as instruções de montagem sejam misturadas com seu próprio código e oferecem um grande grau de liberdade ao programador.
Novamente, há muita coisa acontecendo aqui. Gostaria de continuar explicando como as bibliotecas como arquivos .so e .dll não precisam ser escritas em C / C ++ e podem ser escritas em assembly ou em outras linguagens, mas acho que, se eu adicionar mais, também devo escreva um artigo inteiro e, por mais que eu adorasse, não tenho um site para hospedá-lo.