65.9K
CodeProject 正在变化。 阅读更多。
Home

在 ReactOS 上使用 C/C++ 介绍 OpenGL

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2019 年 9 月 23 日

CPOL

24分钟阅读

viewsIcon

24187

检查 ReactOS 是否能够运行 OpenGL,确定一个令人信服的 IDE 并在 ReactOS 上开始使用 OpenGL。

目录

  1. 引言
  2. 开发环境选择
  3. 添加文档生成器
  4. Using the Code
  5. 关注点
  6. 历史

简介

ReactOS 是 Windows 操作系统的一个开源替代品。尽管 ReactOS 的第一个版本可以追溯到 1998 年,但仍然没有“稳定”的 ReactOS 版本。也许,最重要的原因是缺乏关注。

我一直关注 React OS 的开发一段时间了。我偏爱的语言是 C# - 所以我第一次仔细研究 ReactOS 是为了调查 C# 是否能在 ReactOS 上运行。是的,它能运行。有关如何设置 ReactOS,如何避免陷阱/最大限度地减少安装 C# 的麻烦以及如何运行 C# 的详细信息,可以在提示 ReactOS 上 C# 简介中找到。

通过此提示,我将介绍如何在当前的 ReactOS 版本上编译并运行一个非常简单的 C/C++ OpenGL 应用程序。(我从我最喜欢的编程语言 C# 回到我的根基——即 C/C++,因为原生的 OpenGL API 是用 C 编写的,我希望避免任何可能的副作用。)

由于我几乎只在工作中与 MS Windows 打交道,并且不想处理不同的硬件,所以我能想到的唯一方法是在虚拟机中运行 ReactOS。此外,ReactOS 主页也建议不要将 ReactOS 用于生产用途。我最喜欢的虚拟机是 Oracle VirtualBox 和 VMWare Player - 两者都可供个人免费使用。当前的 ReactOS 版本是 0.4.11。

使用内置视频驱动程序支持 OpenGL

有许多工具可以检查正在运行的 Windows 安装的 OpenGL 功能。不幸的是,这些工具通常假定是最新的且维护良好的 Windows 安装 - 因为它们主要面向游戏玩家。我只找到了两个能够运行在 ReactOS(它自称是一个过时的 Windows 版本)上的工具来检查 OpenGL 功能。

第一个也是更简单的工具是 Jerome GuinotGPUShark。我使用的是版本 0.14.1.0。

第二个工具是 TechPowerUp 的 GPU-Z。我使用的是版本 2.25.0。

VMWare Player

VMWare Player(我使用的是 VMWare Player 12.5.9 版本)只提供内置的 VGA 兼容虚拟显卡,唯一的选择是开启或关闭 3D 加速。

GPUShark 在 3D 加速开启和关闭时报告相同的结果 - 我猜这是因为 ReactOS 内置的标准 VGA 驱动程序不使用 3D 加速。


  • 更新:在 VMWare Player 15.5.6 版本上,ReactOS 0.4.13 版本提供了 2 个扩展。

GPU-Z 在 3D 加速开启和关闭时也报告相同的结果。

总而言之,我认为 VMWare Player 12.5.9 版本和 ReactOS 0.4.11 的组合非常不稳定,不建议使用。此组合在 3D 加速开关以及 GPU-Z 安装方面特别困难。在这些情况下,我更喜欢 Oracle VirtualBox。


  • 更新:使用 VMWare Player 15.5.6 版本和 ReactOS 0.4.13 版本后,稳定性问题似乎已解决。
  • 提示:检查处理器功能虚拟化的最佳组合。请参阅“虚拟机设置 | 硬件 | 处理器 | 虚拟化引擎”。

Oracle VirtualBox

Oracle VirtualBox(我使用的是 VirtualBox 6.0.10 版本)支持三种虚拟显卡:VBoxVGA、MSVGA 和 VBoxSVGA。

GPUShark 为所有三种虚拟显卡报告相同的结果。


  • 更新:在 Oracle VirtualBox 6.1.10 版本上,ReactOS 0.4.13 版本提供了 2 个扩展。

此外,Oracle VirtualBox 还提供了为虚拟显卡模拟 3D 加速的可能性。但是,无论 3D 加速是开启还是关闭,结果都是一样的——我猜这是因为 ReactOS 内置的标准 VGA 驱动程序不使用 3D 加速。

GPU-Z 无法识别任何图形设备信息。无论 3D 加速是开启还是关闭,它对所有三种虚拟显卡也报告相同的结果。

总而言之,我认为 Oracle VirtualBox 6.0.10 版本和 ReactOS 0.4.11 的组合非常稳定,建议使用。

摘要

内置视频驱动程序的最终结论是,ReactOS 在任何情况下都通过 CPU 模拟支持 OpenGL 1.1 版本,不带 OpenGL 扩展,使用来自 Mesa 库(可能仅限于一个古老版本)的选定、非常基本的功能。这足以初步体验 OpenGL。但严肃的 OpenGL 应用程序——特别是由于缺少 FramebufferObject 等重要扩展——无法通过此 OpenGL 1.1 版本实现。

使用高级视频驱动程序支持 OpenGL

我再次使用 GPUSharkGPU-Z 测试 OpenGL 功能。

VMWare Player

VMWare Player 提供了安装 VMWare 工具的选项,其中包含各种实用程序和驱动程序,包括 SVGA II 图形驱动程序。我强烈建议使用 preVista VMWare 工具,并尽可能少地安装驱动程序 - 在我的情况下,我只安装 SVGA II 图形驱动程序。警告:不要被 VMWare Tools 尝试安装所有驱动程序并在安装打印机驱动程序时报告错误的事实所迷惑。只要耐心等待,确认错误消息后,安装程序就会运行完成。
 

GPUShark 报告 3D 加速关闭时的结果与安装 VMWare 工具之前相同


  • 更新:在 VMWare Player 15.5.6 版本上,ReactOS 0.4.13 版本提供了 2 个扩展。

开启 3D 加速后,GPUShark 报告

ReactOS 应用程序管理器提供 Mesa 3D 图形库 17.0.1 安装。Mesa 安装再次将 OpenGL 功能扩展到

GPU-Z 仅在 3D 加速关闭时运行并报告

如果 GPU-Z 在 3D 加速开启时启动,ReactOS 0.4.11 将挂起。

Mesa 安装后的情况完全不同

总的来说,安装 SVGA II 驱动程序和 Mesa 后,ReactOS 在 VMWare Player 下似乎运行更稳定——但从我目前所见,我并不完全信任它。在大量使用此环境之前,进行彻底的测试无疑是明智之举。


  • 提示:检查处理器功能虚拟化的最佳组合。请参阅“虚拟机设置 | 硬件 | 处理器 | 虚拟化引擎”。

Oracle VirtualBox

Oracle VirtualBox 提供了安装增强功能(Guest Additions)的选项。这些扩展包括一个高级视频驱动程序。Oracle VirtualBox 播放器的菜单提供“设备 | 插入增强功能 CD 映像”,CD 提供了“VBoxWindowsAdditions-x86.exe”。但在安装后以及对虚拟硬件进行任何更改时请务必小心:

  • 我强烈建议在安装 Windows 增强功能之前备份虚拟硬盘。——几乎不可能在不损坏 VirtualBox 镜像的情况下卸载 Windows 增强功能。
  • 我不建议在安装 Windows 增强功能后切换虚拟显卡 VBoxVGA、MSVGA 和 VBoxSVGA。我的 VirtualBox 镜像在从任何其他虚拟显卡切换到 MSVGA 后崩溃了——也许 Windows 增强功能需要 VBoxVGA 或 VBoxSVGA 才能正常工作。
  • 专注于支持 3D 的 VBoxVGA 虚拟显卡。MSVGA 虚拟显卡似乎不与 Windows 增强功能协同工作,而 VBoxSVGA 虚拟显卡似乎不将 3D 加速传递给访客系统。此外,支持 3D 的 VBoxVGA 虚拟显卡提供了多种视频模式,分辨率最高可达 1920x1200 - 目前足以满足我的需求。

只要 VBoxVGA 虚拟显卡的 3D 加速功能关闭,GPUShark 和 GPU-Z 报告的结果与内置视频驱动程序所示相同。


  • 更新:在 Oracle VirtualBox 6.1.10 版本上,ReactOS 0.4.13 版本提供了 2 个扩展。

开启 3D 加速后,GPUShark 报告

Mesa 安装再次将 OpenGL 功能扩展到

GPU-Z 再次无法识别任何图形设备信息,无论是否安装了 Mesa

摘要

对于高级视频驱动程序来说,底线是 ReactOS 通过 VMWare Player 的 VMWare 工具以及 Oracle 的 VirtualBox 客户机增强功能支持 OpenGL 2.1 版本,包含 72 个扩展。这应该足以实现严肃的 OpenGL 应用程序——希望重要的扩展(如 `FramebufferObject`)包含在这 72 个扩展中。

安装 Mesa 3D 图形库 17.0.1 后,功能甚至更多。在这种情况下,ReactOS 实际上通过 VMWare Player 的 VMWare 工具以及 Oracle 的 VirtualBox 客户机增强功能支持 OpenGL 3.0 版本,包含 217 个扩展。

开发环境选择

我已检查过这些开发环境...

Code Lite

CodeLite 是一款免费开源(GitHub)的全功能跨平台 IDE,采用 GNU 通用公共许可证 V2.0 发布,支持 C、C++ 和其他语言(由 Eran Ifrah 开发)。

当前的 CodeLite 版本是 13.0,于 2018 年 11 月发布,并于 2019 年 5 月稳定。但它无法安装在 ReactOS 上(似乎不再与 Windows 5.2 版/WindowsXP 兼容)。

相反,CodeLite 11.0 和 12.0 版本可以安装在 ReactOS 上。

CodeLite 11.0 版本于 2017 年 10 月发布,CodeLite 12.0 版本于 2018 年 2 月发布。我测试了 CodeLite 12.0 版本。下载和安装运行顺利。

安装期间,CodeLite 安装程序会扫描预安装的编译器/构建环境并提供匹配项进行注册,或建议安装“mingw-w64-install.exe”。我第一次就注册了预安装的编译器/构建环境“TDM-GCC Compiler Suite for Windows / GCC 5 Series / MinGW 32-bit Edition”。

我也测试了“mingw-w64-install.exe”的安装。下载和安装运行顺利。安装程序包含 32 位和 64 位以及 posix 和 Win32 版本的编译器/构建环境,版本为 8.1.0,还有几个更旧的版本,一直到 4.9.4。我选择了“Mingw-W64 - i686-8.1.0 - Win32 / GCC 7.3.0”。安装和注册进展顺利。

没有 Win32 C/C++ 或 OpenGL C/C++ 的项目模板(参见图 1)。相反,我创建了一个最小化的控制台应用程序并成功编译,但无法启动它——使用两个当前注册的编译器/构建环境(TDM GCC 5 系列和 MinGW-W64 GCC 7.3.0)。

CodeLite 的编译器/构建环境也可以稍后通过“设置 | 构建设置”菜单进行更改/补充。

由于 CodeLite 推荐“`MinGW GCC 7.1.0`”,因此我也安装了这个编译器并成功将其注册到 CodeLite(参见图 2)。对于这个编译器/构建环境,我也得到了相同的结果:我的极简控制台应用程序再次成功编译(`MinGW-W64 GCC 7.1.0`),但我无法启动它。

在所有三种情况下,错误消息都是相同的:应用程序未能启动,因为未找到 libgcc_s_dw2-l.dll。重新安装应用程序可能会解决此问题。

除此之外,不幸的是,GUI 存在许多显示/显示更新错误(参见图 3)。这尤其适用于工具栏、下拉列表和列表视图,它们是透明的,显示背景或过时的内容。

总而言之,GUI 不够灵敏,我不喜欢使用充满显示/显示更新错误的 GUI,而且它还缺少 Win32 C/C++ 和 OpenGL C/C++ 的项目模板。我认为在 ReactOS 上我无法用它高效地工作。这就是我停止寻找错误消息解决方案的原因。


  • 关于 MinGW 的更新:我也测试了 MinGW 8.2.0.5。可以从 https://osdn.net/projects/mingw/releases -> Package MinGW.org Compiler Collection (GCC) 下载为 gcc-c++-8.2.0-5-mingw32-bin.tar.xz。我将包解压到 C:\MinGW,CodeLite 在扫描过程中立即检测到了编译器的存在。
  • 关于 ReactOS 0.4.12 的更新:CodeLite 13.0 版本仍无法安装。CodeLite 12.0 版本的显示错误仍然存在。

因此我决定继续寻找另一个合适的 IDE。

Dev-C++

Dev-C++ 是一款免费的全功能 Windows 平台 IDE,采用 GNU 通用公共许可证 V2.0 发布,支持 C、C++ 和其他语言。目前有两个或多或少活跃的分支。

第一个是 Bloodshed Dev-C++(由 Johan Mes 等人开发)。当前版本是 5.8.3,于 2014 年 11 月发布。

第二个是 wxDev-C++(由 Colin Laplace 等人开发)。当前版本是 7.4.2,于 2017 年 1 月发布。

我决定从文件“Dev-Cpp 5.8.3 TDM-GCC 4.8.1 Setup.exe”安装 Bloodshed Dev-C++。Bloodshed Dev-C++ 5.8.3 版本的下载和安装运行顺利——我选择了包括 TDM-GCC 4.8.1 在内的完整安装。配置编译器/构建环境绝对没有挑战——设置配置立即生效。

有一个 Win32 C/C++ 的项目模板(参见图 1)以及 OpenGL C/C++ 的项目模板(参见图 2)。我创建了两个极简应用程序——分别来自上述项目模板。生成的样板代码第一次就成功编译并运行了。

GUI 简单、整洁、反应迅速,所有控件都无任何显示/显示更新错误——使用起来非常愉快。

但是 Dev-C++ 不支持组织多个项目的解决方案。如果您想通过拆分 DLL 来降低大型应用程序的复杂性,这将是一个缺点。

我认为 Dev-C++ 对于小型应用程序来说是一个非常好的 IDE——但我担心拆分 DLL 会增加工作量。

因此我决定继续寻找另一个合适的 IDE。

NetBeans IDE

NetBeansIDE 是一款免费的全功能跨平台 IDE,采用 Apache 许可证 V2.0 发布,支持 Java、C、C++ 和其他语言。由于 NetBeansIDE 完全基于 Java,因此合适的 Java 运行时环境(JRE)和开发工具包(JDK)的可用性至关重要。最新的可用 32 位 JDK (JavaSE) 版本是 JDK 8,对于 JDK 8 推荐的最高 NetBeans IDE 版本是 8.2。要运行 NetBeans IDE 8.2 的安装程序,需要安装 JRE 8。

ReactOS 在其应用程序管理器中提供了“Java SE 运行时环境 8”的安装。此安装可能需要第二次尝试才能成功——但在成功安装后,可以启动 NetBeans IDE 8.2 安装程序。安装过程中会查询 JDK 的路径——无法跳过此指定。因此,预先安装 JDK 是不可避免的。

目前,JDK 8 只能通过官方 ORACLE 下载获取,这需要 ORACLE 账户并披露大量个人信息。仅仅为了测试,这对我来说似乎过于繁琐,因此我中止了对 NetBeans IDE 8.2 的测试。

所以我必须继续寻找另一个合适的 IDE。

GeanyIDE

GeanyIDE 是一款免费开源(GitHub)的全功能跨平台 IDE,采用 GNU 通用公共许可证 V2.0 发布,支持 C、C++ 和其他语言。

当前的 GeanyIDE 版本是 1.35,于 2019 年 4 月发布。GeanyIDE 1.35 版本的下载和安装运行顺利——但它无法启动。错误消息是:无法在动态链接库 KERNEL32.dll 中找到过程入口点 GetTickCount64。

GeanyIDE 1.34 版本(于 2018 年 12 月发布)也存在同样的问题。这里的错误消息是:无法在动态链接库 KERNEL32.dll 中找到过程入口点 GetFileInformationByHandleEx。

对于 2018 年 2 月发布的 GeanyIDE 1.33 版本和 2017 年 11 月发布的 GeanyIDE 1.32 版本,错误消息是:无法在动态链接库 msvcrt.dll 中找到过程入口点 _time32。安装 Microsoft Visual C++ 2015-2019 可再发行组件 (x86) 没有任何改变。

于 2017 年 7 月发布的 GeanyIDE 1.31 版本和于 2017 年 3 月发布的 GeanyIDE 1.30 版本可以启动——但 IDE 出现后 CPU 立即满载,显示等待光标,IDE 对任何用户输入都没有反应。

所以我必须继续寻找另一个合适的 IDE。

Ajunta 开发工作室

Ajuta Dev Studio 是一款免费开源(GitLab)的多功能 IDE,采用 GNU 通用公共许可证 V2.0 发布,支持 C、C++ 和其他语言(由一群爱好者开发)。不幸的是,此 IDE 不适用于 Windows 平台。

GNAT 编程工作室

GNAT Programming Studio 社区版是一款免费的全功能跨平台 IDE,支持 ADA、C/C++ 和其他语言(由 AdaCore 开发)。当前的 GNAT Programming Studio 版本是 2019,但仅适用于 64 位。最新的 32 位版本是 2017,并附带安装程序“gnat-gpl-2017-x86-windows-bin.exe”。

下载和安装运行顺利。安装了将近 2 GB 后,启动 GPS.exe 出现以下错误消息:应用程序未能正确初始化 (0xc00000142)。单击“确定”终止应用程序。

Visual Studio Code

Visual Studio Code 是微软一款免费的多功能跨平台 IDE,采用专有许可证发布。当前的 Visual Studio Code 版本是 1.38。它无法在 ReactOS 上运行。错误消息是:此程序不支持您的计算机正在运行的 Windows 版本。

Ultimate++

Ultimate++ 是一款免费开源(SVN)的全功能跨平台 IDE,采用 BSD 2-clause license(“简化 BSD 许可证”或“FreeBSD 许可证”)发布,支持 C、C++ 和其他语言。

当前的 Ultimate++ 版本是 2019.1,于 2018 年 11 月发布,并于 2019 年 5 月稳定。我选择了便携版“upp-mingw-13068.7z”。下载和解压运行顺利。解压后,Ultimate++ 可以通过“theide32.exe”启动。

一开始,我想熟悉 Ultimate++,并查看了一个现有包。我从示例集合中选择了“`Bombs`”——但在选择包的过程中,Ultimate++ 就冻结了我的 VirtualBox 镜像。我重复了五次——三次结果相同。两次我能够打开“`Bombs`”示例,编译并运行它。

然而,我设法从“Win32 API 项目 (no U++)”模板创建了一个最小的 Win32 应用程序,并成功编译和运行了它。

之后,我将模板生成的样板代码替换为最小化的 Win32 OpenGL 应用程序代码,并通过菜单“项目 | 包管理器”将 opengl32 库添加到库列表中。

在创建此应用程序期间多次崩溃后,怀疑似乎得到了证实:每当浮动帮助淡入时,VirtualBox 映像就会冻结。

总而言之,Ultimate++ 最大的缺点是 GUI 经常导致 VirtualBox 映像冻结。我认为在 ReactOS 上使用它我不会很高效。

因此我决定继续寻找另一个合适的 IDE。

Code::Blocks

Code::Blocks 是一款免费开源(SVN)的全功能跨平台 IDE,采用 GNU 通用公共许可证 V3.0 发布,支持 C、C++ 和其他语言(由一群爱好者开发)。

当前的 Code::Blocks 版本是 17.12,于 2017 年 12 月发布。有较新的每夜构建可供下载。我遵循建议并选择了“codeblocks-17.12mingw-setup.exe”。下载和安装运行顺利。安装后,Code::Blocks 可以通过“CodeBlocks.exe”启动。

包含的编译器/构建环境“TDM-GCC Compiler Suite for Windows / GCC 5 Series / MinGW 32-bit Edition”在 Code::Blocks 安装后立即配置并可以使用。

Code::Blocks 提供了所有已检查 IDE 中最广泛的项目模板选择。我选择了“OpenGL”模板,生成的样板代码第一次就成功编译并运行了。

现在,我当然也想尝试 GLEW。由于 ReactOS 只支持 OpenGL 2.1 版本,我决定使用 GLEW 开始支持 OpenGL 3.0 之前的最高版本号——因此选择 GLEW 1.5.0。我下载了“glew-1.5.0-win32.zip”并完全按照说明进行安装。将库添加到链接器有点棘手。您必须从所需的项目选择上下文菜单属性以打开项目/目标选项对话框,然后单击第一页项目设置上的项目构建选项...按钮。在项目构建选项对话框中,第二页链接器设置提供了您需要的设置。

要启用调试,必须从菜单 设置 | 调试器 启动 调试器设置 对话框,并在树节点 默认 中设置 可执行文件路径。即使 Code::Blocks 安装了多个 GDB,对我来说只有路径“<安装根目录>\MinGW\gdb32\bin\gdb32.exe”中的 GDB 可以工作。

我能看到的 Code::Blocks 唯一的缺点是 IDE 加载时间长。但这没关系,因为 GUI 功能丰富且反应灵敏——使用起来非常愉快。尽管如此,我在使用 Code::Blocks 的整个过程中从未遇到崩溃或 VirtualBox 映像冻结。

Code::Blocks 能够将多个项目组合到一个解决方案中,可以同时打开多个项目,并支持拆分 DLL 以降低大型应用程序的复杂性。

总而言之,GUI 反应灵敏,所有控件都无任何显示/显示更新错误。我确信我可以在 ReactOS 上高效地使用它。

我想,这是最适合我的 IDE,我停止检查开发环境。

结论

只有 Dev-C++Code::Blocks 能让我信服。Dev-C++ 给我的印象是小项目“之间”的轻量且极快的 IDE,而 Code::Blocks 似乎更重、更强大。

添加文档生成器


  • 更新:我已经使用 Code::Blocks 几个月了。在这里我想简单报告一下我使用 DoxyGen 遇到的惊喜。

我承认——我是一个老派程序员。一个合理的 API 文档对我来说很重要。我不太喜欢诸如:

  • “参数名称说明了一切。”或者
  • “如果你想知道类的用法或方法的调用方式,请查看单元测试。”

之类的说法。我想知道参数是否可以设置为 0/null/NULL,以及在这种情况下方法将如何表现。方法是否假设任何备用值?返回值是否可以是 0/null/NULL,这应该如何评估——作为错误?

我的“家”是 C#。所以我非常熟悉 XML 注释并直观地使用它——不幸的是,在 Code::Blocks 的 C++ 代码中也没有考虑。这是一个示例

/// <summary>
/// Gets the currently registered frame edge.
/// </summary>
/// <returns>The currently registered frame edge on success, or <c>0</c> otherwise.</returns>
UINT GetFrameEdge();

用于自动生成文档的产品是 DoxyGen。第一次尝试在 ReactOS 上安装实际的 GoxyGen 版本,正如预期的那样失败了(Windows shell 的错误消息)。我只给自己有限的时间进行第二次尝试,因此我选择了“doxygen-1.8.3.1.windows.bin.zip”,将其解压缩到 Code::Blocks 安装路径下的一个子文件夹中,并在 DoxyBlocks | 打开偏好设置... 菜单中进行了配置。

一个惊喜

一开始我只是想让 DoxyGen 运行起来,而且我已经默认我必须费很大的劲把 XML 注释改成兼容的感叹号注释——尤其是因为 DoxyGen 版本是 2013 年 1 月的。

当我看到结果时,我完全惊呆了。我的 XML 注释仅使用 DoxyGen 的基本设置(除了“doxygen.exe”的路径,我没有配置任何东西)就被转换成了非常清晰的文档。

几个快速提示

无需自引用

在类文件中不需要设置自引用(例如 <see cref="OgwwBlank"/>)。以下两行注释会创建完全相同的文档

/// The <see cref="OgwwBlank"/> class serves as an canvas for OpenGL or an universal empty window with or without frame.

/// The OgwwBlank class serves as an canvas for OpenGL or an universal empty window with or without frame.

这就是编译后的帮助文档的样子

格式化

  • 代码格式化标签工作正常(如 <c>NULL</c>)。
  • 注释中的多行可以使用 <br/> 创建。

列表

注释中的简单列表可以使用 <list>...<item>...</item>...</list> 创建。以下注释行创建一个示例列表

/// <remarks>Supported values are the ::DrawEdge() edges<br/>
/// <list type="bullet">
/// <item>BDR_RAISEDINNER - Raised inner edge.</item>
/// <item>BDR_SUNKENINNER - Sunken inner edge.</item>
/// <item>BDR_RAISEDOUTER - Raised outer edge.</item>
/// <item>BDR_SUNKENOUTER - Sunken outer edge.</item>
/// <item>EDGE_BUMP       - Combination of BDR_RAISEDOUTER and BDR_SUNKENINNER.</item>
/// <item>EDGE_ETCHED     - Combination of BDR_SUNKENOUTER and BDR_RAISEDINNER.</item>
/// <item>EDGE_RAISED     - Combination of BDR_RAISEDOUTER and BDR_RAISEDINNER.</item>
/// <item>EDGE_SUNKEN     - Combination of BDR_SUNKENOUTER and BDR_SUNKENINNER.</item>
/// </list>
/// <remarks>

这就是编译后的帮助列表的样子

表格

简单表格注释可以使用 <list type="table">...<item>...<term>...</term>...<description>...</description>...</item>...</list> 创建。

/// <param name="iImageListType">Type of image list to set.
/// This parameter can be one of the following values:
/// <list type="table">
/// <item><term><c>TVSIL_NORMAL</c></term>
/// <description>Indicates the normal image list, which contains selected, non-selected, and
/// overlay images for the items of a tree-view control.</description></item>
/// <item><term><c>TVSIL_STATE</c></term>
/// <description>Indicates the state image list. You can use state images to indicate
/// application-defined item states. A state image is displayed to the left of an item's
/// selected or non-selected image.</description></item>
/// </list>
/// </param>

这就是编译后的帮助列表的样子

源文件文档

  • DoxyGen 会评估头文件 (h/hpp) 和代码文件 (c/cpp)。如果两者都维护,那么如果头文件和代码文件中的注释不完全相同,文档中就会出现重复。因此,只应维护头文件 (h/hpp) 或代码文件 (c/cpp)。在头文件中编写文档的优点是,在这种情况下,您还可以文档类内部类型。在代码文件中编写文档的优点是,在这种情况下,代码注释也可以被文档化。我决定维护头文件。
  • 在同一个文件中可以混合使用不同的注释样式(XML 注释、感叹号注释等)。以下注释行显示了一个包含 XML 注释 <summary><remarks> 以及感叹号注释 //!< 的示例
/// <summary>
/// A structure that is used to carry WM_PAINT message supporting window object data and is stored referenced by window.
/// </summary>
/// <remarks>
/// Should be created via ::GlobalAlloc() during WM_NCCREATE message and destroyed via ::GlobalFree() during WM_DESTROY message.
/// Can be accessed cia ::SetWindowLongPtr() and ::GetWindowLongPtr().
/// </remarks>
typedef struct tagBLANKEXTRADATA
{
    BOOL   edOwnerDraw;  //!< Flag whether this window object is ownerdrawn (or not).
    HBRUSH edBackBrush;  //!< Buffer the handle to the background brush (caves a lot of ::Create*Brush() and ::DeleteObject() calls).
    UINT   edFrameEdge;  //!< Supported values are the ::DrawEdge() edges: BDR_RAISEDINNER, BDR_SUNKENINNER, BDR_RAISEDOUTER, BDR_SUNKENOUTER, ...
    UINT   edFrameFlags; //!< Supported values are the ::DrawEdge() flags: BF_ADJUST, BF_BOTTOM, BF_BOTTOMLEFT, BF_BOTTOMRIGHT, BF_DIAGONAL, ...
    UINT   edWidth;      //!< The width of the edge. Currently not used
} BLANKEXTRADATA, *LPBLANKEXTRADATA;

这就是 XML 注释和感叹号注释协同工作的方式

  • XML 注释和感叹号注释的共存也适用于代码示例。以下注释行显示了一个包含 XML 注释 <summary> 和感叹号注释 //! \code ... //! \endcode 的示例
/// <summary>
/// The ConvenientWString object is designed to provide simple smart (auto-destructing) <c>WCHAR</c> strings.
/// It is a wrapper around the <c>std::wstring</c> class to provide an interface similar to the C# String class.
/// </summary>
//! \code
//! WString strMyName(L"My name!");
//! // Or alternatively this way:
//! WString strMyName;
//! strMyName = L"My name!";
//! ...
//! // Use it this way:
//! WORD len = wcslen(wszMyName.Get());
//! ...
//! // Destruction takes place automatically on leaving the block.
//! \endcode
class ConvenientWString : public CRttiProvider
...

这就是 XML 注释和感叹号注释协同工作的方式

关键字“对象”

  • 描述中出现 object 词时请小心。它会自动创建指向当前类的文档的引用(例如,类 OgwwBlank 中的 object 会创建与 <see cref="OgwwBlank"/> 相同的链接——这意味着只有在预期为 <see cref="OgwwBlank"/> 实例 时才应使用 object)。

抑制混淆宏

在某些情况下——我还没有确切找出何时——DoxyGen 在处理 Microsoft 特定的宏时会出现问题。最常见的问题是 __declspec(dllexport)。我的解决方案是抑制这些宏。

// \cond
__declspec(dllexport)
// \endcond

一些内幕

用于运行 DoxyGen 的 DoxyBlocks 插件,每次调用 DoxyGen 之前都会在 <项目文件夹>\doxygen 中创建一个新的 doxyfile(DoxyGen 的配置文件)。这很有用,因为它即使在项目发生更改时也能保持要编译的文件列表是最新的。但这也会阻止在 doxyfile 中进行手动扩展。DoxyBlocks 插件的源代码是开源的,它显示大多数配置值都被简单地硬编码到 doxyfile 中。这对于 PREDEFINED 以及 SEARCH_INCLUDESINCLUDE_PATHINCLUDE_FILE_PATTERNS 的配置值也是如此——当您想要处理由 C/C++ 预处理器处理的条件时,这尤其令人烦恼,例如

#if defined(__GNUC__) || defined(__MINGW32__)
...
#endif

通常关闭 ENABLE_PREPROCESSING 配置值有其他缺点。因此,为了解决这个问题,我利用了导致问题的弱点:我定义 __DOXYGEN__ 并在 GNU C/C++ 或 Microsoft C/C++ 预处理器运行时取消定义它——但不在 DoxyGen 预处理器运行时取消定义。

#define __DOXGEN__
#if defined(__GNUC__) || defined(__MINGW32__)
#undef __DOXGEN__
#define __GNU_RUNTIME__
#endif
#if defined(_MSC_VER)
#undef __DOXGEN__
#endif
#if defined(__DOXGEN__) || defined(__GNU_RUNTIME__)
...
#endif

通过这个小技巧,我不仅可以文档(定义了 __DOXYGEN__)还可以编译(定义了 __GNU_RUNTIME__)我的 ReactOS 特定函数,例如

Using the Code

这是 Code::Blocks 生成的“OpenGL”模板的样板代码。我将其用于所有测试。

#include <windows.h>
#include <gl/gl.h>

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);
void EnableOpenGL(HWND hwnd, HDC*, HGLRC*);
void DisableOpenGL(HWND, HDC, HGLRC);

