Windows程序设计:从API到实践的全面教程

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《Windows程序设计》是一本详细论述在Windows操作系统下进行程序开发的教材。它提供了英文原文和中文翻译,为国内读者学习提供了便利,并且通过提供源代码,将理论与实践相结合。书中涵盖关键知识点,如Windows API、消息驱动机制、窗口和控件管理、GDI图形编程、内存与进程线程管理、文件I/O操作、注册表操作、事件驱动编程、调试技术以及异常处理等,旨在帮助读者深入理解Windows程序设计的原理和实践,并提升编程技能。 windows程序设计

1. Windows程序设计基础

1.1 程序结构概览

Windows程序设计是面向Windows操作系统的软件开发,其核心在于事件驱动和消息处理。一个典型的Windows程序由多个组件构成,包括但不限于窗口、消息、事件处理函数、资源和图形用户界面(GUI)。开发此类程序时,需要遵循一系列编程规范和接口调用规则,即Windows API(应用程序编程接口)。

1.2 环境搭建与工具准备

为了开始Windows程序设计,首先需要配置好开发环境。这通常包括安装最新版本的Microsoft Visual Studio或者其他支持Windows API的集成开发环境(IDE)。安装完毕后,创建一个新的Windows桌面应用程序项目,即可开始编写第一个程序。建议初学者从简单的“Hello World”程序开始实践,并逐步深入学习。

1.3 编写第一个Windows程序

编写Windows程序的典型步骤包括: 1. 创建程序入口点,即WinMain函数。 2. 注册窗口类,定义窗口的样式和消息处理函数。 3. 创建窗口,将之前定义的窗口类实例化。 4. 进入消息循环,接收并处理各种系统或用户消息。 5. 实现窗口过程函数(Window Procedure),用于处理不同类型的消息。

以下是一个简单的Win32程序代码示例,展示了一个基本的窗口创建流程:

#include <windows.h>

// 窗口过程函数声明
LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

// WinMain函数,程序入口点
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR args, int ncmdshow) {
    WNDCLASSW wc = {0};

    wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hInstance = hInst;
    wc.lpszClassName = L"myWindowClass";
    wc.lpfnWndProc = WindowProcedure;

    // 注册窗口类
    if (!RegisterClassW(&wc)) {
        return -1;
    }

    // 创建窗口
    CreateWindowW(L"myWindowClass", L"我的第一个Windows窗口",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 500, 500, NULL, NULL, NULL, NULL);

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

    return 0;
}

// 窗口过程函数的实现
LRESULT CALLBACK WindowProcedure(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) {
    switch (msg) {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProcW(hWnd, msg, wp, lp);
    }
    return 0;
}

通过上述步骤,可以完成第一个基本的Windows程序。这一章为后续更深入的Windows API学习和实际应用打下了基础。

2. 深入理解Windows API

2.1 Windows API概述

Windows应用程序编程接口(API)是Windows操作系统为应用程序开发人员提供的一组预定义功能和协议的集合。API封装了底层的操作系统功能,使得开发人员能够利用这些功能来创建应用程序。

2.1.1 API在Windows程序中的作用

Windows API是连接应用程序和操作系统的桥梁。开发者通过调用API函数来执行各种操作,如创建窗口、管理内存、访问文件系统等。API使得复杂的底层代码对应用程序透明,从而简化了开发过程。

在编写Windows程序时,API的选择至关重要。开发者必须根据应用程序的需求和目标平台选择合适的API。例如,在桌面应用程序中可能会使用到GDI(图形设备接口)来进行图形操作,而在网络通信方面可能会涉及到Winsock API。

2.1.2 如何选择合适的API函数

选择合适的API函数涉及对函数功能的深入理解,以及对其性能影响的评估。开发人员通常需要参考API文档和SDK(软件开发工具包)中的示例代码,同时需要考虑API的版本兼容性和平台支持。

一个关键的步骤是评估API函数调用的性能影响。在某些情况下,调用一个API函数可能会比执行一个复杂的应用程序逻辑消耗更多的资源。因此,开发者需要在易用性和性能之间找到平衡。

2.2 Windows数据结构与API

2.2.1 常用的数据结构介绍

在Windows API中,有许多预定义的数据结构用于处理不同类型的数据。比如, WIN32_FIND_DATA 用于文件查找操作, LPVOID (void pointer)作为通用指针类型,可以指向任意类型的数据。

数据结构的选择对于程序的性能和可维护性有直接影响。正确的数据结构能够确保数据的快速访问和高效管理。

2.2.2 数据结构在API中的应用

数据结构通常作为参数传递给API函数,API函数会返回数据结构的实例或者对其进行操作。例如,在处理文件时, WIN32_FIND_DATA 结构体经常用在 FindFirstFile FindNextFile 函数中,用于存储文件信息。

一个有效的数据结构能够提升API调用的效率,减少数据复制和转换,从而优化整体程序性能。

2.3 API高级应用技巧

2.3.1 API函数的参数传递和返回值

在使用API时,参数传递方式和函数返回值是保证正确执行的关键。API函数通常采用值传递、引用传递或者指针传递参数。了解每种传递方式的适用场景和性能影响对提高程序的效率至关重要。

API函数的返回值会指示函数调用的成功与否,开发者必须检查返回值并相应地处理错误。在某些情况下,函数可能会设置全局错误代码(如 GetLastError ),方便开发者诊断问题。

2.3.2 错误处理和异常管理

错误处理是编程中的一个核心组成部分,良好的错误处理机制能够帮助开发者更快地定位和解决问题。在API调用中,正确处理API返回的错误码和异常是非常重要的。

为了增强程序的健壮性,开发者需要在代码中添加必要的异常捕获逻辑,利用try-catch块来捕获和处理运行时错误。此外,合理地记录错误日志有助于后续的问题分析和性能优化。

// 示例代码块:错误处理
// 使用Windows API时,检查返回值和错误码

#include <windows.h>
#include <stdio.h>

