我正在学习C ++,并且刚刚开始学习Qt的一些功能来编写GUI程序。我问自己以下问题:
以前没有语法能够向操作系统询问窗口或通过网络进行通信的方法(我承认我也不完全理解的API)的C ++突然如何通过用C ++自己编写的库获得这种功能?对我来说,一切似乎都是圆圆的。您可能在这些库中提出了哪些C ++指令?
我意识到对于一个经验丰富的软件开发人员来说,这个问题似乎微不足道,但是我已经进行了数小时的研究,没有找到任何直接的答案。到了我无法理解有关Qt的教程的地步,因为库的存在对我来说是不可理解的。
我正在学习C ++,并且刚刚开始学习Qt的一些功能来编写GUI程序。我问自己以下问题:
以前没有语法能够向操作系统询问窗口或通过网络进行通信的方法(我承认我也不完全理解的API)的C ++突然如何通过用C ++自己编写的库获得这种功能?对我来说,一切似乎都是圆圆的。您可能在这些库中提出了哪些C ++指令?
我意识到对于一个经验丰富的软件开发人员来说,这个问题似乎微不足道,但是我已经进行了数小时的研究,没有找到任何直接的答案。到了我无法理解有关Qt的教程的地步,因为库的存在对我来说是不可理解的。
Answers:
一种计算机像一个洋葱,它有许多许多层,从纯硬件的内芯到最外面的应用层。每一层将其自身的部分暴露给下一个外层,以便外层可以使用某些内层功能。
在Windows的情况下,操作系统为Windows上运行的应用程序公开所谓的WIN32 API。Qt库使用该API向其自己的API提供使用Qt的应用程序。您使用Qt,Qt使用WIN32,WIN32使用较低级别的Windows操作系统,依此类推,直到硬件中出现电信号为止。
Qt这里提供了其下一层的抽象,因为在Linux上Qt调用Linux API而不是WIN32 API。
                    user32.dll,或者可能gdi32.dll。
                    没错,一般来说,库无法使不可能实现的一切变为可能。
