在面向对象编程(OOP)中,C++以其封装性、继承性和多态性而著称。然而,大多数操作系统并不是为C++设计的,它们通常使用非面向对象的方法实现,这导致在C++中封装某些平台依赖资源时会遇到一些挑战。本文将探讨如何在C++中封装Win32线程,使之符合面向对象编程的原则。
面向对象语言如C++的优势在于其能够封装对象的表示和实现,使得编程更加关注于接口级别而非函数级别。然而,大多数操作系统并没有考虑到C++的特性,它们通常使用非OO的方法实现。因此,封装某些平台依赖资源,如线程,可能会变得复杂。本文的方法涵盖了Win32线程的封装。
在Win32中,创建同一进程中的另一个线程需要使用几个API函数来处理线程。然而,它们是C而非C++ API。可以很容易地注意到C的编程习惯,如回调函数、从void*转换到其他类型等。让看看在Win32中创建线程的API函数CreateThread的原型:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
其中,lpStartAddress是一个指向将在新线程中运行的回调函数的指针,lpParameter是传递给新线程的void*类型的参数。
然而,传递指向回调函数的指针并不符合OOP的精神,并且如果想要在类中封装线程,它就像一个严重的障碍。传递给CreateThread的回调函数看起来像这样:
DWORD WINAPI ThreadProc(
LPVOID lpParameter
);
会发现这个原型会阻止将这个回调函数作为类的成员,因为成员函数会传递一个隐藏的参数:this。那么该怎么办呢?失败了吗?还没有。不能使用成员函数,已经看到了为什么,但是类有静态方法,它们对所有对象都有单一实例。它们与类而不是对象相关联。这就是为什么它们不会作为参数传递this。因此,静态函数成为回调函数的一个有趣候选。但是有一个小问题,如果线程在静态方法中,那么不管有多少个该类的对象,都只会有一个线程,因为静态方法每个类只有一个实例。这不是想要的。希望工作线程是一个成员方法,易于被子类覆盖,所有这些工作方式对客户都是透明的。能做到吗?
是的,能做到。如果看ThreadProc,会注意到它可以传递一个void*参数。没有什么可以阻止发送(void*)this,在ThreadProc中只是调用工作方法,现在有了this指针。这样做的代码看起来像这样:
// 这里创建线程
HANDLE CThread::CreateThread()
{
return ::CreateThread((NULL, 0, (unsigned long (__stdcall *)(void *))this->runProcess, (void *)this, 0, NULL);
}
// 静态方法
int CThread::runProcess(void* pThis)
{
return ((CThread*)(pThis))->Process();
}
// 工作方法,虚拟的,可覆盖的
int CThread::Process()
{
// 将在另一个线程中工作
}
到目前为止,一切都很好。设法提供了线程机制的封装,用户只需要实现他们自己的Process,然后调用CreateThread成员函数。它甚至更容易提供可重用性,因为用户可以简单地继承上面定义的类CThread,然后实现Process,然后调用CreateThread,他们就有了一个线程,就是那么简单。但是这里有一个需要注意的小问题:
假设有一个CThread的子类,名为CMyThread。在CreateThread中将this(CMyThread*类型)转换为void*并传递给runProcess,在runProcess中将其重新转换为CThread。C++标准规定,如果将类型X*转换为void*,那么只有转换回相同类型X*是允许的。其他转换会导致未定义行为。这意味着做错了什么。该如何修复呢?嗯,用一个小的变通方法。
struct workAround {
CThread* this_thread;
};
// 传递一个workAround结构而不是this
HANDLE CThread::CreateThread()
{
workAround* wA = new workAround;
wA->this_thread = this;
return ::CreateThread((NULL, 0, (unsigned long (__stdcall *)(void *))this->runProcess, (void *)wA, 0, NULL);
}
// 静态方法
int CThread::runProcess(void* pThis)
{
workAround* wA = (workAround*)pThis;
// 这将调用适当的方法,因为Process是虚拟方法
CThread* thread = wA->this_thread;
delete wA;
return thread->Process();
}
这次没问题了,因为在转换到和从相同的类型(struct workAround)。