int main() {
    HANDLE hFile = CreateFile("example.txt", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (hFile == INVALID_HANDLE_VALUE) {
        printf("Error opening file!\n");
        DWORD lastError = GetLastError();
        printf("Error code: %d\n", lastError);
        // 处理错误或者记录日志
    }
    // 其他文件操作...

    CloseHandle(hFile);
    return 0;
}

在上述代码中,我们尝试打开一个文件,如果 CreateFile 函数返回 INVALID_HANDLE_VALUE 表示打开失败,我们接着获取了错误码并打印了错误信息。这样的错误处理有助于快速定位问题,同时记录错误码可以在后续分析中使用。

3. 消息机制与事件驱动

3.1 消息循环机制解析

在深入了解Windows编程时,消息循环机制是核心内容之一。消息循环是一个程序的主循环,用来处理来自操作系统的各种消息。

3.1.1 消息队列的建立与维护

消息队列是消息循环机制的基础,所有的消息都保存在消息队列中等待处理。Windows系统中的每个线程都可以有自己的消息队列。对于图形界面应用程序来说,消息队列通常是由系统在程序启动时自动创建。

消息队列中包含多种类型的消息,例如键盘输入、鼠标点击、窗口大小改变等事件产生的消息。为了维护消息队列,系统必须保证消息的顺序和及时传递。一个标准的消息循环示例如下:

MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

上述代码中 GetMessage() 负责从消息队列中取出消息,如果消息队列为空,则阻塞等待; TranslateMessage() 处理一些键盘消息; DispatchMessage() 将消息派发给相应的窗口过程函数。

3.1.2 消息的分类与处理流程

在Windows中,消息主要分为系统消息、窗口消息和自定义消息。系统消息由系统内部生成,如定时器消息和硬件事件消息;窗口消息由用户界面操作产生,如窗口创建、鼠标点击等;自定义消息则是程序员根据程序需求自定义的消息类型。

处理消息的流程一般为:首先,系统将消息放入消息队列;接着,消息循环中的 GetMessage() 函数从队列中取出消息;然后, TranslateMessage() 对消息进行翻译(主要是键盘消息);最终通过 DispatchMessage() 将消息分发到特定的窗口过程函数中进行处理。

3.2 窗口过程函数的实现

窗口过程函数是Windows编程中用于处理消息的回调函数,窗口类中的每一个窗口都会有一个与之关联的窗口过程函数。

3.2.1 理解窗口过程函数的作用

窗口过程函数对来自窗口的消息进行解码和处理。它接收一个指向 MSG 结构体的指针作为参数,并返回 LRESULT 类型的数据。窗口过程函数的原型如下:

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)

其中, hwnd 是发出消息的窗口句柄, uMsg 是消息的标识符, wParam lParam 是附加的参数,可以携带额外的信息。

3.2.2 常见的消息处理实例

一个典型的窗口过程函数实现可能如下:

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_DESTROY:
            PostQuitMessage(0);
            return 0;
        case WM_PAINT:
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hwnd, &ps);
            // 在此处添加使用 hdc 进行绘制的代码
            EndPaint(hwnd, &ps);
            return 0;
        // 其他消息的处理代码
        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

在上面的代码中, WM_DESTROY 消息处理会关闭消息循环,而 WM_PAINT 消息则触发了窗口的重绘过程。窗口过程函数中的每个 case 语句都对应一种消息的处理逻辑。

3.3 事件驱动编程模型

事件驱动编程是基于消息机制的另一种表现形式,通过事件来驱动程序执行。

3.3.1 事件驱动编程基础

在事件驱动模型中,事件是程序中发生的动作或者发生的事情。每个事件都对应一个事件处理函数,当事件发生时,事件处理函数就会被调用。

例如,当用户点击一个按钮时,系统会生成一个点击事件,然后调用与该按钮关联的事件处理函数进行处理。这种方式比传统的命令式编程更加灵活,特别适用于图形用户界面程序的开发。

3.3.2 事件驱动与回调函数的应用

回调函数是事件驱动编程中不可或缺的部分。在Windows API中,回调函数是作为函数指针传递给其他函数的。例如,将一个处理绘图的回调函数传递给GDI绘图函数,当需要进行绘图操作时,GDI会调用该回调函数。

回调函数的典型用法是在系统发生某些预定义的事件时,由系统调用用户的代码。在事件驱动模型中,最常见的回调是消息处理函数,例如窗口过程函数。

代码逻辑解读

以上代码片段中的 WindowProc 函数是一个典型的事件处理函数,用于处理窗口发送的各种消息。每当窗口接收到消息时,如 WM_DESTROY WM_PAINT ,会调用 WindowProc 函数并传递消息标识符 uMsg ,函数内部通过 switch 语句针对不同的消息进行分发处理。对于程序中未定义的其他消息,使用 DefWindowProc 函数进行默认处理。

4. 图形用户界面(GUI)设计

图形用户界面(GUI)是软件应用程序与用户交互的重要部分。GUI设计不仅仅是视觉美学的问题,它还涉及到了用户如何与软件进行交互、如何提高应用程序的可用性以及如何创建直观的用户体验。在Windows平台上,GUI设计通常涉及到了GDI、MFC以及现代的.NET框架。本章节将介绍GDI图形界面基础、窗口和控件的高级管理以及GUI设计的实践技巧。

4.1 GDI图形界面基础

GDI(图形设备接口)是Windows操作系统的一个组成部分,它负责管理应用程序的图形输出,处理文本输出、颜色管理和矢量图形等。GDI+是GDI的一个扩展,提供了额外的功能,比如对图形变换、路径、透明度以及高质量的图形渲染的支持。

4.1.1 GDI和GDI+的区别与联系

GDI和GDI+是两个不同的图形库,但它们服务于相同的核心目标:在屏幕上绘制图形。GDI+是GDI的增强版,增加了许多新功能,但同时也保留了GDI的许多功能。它们之间的主要区别在于GDI+支持更复杂的图形操作,比如alpha混合、渐变填充、缩放变换等。此外,GDI+对Unicode的支持比GDI更好。

虽然GDI+提供了更丰富的功能集,但GDI在一些情况下仍然被使用,因为它更轻量级,消耗的系统资源更少。在资源受限的环境下,GDI可能更合适。

4.1.2 GDI图形对象的创建与管理

在GDI中,图形对象如画笔(Pen)、画刷(Brush)、字体(Font)和位图(Bitmap)都需要创建和管理。创建图形对象涉及到指定对象的属性,比如颜色、样式、宽度等。一旦创建,这些对象就可以用于绘制各种图形元素。

管理GDI对象涉及到资源释放,GDI对象是系统资源,因此在不需要时应该被及时释放。在GDI中, DeleteObject 函数用于删除GDI对象,释放相关资源。如果不显式地删除这些对象,它们可能一直占用内存直到应用程序结束。

HGDIOBJ hPen = CreatePen(PS_SOLID, width, color);
HPEN hOldPen = (HPEN)SelectObject(hDC, hPen);

// 在这里使用hPen进行绘制...

// 完成绘制后,清理并释放资源
SelectObject(hDC, hOldPen);
DeleteObject(hPen);

在上述代码示例中,我们创建了一个新的画笔对象 hPen ,然后将它选入设备上下文( hDC )中进行绘制。完成绘制后,我们用 SelectObject 函数将旧的画笔对象选回到 hDC 中,并通过 DeleteObject 函数删除新的画笔对象。

4.2 窗口和控件的高级管理

在Windows程序设计中,窗口和控件是实现GUI的关键组件。窗口提供了应用程序的可视区域,而控件则是窗口中的各种小部件,比如按钮、文本框、列表框等。

4.2.1 自定义控件的创建和应用

创建自定义控件可以让你的GUI与众不同,提供更加丰富和有吸引力的用户交互体验。自定义控件的创建通常涉及到继承已有的控件类,然后添加或覆盖方法来实现新的功能或外观。

例如,如果你想创建一个带有特殊背景图像的按钮,你可以继承 CButton 类,并覆盖 OnCtlColor 方法来改变按钮的背景。然后在该方法中,你可以使用GDI或GDI+函数来绘制背景图像。

