理解消息循环和窗口过程
  
  在利用 MiniGUI 开发应用程序之前,首先要理解的两个概念就是消息循环和窗口过程。消息循环是事件驱动的 GUI 编程之基础。而窗口则是图形用户界面的最基本交互元素。本文描述了 MiniGUI 中与消息相关的几个重要函数,也描述了 MiniGUI-Threads 和 MiniGUI-Lite 在消息循环实现上的几个不同。本文还讲述了在 MiniGUI 中的窗口建立和销毁过程,并解释了窗口过程的概念以及对一些重要消息的处理。
  引言
  我们知道,流行的 GUI 编程都有一个重要的概念与之相关,即"事件驱动编程"。事件驱动的含义就是,程序的流程不再是只有一个入口和若干个出口的串行执行线路;相反,程序会一直处于一个循环状态,在这个循环当中,程序从外部输入设备获取某些事件,比如用户的按键或者鼠标的移动,然后根据这些事件作出某种的响应,并完成一定的功能,这个循环直到程序接受到某个消息为止。"事件驱动"的底层设施,就是常说的"消息队列"和"消息循环"。本文将具体描述 MiniGUI 中用来处理消息的几个重要函数,并描述 MiniGUI-Threads 和 MiniGUI-Lite 在消息循环实现上的一些不同。
  
  窗口是 MiniGUI 当中最基本的 GUI 元素,一旦窗口建立之后,窗口就会从消息队列当中获取属于自己的消息,然后交由它的窗口过程进行处理。这些消息当中,有一些是基本的输入设备事件,而有一些则是与窗口管理相关的逻辑消息。本文将讲述 MiniGUI 中的窗口建立和销毁过程,并解释了窗口过程的概念以及对一些重要消息的处理。
  
  2 消息和消息循环
  在 MiniGUI 中,消息被如下定义(include/window.h):
  
  352 typedef struct _MSG
   353 {
   354 HWND hwnd;
   355 int message;
   356 WPARAM wParam;
   357 LPARAM lParam;
   358 #ifdef _LITE_VERSION
   359 unsigned int time;
   360 #else
   361 struct timeval time;
   362 #endif
   363 POINT pt;
   364 #ifndef _LITE_VERSION
   365 void* pAdd;
   366 #endif
   367 }MSG;
   368 typedef MSG* PMSG;
  
  一个消息由该消息所属的窗口(hwnd)、消息编号(message)、消息的 WPARAM 型参数(wParam)以及消息的 LPARAM 型参数(lParam)组成。消息的两个参数中包含了重要的内容。比如,对鼠标消息而言,lParam 中一般包含鼠标的位置信息,而 wParam 参数中则包含发生该消息时,对应的 SHIFT 键的状态信息等。对其他不同的消息类型来讲,wParam 和 lParam 也具有明确的定义。当然,用户也可以自定义消息,并定义消息的 wParam 和 lParam 意义。为了用户能够自定义消息,MiniGUI 定义了 MSG_USER 宏,可如下定义自己的消息:
  
  #define MSG_MYMESSAGE1 (MSG_USER 1)
  #define MSG_MYMESSAGE2 (MSG_USER 2)
  
  用户可以在自己的程序中使用自定义消息,并利用自定义消息传递数据。
  
  在理解消息之后,我们看消息循环。简而言之,消息循环就是一个循环体,在这个循环体中,程序利用 GetMessage 函数不停地从消息队列中获得消息,然后利用 DispatchMessage 函数将消息发送到指定的窗口,也就是调用指定窗口的窗口过程,并传递消息及其参数。典型的消息循环如下所示:
  
  while (GetMessage (&Msg, hMainWnd)) {
   TranslateMessage (&Msg);
   DispatchMessage (&Msg);
  }
  
  如上所示,GetMessage 函数从 hMainWnd 窗口所属的消息队列当中获得消息,然后调用 TranslateMessage 函数将 MSG_KEYDOWN 和 MSG_KEYUP 消息翻译成 MSG_CHAR 消息,最后调用 DispatchMessage 函数将消息发送到指定的窗口。
  
  
  在 MiniGUI-Threads 版本中,每个建立有窗口的 GUI 线程有自己的消息队列,而且,所有属于同一线程的窗口共享同一个消息队列。因此,GetMessage 函数将获得所有与 hMainWnd 窗口在同一线程中的窗口的消息。
  
  而在 MiniGUI-Lite 版本中,只有一个消息队列,GetMessage 将从该消息队列当中获得所有的消息,而忽略 hMainWnd 参数。
  
  3 几个重要的消息处理函数
  除了上面提到的 GetMessage 和 TranslateMessage、DispatchMessage 函数以外,MiniGUI 支持如下几个消息处理函数。
  
  PostMessage:该函数将消息放到指定窗口的消息队列后立即返回。这种发送方式称为"邮寄"消息。如果消息队列中的邮寄消息缓冲区已满,则该函数返回错误值。在下一个消息循环中,由 GetMessage 函数获得这个消息之后,窗口才会处理该消息。PostMessage 一般用于发送一些非关键性的消息。比如在 MiniGUI 中,鼠标和键盘消息就是通过 PostMessage 函数发送的。
  
  SendMessage:该函数和 PostMessage 函数不同,它在发送一条消息给指定窗口时,将等待该消息被处理之后才会返回。当需要知道某个消息的处理结果时,使用该函数发送消息,然后根据其返回值进行处理。在 MiniGUI-Threads 当中,如果发送消息的线程和接收消息的线程不是同一个线程,发送消息的线程将阻塞并等待另一个线程的处理结果,然后继续运行;否则,SendMessage 函数将直接调用接收消息窗口的窗口过程函数。MiniGUI-Lite 则和上面的第二种情况一样,直接调用接收消息窗口的窗口过程函数。
  
  SendNotifyMessage:该函数和 PostMessage 消息类似,也是不等待消息被处理即返回。但和 PostMessage 消息不同,通过该函数发送的消息不会因为缓冲区满而丢失,因为系统采用链表的形式处理这种消息。通过该函数发送的消息一般称为"通知消息",一般用来从控件向其父窗口发送通知消息。
  
  PostQuitMessage:该消息在消息队列中设置一个 QS_QUIT 标志。GetMessage 在从指定消息队列中获取消息时,会检查该标志,如果有 QS_QUIT 标志,GetMessage 消息将返回 FALSE,从而可以利用该返回值终止消息循环。
  
  4 MiniGUI-Threads 和 MiniGUI-Lite 在消息处理上的不同
  表 1 总结了 MiniGUI-Threads 和 MiniGUI-Lite 在消息处理上的不同
  
  表 1 MiniGUI-Threads 和 MiniGUI-Lite 在消息处理上的不同
  
   MiniGUI-Threads MiniGUI-Lite
  多消息队列 每个创建窗口的线程拥有独立的消息队列只有一个消息队列。所有窗口共享一个消息队列。除非嵌套消息循环,否则一个程序中只有一个消息循环。
  内建多线程处理 是。可以自动处理跨线程的消息传递不能。从一个线程向另外一个线程发送或者邮寄消息时,必须通过互斥处理保护消息队列。
  其他 可以利用 PostSyncMessage 函数跨线程发送消息,并等待消息的处理结果 不能使用 PostSyncMessage、SendAsynMessage 等消息。
  
  5 窗口的建立和销毁
  
  5.1 窗口的建立
  我们知道,MiniGUI 的 API 类似 Win32 的 API。因此,窗口的建立过程和 Windows 程序基本类似。不过也有一些差别。首先我们回顾一下 Windows 应用程序的框架:
  
  在 WinMain () 中创建窗口,使用以下步骤:创建窗口类、登记窗口类、创建并显示窗口、启动消息循环。
  在 WndProc () 中,负责对发到窗口中的各种消息进行响应。
  在 MiniGUI 中也同样要有这两个函数。不过稍微有点不同。程序的入口函数名字叫MiniGUIMain (),它负责创建程序的主窗口。在建立主窗口之后,程序进入消息循环。
  
  
  在 Win32 程序中,在建立一个主窗口之前,程序首先要注册一个窗口类,然后创建一个属于该窗口类的主窗口。MiniGUI 却没有在主窗口中使用窗口类的概念。在 MiniGUI 程序中,首先初始化一个 MAINWINCREATE 结构,该结构中元素的含义是:
  
  CreateInfo.dwStyle: 窗口风格
  CreateInfo.spCaption: 窗口的标题
  CreateInfo.dwExStyle : 窗口的附加风格
  CreateInfo.hMenu: 附加在窗口上的菜单句柄
  CreateInfo.hCursor: 在窗口中所使用的鼠标光标句柄
  CreateInfo.hIcon: 程序的图标
  CreateInfo.MainWindowProc: 该窗口的消息处理函数指针
  CreateInfo.lx: 窗口左上角相对屏幕的绝对横坐标,以象素点表示
  CreateInfo.ty: 窗口左上角相对屏幕的绝对纵坐标,以象素点表示
  CreateInfo.rx: 窗口的长,以象素点表示
  CreateInfo.by: 窗口的高,以象素点表示
  CreateInfo.iBkColor: 窗口背景颜色
  CreateInfo.dwAddData: 附带给窗口的一个 32 位值
  CreateInfo.hHosting: 窗口消息队列所属
  
  其中有如下几点要特别说明:
  
  CreateInfo.dwAddData:在程序编制过程中,应该尽量减少静态变量,但是如何不使用静态变量而给窗口传递参数呢?这时可以使用这个域。该域是一个 32 位的值,因此可以把所有需要传递给窗口的参数编制成一个结构,而将结构的指针赋予该域。在窗口过程中,可以使用 GetWindowAdditionalData 函数获取该指针,从而获得所需要传递的参数。
  CreateInfo.hHosting:该域表示的是将要建立的主窗口使用哪个主窗口的消息队列。使用其他主窗口消息队列的主窗口,我们称为"被托管"的主窗口。当然,这只在 MiniGUI-Threads 版本中有效。
  MainWinProc 函数负责处理窗口消息。这个函数就是主窗口的"窗口过程"。窗口过程一般有四个入口参数,第一个是窗口句柄,第二个是消息类型,第三个和第四个是消息的两个参数。
  
  在准备好MAINWINCREATE 结构之后,就可以调用 CreateMainWindow 函数建立主窗口了。在建立主窗口之后,典型的程序将进入消息循环。如下所示:
  
  int MiniGUIMain (int args, const char* arg)
  {
   MSG Msg;
   MAINWINCREATE CreateInfo;
   HWND hWnd;
  
   // 初始化 MAINWINCREATE 结构
   CreateInfo.dwStyle = WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | WS_CAPTION;
   CreateInfo.spCaption= "MiniGUI step three";
   CreateInfo.dwExStyle = WS_EX_NONE;
   CreateInfo.hMenu = createmenu();
   CreateInfo.hCursor = GetSystemCursor(0);
   CreateInfo.hIcon = 0;
   CreateInfo.MainWindowProc = MainWinProc;
   CreateInfo.lx = 0;
   CreateInfo.ty = 0;
   CreateInfo.rx = 640;
   CreateInfo.by = 480;
   CreateInfo.iBkColor = COLOR_lightwhite;
   CreateInfo.dwAddData = 0;
   CreateInfo.hHosting = HWND_DESKTOP;
  
   // 建立主窗口
   hWnd = CreateMainWindow(&CreateInfo);
   if (hWnd == HWND_INVALID)
   return 0;
  
   // 显示主窗口
   ShowWindow (hWnd, SW_SHOWNORMAL);
  
   // 进入消息循环
   while (GetMessage(&Msg, hWnd)) {
   TranslateMessage (&Msg);
   DispatchMessage(&Msg);
   }
  
   MainWindowThreadCleanup (hWnd);
   return 0;
  }

