随笔 - 16, 文章 - 0, 评论 - 55, 引用 - 0
数据加载中……

fltk剖析 main-loop(二)

做为一个以c++为目标语言且要适配各种平台的界面库,FLTK注定是小众的,所以写的内容要限定一下受众。如果你对c/c++比较熟悉,至少对某一种操作系统的API比较熟悉,希望找到某种一次编写到处编译的界面库,同时对灵活性和尺寸比较在意,那么这个文档就比较适合你。如果你只是希望学会怎么使用fltk,并不想深入了解它背后的原理,那么这个文档就不太适合,fltk的在线文档在这里:http://www.fltk.org/documentation.php

fltk最初的思路来自于1987年的NeXT系统,初始版本针对的是X,所以代码里有一些用X开头的函数名,但随着代码的不断演进,接口逐渐变得和系统无关。

基本上,fltk认为所有的操作系统都会提供以下几种功能:
1.窗口创建和销毁
2.绘图(点,直线,曲线,圆...)
3.字体显示
4.输入设备交互(键盘、鼠标)

只要有这几种功能,不需要系统提供全套的控件,也可以自行构建出界面。另外系统还会提供一些附加功能,对于丰富界面也很有帮助,但并不是充分必要条件,比如
1.图片读写
2.文件操作
3.打印机
4.输入法

基于这样的认知,做为一个GUI库,fltk需要提供一个模型,把这些元素组合在一起,既要有足够的弹性又要足够简单,FLTK采用的是main-loop,相信很多人开始学习c语言的时候都会写下面的代码:
#include <stdio.h>
int main(int argc, char** argv)
{
   printf("hello world\n");
   return 0;
}

fltk所使用的模型就和这个类似,用伪代码表示就是:
#include <fltk.h>
int main(int argc, char **argv)
{
   create_window(); // 创建窗口
   create_widget(); // 创建控件
   while (1) {
      if ( wait() ) break; // 事件循环
   }
   return 0;
}

是不是和gtk很类似?

这个模型的好处是容易理解,如果把所有的流程都用class包裹起来,虽然貌似充满了oo的味道,但是对于理解代码反而是有害的。任何代码都有一个入口,为了面向对象,甚至把入口也藏起来,只会增加学习者的困扰。比如mfc,qt,juce,wxwidgets,如果想分析代码,光是找到起点就很不容易,尤其为了oo,很多GUI库用宏将main都包裹了起来,更增加了理解的难度。代码不应该让编译器舒服,也不应该屈从于某种思想,而是应该以人为本,让程序员看的轻松用的轻松。人的注意力是有限的,短期记忆大概只有十几分钟的时间,同时注意到的目标也不多,而且似乎人的思维模式是线性的,也就是说只能在一条线上做深入思考,并行处理好几个问题,大脑会短路。当然有些发达的大脑有一心多用的本领,但是总要照顾大多数人吧?

首先谈谈这个main(),为什么叫这个名字?这和编译器和操作系统有关,具体原因可以自行百度,重要的只有一条,这是程序的入口。事实上并不是所有的操作系统都用这个名称,osx/ios/linux是用main,windows/wince用的是winmain,android/windows phone干脆没有main,所以要为所有的平台编写统一的main。先看看windows平台的实现,打开fltk的源代码,找到src/fl_call_main.c
extern int main(int, char *[]);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  int rc, i;
  char **ar;

#  ifdef _DEBUG
// 这里用来创建一个cmd窗口,或者叫dos窗口,用以输出调试结果,只在debug版里提供
 /*
  * If we are using compiling in debug mode, open a console window so
  * we can see any printf's, etc...
  *
  * While we can detect if the program was run from the command-line -
  * look at the CMDLINE environment variable, it will be "WIN" for
  * programs started from the GUI - the shell seems to run all WIN32
  * applications in the background anyways...
  */

  AllocConsole();
  freopen("conin$", "r", stdin);
  freopen("conout$", "w", stdout);
  freopen("conout$", "w", stderr);
#  endif /* _DEBUG */

  ar = (char**) malloc(sizeof(char*) * (__argc + 1));
  i = 0;
  while (i < __argc) {
    int l;
    unsigned dstlen;
    if (__wargv ) {
      for (l = 0; __wargv[i] && __wargv[i][l]; l++) {}; /* is this just wstrlen??? */
      dstlen = (l * 5) + 1;
      ar[i] = (char*) malloc(dstlen);
/*    ar[i][fl_unicode2utf(__wargv[i], l, ar[i])] = 0; */
      dstlen = fl_utf8fromwc(ar[i], dstlen, __wargv[i], l);
      ar[i][dstlen] = 0;
    } else {
      for (l = 0; __argv[i] && __argv[i][l]; l++) {};
      dstlen = (l * 5) + 1;
      ar[i] = (char*) malloc(dstlen);
/*      ar[i][mbcs2utf(__argv[i], l, ar[i], dstlen)] = 0; */
      ar[i][mbcs2utf(__argv[i], l, ar[i])] = 0;
    }
    i++;
  }
  ar[__argc] = 0;
  /* Run the standard main entry point function... */
  rc = main(__argc, ar);