但是不必为了使用C ++程序就可以用C ++编写这些库。即使它们是用C ++编写的,它们也可能在内部使用其他非C ++编写的库。因此,只要有某种方法可以在C ++之外执行,C ++不会提供任何方法来阻止添加它。
在相当低的水平上,某些C ++(或C)调用的函数将用汇编语言编写,并且该汇编包含必需的指令,以执行C ++中不可能(或不容易)的事情,例如调用系统功能。到那时,该系统调用可以执行您的计算机能够执行的所有操作,仅仅是因为没有什么可以停止它。
C和C ++具有2个属性,这些属性允许OP谈论所有这些扩展性。
在内核或基本的非保护模式平台中,外设(例如串行端口或磁盘驱动器)以与RAM相同的方式映射到内存映射中。内存是一系列开关,翻转外围设备的开关(例如串行端口或磁盘驱动器)可使外围设备执行有用的操作。
在受保护模式的操作系统中,如果要从用户空间访问内核(例如,在写入文件系统或在屏幕上绘制像素时),则需要进行系统调用。C没有指令进行系统调用,但是C可以调用可以触发正确系统调用的汇编代码,这就是允许C语言与内核对话的原因。
为了使对特定平台的编程更容易,系统调用被包装在更复杂的函数中,这些函数可以在自己的程序中执行某些有用的功能。一个是免费的,可以直接调用系统调用(使用汇编程序),但是仅使用平台提供的包装器功能之一可能会更容易。
API的另一个层次比系统调用有用得多。以malloc为例。这不仅会调用系统获取大容量的内存,而且还会通过记录所有发生的事情来管理此内存。
Win32 API通过公共平台窗口小部件集包装了一些图形功能。Qt通过跨平台方式包装Win32(或X Windows)API来进一步扩展这一功能。
尽管从根本上说,C编译器将C代码转换为机器代码,并且由于计算机被设计为使用机器代码,所以您应该期望C能够完成最大的任务或计算机可以做什么。包装器库所做的全部工作就是为您完成繁重的工作,因此您不必这样做。
语言(如C ++ 11)是规范,通常在纸上用英语书写。查看最新的C ++ 11草案(或从ISO供应商处购买价格昂贵的最终规范)。
通常,您使用具有某种语言实现的计算机(原则上,您可以在没有任何计算机的情况下运行C ++程序,例如使用一群人类奴隶来解释它;这是不道德且低效的)
您的C ++实现一般可以在某些操作系统上运行并与之通信(通常使用某些系统库中的某些实现特定的代码)。通常,通信是通过系统调用完成的。在syscalls(2)中查找实例,以获取Linux内核上可用的系统调用列表。
从应用程序的角度来看,系统调用是基本的机器指令,例如SYSENTERx86-64上的某些约定(ABI)
在我的Linux桌面上,Qt库位于X11客户端库之上,该客户端库通过X Windows协议与X11服务器Xorg 通信。
在Linux上,ldd在可执行文件上使用以查看库的依赖性列表(较长)。pmap在运行的进程上使用,以查看在运行时“加载”了哪些进程。顺便说一句,在Linux上,您的应用程序可能仅使用免费软件,您可以研究其源代码(从Qt到Xlib,libc等),以了解更多信息。
我认为您所缺少的概念是系统调用。每个操作系统都提供了大量资源和功能,您可以利用这些资源和功能来执行与底层操作系统相关的事情。即使调用常规库函数,它也可能在后台进行系统调用。
系统调用是利用操作系统功能的一种低级方法,但是使用起来可能很复杂且繁琐,因此通常将它们“包装”在API中,这样您就不必直接处理它们。但是在底层,几乎所有与操作系统相关的资源都将使用系统调用,包括打印,联网和套接字等。
对于Windows,Microsoft Windows的GUI实际上已写入内核,因此存在用于制作Windows,绘制图形等的系统调用。在其他操作系统中,GUI可能不是内核的一部分,在这种情况下据我所知,不会有任何与GUI相关的系统调用,并且您只能在更低的级别上使用任何可用的低级图形和输入相关调用。
好问题。每个新的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程序(我不包含任何内容)
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 ++将使您总体上成为一个更全面的程序员。
不必始终与汇编程序链接。我之所以提到它,是因为我认为这可以帮助您更好地理解。通常,大多数此类库调用都使用操作系统提供的系统调用/ API(操作系统依次进行硬件交互)。
C ++如何突然通过自己用C ++编写的库获得这些功能?
使用其他库没有什么神奇的。库是您可以调用的简单的大型函数包。
考虑自己编写这样的函数
void addExclamation(std::string &str)
{
    str.push_back('!');
}
现在,如果包含该文件,则可以编写addExclamation(myVeryOwnString);。现在您可能会问:“ C ++是如何突然获得了将感叹号添加到字符串的功能的?” 答案很简单:您编写了一个函数来执行该操作,然后调用它。
因此,要回答有关C ++如何获得通过C ++编写的库绘制窗口的功能的问题,答案是相同的。其他人编写了函数来执行此操作,然后将其编译并以库的形式提供给您。
其他问题回答了窗口绘图的实际工作方式,但是您对库的工作方式感到困惑,因此我想解决您问题中最基本的部分。
首先,我认为有些误解
C ++以前没有语法能够向操作系统询问窗口或通过网络进行通信的方式,该怎么办?
没有用于执行OS操作的语法。这是语义问题。
通过用C ++自己编写的库突然获得了这种功能
好吧,操作系统主要是用C编写的。您可以使用共享库(so,dll)来调用外部代码。此外,操作系统代码可以在syscalls *或您可以使用assembly调用的中断上注册系统例程。共享库通常只是让系统调用您,因此可以使用内联程序集来避免使用。
这是关于
它的不错的教程:http : //www.win.tue.nl/~aeb/linux/lk/lk-4.html
它适用于Linux,但是原理是相同的。
操作系统如何在图形卡,网卡等上进行操作?这是一个非常广泛的主题,但是大多数情况下,您需要访问中断,端口或将某些数据写入特殊的内存区域。由于该操作受到保护,因此无论如何您都需要通过操作系统来调用它们。
为了提供与其他答案略有不同的观点,我将这样回答。
(免责声明:我只是在简化一些事情,我给出的情况纯粹是假设的,其目的是用来展示概念,而不是100%真实地生活)。
从另一个角度考虑问题,想象一下您刚刚编写了一个具有基本线程,窗口和内存管理功能的简单操作系统。您想实现一个C ++库,以允许用户使用C ++进行编程,并执行诸如制作窗口,在Windows上绘制等操作。问题是,如何执行此操作。
首先,由于C ++可以编译为机器代码,因此您需要定义一种使用机器代码与C ++进行接口的方法。这是函数进入的地方,函数接受参数并提供返回值,因此它们提供了在代码的不同部分之间传输数据的标准方法。他们通过建立称为调用约定的方式来做到这一点。
一个调用约定状态,其中和参数应如何放置在内存中,这样当它被执行的功能可以找到他们。当一个函数被调用时,调用函数将参数放入内存中,然后要求CPU跳转到另一个函数,在跳转回被调用的位置之前,它会执行该操作。这意味着被调用的代码绝对可以是任何东西,并且不会改变函数的调用方式。但是,在这种情况下,该功能背后的代码将与操作系统有关,并且将在操作系统的内部状态下运行。
因此,几个月后,您就将所有OS功能都整理好了。您的用户可以调用函数来创建窗口并在其上绘图,它们可以创建线程和各种奇妙的事物。但是,这就是问题所在,您的OS的功能将与Linux的功能或Windows的功能不同。因此,您决定需要为用户提供一个标准界面,以便他们可以编写可移植的代码。QT来了。
您几乎可以肯定知道,QT包含许多有用的类和函数,它们可以执行操作系统所要执行的各种操作,但其方式与底层操作系统无关。这种工作方式是QT提供的类和函数在用户看来是一致的,但是对于每个操作系统,这些函数背后的代码都是不同的。例如,QT的QApplication :: closeAllWindows()实际上将根据所使用的版本来调用每个操作系统的专用窗口关闭功能。在Windows中,很可能会调用CloseWindow(hwnd),而在使用X窗口系统的操作系统上,可能会调用XDestroyWindow(display,window)。
显而易见,一个操作系统具有许多层,所有这些层都必须通过许多种类的接口进行交互。我什至没有涉及很多方面,但是要解释它们都需要很长时间。如果您对操作系统的内部运作进一步感兴趣,建议您查看OS dev wiki。
请记住,尽管许多操作系统选择将接口公开给C / C ++的原因是它们可以编译为机器代码,它们允许将汇编指令与自己的代码混合在一起,并且为程序员提供了极大的自由度。
同样,这里发生了很多事情。我想继续说明为什么.so和.dll文件之类的库不必用C / C ++编写,而可以用汇编语言或其他语言编写,但是我觉得如果再添加的话,我也可以写了整篇文章,尽管我很想做,但我没有网站来托管它。
当您尝试在屏幕上绘制某些内容时,您的代码会调用其他代码,这些代码又会调用其他代码(等),直到最后出现“系统调用”,这是CPU可以执行的特殊指令。这些指令可以用汇编语言编写,也可以用C ++编写,前提是编译器支持它们的“本能”(这些功能是编译器通过将它们转换成CPU可以理解的特殊代码来“专门”处理的功能)。他们的工作是告诉操作系统做某事。
当发生系统调用时,将调用一个函数,该函数将调用另一个函数(等),直到最终告诉显示驱动程序在屏幕上绘制某些内容。此时,显示驱动程序将查看物理内存中的特定区域,该区域实际上不是内存,而是一个可以写入的地址范围,就好像它是内存一样。但是,写入该地址范围会导致图形硬件截取内存写入,并在屏幕上绘制一些内容。可以使用C ++ 
编写对该内存区域的写操作,因为在软件方面,这只是常规的内存访问。只是硬件处理方式有所不同。
因此,这是它如何工作的非常基本的解释。
syscall(及其表亲sysenter)确实是CPU指令。
                    sysenter的指令是优化的调用路径,因为中断处理程序使用的上下文切换并没有所有人所希望的那样快,但是从根本上讲,它仍然是由软件生成的中断,而通过矢量处理由OS内核安装的处理程序进行处理。通过执行的上下文切换过程的一部分sysenter是更改处理器中的模式位以将环0设置为-完全访问所有特权指令,寄存器以及内存和I / O区域。
                    我如何看待这个问题,实际上是一个编译器问题。
