专栏首页音视频直播技术专家为了分析WebRTC, 重学Windows开发

为了分析WebRTC, 重学Windows开发

N多年没有写过 Window 程序了。为了研究 WebRTC 源码,这两天重新学习一下。还记得上大学的时候看过 《Windows95 程式设计》中国台湾版,对那本书印象极为深刻。一是当时国内确实没有一本写的那么深入的书籍,二是那本书翻译的特别好,让人一看就特别明白。10多年过多了,当时的情景还记忆犹新,也可见那本书写的有多好了。

Windows开发有很多知识点,窗口啊,句柄啊,消息啊,重绘啊,baba .....,但一个 Windows 程序的核心就是一个消息处理机制。

Windows程序运行的基本原理

Windows程序是消息为驱动的,所以它的核心就是消息的传递与处理。如鼠标消息、键盘消息,Timer消息,窗口的创建与消毁等等。那么,Windows程序是在哪儿处理消息呢?是否掌握了它,就控制了Windows程序的核心呢?答案是肯定的,它就是 WndProc 函数。所有的消息都要经过这个函数处理。

Windows 程序有两种消息,一种是队列消息,它通过 DispatchMessage 函数分发给 WndProc 函数,像鼠标消息、键盘消息,Timer消息都是这类消息。另一种是非队列消息,它是系统函数直接发送给 WndProc 函数的,像窗口的创建与消毁消息,WM_COMMON消息等等都是非队列消息。

最简单的 Windows 程序

一个最简单的 Windows 程序都包括哪些内容呢?下面我们详细介绍一下:

WinMain 函数

我们都知道无论是Windows程序,还是Linux程序,也无论是C/C++,还是 Java语言,它们都有一个 main 函数。更准确点说应该叫“程序入口点”。

我们写程序时,一般都以 main 开头,编译器在编译该程序时,会将 main 函数地址写入到可执行文件的文件头中,这就是“程序入口点”了。

在执行程序时,操作系统首先通过程序加载器将要运行的程序加载到内存中,然后重新计算符号地址表。一切准备就绪后,才跳到程序入口点,将一条条指令送入CPU流水线开始执行程序。这就是程序的运行的基本流程。

因此,我们可以知道出每个程序都有一个入口点。但是否不一定以 main 为开头呢? 其实,只要编译器能识别出入口点就可以,不必非要以 main 为标志。对于 Windows 程序就是这样,它就不使用 main 而换作了 WinMain 作为程序入口点。格式如下:

int CALLBACK WinMain(  
   _In_  HINSTANCE hInstance,  
   _In_  HINSTANCE hPrevInstance,  
   _In_  LPSTR lpCmdLine,  
   _In_  int nCmdShow  
);  

实现消息中心函数 WndProc()

上面一节我也介绍了 WndProc 是 Windows 程序的消息中心。所有的消息都要在这个函数中处理。如 窗口创建时发送的 WM_CREATE 消息,如果我们不处理它,Windows 操作系统就不会显示创建的窗口。

但 Windows 中有那么多消息,我们每个都处理企不是要累死人?所以 Windows 很贴心的提供了一个API,就是 DefWindowProc 函数。在该函数中对所有的 Windows 消息都做了默认处理。如果我们很懒的话,可以将所有消息都交由它来处理就好了。

有没有坐过山车的感脚?开始觉得很苦闷,突然又拨云见日了。嘿嘿!

LRESULT CALLBACK WndProc(  
        _In_  HWND hwnd,  
        _In_  UINT uMsg,  
        _In_  WPARAM wParam,  
        _In_  LPARAM lParam  
) {   
    return DefWindowProc(hwnd, uMsg, wParam, lParam);  
} 

注册窗口类

我们在创建窗口之前要注册一个窗口类,它是干啥用的呢?就是告诉操作系统,我要创建个什么样子的窗口,是啥背景色,鼠标是啥样子的,程序叫啥名子等等。

有了这个窗口类,我们就可以创建很多这样子的窗口了,这样是不是觉得很方便呢?当然,如果只创建一个貌似也就没啥子优势!

