X11窗口、glfw和链接

文件区分

​ glut跨平台窗口库;glfw跨平台窗口库(glut替代版);glut和glfw封装了Window的窗口管理系统,linux的窗口管理系统和创建上下文等(这些操作在每个系统上不同,所以该文件将其封装使得我们可以直接使用)。它提供了一些渲染物体所需的最低限度的接口。它允许用户创建OpenGL上下文、定义窗口参数以及处理用户输入。

GL/gl.h是基本的OpenGL标头,可提供OpenGL-1.1函数和令牌声明,甚至更多。对于超过1.1版的任何内容,都必须使用OpenGL扩展机制。由于这是一项枯燥而乏味的任务,因此GLEW项目已将其自动化,该项目将所有细节打包在一个易于使用的库中。该库的声明位于头文件GL/glew.h中,该文件隐含了常规的OpenGL标头,因此在包含GL/glew.h时,不再需要包含GL/gl.h,包含该头文件可以使用gl,glu,glext,wgl,glx里的全部函数。glad.h与glew.h作用相同,可以看作其升级版。这两个头文件使用时要放在glfw3.h 或者glut.h文件之前。gl3w.h和glad.h作用类似。

glfw和上下文

​ 除了窗口创建,glfw还封装了上下文创建。glfw可以用elg和glx创建上下文,egl可以支持gl、es和vukan,glx支持gl扩展支持es。glfw系统库为高版本3.3.2,可以在应用程序中对glfw进行配置。如下代码希望在运行gl程序使用glx的上下文,在运行es程序使用egl的上下文。其实在glfwinit的时候已经默认使用glx的上下文,支持gl和es了,所以在if(casetype==”gl”)后不需要显示指定,也可以保证在运行gl程序使用glx的上下文,并且支持es。不过为了使得运行es程序使用egl的上下文,需要设置GLFW_CONTEXT_CREATION_API和GLFW_CLIENT_API,注释如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
if( !glfwInit() )
{
fprintf( stderr, "Failed to initialize GLFW\n" );
getchar();
return -1;
}
if(casetype=="gl"){
//采样
glfwWindowHint(GLFW_SAMPLES, 4);
//版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
//opengl模式
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_COMPAT_PROFILE);
//用户不能调节窗口大小
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
}else{
glfwWindowHint(GLFW_SAMPLES, 16);
//上下文指定egl
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
//API使用opengl es
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
}

​ GLFW_OPENGL_PROFILE用于指定opengl模式,opengl es无该模式。opengl模式有COMPAT和COREl两种,CORE抛弃了部分用法,如果想兼容,使用COMPAT会少很多问题。

​ 在如上代码中,如果程序以gl-es-gl的顺序运行,在运行到es时,进入else使用egl创建上下文,调用es的API;之后运行最后一个gl时,进入if,由于if中没有显示指定上下文和调用的API,因此还是会使用egl创建上下文,调用es的API。

​ 在低版本的glfw文件中,链接的不是系统库而是用户自己指定的库,不能在应用程序设置,而是通过Cmakelists.txt设置,如下,通过ON和OFF设置是否使用egl创建上下文。

image-20230412162211136

​ egl用于关联原生窗口,创建上下文,通过eglBindAPI指定使用gl还是es,与平台无关(窗口-egl-API)。

1
2
eglBindAPI(EGL_OPENGL_ES_API);//绑定ES的API
eglBindAPI(EGL_OPENGL_API);//绑定GL的API

X11窗口

1、在linux中,“X11”指的是“X Window System”,是图形化窗口管理系统。因此调用X11使得窗口可以在linux上运行,不能在windows上运行。

2、使用API时,gl.h头文件中只有函数声明,需要链接到函数实现,glfw已经封装了函数链接,如果直接用X11创建窗口需要在Makefile手动链接。

1
LD_LIBS=-lX11 -lXext -lEGL -lGL //链接GL

3、X11窗口创建的部分函数和窗口监听