int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    WNDCLASSEX wcex;
    HWND hwnd;
    HDC hDC;
    HGLRC hRC;
    MSG msg;
    BOOL bQuit = FALSE;
    float theta = 0.0f;

    /* register window class */
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_OWNDC;
    wcex.lpfnWndProc = WindowProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = "GLSample";
    wcex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);;

    if (!RegisterClassEx(&wcex))
        return 0;

    /* create main window */
    hwnd = CreateWindowEx(0,
                          "GLSample",
                          "OpenGL Sample",
                          WS_OVERLAPPEDWINDOW,
                          CW_USEDEFAULT,
                          CW_USEDEFAULT,
                          256,
                          256,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hwnd, nCmdShow);

    /* enable OpenGL for the window */
    EnableOpenGL(hwnd, &hDC, &hRC);

    /* program main loop */
    while (!bQuit)
    {
        /* check for messages */
        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            /* handle or dispatch messages */
            if (msg.message == WM_QUIT)
            {
                bQuit = TRUE;
            }
            else
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        else
        {
            /* OpenGL animation code goes here */

            glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            glClear(GL_COLOR_BUFFER_BIT);

            glPushMatrix();
            glRotatef(theta, 0.0f, 0.0f, 1.0f);

            glBegin(GL_TRIANGLES);

                glColor3f(1.0f, 0.0f, 0.0f);   glVertex2f(0.0f,   1.0f);
                glColor3f(0.0f, 1.0f, 0.0f);   glVertex2f(0.87f,  -0.5f);
                glColor3f(0.0f, 0.0f, 1.0f);   glVertex2f(-0.87f, -0.5f);

            glEnd();

            glPopMatrix();

            SwapBuffers(hDC);

            theta += 1.0f;
            Sleep (1);
        }
    }

    /* shutdown OpenGL */
    DisableOpenGL(hwnd, hDC, hRC);

    /* destroy the window explicitly */
    DestroyWindow(hwnd);

    return msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_CLOSE:
            PostQuitMessage(0);
        break;

        case WM_DESTROY:
            return 0;

        case WM_KEYDOWN:
        {
            switch (wParam)
            {
                case VK_ESCAPE:
                    PostQuitMessage(0);
                break;
            }
        }
        break;

        default:
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
    }

    return 0;
}

