‘Windows平台编程’ 分类的存档
最近看了下android程序设计,发现其界面布局是采用一个layout配置文件,以xml的形式配置UI,然后通过android框架对这个layout进行解析,动态生成界面控件。 这种界面设计模式,简化了UI的编码,使得我们可以像写HTML页面一样,设计应用程序的UI。
这种UI设计的方式,实际上在CSharp中也比较容易做到。我们可以轻易设计出一种控制器,来读入界面配置文件,并动态生成界面布局配置扎文件中定义的各种控件,最典型的如Button, Label之类。而后在控制器中添加控件事件响应函数,实现应用程序的业务逻辑。
采用CSharp语言实现这种动态UI配置比较容易,控件的生成,可以通过反射来实现。从配置文件指定的dll中,寻找到指定名称的控件,并动态生成即可。
那么使用C++语言进行界面设计时,可以实现这种动态界面布局么? 答案是可以的,只是会有一些麻烦。下面我将对在MFC中实现动态界面布局的问题以及解决方案作出分析。
动态界面布局有两大基础要素,一个是动态创建控件,一个是动态的为控件指定事件响应函数。
1. 动态创建控件
我们知道在CSharp中,可以使用反射的方式,通过类名动态创建类的实例。而在C++中,并没有反射的概念。这个问题可以通过巧妙的方式来解决,在MFC中有运行时类的概念,我们可以通过RUNTIME_CLASS(classname)获取一个CRuntimeClass的指针,进而通过CRuntimeClass类来动态地创建。对于需要使用CRuntimeClass动态创建的类,需要在类的定义中添加DECLARE_DYNCREATE的声明,并在实现文件中,添加 IMPLEMENT_DYCREATE。
我们使用CRuntimeClass动态创建一个类的实例时,首先需要使用宏 RUNTIME_CLASS来获取到CRuntimeClass的实例,RUNTIME_CLASS宏的定义如下:
#define RUNTIME_CLASS(class_name) (class_name::GetThisClass())
而 class_name::GetThisClass() 已经在IMPLEMENT_DYNCREATE中定义,最终定义为返回如下成员:
AFX_COMDAT CRuntimeClass class_name::class##class_name = {
#class_name, sizeof(class class_name), wSchema, pfnNew,
&class_name::_GetBaseClass, NULL, class_init };
通过查看CRuntimeClass,发现其为一个结构体,声明如下:
struct CRuntimeClass
{
// Attributes
LPCSTR m_lpszClassName;
int m_nObjectSize;
UINT m_wSchema; // schema number of the loaded class
CObject* (PASCAL* m_pfnCreateObject)(); // NULL => abstract class
#ifdef _AFXDLL
CRuntimeClass* (PASCAL* m_pfnGetBaseClass)();
#else
CRuntimeClass* m_pBaseClass;
#endif
// Operations
CObject* CreateObject();
BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;
// dynamic name [...]
在多线程编程时,线程间的同步操作是非常重要,也是非常麻烦的。关于windows下的进程和线程,谈得比较多的是线程同步和进程间通信,而进程间的同步,其实和线程同步是有些类似的。对于线程间的通信,如果线程属于同一个进程,则可以直接通过共享进程资源的方式进行通信,如果两个线程从属于不同的进程,则线程通信实际上变成了进程间通信了。
因此,我们谈到多线程编程时,通常只说线程的同步以及进程的通信。
线程同步方式概述
线程的同步可以分为用户态同步和内核态同步。
用户态同步的方式即是使用互锁函数和临界区两种方式进行线程同步,这两种方式在前一篇文章 使用互锁函数和临界区(CriticalSection)实现线程同步 中已经做过介绍。在用户态下进行线程同步的好处在于速度快,使用方便。
内核态同步则是这篇文章要介绍的重点,即使用内核对象进行线程同步。使用内核对象进行线程同步,速度会比使用互锁函数和临界区要慢一些, 但好处在于内核对象可以用于跨进程的同步。原因很简单,内核对象是独立于进程存在的,由系统进行维护,我们可以创建一个内核对象,并在不同的进程中打开它,那么我们就可以使用这个内核对象进行跨进程的同步了。
在另外一个进程中打开内核对象,我们可以有两种思路,一种是利用父子进程间的继承关系,让子进程继承父进程的内核对象。另外一种思路则是创建内核对象时,为起指定一个名称,在另外一个进程中,只需要根据这个名称打开相应的内核对象即可。关于内核对象的更多操作,可以参考 Windows内核对象 一文。
有的内核对象会有两种状态,一种叫做已通知状态,一种叫做未通知状态。对于这种内核对象,我们可以使用等待函数,来等待其从未通知状态变为已通知状态。我们可以通过选择不同的等待函数以及不同的内核对象,来实现线程的同步。
等待函数
最常见的等待函数莫过于 WaitForSingleObject,其原型如下:
DWORD WaitForSingleObject(
HANDLE hObject, // 要等待的内核对象句柄
DWORD dwMilliseconds); // 等待时间,可以设置为 INFINITE表示永久等待
介绍C++Runtime Library的Single-Threaded, Multithreaded的区别,由此引出CreateThread和_beginthreadex的区别