​ 创建窗口前先打开与server 的连接。在程序可以使用display 之前,必须先建立一个和X server 的连接。这个连接建立以后,就可以使用Xlib 的函数或宏定义来获得display 的信息了。当参数设置为NULL时,为默认的display环境变量。这个函数返回一个指向display类型结构的指针,表明与X server建立了连接,并且包含了X server的所有信息,可以使用display之上所有窗口。

1
Display *display = XOpenDisplay(NULL);

​ 在进行了一些配置参数配置后,使用如下两个函数创建窗口(参数含义以及创建窗口前的配置流程暂时未知)。函数返回创建的窗口的ID,并使得X server产生一个CreateNotify 事件。

1
2
XCreateWindow
XCreateSimpleWindow

​ X是一个服务器–客户端的结构。由服务器向客户端发送事件信息,让客户端知道发生了什么事情,然后客户端告诉服务器它感兴趣的是什么事情,也就是说,客户端会对那些事件产生反应。如下函数用于客户端告诉服务器窗口会对哪些事情有响应。StructureNotifyMask 即改变窗口状态,比如尺寸,位置等,对应事件ConfigureNotify;ExposureMask 对应事件Expose ;KeyPressMask 即键盘响应对应事件KeyPress。

1
XSelectInput(display, win,ExposureMask|StructureNotifyMask);

​ 创建窗口之后,窗口并不能显示出来,需要调用如下函数来 画窗口让它显示。如果这个窗口有父窗口,那么在所有父窗口没有画出来之前,这个窗口即使用了这个函数,也是不能显示出来的。必须等所有父窗口都显示了,这个窗口才能画。X server产生一个MapNotify事件。客户端已经有相应操作了,绘制窗口。

1
2
XStoreName (display, win, "gears");//设置窗口名
XMapWindow(display, win);

​ 进入窗口事件循环,获得事件,处理或丢弃。接收到的事件由XNextEvent 函数从消息队列里获得,把事件放到event.type 里并从队列里删除该消息 。当队列为空也就是没有下一个事件被接收时,程序就一直停留在XNextEvent里直到有下一个事件,无法执行之后的步骤。因此可以使用XPending(display)在有事件的时候响应事件,没有事件的时候在窗口上绘制。如果窗口的信息改变了,就需要XFlush 函数让窗口重画,但XNextEvent函数会隐式地调用XFlush。在opengl绘制函数的部分,glClearColor设置窗口颜色后,需要用

​ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

​ 用上述指定颜色初始化颜色缓存,使用默认值初始化深度缓存,再利用函数

​ eglSwapBuffers(mContext.eglDisplay, mContext.eglSurface);才能显示在窗口(此处以egl配置上下文为例)。

1
2
3
4
5
6
7
8
9
10
11
XEvent myevent;
while (1) {
if (XPending(display)) {
XNextEvent(display, &myevent);
switch(myevent.type) {
case ClientMessage:{
}
case ConfigureNotify:{
}}}
/*opengl绘制函数*/
}

​ 关闭窗口事件(若窗口关闭则打印信息)

//while循环外
Atom deleteAtom = XInternAtom(display, "WM_DELETE_WINDOW", False);
XSetWMProtocols(display, win, &deleteAtom, 1);
//while循环内
 case ClientMessage:{
       if ((unsigned)myevent.xclient.data.l[0] == deleteAtom){
           printf("X WINDOW DELETED\n");
           return 0;
       }
   }

​ 窗口改变事件

​ 如下函数通过代码人为改变并重新设置窗口,因此一直存在ConfigureNotify事件。

const unsigned int mask = CWWidth | CWHeight;
XWindowChanges changes;
int newWidth = 128, newHeight = 128;
changes.width = newWidth;
changes.height = newHeight;
XConfigureWindow(display, win, mask, &changes);

​ 如下函数获取由外部事件对窗口产生的变化并改变viewport,使得绘制的图像根据窗口的变化变化,在外部事件发生时才存在ConfigureNotify事件。

case ConfigureNotify:{
    reshape(myevent.xconfigure.width,myevent.xconfigure.height); 
    break;
}