4.2.2 窗口类的注册与消息处理

创建窗口类是创建窗口的必要步骤,每个窗口都必须有一个窗口类。窗口类定义了窗口的属性和消息处理函数。注册窗口类通过调用 RegisterClassEx RegisterClass 函数实现。

WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc; // 指向窗口过程函数的指针
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInst;
wcex.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_APPLICATION));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));

if (!RegisterClassEx(&wcex))
{
    MessageBox(NULL, TEXT("Window Registration Failed!"), TEXT("Error!"), MB_ICONEXCLAMATION | MB_OK);
    return 1;
}

在上面的代码中, WNDCLASSEX 结构体包含了窗口类的所有必要信息,比如窗口样式、窗口过程函数、背景颜色等。通过 RegisterClassEx 函数注册了窗口类。

窗口过程函数 WndProc 是处理所有发往窗口消息的地方。它根据不同的消息类型来决定如何响应,例如绘制窗口、处理鼠标点击等。

4.3 GUI设计的实践技巧

GUI设计的好坏直接影响到最终用户的体验。在实际开发过程中,设计师和开发者必须考虑到易用性、一致性以及美观性。此外,现代的GUI设计也越来越强调响应性和可定制性。

4.3.1 MFC框架下的GUI设计

MFC(Microsoft Foundation Classes)是一个C++类库,用于帮助开发者编写Windows应用程序。MFC框架抽象了Windows API的许多细节,并提供了一个更加面向对象的编程模型。

在MFC中,GUI设计涉及到创建对话框、视图和控件类的实例,并将它们放置在窗口中。通过继承 CFormView 类或 CDialog 类,可以方便地创建和管理表单和对话框。MFC还提供了 CWnd 类的派生类,如 CButton CEdit ,它们封装了特定控件的行为。

4.3.2 高级GUI布局技术和样式定制

高级GUI布局技术包括动态布局、响应式设计和流式布局,它们使得应用程序能够在不同尺寸的屏幕上正确显示,并适应用户的个性化设置。在MFC中,可以通过子类化控件并重写它们的绘制方法来实现高级的样式定制。

此外,控件的创建和管理可以在资源编辑器中通过可视化的方式完成,这样可以更直观地对控件的位置和属性进行编辑。MFC还支持与Visual Studio集成,使得开发者可以利用Visual Studio提供的工具来设计和调试GUI。

在上述章节中,我们讨论了GDI和GDI+、窗口和控件的高级管理,以及在MFC框架下进行GUI设计的实践技巧。在接下来的章节中,我们将转向系统资源管理技术,并探讨内存管理、进程与线程的管理以及文件I/O操作深入。这些内容对于Windows程序员来说是基础且核心的知识,掌握它们对于创建高性能和稳定的应用程序至关重要。

5. 系统资源管理技术

系统资源管理是任何操作系统中的关键组成部分,尤其是在像Windows这样的现代操作系统中,它允许开发者高效地利用和管理硬件资源以提供流畅的用户体验。本章将探讨内存管理、进程和线程的管理以及文件I/O操作的技术细节和最佳实践。

5.1 内存管理与优化

5.1.1 Windows内存管理机制

在Windows操作系统中,内存管理是通过一系列层次结构来实现的。系统通过虚拟内存管理器(Virtual Memory Manager, VMM)来协调物理内存和硬盘上的虚拟内存。该机制让每个进程都拥有自己独立的虚拟地址空间,使得内存管理变得更为高效和安全。

虚拟地址空间的布局对每个进程来说都是相同的,分为用户模式和内核模式两部分。在用户模式下,每个进程被分配了4GB的地址空间(在64位系统上可能是更多)。开发者可以使用内存分配函数如 VirtualAlloc HeapAlloc 来申请内存,并通过指针访问这些内存区域。

内存泄漏是内存管理中常见问题,其可能导致系统资源耗尽和程序性能下降。为了减少内存泄漏的风险,开发者应当始终在不再需要内存时释放它,尽量避免使用全局变量,并使用智能指针等现代C++特性来自动化资源管理。

5.1.2 内存泄漏的检测与预防

检测内存泄漏是确保程序长期稳定运行的重要步骤。Windows提供了一些工具来帮助开发者发现内存泄漏,其中最著名的是Windows调试工具集(WinDbg)和Visual Studio内置的诊断工具。

例如,要使用WinDbg来检测内存泄漏,可以按照以下步骤操作:

  1. 在应用程序开始时,打开WinDbg并附加到进程。
  2. 记录当前的内存状态。
  3. 让应用程序运行一段时间或完成特定任务。
  4. 再次记录内存状态并比较差异。
  5. 使用 !heap 命令查看当前堆的详细信息, !address 来查看内存分配的地址和大小。

为了预防内存泄漏,开发者应当:

  • 使用现代的内存分配和释放模式,例如C++的智能指针。
  • 遵循RAII原则,确保资源在构造函数中分配,在析构函数中释放。
  • 对于动态分配的内存,确保有明确的逻辑来释放资源,无论是正常流还是异常流。
  • 定期进行代码审查和单元测试,以识别和修复潜在的内存泄漏。

5.2 进程与线程的管理

5.2.1 进程的创建与终止

在Windows中,进程是被操作系统用来封装资源的一个单位,一个进程包含了代码、数据和其他资源。开发者可以通过调用 CreateProcess 函数来创建一个新的进程,该函数执行以下操作:

  • 加载可执行文件。
  • 分配内存空间。
  • 创建主线程。
  • 启动程序执行。

进程可以通过调用 TerminateProcess 函数来强制终止。通常情况下,这种方式只应在极端情况下使用,因为它可能导致数据丢失和其他资源未被清理。

BOOL CreateProcess(
    LPCTSTR lpApplicationName,
    LPTSTR lpCommandLine,
    LPSECURITY_ATTRIBUTES lpProcessAttributes,
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCTSTR lpCurrentDirectory,
    LPSTARTUPINFO lpStartupInfo,
    LPPROCESS_INFORMATION lpProcessInformation
);

参数说明:

  • lpApplicationName :要运行的可执行文件的名称。
  • lpCommandLine :命令行字符串。
  • lpProcessAttributes lpThreadAttributes :分别用于设置进程和主线程的安全属性。
  • bInheritHandles :指定子进程是否继承父进程的句柄。
  • dwCreationFlags :创建标志,如 CREATE_SUSPENDED 等。
  • lpEnvironment :指向环境块的指针,若为NULL则使用父进程的环境。
  • lpCurrentDirectory :新进程的当前目录路径。
  • lpStartupInfo :指向 STARTUPINFO 结构的指针,它描述了新进程的窗口状态和属性。
  • lpProcessInformation :指向 PROCESS_INFORMATION 结构的指针,用于接收新进程的标识信息。

5.2.2 线程同步机制与多线程编程