void EnableOpenGL(HWND hwnd, HDC* hDC, HGLRC* hRC)
{
    PIXELFORMATDESCRIPTOR pfd;

    int iFormat;

    /* get the device context (DC) */
    *hDC = GetDC(hwnd);

    /* set the pixel format for the DC */
    ZeroMemory(&pfd, sizeof(pfd));

    pfd.nSize = sizeof(pfd);
    pfd.nVersion = 1;
    pfd.dwFlags = PFD_DRAW_TO_WINDOW |
                  PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
    pfd.iPixelType = PFD_TYPE_RGBA;
    pfd.cColorBits = 24;
    pfd.cDepthBits = 16;
    pfd.iLayerType = PFD_MAIN_PLANE;

    iFormat = ChoosePixelFormat(*hDC, &pfd);

    SetPixelFormat(*hDC, iFormat, &pfd);

    /* create and enable the render context (RC) */
    *hRC = wglCreateContext(*hDC);

    wglMakeCurrent(*hDC, *hRC);
}

void DisableOpenGL (HWND hwnd, HDC hDC, HGLRC hRC)
{
    wglMakeCurrent(NULL, NULL);
    wglDeleteContext(hRC);
    ReleaseDC(hwnd, hDC);
}

这是样板代码的最终应用程序——一个旋转的多色三角形

