深度探索Linux操作系统 —— 构建桌面环境2

简介: 深度探索Linux操作系统 —— 构建桌面环境

深度探索Linux操作系统 —— 构建桌面环境1:https://developer.aliyun.com/article/1598092

(3)示例

1. 创建 configure

   我们将这个迷你窗口管理器命名为 winman,使用 winman 作为顶层目录的名字,在顶层目录下创建一个子目录 src 用来存放源代码。我们基于 Xlib ,使用 C 语言编写 winman 。因此,configure.ac 中除了 Autoconf 要求的必选的宏外,最重要的就是检查 C 编译器和 X 的库了,其内容如下:

// winman/configure.ac:

AC_INIT(winman, 0.1, baisheng_wang@163.com)
AM_INIT_AUTOMAKE(foreign)

AC_PROG_CC

PKG_CHECK_MODULES(X, x11)

AC_CONFIG_FILES(Makefile src/Makefile)
AC_OUTPUT

   Autoconf 要求 configure.ac 以宏 AC_INIT 作为开头,该宏由 Autoconf 定义,接收一些基本信息,如软件包的名称,版本号,开发或者维护人员的 Email 等。制作发布的软件包时,将用到这些信息。


   宏 AM_INIT_AUTOMAKE 由 Automake 定义,用来进行与 Automake 相关的初始化工作,只要使用 Automake,这个宏也是必选的。在默认情况下,Automake 会检查项目目录中是否包含 NEWS、README、ChangeLog 等文件,为简单起见,我们给 Automake 传递了 “foreign” 参数,明确告诉 Automake 我们的项目不需要包含这些文件。


   宏 AC_PROG_CC 用来检测 C 编译器,根据该宏名的前缀 “AC_” 就可判断出其由 Autoconf 定义。其将在系统内搜索 C 编译器,并定义变量 CC 指向搜索到的 C 编译器。


   接下来,我们使用软件包 pkg-config 提供的宏 PKG_CHECK_MODULES 检测 X 的库。该宏将定义两个变量,分别是宏的第一个参数加上后缀 “_CFLAGS” 和 “_LIBS” ,这里就是 X_CFLAGS 和 X_LIBS 。如果查看 congfigure 脚本就可以发现,这个宏定义的核心其实就是执行命令 “pkg-config --cflags x11” 和 “pkg-config --libs x11” 。


   宏 AC_CONFIG_FILES 告诉 Automake 生成哪些 Makefile 模板文件 Makefile.in 。这里,需要在顶层目录和 src 目录下分别创建 Makefile.in 文件。


   在 configure.ac 的最后, Autoconf 要求必须以宏 AC_OUTPUT 结束 configure.ac 。


   准备好 configure.ac 后,我们使用如下命令生成脚本 configure:



960519eb7936f5475b921c821a4036f3.png

2. 生成 Makefile

    窗口管理器的源码保存

   窗口管理器的源码保存在顶层目录下的子目录 src 中,我们在顶层目录和子目录 src 下面分别需要编写 Automake 元文件 Makefile.am 。


   顶层目录 winman 下的 Makefile.am 如下:

// winman/Makefile.am :

SUBDIRS = src

   因为顶层目录下基本没有任何操作,所以该 Makefile.am 非常简单,只是通过变量 SUBDIRS 告诉 Automake ,需要递归编译子目录 src 。


   子目录 src 下的 Makefile.am 如下:

// winman/src/Makefile.am:

bin_PROGRAMS = winman

winman_SOURCES = wm.h main.c ...

winman_CFLAGS = $(X_CFLAGS)
winman_LDADD = $(X_LIBS)

   变量 bin_PROGRAMS 指定了编译最后创建的二进制可执行文件的名称,该变量由两部分构成,其中 “bin” 表示安装在目录 $prefix/bin 下,“PROGRAMS” 指明最后创建的文件是一个可执行文件。


   winman_SOURCES 表示创建 winman 需要的源文件;winman_CFLAGS 和 winman_LDADD 分别表示编译链接时需要传递给编译器和链接器的参数。X_CFLAGS 和 X_LIBS 已经在前面的 configure.ac 中由宏 PKG_CHECK_MODULES 定义了。


   Automake 的元文件已经准备就绪,我们使用下面的命令创建 Makefile 的模板 Makefile.in :

