我正在学习C ++,而我刚刚开始学习Qt编写GUI程序的一些功能。我问自己以下问题:
以前没有语法能够向操作系统询问操作系统窗口或通过网络进行通信的方式的C ++(我承认,我不完全理解的API)突然得到了这样的能力通过用C ++编写的库本身?这对我来说似乎非常循环。你可以在这些库中提出哪些C ++指令?
我意识到这个问题对于经验丰富的软件开发人员来说似乎微不足道,但我已经研究了几个小时而没有找到任何直接的回应。它已经达到了我无法遵循关于Qt的教程的程度,因为库的存在对我来说是不可理解的。
答案 0 :(得分:192)
计算机就像一个洋葱,它有许多许多层,从纯硬件的内核到最外层的应用层。每个层将自身的一部分暴露给下一个外层,以便外层可以使用一些内层功能。
例如Windows操作系统为在Windows上运行的应用程序公开了所谓的WIN32 API。 Qt库使用该API为使用Qt的应用程序提供自己的API。你使用Qt,Qt使用WIN32,WIN32使用较低级别的Windows操作系统,依此类推,直到硬件中的电子信号为止。
答案 1 :(得分:60)
你是对的,一般来说,图书馆无法做出任何可能的事情。
但是库不必用C ++编写,以便可以被C ++程序使用。即使它们是用C ++编写的,它们也可能在内部使用其他不是用C ++编写的库。因此,C ++没有提供任何方法来实现它并不会阻止它被添加,只要有某种方式在C ++之外完成它。
在一个相当低的层次上,C ++(或C)调用的一些函数将用汇编语言编写,汇编包含所需的指令,用于在C ++中执行任何不可能(或不容易)的操作,调用系统函数的示例。此时,系统调用可以执行您的计算机能够执行的任何操作,因为没有任何东西可以阻止它。
答案 2 :(得分:43)
C和C ++有2个属性,允许OP讨论的所有这些可扩展性。
在内核或基本的非保护模式平台中,串行端口或磁盘驱动器等外设以与RAM相同的方式映射到内存映射中。内存是一系列交换机,翻转外围设备的交换机(如串口或磁盘驱动程序)可以让外围设备做有用的事情。
在受保护模式的操作系统中,当想要从用户空间访问内核时(比如写入文件系统或在屏幕上绘制像素时),需要进行系统调用。 C没有指令进行系统调用,但C可以调用汇编程序代码来触发正确的系统调用,这就是允许C代码与内核通信的原因。
为了使特定平台的编程更容易,系统调用包含在更复杂的函数中,这些函数可以在自己的程序中执行一些有用的功能。可以直接调用系统调用(使用汇编程序),但可能更容易使用平台提供的其中一个包装函数。
还有另一个级别的API比系统调用更有用。以malloc为例。这不仅会调用系统来获取大块内存,而且会通过对所发生的事情进行所有记录来管理这个内存。
Win32 API使用通用平台窗口小部件集包装一些图形功能。 Qt通过跨平台方式包装Win32(或X Windows)API来进一步解决这个问题。
基本上,虽然C编译器将C代码转换为机器代码,并且由于计算机设计为使用机器代码,因此您应该期望C能够完成狮子会共享或计算机可以执行的操作。包装库所做的一切都是为你做的繁重工作,所以你不必这样做。
答案 3 :(得分:23)
语言(如C++11)是规范,在纸上,通常用英文书写。查看最新的C++11 draft(或从ISO供应商处购买昂贵的final spec)。
您通常使用具有某种语言实现的计算机(您原则上可以在没有任何计算机的情况下运行C ++程序,例如使用一堆解释它的人类奴隶;这将是不道德和低效的)< / p>
您的C ++实现通用工作在某些操作系统之上并与之通信(使用某些实现特定的代码,通常在某些系统库中)。通常,通信是通过system calls完成的。查找syscalls(2)中的实例,查看Linux kernel上可用的系统调用列表。
从应用程序的角度来看,系统调用是一个基本的机器指令,如x86-64上的SYSENTER
,带有一些约定(ABI)
在我的Linux桌面上,Qt库位于X11个客户端库之上,与{11}} Xorg的X11服务器进行通信。
在Linux上,在可执行文件上使用ldd
来查看库的依赖项(长)列表。在正在运行的进程中使用pmap
来查看哪些是&#34;已加载&#34;在运行时。顺便说一下,在Linux上,你的应用程序可能只使用免费软件,你可以研究它的源代码(从Qt到Xlib,libc,......内核)来了解更多正在发生的事情
答案 4 :(得分:19)
我认为您缺少的概念是system calls。每个操作系统都提供了大量的资源和功能,您可以利用这些资源和功能来执行与操作系统相关的低级操作。即使您调用常规库函数,它也可能在幕后进行系统调用。
系统调用是一种利用操作系统强大功能的低级方法,但使用起来既复杂又麻烦,因此常常会被包裹起来。在API中,这样您就不必直接处理它们。但是在下面,你所做的涉及O / S相关资源的任何事情都将使用系统调用,包括打印,网络和套接字等。
在Windows的情况下,Microsoft Windows将其GUI实际写入内核,因此存在用于制作窗口,绘制图形等的系统调用。在其他操作系统中,GUI可能不是内核的一部分,在这种情况下,据我所知,不会有任何系统调用GUI相关的东西,你只能在更低级别工作,无论任何低级图形和输入相关的调用是可用的。
答案 5 :(得分:15)
好问题。每个新的C或C ++开发人员都会考虑到这一点。我正在为这篇文章的其余部分假设一个标准的x86机器。如果您使用的是Microsoft C ++编译器,请打开记事本并键入(将文件命名为Test.c)
int main(int argc, char **argv)
{
return 0
}
现在编译此文件(使用开发人员命令提示符)cl Test.c /FaTest.asm
现在在你的记事本中打开Test.asm。你看到的是翻译的代码 - C / C ++被翻译成汇编程序。你有提示吗?
_main PROC
push ebp
mov ebp, esp
xor eax, eax
pop ebp
ret 0
_main ENDP
C / C ++程序旨在运行在金属上。这意味着他们可以访问更低级别的硬件,从而更容易利用硬件的功能。说,我打算在x86机器上写一个C库getch()。
根据汇编程序的不同,我会这样输入:
_getch proc
xor AH, AH
int 16h
;AL contains the keycode (AX is already there - so just return)
ret
我用汇编程序运行它并生成一个.OBJ - 将它命名为getch.obj。
然后我写了一个C程序(我不#include任何东西)
extern char getch();
void main(int, char **)
{
getch();
}
现在命名此文件 - GetChTest.c。通过传递getch.obj来编译此文件。 (或者单独编译为.obj和LINK GetChTest.Obj和getch.Obj一起生成GetChTest.exe。)
运行GetChTest.exe,你会发现它等待键盘输入。
C / C ++编程不仅仅与语言有关。要成为一名优秀的C / C ++程序员,您需要对其运行的机器类型有一个很好的理解。您将需要知道如何处理内存管理,如何构建寄存器等等。您可能不需要所有这些信息用于常规编程 - 但它们会极大地帮助您。除了基本的硬件知识之外,如果您了解编译器的工作原理(即,它是如何翻译的),它肯定会有所帮助 - 这可以让您根据需要调整代码。这是一个有趣的包!
两种语言都支持__asm关键字,这意味着您也可以混合使用汇编语言代码。学习C和C ++将使你成为一个更好的整体程序员。
没有必要始终与Assembler链接。我曾经提到它,因为我认为这会帮助你更好地理解。大多数情况下,大多数此类库调用都使用操作系统提供的系统调用/ API(操作系统反过来会执行硬件交互)。
答案 6 :(得分:10)
C ++如何通过库突然获得这样的功能 用C ++自己编写?
使用其他库并不神奇。图书馆是你可以打电话的简单大包。
考虑自己编写一个像这样的函数
void addExclamation(std::string &str)
{
str.push_back('!');
}
现在,如果您包含该文件,则可以编写addExclamation(myVeryOwnString);
。现在您可能会问,&#34; C ++是如何突然获得向字符串添加感叹号的功能的?&#34;答案很简单:你写了一个函数,然后你调用它。
所以回答你关于C ++如何通过用C ++编写的库来绘制窗口的能力的问题,答案是一样的。其他人写了一些函数来编写它们,然后编译它们并以库的形式发给你。
其他问题回答了窗口绘图的实际效果,但你对图书馆的运作方式感到困惑,所以我想解决你问题中最基本的部分。
答案 7 :(得分:8)
关键是操作系统可能会公开API以及有关如何使用此API的详细说明。
操作系统提供一组具有调用约定的API。 调用约定是定义参数给API的方式,以及如何返回结果以及如何执行实际调用。
操作系统和为它们创建代码的编译器可以很好地协同工作,因此您通常不必考虑它,只需使用它。
答案 8 :(得分:7)
创建窗口不需要特殊的语法。所需要的只是操作系统提供了一个API来创建窗口。这样的API由简单的函数调用组成,C ++确实提供了语法。
此外,C和C ++是所谓的系统编程语言,能够访问任意指针(可能由硬件映射到某些设备)。此外,调用程序集中定义的函数也非常简单,它允许处理器提供的全部操作。因此,可以使用C或C ++以及少量的汇编来编写OS本身。
还应该提到Qt是一个不好的例子,因为它使用所谓的元编译器扩展 C ++'语法。然而,这与它调用操作系统提供的API以实际绘制或创建窗口的能力无关。
答案 9 :(得分:7)
首先,我认为有一点误解,
以前没有语法能够向操作系统询问窗口或通过网络进行通信的C ++如何?
执行操作系统操作时没有语法。这是语义的问题。
突然通过用C ++编写的库来获得这样的功能
好吧,操作系统主要用C编写。您可以使用共享库(因此,dll)来调用外部代码。此外,操作系统代码可以在 syscalls * 或中断上注册系统例程,您可以使用程序集调用它们。共享库通常只是让系统为您调用,因此您可以使用内联汇编。
以下是关于此的精彩教程:http://www.win.tue.nl/~aeb/linux/lk/lk-4.html
这是针对Linux的,但原则是一样的。
操作系统如何对图形卡,网卡等进行操作?这是一个非常广泛的主题,但大多数情况下你需要访问中断,端口或将一些数据写入特殊的内存区域。由于这些操作受到保护,因此无论如何都需要通过操作系统调用它们。
答案 10 :(得分:7)
为了对其他答案提供略微不同的观点,我将这样回答。
(免责声明:我稍微简化了一些事情,我给出的情况纯粹是假设的,并且是作为一种展示概念而不是100%真实生活的方式而写的。)
从另一个角度思考问题,想象一下你刚刚编写了一个具有基本线程,窗口和内存管理功能的简单操作系统。你想实现一个C ++库,让用户在C ++中编程,并做一些事情,如制作窗口,绘制到窗口等等。问题是,如何做到这一点。
首先,由于C ++编译为机器代码,因此需要定义一种使用机器代码与C ++接口的方法。这是函数的来源,函数接受参数并给出返回值,因此它们提供了在不同代码段之间传输数据的标准方法。他们通过建立一个称为调用约定的东西来实现这一点。
调用约定指出参数应放在内存中的位置和方式,以便函数在执行时可以找到它们。当一个函数被调用时,调用函数将参数放在内存中,然后要求CPU跳转到另一个函数,在跳转回调用函数之前它会执行它所执行的操作。这意味着被调用的代码绝对是任何东西,它不会改变函数的调用方式。但是,在这种情况下,函数背后的代码将与操作系统相关,并将在操作系统的内部状态下运行。
所以,几个月后,你已经整理了所有的OS功能。您的用户可以调用函数来创建窗口并绘制它们,它们可以创建线程和各种奇妙的东西。这是问题所在,你的操作系统的功能将与Linux的功能或Windows的功能不同。因此,您决定需要为用户提供标准接口,以便他们可以编写可移植代码。这就是QT的用武之地。
正如您几乎可以肯定的那样,QT拥有大量有用的类和函数来执行操作系统所做的各种事情,但其方式与底层操作系统无关。这种方式的工作方式是QT提供的类和函数在用户看来是一致的,但函数背后的代码对于每个操作系统都是不同的。例如,QT的QApplication :: closeAllWindows()实际上将根据使用的版本调用每个操作系统的专用窗口关闭功能。在Windows中,它很可能会调用CloseWindow(hwnd),而在使用X Window系统的操作系统上,它可能会调用XDestroyWindow(显示,窗口)。
很明显,操作系统有很多层,所有层都必须通过多种接口进行交互。有许多方面我甚至没有涉及,但解释它们都需要很长时间。如果您对操作系统的内部工作方式更感兴趣,我建议您查看OS dev wiki。
请记住,许多操作系统选择向C / C ++公开接口的原因是它们编译为机器代码,它们允许汇编指令与自己的代码混合在一起,它们提供了很大的自由度。程序员。
同样,这里有很多事情要做。我想继续解释像.so和.dll文件这样的库不必用C / C ++编写,可以用汇编语言或其他语言编写,但我觉得如果我再添加,我也可以写一整篇文章,尽管我很乐意这么做,但我没有网站来托管它。
答案 11 :(得分:6)
当您尝试在屏幕上绘制某些内容时,您的代码会调用其他一些代码来调用其他代码(等等)直到最后有一个&#34;系统调用&#34;这是一个特殊的指令CPU可以执行。这些指令可以用汇编语言编写,也可以用C ++编写,如果编译器支持他们的&#34; intrinsics&#34; (这些是编译器处理的函数&#34;特别是#34;通过将它们转换为CPU可以理解的特殊代码)。他们的工作是告诉操作系统做些什么。
当系统调用发生时,调用一个调用另一个函数(等)的函数,直到最后显示驱动程序被告知在屏幕上绘制某些东西。此时,显示驱动程序会查看物理内存中的特定区域,该区域实际上是不是内存,而是可以写入的地址范围,就好像这是记忆。但是,写入该地址范围会导致图形硬件拦截内存写入,并在屏幕上绘制内容。
写入这个内存区域是可以用C ++编码的东西,因为在软件方面它只是一个常规的内存访问。只是硬件处理它的方式不同
这是对它如何运作的一个非常基本的解释。
答案 12 :(得分:4)
你的C ++程序正在使用Qt库(也用C ++编写)。 Qt库将使用Windows CreateWindowEx函数(在kernel32.dll中的C编码)。或者在Linux下它可能正在使用Xlib(也用C编码),但它也可以发送X协议中的原始字节意味着&#34; 请为我创建一个窗口&#34;
与 catch-22 相关的问题是“第一个C ++编译器是用C ++编写的”的历史记录,虽然实际上它是一个带有一些C ++概念的C编译器,足够了它可以编译第一个版本,然后可以自行编译。
类似地,GCC编译器使用GCC扩展:它首先被编译为一个版本,然后用于重新编译自己。 (GCC build instructions)
答案 13 :(得分:2)
我怎么看问题这实际上是一个编译器问题。
以这种方式看待它,你在Assembly中编写了一段代码(你可以用任何语言编写),它将你想要调用Z ++的新编写的语言翻译成Assembly,为简单起见,我们称之为编译器(它是编译器)。
现在你给这个编译器一些基本的函数,这样你就可以编写int,string,arrays等。实际上你给它足够的能力,这样你就可以用Z ++编写编译器了。现在你有一个用Z ++编写的Z ++编译器,非常整洁。
更酷的是,现在你可以使用它已有的功能为该编译器添加功能,从而通过使用以前的功能扩展Z ++语言的新功能
例如,如果您编写足够的代码来绘制任何颜色的像素,那么您可以使用Z ++将其展开以绘制您想要的任何内容。
答案 14 :(得分:0)
硬件是允许这种情况发生的原因。您可以将图形内存视为一个大型数组(由屏幕上的每个像素组成)。要绘制到屏幕,您可以使用C ++或任何允许直接访问该内存的语言写入此内存。该内存恰好可以通过显卡访问或位于显卡上。
在现代系统上直接访问图形内存需要编写驱动程序,因为有各种限制因此您使用间接方法。创建窗口的库(实际上只是像任何其他图像一样的图像),然后将该图像写入GPU然后在屏幕上显示的图形内存。除了能够写入特定的内存位置之外,没有什么必须添加到语言中,这就是指针的用途。