注意,和 Windows 程序不同的是,在退出消息循环之后,还要调用一个函数,即 MainWindowThreadCleaup 函数。该函数的工作是销毁主窗口的消息队列,一般在线程或者进程的最后调用。
  
  5.2 窗口的销毁
  要销毁一个主窗口,可以利用 DestroyMainWindow (hWnd) 函数。该函数将销毁主窗口,但不会销毁主窗口所使用的消息队列,而要使用MainWindowThreadCleaup 最终清除主窗口所使用的消息队列。
  
  一般而言,一个主窗口过程在接收到 MSG_CLOSE 消息之后会销毁主窗口,并调用 PostQuitMessage 消息终止消息循环。如下所示:
  
  case MSG_CLOSE:
   // 销毁窗口使用的资源
   DestroyLogFont (logfont1);
   DestroyLogFont (logfont2);
   DestroyLogFont (logfont3);
  
   // 销毁子窗口
   DestroyWindow(hWndButton);
   DestroyWindow(hWndEdit);
   // 销毁主窗口
   DestroyMainWindow (hWnd);
   // 发送 MSG_QUIT 消息
   PostQuitMessage(hWnd);
   return 0;
  
  6 几个重要消息
  在窗口(包括主窗口和子窗口在内)的生存周期当中,有几个重要的消息需要仔细处理。下面描述这些消息的概念和典型处理。
  
  6.1 MSG_NCCREATE
  该消息在 MiniGUI 建立主窗口的过程中发送到窗口过程。lParam 中包含了由 CreateMainWindow 传递进入的 pCreateInfo 结构指针。您可以在该消息的处理过程中修改 pCreateInfo 结构中的某些值。
  
  6.2 MSG_SIZECHANGING
  该消息窗口尺寸发生变化时,或者建立窗口时发送到窗口过程,用来确定窗口大小。wParam 包含预期的窗口尺寸值,而 lParam 用来保存结果值。MiniGUI 的默认处理是,
  
  case MSG_SIZECHANGING:
   memcpy ((PRECT)lParam, (PRECT)wParam, sizeof (RECT));
   return 0;
  
  你可以截获该消息的处理,从而让即将创建的窗口位于指定的位置,或者具有固定的大小,比如在 SPINBOX 控件中,就处理了该消息,使之具有固定的大小:
  
  case MSG_SIZECHANGING:
  {
   const RECT* rcExpect = (const RECT*) wParam;
   RECT* rcResult = (RECT*) lPraram;
  
   rcResult->left = rcExpect->left;
   rcResult->top = rcExpect->top;
   rcResult->right = rcExpect->left _WIDTH;
   rcResult->bottom = rcExpect->left _HEIGHT;
   return 0;
  }
  
  6.3 MSG_CHANGESIZE
  在确立窗口大小之后,该消息被发送到窗口过程,用来通知确定之后的窗口大小。wParam 包含了窗口大小 RECT 的指针。注意应用程序应该将该消息传递给 MiniGUI 进行默认处理。
  
  6.4 MSG_SIZECHANGED
  该消息用来确定窗口客户区的大小,和 MSG_SIZECHANGING 消息类似。wParam 参数包含窗口大小信息,lParam 参数是用来保存窗口客户区大小的 RECT 指针,并且具有默认值。如果该消息的处理返回非零值,则将采用 lParam 当中包含的大小值作为客户区的大小;否则,将忽略该消息的处理。比如在 SPINBOX 控件中,就处理了该消息,并使客户区占具所有的窗口范围:
  
  case MSG_SIZECHANGED
  {
   RECT* rcClient = (RECT*) lPraram;
  
   rcClient->right = rcClient->left _WIDTH;
   rcClient->bottom = rcClient->top _HEIGHT;
   return 0;
  }
  
  6.5 MSG_CREATE
  该消息在建立好的窗口成功添加到 MiniGUI 的窗口管理器之后发送到窗口过程。这时,应用程序可以在其中创建子窗口。如果该消息返回非零值,则将销毁新建的窗口。注意,在 MSG_NCCREATE 消息被发送时,窗口尚未正常建立,所以不能在 MSG_NCCREATE 消息中建立子窗口。
  
  6.6 MSG_PAINT
  该消息在需要进行窗口重绘时发送到窗口过程。MiniGUI 通过判断窗口是否含有无效区域来确定是否需要重绘。当窗口在初始显示、从隐藏状态变化为显示状态、从部分不可见到可见状态,或者应用程序调用 InvalidateRect 函数使某个矩形区域变成无效时,窗口将具有特定的无效区域。这时,MiniGUI 将在处理完所有的邮寄消息、通知消息之后处理无效区域,并向窗口过程发送 MSG_PAINT 消息。该消息的典型处理如下:
  
  case MSG_PAINT:
  {
   HDC hdc;
  
   hdc = BeginPaint (hWnd);
  
   // 使用 hdc 绘制窗口
   ...
  
   EndPaint (hWnd, hdc);
   break;
  }
  
  6.7 MSG_DESTROY
  该消息在应用程序调用 DestroyMainWindow 或者 DestroyWindow 时发送到窗口过程当中,用来通知系统即将销毁一个窗口。如果该消息的处理返回非零值,则将取消销毁过程。
  
  7 Hello, World
  在这个小节当中,我们给出一个简单的示例程序,该程序在窗口中打印"Hello, world!":
  
  #include
  #include
  #include
  
  #include
  #include
  #include
  #include
  
  static int HelloWinProc (HWND hWnd, int message, WPARAM wParam, LPARAM lParam)
  {
   HDC hdc;
  
   switch (message) {
   case MSG_PAINT:
   hdc = BeginPaint (hWnd);
   TexOut (hdc, 0, 0, "Hello, world!");
   EndPaint (hWnd, hdc);
   break;
  
   case MSG_CLOSE:
   DestroyMainWindow (hWnd);
   PostQuitMessage (hWnd);
   return 0;
   }
  
   return DefaultMainWinProc(hWnd, message, wParam, lParam);
  }
  
  static void InitCreateInfo (PMAINWINCREATE pCreateInfo)
  {
   pCreateInfo->dwStyle = WS_CAPTION | WS_VISIBLE;
   pCreateInfo->dwExStyle = 0;
   pCreateInfo->spCaption = "Hello, world!" ;
   pCreateInfo->hMenu = 0;
   pCreateInfo->hCursor = GetSystemCursor (0);
   pCreateInfo->hIcon = 0;
   pCreateInfo->MainWindowProc = HelloWinProc;
   pCreateInfo->lx = 0;
   pCreateInfo->ty = 0;
   pCreateInfo->rx = 320;
   pCreateInfo->by = 240;
   pCreateInfo->iBkColor = PIXEL_lightwhite;
   pCreateInfo->dwAddData = 0;
   pCreateInfo->hHosting = HWND_DESKTOP;
  }
  
  int MiniGUIMain (int args, const char* arg)
  {
   MSG Msg;
   MAINWINCREATE CreateInfo;
   HWND hMainWnd;
  
  #ifdef _LITE_VERSION
   SetDesktopRect (0, 0, 800, 600);
  #endif
  
   InitCreateInfo (&CreateInfo);
  
   hMainWnd = CreateMainWindow (&CreateInfo);
   if (hMainWnd == HWND_INVALID)
   return -1;
  
   while (GetMessage (&Msg, hMainWnd)) {
   DispatchMessage (&Msg);
   }
  
   MainWindowThreadCleanup (hMainWnd);
   return 0;
  }
  
  很显然,这是一个非常简单的程序。该程序使用了 MiniGUI 的默认过程来处理我们前面提到的许多消息,而仅仅处理了 MSG_PAINT 和 MSG_CLOSE 两条消息。当用户单击标题栏上的关闭按钮时,MiniGUI 将发送 MSG_CLOSE 到窗口过程。这时,应用程序就可以销毁窗口,并终止消息循环,最终退出程序。
  
  
  8 小结
  本文描述了 MiniGUI 中与消息相关的几个重要函数,并讲述了 MiniGUI-Threads 和 MiniGUI-Lite 在消息机制实现上的几个不同。本文还讲述了在 MiniGUI 中的窗口建立和销毁过程,并解释了窗口过程的概念以及一些重要消息的处理。最后,本文给出了一个简单的 MiniGUI 的示例程序,该程序建立窗口,并在其中打印"Hello, world!"。