automake --add-missing -copy

   其中选项 “–add-missing” 和 “–copy” 是告诉 automake 将其需要的一些脚本文件,比如 install-sh 等,直接复制到项目目录中,而不是建立这些脚本文件的链接。这么做是为了分发到其他系统时,避免因为脚本位置不同或者系统中没有安装相应脚本而导致编译链接失败。


   上述命令执行后,将分别在顶层目录和子目录 src 下创建 Makefile 的模板 Makefile.in 。


   最后,执行 confugure 脚本探测编译过程所需的各个变量,然后用探测到的具体的值替换 Makefile.in 中的变量,比如 X_CFLAGS、X_LIBS,生成 Makefile 文件:


./configure --prefix=/usr

3、主要数据结构

   在 winman 中,将为每个被管理的窗口创建一个对象,记录其相关信息,为此我们抽象了结构体 Client 。另外,我们抽象了结构体 WinMan ,其中记录了一些全局信息。在讨论具体的实现前,我们先来了解一下这两个数据结构。读者不必全部理解每一项的含义,后面具体遇到时,读者可以再回到这里,前后结合进行理解。


1. 结构体 Client

   结构体 Client 中主要包含窗口属性信息以及与窗口操作相关的函数,定义如下:


d8df112b0eba8981155efc537af1ea24.png


我们结合图 7-6 来解释其中相关数据项。


07f7453ca349f53c1a41d0dfe66b8c79.png


   ◆ 每个窗口作为 X 服务器的一个资源,都有一个 ID 来唯一标识。这里的数据项 window 就是被管理的窗口的 ID 。


   ◆ wm 指向全局的结构体 WinMan 的对象。


   ◆ frame 是 Frame 窗口的 ID ;titlebar 是标题栏窗口的 ID ;minimize_btn 是最小化按钮对应的窗口的 ID ;maximize_restore_btn 是最大化/恢复按钮对应的窗口的 ID;close_btn 是关闭按钮对应的窗口的 ID 。acting_btn 是一个为了编程方便定义的辅助变量,用来记录用户点击了最大化、最小化以及关闭按钮中的哪一个。


   ◆ 以 “rsz_” ( resize 的简写)开头的 8 个窗口,是在 Frame 窗口上创建的 8 个不可见窗口,它们分别位于 Frame 窗口的 4 个边和 4 个角,目的是为了便于判断鼠标是否落在调整窗口尺寸的区域。也就是说,一旦用户的鼠标落入这个窗口区域,程序的鼠标将使用特定的指针,提示用户可以进行改变窗口的尺寸了。resizing_area 也是一个辅助变量,用来记录鼠标落在了调整窗口尺寸的 8 个区域的哪个区域,也就是哪个窗口上。


   ◆ 变量 moving 用来标识用户是否正在移动窗口,anchor_x、anchor_y 记录用户在标题栏上按下鼠标左键、准备开始移动时的位置,目的是为了计算鼠标按下位置与当前位置的距离。


   ◆ x、y、width、height 记录窗口的位置及大小。min_width、min_height 表示允许用户改变窗口尺寸时允许的最小值,主要目的是避免用户不小心将窗口缩小的太小,导致窗口 “丢失” 了。


   ◆ state 记录窗口的状态,winman 只处理最大化及其标准状态。restore_x、restore_y、restore_w、restore_h 分别记录窗口在最大化之前的位置和尺寸,以便从最大化恢复为标准尺寸时使用。


   ◆ trans_for 的目的是为了记录某个窗口是否是临时窗口,如果是临时窗口,那么它是谁的临时窗口。在讨论窗口切换时,我们将看到这个数据项的意义。


   ◆ 变量 ignore_unmap 涉及的内容有点复杂,将在 7.1.12 节详细讨论。


   ◆ 每个窗口通过指针 above 和 below 链接到窗口栈中。


   与窗口操作相关的一些函数的实现,后面章节中我们会讨论。