除了上面那些,它其实最最重要的作用是指定 WndProc 函数,也就是 Window 程序的 "消息中心"。消息中心是谁,完全是由 RegisterClass 说了算。它说 WndProc 就是 WndProc,它说 WindowProc 就是 WindowProc。

它长的像下面这个样子:

     // 类名  
     WCHAR* cls_Name = L"My Class";  
     // 设计窗口类  
     WNDCLASS wc = { };  
     wc.hbrBackground = (HBRUSH)COLOR_WINDOW;  
     wc.lpfnWndProc = WndProc;  
     wc.lpszClassName = cls_Name;  
     wc.hInstance = hInstance;  
     // 注册窗口类  
     RegisterClass(&wc);

创建窗口

创建窗口就比较简单了,高多少,宽多少,透明的还是非透明的,可显示还是不可显示,标题栏上要写啥字等等,这些都是它说了算。形式如下:

// 创建窗口  
      HWND hwnd = CreateWindow(  
      cls_Name,           //类名,要和刚才注册的一致  
      L"我的应用程序",  //窗口标题文字  
      WS_OVERLAPPEDWINDOW, //窗口外观样式  
      38,                 //窗口相对于父级的X坐标  
      20,                 //窗口相对于父级的Y坐标  
      480,                //窗口的宽度  
      250,                //窗口的高度  
      NULL,               //没有父窗口,为NULL  
      NULL,               //没有菜单,为NULL  
      hInstance,          //当前应用程序的实例句柄  
      NULL);              //没有附加数据,为NULL  

显示窗口

窗口创建完了,还要主动调函数让它显示出来,否则它是不会出来干活的。形式如下:

// 显示窗口  
ShowWindow(hwnd, SW_SHOW);

循环处理,检索与分发消息

这部分工作是在 WinMain 函数中要做的事儿。在 WinMain 中写一个循环,不停的从系统消息队列中取消息。

如果此时没有消息,则该线被程阻塞,并将CPU资源释放;如果有消息,需要判断是不是退出消息?如果不是,使用 DispatchMessage 将该消息分配出去。如果是退出消息,则退出消息循环,程序结束。代码如下:

void WinMan(...){

    ...

    // 消息循环  
    MSG msg;  
    while(GetMessage(&msg, NULL, 0, 0))  
    {  
        TranslateMessage(&msg);  
        DispatchMessage(&msg);  
    }

} 

以上就是一个最简单的窗口 Window 程序。了解了上在的知识,大家是不是觉得不用 MFC 自己写个 Windows 程序也不是很难了?

重要函数详细介绍

WinMain

int CALLBACK WinMain(
  _In_ HINSTANCE hInstance, //句柄
  _In_ HINSTANCE hPrevInstance, //总是 NULL
  _In_ LPSTR     lpCmdLine, //在命令行启动程序时的命令
  _In_ int       nCmdShow //程序启动时的显示方式
);
  • hInstance:句柄,就是一个内存地址,在该地址上有该程序的基本信息。
  • hPrevInstance:总是NULL,没啥用。
  • lpCmdLine: 用命令行启动时的命令,有兴趣的可以自己打印出来。
  • nCmdShow:程序启动时的显示方式,是隐藏,还是显示,是最大化,还是最小化显示。

注册窗口

typedef struct tagWNDCLASS {
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCTSTR   lpszMenuName;
  LPCTSTR   lpszClassName;
} WNDCLASS, *PWNDCLASS;

ATOM WINAPI RegisterClass(
  _In_ const WNDCLASS *lpWndClass
);
  • style :设置窗口样式。可以不设置。
  • lpfnWndProc :这个字段特别重要,设置消息处理函数,它是消息的中心。
  • cbClsExtra :不用设置。
  • cbWndExtra :不用设置。
  • hInstance :窗口句柄,与WinMain中的一样。
  • hIcon :窗口图标。如果是NULL,使用默认图标。
  • hCursor :设置光标样式。可以不设置
  • hbrBackground :设置窗口背景色。
  • lpszMenuName:菜单名。如果为NULL说明没有菜单。
  • lpszClassName:这个参数要提供,长度不超过 256。

创建窗口