提示 1NeonHelium Production 维护着一个优秀的 OpenGL 教程。

提示 2opengl-tutorial.org 提供了另一个优秀的 OpenGL 教程。

我用 Code::Blocks 介绍的 GLEW 测试,代码改动如下

#include <windows.h>
#include <gl/glew.h>
#include <gl/wglew.h>
//#include <gl/gl.h>

HANDLE hConsole = NULL;


...


void LogToConsoleW(LPCWSTR text)
{
    if (hConsole == NULL)
        hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

    if(hConsole != NULL && hConsole != INVALID_HANDLE_VALUE && text != NULL)
    {
        DWORD written;
        WriteConsoleW(hConsole, text, wcslen(text), &written, NULL);
    }
}

void LogToConsoleWI(LPCWSTR format, int val)
{
    if (hConsole == NULL)
        hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

    if(hConsole != NULL && hConsole != INVALID_HANDLE_VALUE && format != NULL)
    {
        WORD len = wcslen(format) + 32;
        LPWSTR text = (LPWSTR)LocalAlloc(LPTR, sizeof(WCHAR) * len);
        swprintf(text, format, val);

        DWORD written;
        WriteConsoleW(hConsole, text, wcslen(text), &written, NULL);

        LocalFree(text);
    }
}

void LogToConsoleSS(LPCSTR format, LPCSTR val)
{
    if (hConsole == NULL)
        hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

    if(hConsole != NULL && hConsole != INVALID_HANDLE_VALUE && format != NULL)
    {
        WORD len = strlen(format) + (val != NULL ? strlen(val) - 1 : 0);
        LPSTR text = (LPSTR)LocalAlloc(LPTR, sizeof(CHAR) * len);
        sprintf(text, format, val);

        DWORD written;
        WriteConsoleA(hConsole, text, strlen(text), &written, NULL);

        LocalFree(text);
    }
}