2. 结构体 WinMan

   结构体 WinMan 中包含了全局需要使用的变量,定义如下:


c3ee9468827e46115a0527e712289b4c.png


   上述代码中各个参数含义如下:


   ◆ 第一个数据项 dpy 无需多说了,它是代表应用到 X 服务器的连接。一个 X 服务器可以支持多屏,每个屏上都会有一个根窗口。虽然我们不考虑多屏的情况,但是某些函数使用屏幕号和根窗口作为参数,为了避免每次使用时都要从 X 服务器获取,winman 在结构体中保留了一份副本,即数据项 screen 和 root ,分别代表屏幕号和根窗口 ID 。


   ◆ 数组 atoms 中记录的是用于窗口间通信的 Atom ,winman 也不希望应用反复的从 X 服务器获取这些 Atom ,所以将它们保存在 winman 的本地。


   ◆ winman 使用栈的方式记录窗口对象(即为每个窗口创建的结构体 Client 的实例),stack_top 和 stack_bottom 分别指向这个窗口栈的栈顶和栈底。距离用户最远的窗口记录在栈底,距离用户最近的窗口记录在栈顶。stack_items 记录栈中窗口对象的数量。


   ◆ active、desktop 以及 taskbar 分别指向当前活动的窗口、构成桌面环境的桌面组件以及任务条组件。


4、初始化

   谈及初始化,大多给人的印象是进行一些琐碎的准备工作,但是 winman 中有几处却非常关键,相关代码如下:

// winman/src/main.c:
int main(int argc, char *argv[]) {
  WinMan *wm;
  wm = malloc(sizeof(WinMan));
  memset(wm, 0, sizeof(WinMan));
  
  if(!(wm->dpy = XOpenDisplay(NULL))) {
    ...
  }
  wm->screen = DefaultScreen(wm->dpy);
  wm->root = RootWindow(wm->dpy, wm->screen);
  XSetErrorHandler(error_handler);
  
  atom_init(wm);
  XSelectInput(wm->dpy, wm->root, SubstructureRedirectMask
        | SubstructureNotifyMask);
        
  init_clients(wm);
  
  wm_event_loop(wm);
  
  return 0;
}

下面介绍该函数主要执行的操作。


(1)连接 X 服务器

   窗口管理器与普通的 X 应用并无本质区别,只是具有一点点特权,它也是 X 服务器的一个客户端。从服务器和客户端的体系架构角度而言,应用程序当然需要和服务器建立连接后才能通信,为此,Xlib 提供了函数 XOpenDisplay 用于建立它们之间的连接。


(2)错误处理

   Xlib 将错误分为两类:一类错误是不可恢复的,这类错误是致命的,一旦发生后,应用基本不可能执行下去了,如应用程序和服务器的连接断开了,除了终止应用程序外别无选择;另外一类是协议错误,比如当应用读取某个窗口的属性时,这个窗口可能在服务器中已经不存在了。显然,这类错误不是致命的,应用完全可以自己决定是忽略错误还是终止执行。


   Xlib 为这两类错误都设置了默认的错误处理函数,它们的行为均是打印错误提示并终止应用。但是 Xlib 也分别提供了接口 XSetIOErrorHandler 和 XSetErrorHandler 允许应用设置自己的致命错误和协议错误处理函数。winman 设置了协议错误处理函数为 error_handler ,当发生协议错误时,error_handler 只打印错误信息,并不终止执行。


(3)创建 Atoms

   函数 atom_init 使用 Xlib 的函数 XInternAtoms ,一次性创建后面用到的属性的 Atom,并将 Atom 保存在结构体 WinMan 的 atoms 数组中。相关代码如下:


// winman/src/main.c:
static void atom_init(WinMan *wm) {
  char *atom_names[] = {
    "_NET_WM_WINDOW_TYPE",
    ...
  };
  XInternAtoms(wm->dpy, atom_names, ATOM_COUNT, False, wm->atoms);
}

   最初,X 标准协会制定了 ICCC 通信协议。后来,随着现代桌面的发展,又制定了 EWMH ,即对 ICCCM 进行的补充扩展。这两个协议中定义了大量的属性,在函数 atom_init 中,以 WM_ 开头的基本是 ICCCM 标准中定义的,以 _NET_ 开头的是 EWMH 中定义的。除了标准定义的属性外,应用也可以自定义属性,其中以 _CUSTOM_ 开头的就是 winman 自定义的属性。