以这种方式查看,您可以在Assembly中编写一段代码(可以用任何一种语言来完成),它将您要调用Z ++的新编写的语言转换为Assembly,为简单起见,可以将其称为编译器(它是编译器) 。
现在,为该编译器提供一些基本功能,以便您可以编写int,字符串,数组等。实际上,您为其提供了足够的功能,以便可以使用Z ++编写编译器本身。现在您有了一个用Z ++编写的Z ++编译器,非常简洁。
更酷的是,现在您可以使用已具备的功能向该编译器添加功能,从而通过使用以前的功能来扩展具有新功能的Z ++语言
例如,如果您编写了足够的代码来绘制任何颜色的像素,则可以使用Z ++对其进行扩展以绘制所需的任何内容。
硬件可以使这种情况发生。您可以将图形内存视为一个大阵列(由屏幕上的每个像素组成)。要绘制到屏幕上,您可以使用C ++或允许直接访问该内存的任何语言写入此内存。该内存恰好可以被图形卡访问或位于图形卡上。
在现代系统上,由于各种限制,直接访问图形内存将需要编写驱动程序,因此您可以使用间接方式。库会创建一个窗口(实际上只是一个图像,就像其他任何图像一样),然后将该图像写入图形内存,然后GPU将其显示在屏幕上。除了能够写入特定内存位置(指针就是用于此目的)之外,无需在语言中添加任何内容。