基于Linux和MiniGUI的嵌入式系统软件开发指南(二)相关推荐

  1. 基于 Linux 和 MiniGUI 的嵌入式系统软件开发指南(一)(转)

    自 MiniGUI 从 1998 年底推出以来,越来越多的人开始选择 MiniGUI 在 Linux 上开发实时嵌入式系统.为了帮助嵌入式软件开发人员使用 MiniGUI编写出更好的应用程序,我们将撰 ...

  2. 基于 Linux 和 MiniGUI 的嵌入式系统软件开发指南题八——MiniGUI 和其他嵌入式 Linux 上的图形及图形用户界面系统

    简介: 为了让读者对嵌入式 Linux 当中能够使用的图形及图形用户界面有个较为全面的认识,本文将为读者介绍一些嵌入式 Linux 系统中常见的图形及图形用户界面系统,并作为<基于 Linux ...

  3. 基于 Linux 和 MiniGUI 的嵌入式系统软件开发指南

    内容: 1 Linux 图形领域的基础设施 2 Linux 图形领域的高级函数库 3 面向嵌入式Linux 系统的图形用户界面 4 小结 关于作者 相关内容: 主题一:选择MiniGUI-Thread ...

  4. 基于 Linux 和 MiniGUI 的嵌入式系统软件开发指南(八)

    基于 Linux 和 MiniGUI 的嵌入式系统软件开发指南(八) MiniGUI 和其他嵌入式 Linux 上的图形及图形用户界面系统 魏永明 (ymwei@minigui.org) 自由撰稿人 ...

  5. 10001.基于Linux和MiniGUI的嵌入式系统软件开发指南(转载)

    1 Linux 图形领域的基础设施 2 Linux 图形领域的高级函数库 3 面向嵌入式Linux 系统的图形用户界面 4 小结 关于作者 相关内容: 主题一:选择MiniGUI-Threads 或者 ...

  6. 基于 Linux 和 MiniGUI 的嵌入式系统软件开发指南(六) MiniGUI 提供的非 GUI/GDI 接口...

    1 引言 一般而言,GUI 系统的应用程序编程接口主要集中于窗口.消息队列.图形设备等相关方面.但因为 GUI 系统在处理系统事件时通常会提供自己的机制,而这些机制往往会和操作系统本身提供的机制不相兼 ...

  7. 基于Linux和MiniGUI的嵌入式系统软件开发指南(五)

    MiniGUI 1.1.0 版本引入的新 GDI 功能和函数 本文向读者展现了 MiniGUI 的最新开发成果,即在 MiniGUI 1.1.0Pre4 版本中引入的新的 GAL 和新的 GDI 功能 ...

  8. 基于Linux和MiniGUI的嵌入式系统软件开发指南(六)

    本文讲述了 MiniGUI 为应用程序提供的非 GUI/GDI 接口,这些接口能够帮助应用程序更好地和操作系统交互,扩展应用程序功能,并提高应用程序的可移植性.内容主要涉及到如下几个方面:MiniGU ...

  9. 基于 Linux 和 MiniGUI 的嵌入式系统软件开发指南(三)

    1 引言 对话框编程是一个快速构建用户界面的技术.通常,我们编写简单的图形用户界面时,可以通过调用 CreateWindow 函数直接创建所有需要的子窗口,即控件.但在图形用户界面比较复杂的情况下,每 ...

  10. 基于Linux和MiniGUI的嵌入式系统软件开发指南(七)

    1 引言 我们在本系列主题五中曾经详细描述了在 MiniGUI 1.1.0 版本开发过程中添加的新 GDI 功能和函数.这些接口首次出现在版本 1.1.0Pre4 当中.目前 MiniGUI 1.1. ...