(4)拦截事件

   函数 XSelectInput 可以说是窗口管理器的画龙点睛之笔了。winman 按照前面的讨论,选择了根窗口的 “SubstructureRedirectMask” 和 “SubstructureNotifyMask” 。


(5)管理已存在的窗口

   在窗口管理器启动之前,系统上可能已经有 X 应用在运行。因此,winman 启动时需要管理这些已存在的窗口,函数 init_clients 就是做这件事的,其具体细节请参见 7.1.13 节。


(6)事件循环

   初始化完成后,窗口管理器进入事件循环,函数 wm_event_loop 调用 Xlib 的函数 XNextEvent 将窗口管理器对 X 的请求发送给 X 服务器,然后检查事件队列,调用相应的事件处理函数。接下来的的章节中,我们会陆续讨论这些事件处理函数。


   需要特殊指出的是 Expose 事件,以图 7-7 所示窗口布局为例。

5d75b562058b6f8be41b20fd1d71dc76.png



   Window E 有四个区域分别被 Window A~D 遮挡,当 Window E 成为当前活动窗口时,X 服务器将为每一个被遮挡的部分都报告一个 Expose 事件,并将同一个动作引发的 Expose 事件连续的放到事件队列中。结构体 XExposeEvent 中的变量 count 就是用来记录一个 Expose 事件后面还有多少个 Expose 事件的。


   因此,从效率的角度来讲,对于多个连续的 Expose 事件,应用应该忽略掉最后一个 Expose 事件前面所有的 Expose 事件,而在收到最后一个 Expose 事件时才进行绘制。


5、为窗口“落户”

   一旦收到 X 服务器转发来的 MapRequest ,就说明有应用的顶层窗口请求显示了,显然,这个时机是窗口管理器切入的最佳时机。winman 首先遍历窗口栈确认窗口是否已经被管理了,如果请求映射的窗口尚未被管理,则调用 wm_new_client 开始管理窗口,函数 wm_new_client 的代码如下:

// winman/src/main.c:
static Client* wm_new_client(WinMan *wm, Window win) {
  Atom type;
  int format, status;
  unsigned long n, extra;
  Atom *value = NULL;
  Client *c = NULL;
  
  status = XGetWindowProperty(wm->dpy, win,
      wm->atoms[_NET_WM_WINDOW_TYPE],
      0, 1, False, XA_ATOM, &type, &format, &n,
      &extra, (unsigned char **) &value);
      
  if (status == Success && type == XA_ATOM
      && format == 32 && value) {
    if (value[0] == wm->atoms[_NET_WM_WINDOW_TYPE_NORMAL])
      c = normal_client_new(wm, win);
    else if (value[0] == wm->atoms[_NET_WM_WINDOW_TYPE_DIALOG])
    ...
  }
  ...
  c->reparent(c);
  c->show(c);
  
  return c;
}

   该函数执行的主要操作如下:


   1)如同我们每个人要有一个户口,在落户时需要提供各种自然人信息一样,窗口管理器也要收集窗口的各种 “自然人” 信息,为窗口在窗口管理器中 “落户” 。


   2)绘制窗口装饰。


   3)一切准备妥当后,申请 X 服务器显示应用的窗口,当然也包括窗口管理器附加的装饰。


   这一节,我们先来讨论为窗口 “落户” 这一过程。


   如前所述,在一个典型的 X 环境中,可能有多种类型的 X 应用程序,比如构成桌面环境的任务条等组件,以及普通的应用程序。即使是普通的应用的窗口,也可分为标准的窗口以及对话框等。显然,不同的类型的窗口需要区别对待,我们不能给任务条也加个标题栏,那样就会闹出笑话。


   EWMH 规定窗口需要设置属性 _NET_WM_WINDOW_TYPE 来表明自己的类型,函数 wm_new_client 依据的就是 EWMH 这个规定来判别窗口的类型。因此,函数 wm_new_client 调用 Xlib 的函数 XGetWindowProperty 获取窗口的属性 _NET_WM_WINDOW_TYPE 的值,根据窗口的不同类型,创建不同类型的窗口对象。