​ Expose 事件可能用于绘制和显示,在opengl绘制部分外部可以添加case Expose 。

​ 退出窗口时,需要关闭和X server 的连接,于是也就销毁了相关资源,关闭了窗口。

1
2
XDestroyWindow(display, win);
XCloseDisplay(display);

参考链接:

https://blog.csdn.net/rufanchen_/article/details/7640584

https://www.cnblogs.com/okgogo2000/p/4322753.html

源文件->可执行文件过程

1.预处理:包括删除注释、宏扩展、#include文件包含等。预处理后生成.i文件。

2.编译:由编译器完成,检查语法及语义,生成错误或警告。编译后生成汇编语言.s文件。

3.汇编:将汇编文件翻译成机器码指令(二进制),将指令打包形成.o目标文件。

4.链接:完成调用的各种函数、静态库和动态库的链接,从函数原型链接到函数实现。形成.exe文件。

Linux平台下cmake和make的区别

Makefile编译规则文件。

make通过Makefile文件自动化批量处理编译,将源文件变成最终的可执行文件(包含gcc的功能)。

不同平台的Makefile文件不同,cmake作为跨平台编译工具可以将CMakeLists.txt文件转化为所需要的Makefile文件

Cmake

window平台下利用Cmake软件代替Linux下的cmake命令,用编译器如vs代替Linux下的make命令。

Cmake软件将cmake分为两步:

1.Configure:配置,生成需要的文件夹及准备文件

2.Generate:根据CMakeLists.txt生成工程文件。如果编译器选择vs,则生成vs工程文件,如果是Linux,则生成Makefile。生成vs工程文件,同Linux生成Makefile文件,都是规定了编译规则。

最后由vs执行工程,即执行make。

动态库和静态库

1.区别

​ 使用封装好的库函数十分方便并且十分高效,库分为静态库和动态库。

库类型 windows linux
静态库 .lib .a
动态库 .dll .so

​ 当程序与静态库链接时,静态库中所包含的所有函数方法都会被拷贝到最终的可执行文件中去。这就会导致最终生成的可执行代码量相对变多,相当于编译器将代码补充完整了。编译后的可执行程序不需要静态库,因为所有使用的函数都已经被编译进去了。这种方式会让程序运行起来相对快一些,不过也会有个缺点: 占用磁盘和内存空间,导致可执行程序过大。另外,静态库会被添加到和它链接的每个程序中去, 而且这些程序运行时, 都会被加载到内存中,无形中又多消耗了更多的内存空间。 与动态库链接的可执行文件只包含它需要的函数方法的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才会被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间,更进一步,操作系统使用虚拟内存,使得一份动态库驻留在内存中被多个程序使用,也同时节约了内存。不过由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些。如果要修改的刚好是库函数的话,在接口不变的前提下,使用动态库的程序只需要将动态库重新编译就可以了,而使用静态库的程序则需要将静态库重新编译好后,将程序再重新编译一遍。

2.两种链接

​ 现在源程序main.cpp链接库sub,sub链接库add

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*add.h */
#ifndef _ADD_H_
#define _ADD_H_
void add();
#endif
------------------------------------------------------------
/*add.c*/
#include "add.h"
void add()
{
printf("add\n");
}
------------------------------------------------------------
/*sub.h*/v
#ifndef _SUB_H_
#define _SUB_H_
void sub();
#endif
----------------------------------------------------------
/*sub.c*/
#include "add.h"
#include "sub.h"
void sub()
{
printf("sub\n");
add();
}
------------------------------------------------------------
/*main.c*/
#include "sub.h"
void main()
{
sub();
}
1
2
gcc -c add.c
gcc -c sub.c

​ 首先生成的文件:sub.o ,add.o,无论是静态库文件还是动态库文件,都是由 .o 文件创建。使用-c不会链接、生成可执行文件,只会预处理、编译和汇编。

(1)静态库

1
2
ar cr libadd.a add.o
ar cr libsub.a sub.o

​ ar:静态函数库创建的命令

​ -c :create创建