多线程编程允许开发者同时运行多个任务,从而提高应用程序的响应性和吞吐量。在Windows中,线程是由 CreateThread 函数创建,但通常更推荐使用 CreateThreadpoolThread ,因为它提供了更好的资源管理。

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    SIZE_T dwStackSize,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    DWORD dwCreationFlags,
    LPDWORD lpThreadId
);

参数说明:

  • lpThreadAttributes :用于设置线程的安全属性。
  • dwStackSize :新线程的初始堆栈大小。
  • lpStartAddress :线程函数的地址。
  • lpParameter :传递给线程函数的参数。
  • dwCreationFlags :创建标志,如 CREATE_SUSPENDED
  • lpThreadId :用于接收新线程的标识符。

当多个线程访问同一资源时,必须使用线程同步机制来防止竞态条件和数据不一致。Windows提供了多种同步对象,例如:

  • 事件(Event)
  • 互斥量(Mutex)
  • 信号量(Semaphore)
  • 临界区(Critical Section)

这些同步对象通过设置某种状态来控制对共享资源的访问。临界区提供了最轻量级的同步机制,适合用于保护短时间内的资源访问。

CRITICAL_SECTION CriticalSection;
InitializeCriticalSection(&CriticalSection);

// 使用临界区
EnterCriticalSection(&CriticalSection);
// 访问或修改共享资源
LeaveCriticalSection(&CriticalSection);

5.3 文件I/O操作深入

5.3.1 文件操作API详解

文件I/O操作是任何应用程序中必不可少的,Windows提供了多种API来处理文件操作,如 CreateFile , ReadFile , WriteFile , CloseHandle 等。

CreateFile 函数用于创建和打开文件、管道、邮槽、控制台缓冲区等,其返回一个可以用于其他I/O操作的句柄。

HANDLE CreateFile(
    LPCTSTR lpFileName,
    DWORD dwDesiredAccess,
    DWORD dwShareMode,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD dwCreationDisposition,
    DWORD dwFlagsAndAttributes,
    HANDLE hTemplateFile
);

参数说明:

  • lpFileName :文件名或对象名。
  • dwDesiredAccess :请求的访问权限。
  • dwShareMode :共享模式,定义了其他进程如何访问文件。
  • lpSecurityAttributes :指向安全属性的指针。
  • dwCreationDisposition :当文件不存在时的操作,如 CREATE_NEW , OPEN_EXISTING 等。
  • dwFlagsAndAttributes :文件属性和标志。
  • hTemplateFile :如果创建文件时使用了模板,则此参数指定模板文件的句柄。

5.3.2 高级文件处理技巧

在处理文件时,特别是在需要大量文件I/O的应用程序中,性能优化是至关重要的。以下是一些高级技巧:

  • 异步I/O :使用异步I/O可以提高性能,因为读写操作不会阻塞主线程。可以使用 ReadFile WriteFile 函数的异步版本。
  • 内存映射文件 :内存映射文件允许开发者将文件的一部分或全部映射到进程的地址空间中,这样可以通过指针访问文件内容,而不需要使用 ReadFile WriteFile 函数。
  • 缓冲区管理 :合理管理缓冲区的大小和缓冲策略可以减少磁盘I/O操作,提升性能。

此外,文件I/O操作也是错误诊断和异常处理中的重点,开发人员应当妥善处理可能出现的错误情况,以确保应用程序的健壮性。

// 示例:使用CreateFile和WriteFile进行文件写操作
HANDLE hFile = CreateFile(
    L"example.txt",
    GENERIC_WRITE,
    0,
    NULL,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    NULL
);

if (hFile == INVALID_HANDLE_VALUE) {
    // 处理错误
}

DWORD bytesWritten;
BYTE data[] = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };
BOOL result = WriteFile(hFile, data, sizeof(data), &bytesWritten, NULL);

if (!result) {
    // 处理错误
}

CloseHandle(hFile);

本章节从内存管理机制开始,讲解了进程和线程的创建、终止及同步机制,最后深入探讨了文件I/O操作的高级技巧。这些技术对于确保应用程序的高效稳定运行至关重要,也是开发人员必须熟练掌握的技能。

6. 注册表操作与程序调试

6.1 注册表安全读写技术

注册表是Windows操作系统中非常重要的一个数据库,它存储了系统配置和安装的应用程序设置。对注册表的操作需要谨慎,因为不当的操作可能会导致系统不稳定甚至崩溃。

6.1.1 注册表的结构与重要性

注册表由键(Key)和值(Value)组成,这些键值对构成了一个层次结构。注册表的根键包含了系统的核心配置信息。每一个键可以包含子键(Subkey)和值项。常见的根键有五个,包括HKEY_CLASSES_ROOT、HKEY_CURRENT_USER、HKEY_LOCAL_MACHINE、HKEY_USERS和HKEY_CURRENT_CONFIG。

6.1.2 安全读写注册表的方法和策略

为了安全地操作注册表,可以采取以下方法:

  • 备份注册表 :在进行修改之前备份当前的注册表,确保可以通过还原来修复任何错误。
  • 使用注册表编辑器 :可以使用 regedit.exe 或者 regedt32.exe 来查看和编辑注册表,但建议不频繁使用。
  • 使用API函数 :编程中推荐使用 RegOpenKeyEx RegQueryValueEx RegSetValueEx RegDeleteKey RegDeleteValue 等API函数来执行操作,避免直接对注册表键值进行字符串操作,以减少错误。
  • 权限检查 :确保程序有足够的权限来读写注册表,特别是在修改系统级别或者敏感键值时。
  • 异常处理 :操作注册表时应该添加异常处理,当发生错误时能够及时响应并处理。

以下是使用 RegOpenKeyEx 打开注册表键的代码示例:

#include <windows.h>

HKEY hKey;
DWORD dwDisposition;

// 打开注册表键,如果键不存在则创建
 LONG lResult = RegCreateKeyEx(
     HKEY_CURRENT_USER, // 预定义的根键
     TEXT("Software\\MyApp"), // 子键路径
     0, // 保留,必须为0
     NULL, // 类名称,必须为NULL
     REG_OPTION_NON_VOLATILE, // 保留,必须为REG_OPTION_NON_VOLATILE
     KEY_ALL_ACCESS, // 访问权限
     NULL, // 默认安全属性
     &hKey, // 输出参数,打开的键句柄
     &dwDisposition // 输出参数,指示键的新创建或打开情况
 );

if(lResult != ERROR_SUCCESS) {
    // 错误处理代码
}

6.2 调试技术与工具应用

调试是程序开发过程中的一个重要环节,它帮助开发者找到和修复代码中的错误。

6.2.1 常用的调试工具和方法

  • Visual Studio调试器 :提供了断点、单步执行、调用堆栈、变量监视等强大功能。
  • WinDbg :一个功能丰富的调试工具,支持内核级调试。
  • Sysinternals工具 :提供了诸如Process Explorer、Process Monitor等工具用于进程和系统级调试。
  • 日志记录 :在代码中适当地添加日志记录,有助于跟踪程序的执行流程和调试。