下面,我们以标准窗口为例,讨论其 “落户” 过程。

// winman/src/normal_client.c:

Client* normal_client_new(Wingman *wm, Window win) {
  Client *c;
  XWindowAttributes attr;
  XSizeHints *hints = NULL;
  long dummy;
  Window trans_for = None;
  
  c = malloc(sizeof(Client));
  memset(c, 0, sizeof(Client));
  c->window = win;
  c->wm = wm;
  
  XSetWindowBorderWidth(wm->dpy, c->window, 0);
  XGetWindowAttributes(wm->dpy, win, &attr);
  c->x = attr.x;
  c->y = attr.y;
  c->width = attr.width;
  c->height = attr.height;
  
  if (!(hints = XAllocSizeHints()))
    return;
  if (XGetWMNormalHints(wm->dpy, c->window, hints, &dummy)) {
    if (hints->flags & PMinSize) {
      c->min_width = hints->min_width;
      c->min_height = hints->min_height;
    }
  }
  XFree(hints);
  
  c->min_width = c->min_width > MIN_WIDTH ?
      c->min_width : MIN_WIDTH ;
  c->min_height = c->min_height > MIN_HEIGHT ?
      c->min_height : MIN_HEIGHT;
      
  ewmh_get_net_wm_state(c);
  if (c->state & (NET_WM_STATE_MAXIMIZED_V
      | NET_WM_STATE_MAXIMIZED_H))
    custom_get_restore_size(c);
  XGetTransientForHint(wm->dpy, c->window, &trans_for);
  if (trans_for)
    c->transfor = wm_find_client_by_window(wm, trans_for);
  if (!c->trans_for) {
    if (wm->active) {
      XGrabButton(wm->dpy, Button1, 0, wm->active->window,
        True, ButtonPressMask, GrabModeaSync,
        GrabModeaSync, None, None);
      
      Item *trans = normal_client_get_transients(wm->active);
      Item *i;
      for (i = trans; i; i = i->next) {
        XGrabButton(c->wm->dpy, Button1, 0,
            i->client->window, True, ButtonPressMask,
            GrabModeaSync, GrabModeaSync, None, None);
      }
      list_free(&trans);
    }
    wm->active = c;
    ewmh_set_net_active_window(wm->active);
  }
  
  c->configure = &normal_client_configure;
  c->reparent = &normal_client_reparent;
  ...
  stack_append_top(c);
  ewmh_update_net_client_list_stacking(wm);
  return c;
}

下面介绍函数 normal_client_new 执行的主要操作。


(1)设置窗口边框

   通常,窗口可以请求 X 服务器绘制边框。但是为了统一,我们调用 XSetWindowBorderWidth 人为地将窗口的自身的边框设置为 0,而是在 Frame 窗口上为被管理的窗口绘制统一的边框。在 winman 中,为简单起见,边框的宽度采用了一个固定的值。但是窗口管理器可以尊重窗口的诉求,在绘制窗口边框前,读取窗口属性中设定的边框宽度。


(2)获取窗口几何尺寸

   接下来,我们读取窗口的几何尺寸,包括位置、宽度和高度,以及窗口所允许的最小的尺寸。这里我们分别使用了 Xlib 的函数 XGetWindowAttributes 及 XGetWMNormalHints ,主要是因为 Xlib 不推荐通过 XGetWMNormalHints 获取的窗口的位置和大小,但是通过 XGetWindowAttributes 又不能获取窗口的最小宽度和最小高度。