HWND WINAPI CreateWindow(
  _In_opt_ LPCTSTR   lpClassName,
  _In_opt_ LPCTSTR   lpWindowName,
  _In_     DWORD     dwStyle,
  _In_     int       x,
  _In_     int       y,
  _In_     int       nWidth,
  _In_     int       nHeight,
  _In_opt_ HWND      hWndParent,
  _In_opt_ HMENU     hMenu,
  _In_opt_ HINSTANCE hInstance,
  _In_opt_ LPVOID    lpParam
);
  • lpClassName : 与注册的类名子一致。
  • lpWindowName :窗口标题栏名子。
  • dwStyle :窗口外观样式。
  • x :窗口起始位置 x。
  • y :窗口起始位置 y。
  • nWidth :窗口宽度。
  • nHeight :窗口高度。
  • hWndParent :父窗口,没有的话设置为NULL
  • hMenu :窗口菜单,没有设置为NULL
  • hInstance : 窗口句柄。
  • lpParam :符加数据,没有设置为 NULL

小结

本文首先介绍了一个Windows程序程序是由消息驱动的,它的核心是注册窗口类API RegisterClass 中指定的 WinProc 函数。WinProc是Windows消息处理中心,所有的消息都要交由它来处理。然后对一个最简单的 Windows程序做了剖析,指出通过 6 大步可以创建出一个最简单的 Windows程序,它们分别是:

  • 设置入口点,WinMain。
  • 创建 WinProc 函数。
  • 注册窗口类。
  • 创建窗口。
  • 显示窗口。
  • 循环处理,检索与分发消息

至此,一个Windows程序窗口已经展现在你面前了。后面就可以往里不断的增加内容了。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Mac下的 sshd 服务

    最近要用到 Mac 下的 sshd 服务, 但每次使用的时候都是出现各种状况,所以特写此篇文章对 Mac下的sshd服务做一下梳理。在 Mac 下启动 sshd...

    音视频_李超
  • medooze源码分析--SDP

    SDP(Session Description Protocol) 的目的是在媒体会话中传递媒体信息。SDP在很多地方使用,WebRTC也会使用它做媒体信息交换...

    音视频_李超
  • Android端实现1对1音视频实时通话

    在学习 WebRTC 的过程中,学习的一个基本步骤是先通过 JS 学习 WebRTC的整体流程,在熟悉了整体流程之后,再学习其它端如何使用 WebRTC 进行互...

    音视频_李超
  • Python爬虫入门教程 22-100 CSDN学院课程数据抓取

    今天又要抓取一个网站了,选择恐惧症使得我不知道该拿谁下手,找来找去,算了,还是抓取CSDN学院吧,CSDN学院的网站为 https://edu.csdn.net...

    梦想橡皮擦
  • 【Rust日报】 2019-10-15 使Tokio调度程序快10倍

    在v0.2我们实现了所有运算符和可视线程的安全性之后,RxRust现在可以通过调度程序跨线程传递任务。这样,所有用户提供的闭包都必须满足Send + Sync ...

    MikeLoveRust
  • 贴心 | Visual Studio 2019 空引用异常的改进

    空引用异常是最常见的程序错误之一。微软昨天发布的VS2019对此有了非常贴心的改进,我们来看看吧!

    Edi Wang
  • rhel7修改ssh端口

    rhel7在安全上考虑的比较细,规则设计比较严密。列出关键修改步骤,针对SSH调整实验工作记录。

    孙杰
  • 【吊打面试,击中要害】分布式事务解决方案

    分布式事务解决方案有很多种,最要包括基于XA协议的两阶段提交方案、本地消息表方案、TCC事务补偿型方案、可靠消息最终一致性方案、尽最大努力通知型方...

    java乐园
  • Waymo消灭方向盘之心不死,退休三个月的“萤火虫”要回来了?

    夏乙 编译整理 量子位 报道 | 公众号 QbitAI ? 2016年底,Google拆分无人车团队,成立Waymo。Waymo说:“我们是开发自动驾驶技术的公...

    量子位
  • Java I/O 模型的演进

    什么是同步?什么是异步?阻塞和非阻塞又有什么区别?本文先从 Unix 的 I/O 模型讲起,介绍了5种常见的 I/O 模型。而后再引出 Java 的 I/O 模...

    九州暮云

扫码关注云+社区

领取腾讯云代金券