​ 库文件的命名规范是以lib开头(前缀),紧接着是静态库名,以 .a 为后缀名。

1
gcc -o main main.c -L . –l sub -L . –l add

​ -L :指定函数库查找的位置,’.’表示在当前目录下查找

​ -l:指定函数库名,其中的lib和.a(.so)省略。

​ -o:链接,生成可执行文件,指名生成的可执行文件名

​ 使用静态库内部函数,只需要在使用到这些函数的源程序中包含这些函数的原型声明(头文件),然后在用gcc命令生成可执行文件时指明静态库名,gcc将会从静态库中将函数拷贝到可执行文件中。gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件。在程序main.c中,包含了静态库的头文件sub.h,然后在主程序main中直接调用函数sub()即可。生成可执行文件后静态库可以不再需要。

​ 注意:

1
2
gcc -o main main.c -L . –l sub 
gcc -o main main.c -L . –l add -L . –l sub

​ 都会发生sub无法链接到add导致sub中的函数undefined reference to,因此链接顺序不能变,而且用main链接sub和add即可,不需要单独写一行sub链接add。

(2)动态库

1
2
3
4
gcc -fPIC -o add.o -c add.c
gcc -fPIC -o sub.o -c sub.c
gcc -shared -o libadd.so add.o
gcc -shared -o libsub.so sub.o
     -fpic:产生代码位置无关代码,则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

​ -shared :生成共享库

1
gcc -o main main.c -L . –l sub -L . –l add

​ ./执行时会发生如下报错:

     error while loading shared libraries:libmymath.so: cannot open shared object file: No such file or directory

​ 原因如下:

​ gcc -o 链接器工作于链接阶段,工作时需要-l指定库名和-L指定库路径。如果在指定库路径没有找到库,会发生

undefined reference to的报错,导致可执行文件无法生成。但是有时不会发生该报错,可以对make添加-WL -Z DEFS编译选项,在生成可执行文件前找到该错误。

​ ./运行可执行程序时还需要动态链接器,工作于程序运行阶段,工作时需要提供动态库所在目录位置。该目录与上述-L指定的路径不同,动态链接器去固定位置(通过环境变量)查找动态库,如果找不到就会报错,所以需要先将动态库的工作目录加入到环境变量中。一般会去默认的动态库搜索路径/usr/lib查找,可以将.so文件复制到/usr/lib中来解决上述报错。

其他

1
$ ldd exe 查看可执行程序运行时链接的动态库

​ 如果在源文件的头文件中使用

1
#include<GLFW/glfw3.h>

​ 则之后会默认链接到/usr/lib下的系统库(库名=>目录)

image-20230412085632785

​ 在项目文件中添加一个新文件夹,使用自定义的glfw库文件。

image-20230412090145388

​ 头文件会链接到自己的库文件而不是系统的库文件。编译源文件时,需要将glfw文件一起编译,因此需要修改源文件的Cmakelist.txt文件。

1
add_subdirectory(external) 

​ cmake会通过源文件的Cmakelist.txt找到external目录下的Cmakelist.txt

1
include_directories(external/glfw-3.1.2/include/GLFW)

​ 向工程添加多个特定的头文件搜索路径,添加该行后源文件的头文件部分可以改成

1
#include<glfw3.h>

​ 如果包含头文件后,没有找到external目录下的Cmakelist.txt,无法生成自己的库文件,最后还是会链接到系统库。如果glfw文件编译链接成功,最后就会链接到自己的库。在Cmakelist.txt或者其他文件中指明了动态链接库的搜索位置,因此不会去默认的路径/usr/lib。

image-20230412091529196

gdb调试

image-20230412091902342

​ 可以用如下方式调试:

1
2
3
4
5
$ gdb 可执行程序名
(gdb) b 函数名 //设置断点,运行到该函数暂停
(gdb) run //运行到第一个断点暂停
(gdb) c //继续运行到下一个断点暂停
(gdb) quit //退出gdb环境
Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

扫一扫,分享到微信

微信分享二维码

请我喝杯咖啡吧~

支付宝
微信