(3)读取窗口状态

   在 EWMH 中,规定了窗口的状态属性 _NET_WM_STATE 包括 _NET_WM_STATE_MODAL 、 _NET_WM_STATE_MAXIMIZED_VERT 、_NET_WM_STATE_MAXIMIZED_HORZ 、 _NET_WM_STATE_FULLSCREEN 等。


   为简单起见, winman 中只示例处理了窗口最大化的状态。函数 ewmh_get_net_wm_state 调用 Xlib 的接口 XGetWindowProperty 读取窗口的属性 _NET_WM_STATE 。如果属性中包含 _NET_WM_STATE_MAXIMIZED_VERT 和 _NET_WM_STATE_MAXIMIZED_HORZ ,那么就说明窗口是处于最大化状态,则 winman 尝试读取窗口中的标准状态( Restore 状态)下窗口的位置和尺寸信息,以便窗口从最大化切换到标准状态时使用。


   读者可能会问,结构体 Client 中数据项 restore_x、restore_y、restore_w、restore_h 不是记录了窗口在最大化之前的位置和尺寸吗?但是设想这样一个场景:当窗口处于最大化时,窗口管理器异常退出了,那么当窗口管理器再次启动时,这个数据如何初始化?这就是 winman 为窗口自定义属性 _CUSTOM_WM_RESTORE_GEOMETRY 的目的,winman 将这些信息保存在窗口中,只要窗口在,这些信息就可以从窗口中读出来,可谓是 “人在阵地就在” 。


(4)捕捉 “旧” 窗口

   当新的窗口出现后,如果这个新窗口不是一个临时窗口,其将成为当前活动的窗口,而上一个活动窗口将退居二线。因此,winman 需要捕捉这个退居二线的窗口,以便可以将其顺利切换回来。而且,这个退居二线的窗口可能还有临时窗口,而且临时窗口可能还有临时窗口,因此,函数 normal_client_get_transients 遍历窗口栈,返回这个退居二线窗口的临时窗口组成的链,捕捉这个链上的所有窗口。


(5)设置窗口对象的函数指针

   创建了窗口对象后,显然需要设置操作窗口的函数指针。根据不同的窗口类型,设置这些指针指向不同的函数实现。对于标准窗口,设置这些指针指向标准窗口的实现。


(6)更新根窗口的属性

   新窗口的 “自然人” 信息收集完毕后,就可以给其 “落户” 了。函数 normal_client_new 调用 stack_append_top 将新创建的窗口对象压入 winman 自己维护的窗口栈。


   为了让其他应用知晓又有新成员加入了,当然需要更新一些状态信息,比如任务条就时刻关注着系统中应用的变化情况。一个是记录当前活动窗口的属性 _NET_ACTIVE_WINDOW ;另外一个是记录 X 服务器中所有窗口的列表的属性 _NET_CLIENT_LIST_STACKING 。这两个属性都是 EWMH 标准规定的,它们都是根窗口的属性。函数 normal_client_new 中调用的两个子函数 ewmh_set_net_active_window 和 ewmh_update_net_client_list_stacking 目的就是分别更新这两个属性。

深度探索Linux操作系统 —— 构建桌面环境3:https://developer.aliyun.com/article/1598094