#  ifdef _DEBUG
  fclose(stdin);
  fclose(stdout);
  fclose(stderr);
#  endif /* _DEBUG */

  return rc;
}

看起来很简单,就是将winmain包装了一下,做了一些初始化的工作,再引出main。

osx/linux直接使用了main,所以没什么可解释的

接下来是loop。在windows下面比较好理解,打开src/Fl_win32.cxx,找到如下的代码:
int fl_wait(double time_to_wait) {
  ...
  if (Fl::idle && !in_idle) { // 若处于空闲时间且存在idle函数,执行之
    in_idle = 1;
    Fl::idle();
    in_idle = 0;
  }
  ... 
  while ((have_message = PeekMessageW(&fl_msg, NULL, 0, 0, PM_REMOVE)) > 0) {
    if (fl_send_system_handlers(&fl_msg))
      continue;

    // Let applications treat WM_QUIT identical to SIGTERM on *nix
    if (fl_msg.message == WM_QUIT)
      raise(SIGTERM);

    if (fl_msg.message == fl_wake_msg) {
      // Used for awaking wait() from another thread
      thread_message_ = (void*)fl_msg.wParam;
      process_awake_handler_requests();
    }

    TranslateMessage(&fl_msg);
    DispatchMessageW(&fl_msg);
  }
  ...
  return 1;
}
基本上就是<<Windows程序设计>>上的那一套,就不做说明了

再打开src/Fl_x.cxx,找到fl_wait函数,这里是linux下的loop主体,具体代码就不分析了,有兴趣的可以去找X编程的资料

最后是osx的loop,在osx下面runlooper是不能由程序直接控制的,只能通过外围发送和接收消息的方式曲线救国,所以FLTK用了一个线程,然后在线程里和runlooper交互。打开src/Fl_cocoa.mm,找到fl_wait函数,再找到DataReady类,这两个部分组合起来就构成了osx的loop功能,具体实现是用object-c和c/c++混合完成的

以上是各个系统各自的loop功能,最后还要将他们整合起来,打开src/Fl.cxx:
int Fl::run() {
  while (Fl_X::first) wait(FOREVER);
  return 0;
}

double Fl::wait(double time_to_wait) {
  // delete all widgets that were listed during callbacks
  do_widget_deletion();

#ifdef WIN32

  return fl_wait(time_to_wait);

#elif defined(__APPLE__)

  run_checks();
  return fl_mac_flush_and_wait(time_to_wait);

#else

  if (first_timeout) {
    elapse_timeouts();
    Timeout *t;
    while ((t = first_timeout)) {
      if (t->time > 0) break;
      // The first timeout in the array has expired.
      missed_timeout_by = t->time;
      // We must remove timeout from array before doing the callback:
      void (*cb)(void*) = t->cb;
      void *argp = t->arg;
      first_timeout = t->next;
      t->next = free_timeout;
      free_timeout = t;
      // Now it is safe for the callback to do add_timeout:
      cb(argp);
    }
  } else {
    reset_clock = 1; // we are not going to check the clock
  }
  run_checks();
//  if (idle && !fl_ready()) {
  if (idle) {
    if (!in_idle) {
      in_idle = 1;
      idle();
      in_idle = 0;
    }
    // the idle function may turn off idle, we can then wait:
    if (idle) time_to_wait = 0.0;
  }
  if (first_timeout && first_timeout->time < time_to_wait)
    time_to_wait = first_timeout->time;
  if (time_to_wait <= 0.0) {
    // do flush second so that the results of events are visible:
    int ret = fl_wait(0.0);
    flush();
    return ret;
  } else {
    // do flush first so that user sees the display:
    flush();
    if (idle && !in_idle) // 'idle' may have been set within flush()
      time_to_wait = 0.0;
    return fl_wait(time_to_wait);
  }
#endif
}

看起来很明显,就是将各个平台的fl_wait包装起来组合成统一的接口,现在看一个fltk的示例代码:test/hello.cxx
#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Box.H>

int main(int argc, char **argv) {
  Fl_Window *window = new Fl_Window(340,180);
  Fl_Box *box = new Fl_Box(20,40,300,100,"Hello, World!");
  box->box(FL_UP_BOX);
  box->labelfont(FL_BOLD+FL_ITALIC);
  box->labelsize(36);
  box->labeltype(FL_SHADOW_LABEL);
  window->end();
  window->show(argc, argv);
  return Fl::run();
}

将Fl::run()展开,就是
int main(int argc, char **argv) {
  .. // create windows and widgets
 
  while (Fl_X::first) wait(FOREVER);
  return 0;
}

这就是FLTK的main-loop模型。简单,实用,好理解

posted on 2015-11-01 11:58 cyantree 阅读(2179) 评论(0)  编辑 收藏 引用


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理