精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
Win32++ is a simple and easy to understand library for creating windows applications. It runs on the commonly available free compilers, making it a free alternative to MFC.
Win32++ has been designed to make life a little easier for those learning to use C++ to program using the Windows API directly. Win32++ doesn't attempt to hide the Windows API. On the contrary, it exposes the Windows API, allowing it to be more easily learnt and understood. Win32++ is also a good choice for those professional programmers looking for a simple, robust and efficient framework.
Win32 ++是一个简单易懂的库,用于创建Windows应用程序。它运行在常用的免费编译器上,使其成为MFC的免费替代品。
Win32 ++旨在让那些学习使用C ++直接使用Windows API进行编程的人们的生活更轻松。Win32 ++不会尝试隐藏Windows API。相反,它暴露了Windows API,使其更容易学习和理解。对于那些寻求简单,强大和高效框架的专业程序员来说,Win32 ++也是一个不错的选择。
The code has been designed to run on a wide range of C++ compilers, including those from Microsoft, Borland and the free MinGW compiler from GNU. Win32++ supports all Windows operating systems, from Windows 95 through to Windows 7. It can be used to create both 32 bit and 64 bit applications.
Win32++ also directly supports the Windows CE operating system. Windows CE is the operating system which runs on the various Pocket PCs, Smartphones, as well as industrial devices and embedded systems. The Windows CE API is a subset of the Windows API. It also includes some new common controls tailored for the smaller computers and devices it runs on.
Win32++ brings an object oriented approach to programming directly with the Windows API. Each window created is a C++ class object capable of having its own window procedure for routing messages.
该代码被设计为在各种C ++编译器上运行,包括来自Microsoft,Borland和GNU的免费MinGW编译器。Win32 ++支持从Windows 95到Windows 7的所有Windows操作系统。它可用于创建32位和64位应用程序。
Win32 ++还直接支持Windows CE操作系统。Windows CE是在各种Pocket PC,智能手机以及工业设备和嵌入式系统上运行的操作系统。Windows CE API是Windows API的子集。它还包括一些新的通用控件,适用于运行的小型计算机和设备。
Win32 ++带来了一种面向对象的方法,可以直接使用Windows API进行编程。创建的每个窗口都是一个C ++类对象,它有自己的窗口过程来路由消息。
When I first approached the task of teaching myself to program Windows using C++, I took a brief look at some of the Win32 tutorials on the Web and then jumped straight into using MFC. I hoped that using MFC might make the task of learning Windows programming easier.
With the benefit of hindsight, I now realize that this approach was a mistake. I should have taken the time to study the Windows API more thoroughly before moving on to MFC. It would have been far easier (and faster) to approach these two topics one at a time, instead of trying to learn both of them at once. In a sense, I should have learned to walk before trying to run. The two main challenges I faced when writing Win32 applications were:
With this in mind, I decided to revisit my Windows API programming and develop a generic Framework for my applications that could be used as an alternative to MFC. My goal was to produce a Framework that was robust, object-oriented and that produced professional-looking results.
当我第一次接受教我自己使用C ++编程Windows的任务时,我简要地看了一下Web上的一些Win32教程,然后直接使用MFC。我希望使用MFC可以使学习Windows编程的任务更容易。
事后看来,我现在意识到这种方法是错误的。在继续使用MFC之前,我应该花时间更全面地研究Windows API。一次一个地处理这两个主题会更容易(也更快),而不是试图同时学习这两个主题。从某种意义上说,我应该在尝试跑步之前学会走路。编写Win32应用程序时遇到的两个主要挑战是:
考虑到这一点,我决定重新接触我的Windows API编程并为我的应用程序开发一个通用框架,可以用作MFC的替代方案。我的目标是生成一个强大的,面向对象的框架,并产生具有专业外观的结果。
The following diagram illustrates the classes used in Win32++:下图说明了Win32 ++中使用的类
The classes which define the Framework itself are contained within the Win32xx namespace. These classes are as follows:
定义框架本身的类包含在Win32xx命名空间中。这些类如下:
The file download from Sourceforge includes the following:
Sourceforge的文件下载包括以下内容:
The sample applications include:
示例应用程序包括:
The code which forms the basis of the Framework is located in the Win32++ directory. You shouldn't need to modify these files, but rather inherit from Win32++ and add any additional code in your derived classes. To use the Framework to create an SDI frame window, for example, you would typically derive your own class from CFrame and place any modifications to the standard frame there. You can override the WndProc member function to include any additional messages you would like to handle.
A separate view window is placed over the client area of the frame window. Typically, this view window is created by inheriting a class from CWnd. The CFrame::SetView function is used to assign the view window to the frame. For MDI frames however, the CMDIFrame already uses CMDIClient as the view window, and you would use CMDIFrame::AddMDIChild to create a new instance of an MDI child window.
One of the important advantages of programming directly with the Windows API is that the code produced is portable, which is to say that it can be compiled on different compilers. The code in this Framework has been checked for compatibility with Visual C++ 6.0, Visual Studio .NET 2003, Visual C++ 2008 Express Edition, and also Dev-C++ version 4.9.9.2. Dev-C++ is a free C++ compiler and Integrated Development Environment available for download from here. The Framework is also compatible with Visual C++ Toolkit 2003 (a free compiler from Microsoft) and Borland's free Turbo C++ 2006.
A tutorial which provides step by step instructions for using the Framework is included with the framework.
构成Framework基础的代码位于Win32 ++目录中。您不需要修改这些文件,而是从Win32 ++继承并在派生类中添加任何其他代码。例如,要使用框架创建SDI框架窗口,通常可以从CFrame派生自己的类,在派生类里对标准框架进行任何修改。您可以覆盖WndProc成员函数以包含您要处理的任何其他消息。
在框架窗口的客户区域上放置单独的视图窗口。通常,此视图窗口是通过继承CWnd类来创建。该CFrame::SetView功能用于将视图窗口分配给框架。但是,对于MDI框架窗口,CMDIFrame已经把CMDIClient用作视图窗口,您将用CMDIFrame::AddMDIChild 创建MDI子窗口的新实例。
直接使用Windows API编程的一个重要优点是生成的代码是可移植的,也就是说它可以在不同的编译器上编译。已检查此框架中的代码是否与Visual C ++ 6.0,Visual Studio .NET 2003,Visual C ++ 2008 Express Edition以及Dev-C ++版本4.9.9.2兼容。Dev-C ++是一个免费的C ++编译器和集成开发环境,可从此处下载。该框架还兼容Visual C ++ Toolkit 2003(来自Microsoft的免费编译器)和Borland的免费Turbo C ++ 2006。
框架中包含一个教程,该教程提供了使用框架的分步说明。
The key to bringing an object-oriented approach to programming directly with the Windows API is to have a C++ class that can create a window and which includes its own window procedure as a member function. Once we have this class, we can inherit from it and override the window procedure member function to handle messages the way we want for each derived window type.
Creating a class like this is not trivial and I suspect that's one of the reasons why MFC was created in the first place. The problem stems from the way a "window class" is registered before the window can be created. The term "class" here refers to the Windows API "window class," which is not the same thing as a C++ class. The following code snippet shows how a window class might be registered using the API:
使用Windows API直接引入面向对象方法的关键是拥有一个可以创建窗口的C ++类,它包含自己的窗口过程作为成员函数。一旦我们有了这个类,我们就可以从它继承并重写窗口过程成员函数来按照我们想要的方式处理每个派生窗口类型的消息。
创建这样的类并非易事,我怀疑这是MFC首先创建的原因之一。问题源于在创建窗口之前注册“窗口类”的方式。这里的术语“类”是指Windows API“窗口类”,它与C ++类不同。以下代码段显示了如何使用API注册窗口类:
WNDCLASSEX wc = {0};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = WindowProc; //Window procedure function
wc.hInstance = hInstance;
wc.lpszClassName = "TEST";
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
//Register the window class
::RegisterClassEx(&wc);
Note that we need to supply the function name of our window procedure. The window procedure is where we control what is to be done when a window message is received. This function must conform precisely to the predefined standards required by the Windows API. A typical declaration of the callback function looks like this:请注意,我们需要提供窗口过程的函数名称。窗口过程是我们控制接收窗口消息时要执行的操作的位置。此功能必须严格符合Windows API所需的预定义标准。回调函数的典型声明如下所示:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
We might be tempted to set the WindowProc function as a member of the class. Unfortunately, each class member function has an implicit this pointer as one of its arguments and therefore cannot be used as the callback function for a window. If we did this, our WindowProc function would no longer conform to the predefined standards and the program would fail to compile.
We can make the WindowProc function a static member function of the class. There is no implicit this in a static function and this will compile correctly. Unfortunately, a static member function doesn't have access to the class object (i.e. it doesn't have a this pointer) and it cannot access other members of the class. It is this that prevents the static member function from being used in an object-oriented way. The following code demonstrates the limitations of a static member function approach:
我们可能想要将WindowProc函数设置为类的成员。不幸的是,每个类成员函数都有一个隐式this指针作为其参数之一,因此不能用作窗口的回调函数。如果我们这样做,我们的WindowProc函数将不再符合预定义的标准,并且程序将无法编译。
我们可以使WindowProc函数成为static类的成员函数。没有隐含的this一个static功能,这将正确编译。不幸的是,static 成员函数无法访问类对象(即它没有this指针),也无法访问类的其他成员。这是为了防止static 成员函数以面向对象的方式使用。以下代码演示了static 成员函数方法的局限性:
class TestStatic
{
public:
int member;
void NormalFunction()
{
//We can access member variables in a normal
//member function
member = 5;
//The following line is equivalent to the one above
this->member = 5;
}
void static StaticFunction()
{
//We cannot access member variables
//in a static member function
//The following line will give a compile error
member = 5;
//This will give an error too
this->member = 5;
}
};
A static member function for the window procedure would be useful if we could just get our hands on a pointer to the window class object (our this pointer). There are a number of techniques that we can use to get access to our pointer as the window is being created. The one I have chosen takes advantage of Thread Local Storage to store our pointer, which is later inserted into an STL map. This is how it's done:
Step 1: Set up the Thread Local Storage to store our this pointer. This is done in the CWinApp class:
如果我们能够拿到窗口类对象指针(this指针),一个窗口过程的static成员函数将是有益的。在创建窗口时,我们可以使用许多技术来访问指针。我选择的那个利用线程本地存储来存储我们的指针,稍后将其插入到STL映射中。这就是它的完成方式:
步骤1:设置线程本地存储以存储我们的this指针。这是在CWinApp课堂上完成的:
CWinApp::CWinApp(HINSTANCE hInstance) : m_hInstance(hInstance)
{
if (GetApp() == 0)
{
st_dwTlsIndex = ::TlsAlloc();
//snip
}
}
Step 2: Store our this pointer in the Thread Local Storage when we use CreateEx to create the window:第2步:this当我们CreateEx用来创建窗口时,将指针存储在线程本地存储中:
// Ensure this thread has the TLS index set
TLSData* pTLSData = GetApp()->SetTlsIndex();
// Store the CWnd pointer in thread local storage
pTLSData->pCWnd = this;
Step 3: Extract the pointer from Thread Local Storage and add it to the STL map during the initial creation of the window:步骤3:从线程本地存储中提取指针,并在初始创建窗口期间将其添加到STL映射:
// Retrieve the pointer to the TLS Data
TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());
// Retrieve pointer to CWnd object from Thread Local Storage TLS
w = pTLSData->pCWnd;
// Store the CWnd pointer in the HWND map
GetApp()->AddToMap(hWnd, w);
return w->WndProc(hWnd, uMsg, wParam, lParam);
Step 4: For each subsequent window message, we extract the pointer from the STL map and use it to redirect the message handling to the appropriate WndProc function:步骤4:对于每个后续窗口消息,我们从STL映射中提取指针并使用它将消息处理重定向到适当的WndProc函数:
CWnd* w = GetApp()->GetCWndFromMap(hWnd);
return w->WndProc(hWnd, uMsg, wParam, lParam);
Now that we've had a look at thread local storage and the window procedure, it is time to see how these fit together as we create the window. This is the code that creates the window:现在我们已经了解了线程本地存储和窗口过程,现在是时候看看它们在创建窗口时如何组合在一起了。这是创建窗口的代码:
HWND CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName,
DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hParent,
HMENU hMenu, LPVOID lpParam /*= NULL*/)
{
try
{
// Test if Win32++ has been started
if (0 == GetApp())
throw CWinException(_T("Win32++ has not been initialised properly.\n
Start the Win32++ by inheriting from CWinApp."));
// Only one window per CWnd instance allowed
if (::IsWindow(m_hWnd))
throw CWinException(_T("CWnd::CreateEx ... Window already exists"));
// Ensure a window class is registered
TCHAR ClassName[MAX_STRING_SIZE] = _T("");
if (0 == lstrlen(lpszClassName) )
lstrcpyn (ClassName, _T("Win32++ Window"), MAX_STRING_SIZE);
else
// Create our own local copy of szClassName.
lstrcpyn(ClassName, lpszClassName, MAX_STRING_SIZE);
WNDCLASS wc = {0};
wc.lpszClassName = ClassName;
wc.hbrBackground = (HBRUSH)::GetStockObject(WHITE_BRUSH);
wc.hCursor = ::LoadCursor(NULL, IDC_ARROW);
if (!RegisterClass(wc)) // Register the window class (if not already registered)
throw CWinException(_T("CWnd::CreateEx Failed to register window class"));
// Ensure this thread has the TLS index set
TLSData* pTLSData = GetApp()->SetTlsIndex();
// Store the CWnd pointer in thread local storage
pTLSData->pCWnd = this;
// Create window
m_hWnd = ::CreateWindowEx
(dwExStyle, ClassName, lpszWindowName, dwStyle, x, y, nWidth,
nHeight, hParent, hMenu, GetApp()->GetInstanceHandle(), lpParam);
// Now handle window creation failure
if (!m_hWnd)
throw CWinException(_T("CWnd::CreateEx ... Failed to Create Window"));
m_hWndParent = hParent;
// Automatically subclass predefined window class types
::GetClassInfo(GetApp()->GetInstanceHandle(), lpszClassName, &wc);
if (wc.lpfnWndProc != st_pfnWndProc)
{
Subclass();
// Send a message to force the HWND to be added to the map
::SendMessage(m_hWnd, WM_NULL, 0, 0);
OnCreate(); // We missed the WM_CREATE message, so call OnCreate now
}
// Clear the CWnd pointer from TLS
pTLSData->pCWnd = NULL;
// Window creation is complete. Now call OnInitialUpdate
OnInitialUpdate();
}
catch (const CWinException &e)
{
e.MessageBox();
}
return m_hWnd;
} // HWND CWnd::CreateEx()
The next code segment handles the window procedure which first receives the messages. We extract the pointer to the CWnd object from the map, and use it to redirect the handling of the window messages to the appropriate WndProc function:下一个代码段处理首先接收消息的窗口过程。我们从地图中提取CWnd对象的指针,并使用它将窗口消息的处理重定向到适当的WndProc函数:
LRESULT CALLBACK CWnd::StaticWindowProc
(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
try
{
CWnd* w = GetApp()->GetCWndFromMap(hWnd);
if (0 != w)
{
// CWnd pointer found, so call the CWnd's WndProc
return w->WndProc(hWnd, uMsg, wParam, lParam);
}
else
{
// The CWnd pointer wasn't found in the map, so add it now
// Retrieve the pointer to the TLS Data
TLSData* pTLSData = (TLSData*)TlsGetValue(GetApp()->GetTlsIndex());
if (NULL == pTLSData)
throw CWinException(_T("CWnd::StaticCBTProc ... Unable to get TLS"));
// Retrieve pointer to CWnd object from Thread Local Storage TLS
w = pTLSData->pCWnd;
if (NULL == w)
throw CWinException(_T("CWnd::StaticWindowProc .. Failed to route message"));
pTLSData->pCWnd = NULL;
// Store the CWnd pointer in the HWND map
GetApp()->AddToMap(hWnd, w);
// Store the HWND in the CWnd object early
w->m_hWnd = hWnd;
return w->WndProc(hWnd, uMsg, wParam, lParam);
}
}
//snip
Finally, the next code segment shows the function called by StaticWindowProc. Typically, when we derive a new class from CWnd, we would override this function to control the way various window messages are handled:最后,下一个代码段显示了被调用的函数StaticWindowProc。通常,当我们从中派生一个新类时CWnd,我们将覆盖此函数以控制处理各种窗口消息的方式:
LRESULT CWnd::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// Override this function in your class derived from CWnd to handle
// window messages. A typical function might look like this:
// switch (uMsg)
// {
// case MESSAGE1: // Some Windows API message
// OnMessage1(); // A user defined function
// break; // Also do default processing
// case MESSAGE2:
// OnMessage2();
// return x; // Don't do default processing, but instead return
// // a value recommended by the Windows API documentation
// }
// Always pass unhandled messages on to WndProcDefault
return WndProcDefault(hWnd, uMsg, wParam, lParam);
}
The method described here uses a global map to associate a windows handle (HWND) with a CWnd object. This map uses Thread Local Storage (TLS) to ensure that the creation of the windows thread safe. If TLS wasn't used, attempts to create multiple windows simultaneously in different threads could fail. Using a map to associate a windows handle (HWND) with a CWnd object also allows every message for the window to be processed. There is no need, for example, to discard window messages prior to WM_NCCREATE when using this method.此处描述的方法使用全局映射将窗口句柄(HWND)与CWnd对象相关联。此映射使用线程本地存储(TLS)来确保创建Windows线程安全。如果未使用TLS,则尝试在不同线程中同时创建多个窗口可能会失败。使用映射将窗口句柄(HWND)与CWnd对象相关联也允许处理窗口的每条消息。例如,在使用此方法时,不需要在WM_NCCREATE之前丢弃窗口消息。