6.2.2 调试过程中的技巧和注意事项

  • 设置断点 :合理地设置断点可以快速定位问题所在。
  • 使用监视窗口 :监视变量和表达式的值,特别是在复杂的逻辑中。
  • 理解调用堆栈 :通过调用堆栈可以查看函数调用顺序和当前执行位置。
  • 避免调试器干扰 :某些情况下调试器可能会影响程序的正常运行,比如调试某些类型的游戏时。
  • 记录调试日志 :将调试过程中的重要发现和结果记录下来,为以后的调试提供参考。

6.3 异常处理与错误诊断

异常处理和错误诊断是程序稳定性的重要保障。

6.3.1 理解异常处理机制

异常处理机制是语言级别上的错误处理方法。在C++中,可以使用try、catch和finally语句块来捕获和处理异常。

6.3.2 错误诊断和日志记录策略

错误诊断是找出软件中问题的过程,通常与日志记录紧密相关。

  • 日志级别 :定义不同的日志级别,如Info、Warning、Error等,有助于更好地记录和分析问题。
  • 日志格式 :标准化日志格式,使日志信息易于阅读和解析。
  • 日志分析工具 :使用日志分析工具,如ELK(Elasticsearch、Logstash和Kibana)堆栈进行日志分析。
  • 远程日志记录 :将日志发送到远程服务器,便于集中管理和分析。
  • 代码覆盖率和性能分析工具 :使用这些工具监控程序的执行效率,以及执行路径,来定位潜在的性能瓶颈或错误。

通过上述章节的讨论,我们深入了解了注册表操作的安全性、程序调试的有效工具以及异常处理和错误诊断的重要性。对于有经验的IT专业人士来说,掌握这些技术是日常工作的基本技能。对于新入门的开发者,通过实践和不断的学习,可以在此基础上提升自己解决实际问题的能力。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:《Windows程序设计》是一本详细论述在Windows操作系统下进行程序开发的教材。它提供了英文原文和中文翻译,为国内读者学习提供了便利,并且通过提供源代码,将理论与实践相结合。书中涵盖关键知识点,如Windows API、消息驱动机制、窗口和控件管理、GDI图形编程、内存与进程线程管理、文件I/O操作、注册表操作、事件驱动编程、调试技术以及异常处理等,旨在帮助读者深入理解Windows程序设计的原理和实践,并提升编程技能。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