最新文章

  1. The CLR’s Execution Model(Chapter 1 of CLR via C#)
  2. 文件操作03——图片文件合成器
  3. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager
  4. 启动spark集群,JAVA_HOME is not set
  5. 几何画板200个经典课件_动态几何画板 Geogebra
  6. 自媒体各大平台收益对比_哪些自媒体平台没有新手期,适合小白撸收益?
  7. python 管道队列_关于python:Multiprocessing-管道与队列
  8. 作者:罗威,男,中国国防科技信息中心副研究员。
  9. 转android项目开发 工作日志 2011.10.8--toast消息框使用
  10. MSN下载2010最新版
  11. 堆排序算法设计与分析
  12. SAR图像的统计信息
  13. Guava 之 Splitter
  14. 44186818 mipi屏的艰难之旅
  15. 精算未来会被计算机代替吗,年薪过百万还不用加班?揭开精算专业的真面目(下篇)...
  16. 微软与GitHub百人签名,力挺996.ICU项目
  17. 51CTO与我的大学生活
  18. 500块搞定Windows下NAS和高清播放平台
  19. BPF编程-使用libbpf-bootstrap构建BPF应用程序【译】
  20. jQuery 特效:盒子破碎和移动动画效果

热门文章

  1. android系统性能优化(13)---Android性能优化典范 - 第1季
  2. 这才是2018年的技术趋势:云、大数据、IOT深度融合
  3. java定义一个静态类_Java中的静态类
  4. 一些社会运行的底层规律,和你的利益息息相关
  5. SELECT语句选项
  6. java程序动态加载jar包,并调用其中的方法
  7. 创业,白手起家需要些什么?
  8. 9999元起!荣耀首部折叠屏手机Magic V正式发布
  9. 台积电已开始试生产3nm芯片 率先为苹果、英特尔供货
  10. 联想拯救者电竞手机Pro透明版马上就到:一眼就能看到“芯”