void LogToConsoleWW(LPCWSTR format, LPCWSTR val)
{
    if (hConsole == NULL)
        hConsole = GetStdHandle(STD_OUTPUT_HANDLE);

    if(hConsole != NULL && hConsole != INVALID_HANDLE_VALUE && format != NULL)
    {
        WORD len = wcslen(format) + (val != NULL ? wcslen(val) - 1 : 0);
        LPWSTR text = (LPWSTR)LocalAlloc(LPTR, sizeof(WCHAR) * len);
        swprintf(text, format, val);

        DWORD written;
        WriteConsoleW(hConsole, text, wcslen(text), &written, NULL);

        LocalFree(text);
    }
}

void EnableOpenGL(HWND hwnd, HDC* hDC, HGLRC* hRC)

{


...


    LogToConsoleW(L"OpenGL initialized.\n");
    LogToConsoleSS("OpenGL vendor: %s\n", glGetString(GL_VENDOR));
    LogToConsoleSS("OpenGL renderer: %s\n", glGetString(GL_RENDERER));
    LogToConsoleSS("OpenGL version: %s\n", glGetString(GL_VERSION));
    LogToConsoleSS("OpenGL shader: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));

    GLenum err = glewInit();
    if (err != GLEW_OK)
    {
        LogToConsoleW(L"ERROR: GLEW not initialized.\n");
    }
    else
    {
        LogToConsoleW(L"GLEW successfully initialized.\n");
        LogToConsoleSS("GLEW version: %s\n", glewGetString(GLEW_VERSION));
        if (glewIsSupported("GL_ARB_texture_border_clamp"))
            LogToConsoleSS("OpenGL supports 'GL_ARB_texture_border_clamp'.\n",
                           glewGetString(GLEW_VERSION));
    }
}

更改的结果是这个控制台日志

提示 3:GLEW 附带了两个有趣的测试工具:“glewinfo.exe”和“visualinfo.exe”。试试它们吧!在我的 ReactOS 上,“visualinfo.exe”报告 GL_EXT_framebuffer_objectGL_ARB_vertex_buffer_objectGL_ARB_pixel_buffer_object 存在。

关注点

现在我们可以为 ReactOS 创建 OpenGL 应用程序了。玩得开心!

对我来说,下一步的逻辑步骤是开发一个严肃的 3D 应用程序——我已在文章 更多关于 ReactOS(或 Windows)上的 C/C++ 和 C# 的 OpenGL 中讨论过。

与此同时,我注意到没有 IconEditor 在 ReactOS 下运行得令人满意。因此,我被这个分散了注意力,并开始撰写文章 一个运行在 ReactOS(以及因此在 Windows XP 和更高版本)上的基本图标编辑器

历史

  • 2019 年 9 月 23 日:初始版本
  • 2020 年 5 月 13 日:更新 1(新增章节“添加文档生成器”)
  • 2020 年 5 月 15 日:更新 2(修订并扩展了“添加文档生成器”章节)
  • 2020 年 10 月 14 日:更新 3(VMWare Player 和 Oracle VirtualBox 之间的结果明确分离;Mesa 3D 图形库安装的额外测试;更新到 VMWare Player 15.5.6、Oracle VirtualBox 6.1.10 和 ReactOS 0.4.13 版本的额外测试)
  • 2021 年 5 月 11 日:更新 4(添加了 DoxyGen 表格语法和描述,如何抑制令人困惑的宏)
© . All rights reserved.