WINDOWS环境 Windows几乎不需要介绍。然而人们很容易忘记Windows给办公室和家庭桌上型计算机所带来的重大改变。Windows在其早期曾经走过一段坎坷的道路,征服桌上型计算机市场的前途一度相当渺茫。 Windows简史 在1981年秋天IBM PC推出之后不久,MS-DOS就已经很明显成为PC上的主流操作系统。MS-DOS代表Microsoft Disk Operating System(磁盘操作系统)。MS-DOS是一个小型的操作系统。MS-DOS提供给用户一种命令列接口,提供如DIR和TYPE的命令,也可以将应用程序加载内存执行。对于应用程序写作者,它提供了一组函数呼叫,进行文件的输入输出(I/O )。对于其它的外围处理-尤其是将文字或图形写到显示器上-应用程序可以直接存取PC的硬件。 由于内存和硬件的限制,成熟的图形环境缓慢地才到来。当苹果计算机公司不幸的Lisa计算机在1983年1月发表时,它提供了不同于文字模式环境的另一种选择,并在1984年1月成为Macintosh上图形环境的一种标准。尽管Macintosh的市场占有率在下降,但是它仍然被认为是衡量所有其它图形环境的标准。包括Macintosh和Windows的所有图形环境,其实都要归功于Xerox Palo Alto Research Center(PARC)在70年代中期所作的开拓性研究工作。 Windows是由微软在1983年11月(在Lisa之后,Macintosh之前)宣布,并在两年后(1985年11月)发行。在此后的两年中,紧随着Microsoft Windows早期版本1.0之后,又推出了几种改进版本,以支持国际商业市场,并提供新型视讯显示器和打印机的驱动程序。 Windows版本2.0是在1987年11月正式在市场上推出的。该版本对使用者接口做了一些改进。这些改进中最有效的是使用了可重迭式窗口,而Windows 1.0中使用的是并排式窗口。Windows 2.0还增强了键盘和鼠标接口,特别是加入了菜单和对话框。 至此,Windows还只要求Intel 8086或者8088等级的微处理器,以「实际模式」执行,只能存取地址在1MB以下的内存。Windows/386(在Windows 2.0之后不久发行的)使用Intel 386微处理器的「虚拟8086」模式,实现将直接存取硬件的多个MS-DOS程序窗口化和多任务化。为了统一起见,Windows版本2.1被更名为Windows/286。 Windows 3.0是在1990年5月22日发表的。它将Windows/286和Windows/386结合到同一种产品中。Windows 3.0有了一个很大的改变,这就是对Intel的286、386和486微处理器保护模式的支持。这能使WindowsWindows应用程序能存取高达16MB的内存。Windows用于执行程序和维护文件的「外壳」程序得到了全面的改进。Windows 3.0是第一个在家用和办公室市场上取得立足点的版本。 任何Windows的历史介绍都必须包括一些OS/2的说明,OS/2是对DOS和Windows的另一种选择,最初是由Microsoft和IBM合作开发的。OS/2版本1.0(只有文字模式)在Intel 286(或者后来的)微处理器上运行,在1987年末发布。在1988年10月的OS/2版本1.1中出现了管理图形使用者接口的PM(Presentation Manager)。PM最初的设计构想是成为Windows的一种保护模式版本,但是图形API改变程度太大,致使软件生产厂商很难提供对这两种平台的支持。 到1990年9月,IBM和Microsoft之间的冲突达到了高峰,导致这两个公司最后分道扬镳。IBM接管了OS/2,而Microsoft明确表示Windows将是他们操作系统策略的中心。虽然OS/2仍然拥有一些狂热的崇拜者,但是它远不及Windows这样的普及程度。 Microsoft Windows版本3.1是1992年4月发布的,其中包括的几个重要特性是TrueType字体技术(给Windows带来可缩放的轮廓字体)、多媒体(声音和音乐)、对象连结和嵌入(OLE:Object Linking and Embedding)和通用对话框。跟OS/2一样,Windows 3.1只能在保护模式下运作,并且要求至少配置了1MB内存的286或386处理器。 在1993年7月发表的Windows NT是第一个支持Intel 386、486和Pentium微处理器32位保护模式的Windows版本。Windows NT提供32位平坦寻址,并使用32位的指令集。(本章后面我会谈到一些寻址空间的问题)。Windows NT还可以移植到非Intel处理器上,并在几种使用RISC芯片的工作站上执行。 Windows 95是在1995年8月发布的。和Windows NT一样,Windows 95也支持Intel 386或更高等级处理器的32位保护模式。虽然它缺少Windows NT中的某些功能,诸如高安全性和对RISC机器的可移植性等,但是Windows 95具有需要较少硬件资源的优点。 Windows 98在1998年6月发布,具有许多加强功能,包括执行效能的提高、更好的硬件支持以及与因特网和全球信息网(WWW)更紧密的结合。 Windows方面 Windows 98和Windows NT都是支持32位优先权式多任务(preemptive multitasking)及多线程的图形操作系统。Windows拥有图形使用者接口(GUI ),这种使用者界面也称作「可视化接口」或「图形窗口环境」。有关GUI的概念可追溯至70年代中期,在Alto和Star等机器上以及SmallTalk等环境中由Xerox PARC所作的研究工作。该项研究的成果后来被Apple Computer和Microsoft引入主流并流行起来。虽然有一些争议,但现在已非常清楚,GUI是(Microsoft的Charles Simonyi的说法)一个在个人计算机工业史上集各方面技术大成于一体的最重要产物。 所有GUI都在点矩阵对应的视讯显示器上处理图形。图形提供了使用屏幕的最佳方式、传递信息的可视化丰富多彩环境,以及能够WYSIWYG(what you see is what you get:所见即所得)的图形视讯显示和为书面文件准备好格式化文字输出内容。 在早期,视讯显示器仅用于响应使用者通过键盘输入的文字。在图形使用者接口中,视讯显示器自身成为使用者输入的一个来源。视讯显示器以图标和输入设备(例如按钮和滚动条)的形式显示多种图形对象。使用者可以使用键盘(或者更直接地使用鼠标等指向设备)直接在屏幕上操纵这些对象,拖动图形对象、按下鼠标按钮以及滚动滚动条。 因此,使用者与程序的交流变得更为亲密。这不再是一种从键盘到程序,再到视讯显示器的单向信息流动,使用者已经能够与显示器上的对象直接交互作用了。 使用者不再需要花费长时间学习如何使用计算机或掌握新程序了。Windows让这一切成真,因为所有应用程序都有相同的基本外观和感觉。程序占据一个窗口-屏幕上的一块矩形区域。每个窗口由一个标题列标识。大多数程序功能由程序的菜单开始。用户可使用滚动条观察那些无法在一个屏幕中装下的信息。某些菜单项目触发对话框,用户可在其中输入额外的信息。几乎在每个大的Windows程序中都有一个用于开启文件的特殊对话框。该对话框在所有这些Windows程序中看起来都一样(或接近相同),而且几乎总是从同一菜单选项中启动。 一旦您了解使用一个Windows程序的方法,您就非常容易学习其它的Windows程序。菜单和对话框允许用户试验一个新程序并探究它的功能。大多数Windows程序同时具有键盘接口和鼠标接口。虽然Windows程序的大多数功能可通过键盘控制,但使用鼠标要容易得多。 从程序写作者的角度看,一致的使用者接口来自于Windows建构菜单和对话框的内置程序。所有菜单都有同样的键盘和鼠标接口,因为这项工作是由Windows处理,而不是由应用程序处理。 为便于多个程序的使用,以及这些程序间信息的交换,Windows支持多任务。在同一时刻能有多个Windows程序显示并运行。每个程序在屏幕上占据一个窗口。用户可在屏幕上移动窗口,改变它们的大小,在不同程序间切换,并从一个程序向另一个程序传送数据。因为这些窗口看起来有些像桌面上的纸(当然,这是计算机还未占据办公桌之前的年代),Windows有时被称作:一个显示多个程序的「具象化桌面」。 Windows的早期版本使用一种「非优先权式(non-preemptive)」的多任务系统。这意味着Windows不使用系统定时器将处理时间分配给系统中运行的多个应用程序,程序必须自愿放弃控制以便其它程序运行。在Windows NT和Windows 98中,多任务是优先权式的,而且程序自身可分割成近乎同时执行的多个执行绪。 操作系统不对内存进行管理便无法实现多任务。当新程序启动、旧程序终止时,内存会出现碎裂空间。系统必须能够将闲置的内存空间组织在一起,因此系统必须能够移动内存中的程序代码和数据块。 即使是在8088微处理器上跑的Windows 1.0也能进行这类内存管理。在实际模式限制下,这种能力被认为是软件工程一个令人惊讶的成就。在Windows 1.0中,PC硬件结构的640KB内存限制,在不要求任何额外内存的情况下被有效地扩展了。但Microsoft并未就此停步:Windows 2.0允许Windows应用程序存取扩充内存(EMS);Windows 3.0在保护模式下,允许Windows应用程序存取高达16MB的扩展内存。Windows NT和Windows 98通过成熟的32位操作系统及平坦寻址空间,摆脱了这些旧的限制。 Windows上执行的程序可共享在称为「动态链接库」的文件中的例程。Windows包括一个机制,能够在执行时连结使用动态链接库中例程的程序。Windows自身基本上就是一个动态链接库的集合。 Windows是一个图形接口,Windows程序能够在视讯显示器和打印机上充分利用图形和格式化文字。图形接口不仅在外观上更有吸引力,而且还能够让使用者传递高层次的信息。 Windows应用程序不能直接存取屏幕和打印机等图形显示设备硬件。相反,Windows提供一种图形程序语言(称作图形设备接口,或者GDI),使显示图形和格式化文字更容易。Windows虚拟化了显示硬件,使为Windows编写的程序可使用任何具有Windows设备驱动程序的视频卡或打印机,而程序无需确定系统相连的设备类型。 对Windows开发者来说,将与设备无关的图形接口输出到IBM PC上不是件轻松的事。PC的设计是基于开放式架构的原则,鼓励第三方硬件制造商为PC开发接口设备,而且开发了大量这样的设备。虽然出现了多种标准,PC上的传统MS-DOS程序仍不得不各自支持许多不同的硬设备。这对MS-DOS字处理软件来说非常普遍,它们连同1到2张有许多小文件的磁盘一同销售,每个文件支持一种特定的打印机。Windows程序不要求每个应用程序都自行开发这些驱动程序,因为这种支持是Windows的一部分。 动态链接 Windows运作机制的核心是一个称作「动态链接」的概念。Windows提供了应用程序丰富的可呼叫函数,大多数用于实作其使用者接口和在视讯显示器上显示文字和图形。这些函数采用动态链接库(Dynamic Linking Library,DLL)的方式撰写。这些动态链接库是些具有.DLL或者有时是.EXE扩展名的文件,在Windows 98中通常位于\WINDOWS\SYSTEM子目录中,在Windows NT中通常位于\WINNT\SYSTEM和\WINNT\SYSTEM32子目录中。 在早期,Windows的主要部分仅通过三个动态链接库实作。这代表了Windows的三个主要子系统,它们被称作Kernel、User和GDI。当子系统的数目在Windows最近版本中增多时,大多数典型的Windows程序产生的函数呼叫仍对应到这三个模块之一。Kernel(日前由16位的KRNL386.EXE和32位的KERNEL32.DLL实现)处理所有在传统上由操作系统核心处理的事务-内存管理、文件I/O和多任务管理。User(由16位的USER.EXE和32位的USER32.DLL实作)指使用者接口,实作所有窗口运作机制。GDI(由16位的GDI.EXE和32位的GDI32.DLL实作)是一个图形设备接口,允许程序在屏幕和打印机上显示文字和图形。 Windows 98支持应用程序可使用的上千种函数呼叫。每个函数都有一个描述名称,例如CreateWindow。该函数(如您所猜想的)为程序建立新窗口。所有应用程序可以使用的Windows函数都在表头文件里预先声明过。 在Windows程序中,使用Windows函数的方式通常与使用如strlen等C语言链接库函数的方式相同。主要的区别在于C语言链接库函数的机械码连结到您的程序代码中,而Windows函数的程序代码在您程序执行文件外的DLL中。 当您执行Windows程序时,它通过一个称作「动态链接」的过程与Windows相接。一个Windows的.EXE文件中有使用到的不同动态链接库的参考数据,所使用的函数即在那些动态链接库中。当Windows程序被加载到内存中时,程序中的呼叫被指向DLL函数的入口。如果该DLL不在内存中,就把它加载到内存中。 当您连结Windows程序以产生一个可执行文件时,您必须连结程序开发环境提供的特定「引用链接库(import library)」。这些引用链接库包含了动态链接库名称和所有Windows函数呼叫的引用信息。连结程序使用该信息在.EXE文件中建立一个表格,在加载程序时,Windows使用它将呼叫转换为Windows函数。 WINDOWS程序设计选项 为说明Windows程序设计的多种技术,本书提供了许多范例程序。这些程序使用C语言撰写并原原本本的使用Windows API来开发程序。我将这种方法称作「古典」Windows程序设计。这是我们在1985年为Windows 1.0写程序的方法,它今天仍是写作Windows程序的有效方法。 API和内存模式 对于程序写作者来说,操作系统是由本身的API定义的。API包含了所有应用程序能够使用的操作系统函数呼叫,同时包含了相关的数据型态和结构。在Windows中,API还意味着一个特殊的程序架构,我们将在每章的开头进行研究。 一般而言,Windows APIWindows 1.0以来一直保持一致,没什么重大改变。具有Windows 98程序写作经验的Windows程序写作者会对Windows 1.0程序的原始码感觉非常熟悉。API改变的一种方式是进行增强。Windows 1.0支持不到450个函数呼叫,现在已有了上千种函数呼叫。 Windows API和它的语法的最大变化来自于从16位架构向32位架构转化的过程中。Windows从版本1.0到版本3.1使用16位Intel 8086、8088、和286微处理器上所谓的分段内存模式,由于兼容性的原因,从386开始的32位Intel微处理器也支持该模式。在这种模式下,微处理器缓存器的大小为16位,因此C的int数据型态也是16位宽。在分段内存模式下,内存地址由两个部分组成-一个16位段(segment)指针和一个16位偏移量(offset)指标。从程序写作者的角度看,这非常凌乱并带来了long或far指针(包括段地址和偏移量地址)和short或near指标(包括带有假定段地址的偏移量地址)的区别。 从Windows NT和Windows 95开始,Windows支持使用Intel 386、486和Pentium处理器32位模式下的32位平坦寻址内存模式。C语言的int数据型态也扩展为32位的值。为32位版本Windows编写的程序使用简单的平坦线性空间寻址的32位指针值。 用于16位版本WindowsAPIWindows 1.0到Windows 3.1)现在称作Win16。用于32位版本WindowsAPIWindows 95、Windows 98和所有版本的Windows NT)现在称作Win32。许多函数呼叫在从Win16到Win32的转变中保持相同,但有些需要增强。例如,图像坐标点由Win16中的16位值变为Win32中的32位值。此外,某些Win16函数呼叫返回一个包含在32位整数值中的二维坐标点。这在Win32中不可能,因此增加的新函数呼叫以不同方式运作。 所有32位版本的Windows都支持Win16 API(以确保和旧有应用程序兼容)和Win32 API(以运行新应用程序)。非常有趣的是,Windows NT与Windows 95及Windows 98的工作方式不同。在Windows NT中,Win16函数呼叫通过一个转换层被转化为Win32函数呼叫,然后被操作系统处理。在Windows 95和Windows 98中,该操作正相反:Win32函数呼叫通过转换层转换为Win16函数呼叫,再由操作系统处理。 在同一时刻有两个不同的Windows API集(至少名称不同)。Win32s (「s」代表「subset(子集)」)是一个API,允许程序写作者编写在Windows 3.1上执行的32位应用程序。该API仅支持已被Win16支持的32位函数版本。此外,Windows 95 API一度被称作Win32c(「c」代表「compatibility(兼容性)」),但该术语已被抛弃了。 现在,Windows NT和Windows 98都被认为能够支持Win32 API。然而,每个操作系统依然都支持某些不被别的操作系统支持的某些功能特性。因为它们的相同之处是相当可观的,所以有可能编写在两个操作系统下都可执行的程序。而且,人们普遍认为这两个产品最终会合而为一。 语言选项 使用C语言和原始的API不是编写Windows 98程序的唯一方法。然而,这种方法却提供给您最佳的性能、最强大的功能和在发掘Windows特性方面最大的灵活性。可执行文件相对较小且运行时不要求外部链接库(自然,Windows DLL自身除外)。最重要的是,不管您最终以什么方式开发Windows应用程序,熟悉API会使您对Windows内部有更深入的了解。 虽然我认为学习古典的Windows程序设计对任何Windows程序写作者都是重要的,我没有必要建议使用C和API编写每个Windows应用程序。许多程序写作者,特别是那些为公司内部开发程序或在家编写娱乐程序的程序写作者喜欢轻松的开发环境,例如Microsoft Visual Basic或者Borland Delphi(它结合了对象导向的Pascal版本)。这些环境使程序写作者将精力集中于应用程序的使用者接口和相关使用者接口对象的程序代码上。要学习Visual Basic,您也许需要参考Microsoft Press的一些其它图书,例如Michael Halvorson1996年着的《Learn Visual Basic Now》。 在专业程序写作者中-特别是那些开发商业应用程序的程序写作者-Microsoft Visual C++和Microsoft Foundation Class Library(MFC)是近年来流行的选择。MFC在一组C++对象类别中封装了许多Windows程序设计中的琐碎细节。Jeff Prosise的《Programming Windows with MFC,第二版》(Microsoft Press,1999年)提供了MFC程序的写作指南。 最近,Internet和World Wide Web的流行大力推广着Sun Microsystems的Java,这是一个受C++启发却与微处理器无关的程序设计语言,而且结合了可在几个操作系统平台上执行的图形应用程序开发工具组。Microsoft Press有一本关于Microsoft J++(Microsoft的Java)开发工具的好书,《Programming Visual J++ 6.0》(1998年),由Stephen R. Davis着。 显然,很难说哪种方法更有利于开发Windows应用程序。更主要的是,也许是应用程序自身的特性决定了所使用的工具。不管您最后实际上使用什么工具写作程序,学习Windows API将使您更深入地了解Windows工作的方式。Windows是一个复杂的系统,在API上增加一个程序写作层并未减少它的复杂性,仅仅是掩盖了它,早晚您会碰到它。了解API会给您更好的补救机会。 在原始的Windows API之上的任何软件层都必定将您限制在全部功能的一个子集内。您也许发现,例如,使用Visual Basic编写应用程序非常理想,然而它不允许您做一个或两个很简单的基本工作。在这种情况下,您将不得不使用原始的API呼叫。API定义了作为Windows程序写作者所需的一切。没有什么方法比直接使用API更万能的了。 MFC尤其问题百出。虽然它大幅简化了某些工作(例如OLE),我却经常发现要让它们按我所想的去工作时,会在其它特性(例如Document/View架构)上碰壁。MFC还不是Windows程序设计者所追求的灵丹妙药,很少有人认为它是一个好的对象导向设计的模型。MFC程序写作者从他们使用的对象类别定义如何工作中受益颇深,并会发现他们经常参考MFC原始码,搞懂这些原始码是学习Windows API的好处之一。 程序开发环境 在本书中,假定您正使用Microsoft Visual C++ 6.0,标准版、专业版和企业版都可以。经济的标准版足以应付本书中的程序设计需求。Visual C++ 还是Visual Studio 6.0中的一部分。 Microsoft Visual C++ 软件包中包括C编译器和其它编译及连结Windows程序所需的文件和工具等。它还包括Visual C++ Developer Studio,一个可编辑原始码、以交谈方式建立资源(如图标和对话框)以及编辑、编译、执行和测试程序的环境。 如果您正使用Visual C++ 5.0,则需要为Windows 98和Windows NT 5.0更新表头文件和引用链接库,这些东西可从Microsoft的网站上得到。在 http://www.microsoft.com/msdn/,选择「Downloads」,然后选择「 Platform SDK」(软件开发套件),您就能在选择的目录中下载和安装更新文件。要让Microsoft Developer Studio浏览这些目录,可以从「Tool」菜单项选择「 Options」然后按下「Directories」标签。 Microsoft网站上的msdn部分代表「Microsoft Developer Network(Microsoft软件开发者网络)」。这是一个向程序写作者提供了经常更新的CD-ROM的计划,这些CD-ROM中包含了程序写作者在Windows开发中所需的最新东西。您也可以订阅MSDN,这样就避免经常得从Microsoft的网站下载文件。 API文件 本书不是Windows API权威的正式文件的替代品。那组文件不再以印刷形式出版,它仅能从CD-ROM或Internet上取得。 当您安装Visual C++ 6.0时,您将得到一个包括API文件的在线求助系统。您可通过订阅MSDN或使用Microsoft网站上的在线求助系统更新该文件。连接到 http://www.microsoft.com/msdn/,并选择「MSDN Library Online」。 在Visual C++ 6.0中,从「Help」菜单项选择「Contents」项目开启MSDN窗口。API文件按树形结构组织,寻找标有「 Platform SDK」的部分,所有在本书中引用的文件都来自于该部分。我将向您介绍如何从「 Platform SDK」开始寻找以斜线分层分门别类的文件的位置。(我知道「Platform SDK」是整个MSDN知识库中较为晦涩的部分,但我敢保证那是Windows程序设计的基本核心。)例如,对于如何在Windows程序中使用鼠标的文件,您可参考/ Platform SDK / User Interface Services / User Input / Mouse Input。 我在前面提到Windows大致分为Kernel、User和GDI子系统。kernel接口在/ Platform SDK / Windows Base Services中,User界面函数在 / Platform SDK / User Interface Services中,GDI位于 / Platform SDK / Graphics and Multimedia Services / GDI中。 编写第一个WINDOWS程序 现在是开始写些程序的时候了。为了便于对比,让我们以一个非常短的Windows程序和一个简短的文字模式程序开始。这会帮助我们找到使用开发环境并感受建立和编译程序机制的正确方向。 文字模式(Character-Mode)模型 程序写作者们喜爱的一本书是《The C Programming Language》(Prentice Hall,1978年和1988年),由Brian W. Kernighan和Dennis M. Ritchie(亲切地称为K&R)编着。该书的第一章以一个显示「hello, world」的C语言程序开始。 这里是在《The C Programming Language》第一版第6页中出现的程序: main () { printf ("hello, world\n") ; } 以前C程序写作者在使用printf等C执行期链接库函数时,无需先声明它们。但这是90年代,我们愿意给编译器一个在我们的程序中标出错误的机会。这里是在K&R第二版中修正的程序: #include <stdio.h> main () { printf ("hello, world\n") ; } 该程序仍然是那么短。但它可通过编译并执行得很好,但当今许多程序写作者更愿意清楚地说明main函数的返回值,在这种情况下ANSI C规定该函数必须返回一个值: #include <stdio.h> int main () { printf ("hello, world\n") ; return 0 ; } 我们还可以包括main的参数,把程序弄得更长一些,但让我们暂且这样就好了-包括一个include声明、程序的进入点、一个对执行期链接库函数的呼叫和一个return语句。 同样效果的Windows程序 Windows关于「hello, world」程序的等价程序有和文字模式版本完全相同的组件。它有一个include声明、一个程序进入点、一个函数呼叫和一个return语句。下面便是该程序: /*------------------------------------------------------------------ HelloMsg.c -- Displays "Hello, Windows 98!" in a message box (c) Charles Petzold, 1998 --------------------------------------------------------------------*/ #include <windows.h> int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { MessageBox (NULL, TEXT ("Hello, Windows 98!"), TEXT ("HelloMsg"), 0); return 0 ; } 在剖析该程序之前,让我们看一下在Visual C++ Developer Studio中建立新程序的方式。 首先,从File菜单中选New。在 New对话框中,单击Projects页面标签,选择 Win32 Application。在Location栏中,选择一个子目录,在 Project Name栏中,输入该项目的名称,此时该名称是HelloMsg,这便是在 Location栏中显示的目录的子目录。Create New Workspace复选框应该勾起来,Platforms部分应该显示 Win32,选择OK。 将会出现一个标题为Win32 Application - Step 1 Of 1的对话框,指出要建立一个Empty Project,并按下Finish按钮。 从File菜单中再次选择New。在 New对话框中,选择Files页面标签,选择 C++ Source File。Add To Project复选框应被选中,并应显示HelloMsg。在 File Name栏中输入HelloMsg.c,选中OK。 现在您可输入上面所示的HELLOMSG.C文件,您也可以选择Insert菜单和 File As Text选项从本书附带的CD-ROM上复制HELLOMSG.C的内容。 从结构上说,HELLOMSG.C与K&R的「hello,world」程序是相同的。表头文件STDIO.H已被WINDOWS.H所代替,进入点main被WinMain所代替,而且C语言执行时期链接库函数printf被Windows API函数MessageBox所代替。然而,在程序中有许多新东西,包括几个陌生的大写标识符。 让我们从头开始。 表头文件 HELLOMSG.C以一个前置处理器指示命令开始,实际上在每个用C编写的Windows程序的开头都可看到:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值