目录
相关文章
|
11天前
|
NoSQL 关系型数据库 Redis
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
mall在linux环境下的部署(基于Docker容器),docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongodb、minio详细教程,拉取镜像、运行容器
mall在linux环境下的部署(基于Docker容器),Docker安装mysql、redis、nginx、rabbitmq、elasticsearch、logstash、kibana、mongo
|
2天前
|
存储 安全 Linux
探索Linux操作系统的心脏:内核
在这篇文章中,我们将深入探讨Linux操作系统的核心—内核。通过简单易懂的语言和比喻,我们会发现内核是如何像心脏一样为系统提供动力,处理数据,并保持一切顺畅运行。从文件系统的管理到进程调度,再到设备驱动,我们将一探究竟,看看内核是怎样支撑起整个操作系统的大厦。无论你是计算机新手还是资深用户,这篇文章都将带你领略Linux内核的魅力,让你对这台复杂机器的内部运作有一个清晰的认识。
12 3
|
2天前
|
存储 数据挖掘 Linux
服务器数据恢复—Linux操作系统网站服务器数据恢复案例
服务器数据恢复环境: 一台linux操作系统服务器上跑了几十个网站,服务器上只有一块SATA硬盘。 服务器故障: 服务器突然宕机,尝试再次启动失败。将硬盘拆下检测,发现存在坏扇区
|
4天前
|
安全 Android开发 数据安全/隐私保护
构建未来:移动应用开发与操作系统的融合之道
在数字化浪潮中,移动应用和操作系统如同现代文明的双子星座。本文将探索它们之间的紧密联系,揭示如何通过技术创新和设计哲学的融合,共同塑造我们的数字生活。从用户体验到系统性能,我们将一窥这些看似简单却复杂的互动是如何影响我们日常生活的。
|
12天前
|
Java 开发工具 Android开发
移动应用开发之旅:探索移动操作系统与应用构建的奥秘
【8月更文挑战第33天】在数字时代的浪潮中,移动应用已成为我们日常生活的一部分。本文将带您深入理解移动操作系统的工作原理,并揭示如何在这个多姿多彩的平台上开发出引人入胜的应用。我们将从基础概念出发,逐步深入到高级编程技巧,最终通过一个实际的代码示例,展示如何将理论应用于实践。无论您是初学者还是有经验的开发者,这篇文章都将为您提供宝贵的见解和灵感。让我们一起踏上这场激动人心的移动应用开发之旅吧!
|
10天前
|
Shell Linux API
C语言在linux环境下执行终端命令
本文介绍了在Linux环境下使用C语言执行终端命令的方法。首先,文章描述了`system()`函数,其可以直接执行shell命令并返回结果。接着介绍了更强大的`popen()`函数,它允许程序与命令行命令交互,并详细说明了如何使用此函数及其配套的`pclose()`函数。此外,还讲解了`fork()`和`exec`系列函数,前者创建新进程,后者替换当前进程执行文件。最后,对比了`system()`与`exec`系列函数的区别,并针对不同场景推荐了合适的函数选择。
|
13天前
|
机器学习/深度学习 vr&ar Android开发
构建未来:移动应用开发与操作系统的革新之路
【9月更文挑战第1天】 在数字时代的浪潮中,移动应用和操作系统是推动技术前进的重要力量。本文将深入探讨移动应用开发的新趋势、移动操作系统的创新演进,以及它们如何共同塑造我们的未来生活。通过分析当前技术发展的现状和挑战,我们将一窥即将到来的技术变革,并理解这些变化对个人和社会的深远影响。
28 1
|
14天前
|
开发者 API 开发框架
Xamarin 在教育应用开发中的应用:从课程笔记到互动测验,全面解析使用Xamarin.Forms构建多功能教育平台的技术细节与实战示例
【8月更文挑战第31天】Xamarin 作为一款强大的跨平台移动开发框架,在教育应用开发中展现了巨大潜力。它允许开发者使用单一的 C# 代码库构建 iOS、Android 和 Windows 应用,确保不同设备上的一致体验。Xamarin 提供广泛的 API 支持,便于访问摄像头、GPS 等原生功能。本文通过一个简单的教育应用示例——课程笔记和测验功能,展示了 Xamarin 在实际开发中的应用过程。从定义用户界面到实现保存笔记和检查答案的逻辑,Xamarin 展现了其在教育应用开发中的高效性和灵活性。
24 0
|
14天前
|
人工智能 Java 物联网
构建未来:移动应用开发与操作系统的融合之旅
【8月更文挑战第31天】在数字时代的浪潮中,移动应用和操作系统成为我们日常生活和工作的基石。本文将深入探讨移动应用开发的核心概念、移动操作系统的架构,并通过一个实际的开发案例,展示如何将创新理念融入应用设计之中。我们将从基础出发,逐步深入到高级话题,旨在为读者提供一个全面而深刻的技术洞见。
|
14天前
|
网络协议 Linux Shell
探索Linux操作系统:从基础到高级编程
【8月更文挑战第31天】本文旨在为读者提供一条清晰的路径,从Linux操作系统的基础知识出发,逐步深入到高级编程技巧。我们将一起揭开Linux神秘的面纱,了解其内部工作原理,并通过实际代码示例加深理解。无论你是初学者还是有一定经验的开发者,这篇文章都将为你带来新的视角和技能提升。