Direct2D API 简单封装心得

最近因为一个自己写的一个小项目的需要,要封装一下Direct2D的API,其间踩了很多坑,写下来以备记录。

Direct2D 是微软推出的一套2D图形API,和以前的GDI与GDI+不同,Direct2D可以采用硬件来进行加速,从而速度较快,同时展示效果也要更好一点。

在Windows操作系统上写GUI程序势必离不开Win32 API,然而使用Win32 API创建一个窗口的初始化代码并没有很简单,初始化并注册窗口类、创建窗口、消息循环以及窗口过程函数加起来也是有个几十行代码的。为此在封装D2D API的时候还是要把Win 32 API进行一下简单的封装,下面从这两个方面总结一下。(只描述基本的方法,具体代码见附录)

2.Win 32 API 的简单封装

2.1 Windows创建一个窗口的基本步骤

在封装Win32 API之前,我们需要先来看一下Windows下使用Win32 API 创建窗口的基本步骤。大体来讲,这主要分为四步:

(1) 创建并注册窗口类

这里的窗口类和C++里的类并不是一个东西。他只是一个存储窗口的相关信息的结构体,大致需要先定义一个 WNDCLASSEX 类型的结构体,然后通过对相关的成员赋值设置一下窗口的基本样式,最后调用一下RegisterClassEx函数注册窗口类并判断是否成功即可。

(2) 创建窗口

这个简单,只要调用一下CreateWindowEx函数并调用一下ShowWindow和UpdateWindow即可,记得保存返回的窗口句柄即可。

(3) 消息循环

如果没有特殊要求的话,几乎所有的程序都是差不多的,万年不变,就是这么写

        
            while(GetMessage(&Msg, NULL, 0, 0) > 0)

  {

          TranslateMessage(&Msg);

          DispatchMessage(&Msg);

   }
        
    

值得注意的是DispatchMessage函数会把消息发送到回调函数WndProc里面(当然也可以是你自己指定的函数)

(4) 过程函数

这是Win32程序的主体部分,其基本结构就是一个大大的switch来对不同的消息进行处理,程序的功能部分基本在这里实现。

2.2 封装Win32 API的大体方法和注意事项

封装Win32 API 基本上就是定义一个class,把窗口的相关的东西都扔在里面,比如说窗口的句柄、窗口类、窗口的大小或者是一些其他的主要的可能会用到的东西,基本上你需要对用户暴露的东西都可以定义在类里面,然后通过一个初始化函数完成上面的四步,并定义一个专属的窗口过程函数即可。但是这里面在具体实现的时候可能有下面的几个问题:

(1) 窗口的消息循环的问题。

从上面可以看出来,窗口的消息循环是一个近似于死循环的一个东西,只要GetMessage的返回值不是0就不会结束。因此如果程序需要多个窗口就一定会因为这个循环的问题不能进行第二个窗口的创建。因此我们需要使用多线程来解决问题。专门为每一个窗口创建一个线程即可解决问题。 不过正如笑话里面说的,“我知道可以用多线程来解决这个问题,好了,我现在有两个问题了”,在引入多线程后,我们又不得不面临一个问题就是如何让类的成员函数使用单独的线程。因为创建线程的函数_beginthreadx函数中要求传入的函数参数必须是全局的函数,也就是说可以使普通的全局函数或者是类的静态函数。但是类的静态函数又不能像类成员函数一样直接对自己所属的这个类的东西进行方便的操作,因此收到了很大的限制。 这个问题其实可以现在类里面定义一个全局的静态函数,然后通过参数传入要操作的类的this指针,然后再用这个指针来调用另一个在这个类里面定义的普通的成员函数即可。(也就是做一个跳转)。

(2) 窗口的过程函数的问题

和前面提到的在类中实现多线程类似,窗口的过程函数因为也被要求是全局函数,同样存在类似的问题。但是不同的是窗口过程函数的参数是固定的几个参数,并没有机会和上面一样把this指针传到过程函数中所以需要采取其他的方法。最后经过思考我采用的方法是在类中定义一个静态的vector来保存窗口句柄和this指针。在类的构造函数里把窗口的句柄和这个类的this指针保存起来放到这个vector里面。因为窗口过程函数是可以接受一个hwnd参数的,因此这样以后我们就可以通过在一个静态的过程函数里面对这个vector遍历并比较其中储存的窗口句柄与传入的窗口句柄,就可以获得那个具体的类的this指针,然后就可以直接调用类专属的过程函数了。

3.Direct2D API 的封装和主要问题

3.1 Direct2D API使用的基本方法和初始化

Direct2D 的初始化还是相对比较复杂的。下面大致介绍一下基本的概念初始化的基本过程。 Direct2D 主要有以下几个概念: 1.Brush:画刷,负责绘制一些图形之类的东西。

2.Geomotry Text Bitmap:几何图形、文本以及位图。值得注意的是Direct2D中并没有提供加载图片的方法。因此这里需要使用WIC来实现。另外,对文本的绘制是通过DirectWrite来解决的。

3.Render Target:渲染目标。大体可以理解为这是一张纸,所有的绘制都是在Render Target上实现的。

4.Resource:资源。Direct2D的资源分为设备相关资源和设备无关资源两种。设备无关资源是指的和渲染设备无关的资源,被分配在CPU中。设备相关资源指的是和渲染设备关联的资源,一般来讲在硬件加速可用的时候系统会通过硬件来操作,不可用的时候通过CPU来解决。一般来讲,RenderTarget创建的资源基本都是设备相关的。关于具体的内容,可以查阅MSDN。

Direct2D的初始化主要是下面这么个流程: (1) 创建D2D工厂。这个简单,调用一下D2D1CreateFactory函数即可。 (2) 创建RenderTarget。调用一下D2DFactory.CreateHwndRenderTarget即可解决。 (3) 如果有必要,创建画刷、位图等相关的东西。 (4) 渲染。通过响应WM_PAINT消息来进行相应的绘制操作。也可以通过WM_PRINT消息结合SendMessage来解决。 (5) 清理相关资源、退出程序。

3.2 Direct2D API封装的基本方法和主要问题。

一般Direct2D作为GUI相关的东西,一般都是和前面Win 32 API结合的。因此在项目不是特别大的时候可以把Direct 2D和Win32封装在一起。当然,在项目相对比较大的情况下这种做法还是不是很可取的。这里就不关心啦。 封装也不难,主要就是在构造函数里面创建D2D 工厂、Render Target等必要的资源,同时在析构函数里注意清理资源即可。基本查文档可以解决大部分问题。 当然,在D2D的封装过程中还是遇到了一些问题。主要就是渲染的时机的问题。在创建D2DFactory的时候需要hwnd窗口句柄参数,也就是说初始化D2D资源只能放在创建窗口之后。但是我们在渲染的时候是响应WM_PAINT消息的。然而在创建窗口的时候经过测试也会产生WM_PAINT这个消息,而这个时候并没有进行初始化,因此在渲染的时候会出现问题导致程序崩溃。因此实际在响应WM_PAINT消息的时候要单独判断一下这时候相关的资源有没有初始化,如果没有初始化直接返回,只有在初始化以后才进行渲染即可。