本文讲解如何基于premake5创建一个典型的C++解决方案,该解决方案包含三个项目:
- 一个依赖GLFW动态库的动态库项目ExampleDll
- 一个依赖ExampleDll动态库的窗口程序APP
- 一个基于Catch的单元测试程序UnitTest
- premake5.exe程序
- 一个文本编辑器
- 一个支持C++11的编译器,因为源代码使用了C++11特性
- 一个支持OpenGL 1.1的显卡
首先需要确定整个解决方案中源代码、三方库、二进制等文件的组织结构,本文创建的文件结构如下:
premakeExample/
|--premake5.lua (premake脚本文件) |--prj/ (生成的解决方案与各项目配置文件路径)
|--build/
| |--target/ (目标生成路径)
| |--obj/ (中间文件生成路径)
|--bin/ (最终发布的二进制文件路径)
|--3rd/ (第三方库文件路径)
| |--Catch (Catch单元测试框架头文件路径)
| |--glfw (glfw三方库相关文件路径)
- 本文以64位系统为例,需下载64位二进制库,也可以下载32位,但要记得在后面lua脚本中改变architecture配置。
- 将下载解压后的include和
lib-vc*
文件夹放在premakeExample/3rd/glfw文件夹下,*
指代不同的vs版本,需与后续生成配置文件的vs版本一致。- 将lib-vc*路径下的glfw3.dll拷贝纸premakeExample/bin路径下
- catch是一个只有头文件的依赖库,将Catch.hpp(下载)放在premakeExample/3rd/Catch/include路径下即可。
ExampleDll库只是对glfw中的GLFWwindow进行了简单封装,隐藏了window的内部实现细节。Window类通过_declspec(dllexport)
导出。
- ExampleDll.hpp代码如下:
#ifndef EXAMPLE_DLL_HPP
#define EXAMPLE_DLL_HPP 1
#include <string>
#include <memory>
struct GLFWwindow;
namespace ExDLL
{
class _declspec(dllexport) Window
{
public:
Window(int width, int height, const std::string& title);
~Window();
bool shouldClose() const noexcept;
void pollEvents() const noexcept;
void swapBuffers() const noexcept;
std::pair<int, int> getWindowSize() const noexcept;
private:
GLFWwindow* wnd;
};
}
#endif
- ExampleDll.cpp代码如下
#include "ExampleDll.hpp"
#include <GLFW/glfw3.h>
namespace ExDLL
{
Window::Window(int width, int height, const std::string& title)
{
glfwInit();
wnd = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
glfwMakeContextCurrent(wnd);
}
Window::~Window()
{
glfwDestroyWindow(wnd);
glfwTerminate();
}
bool Window::shouldClose() const noexcept
{
return glfwWindowShouldClose(wnd) != 0;
}
void Window::pollEvents() const noexcept
{
glfwPollEvents();
}
void Window::swapBuffers() const noexcept
{
glfwSwapBuffers(wnd);
}
std::pair<int, int> Window::getWindowSize() const noexcept
{
std::pair<int, int> sz{};
glfwGetWindowSize(wnd, &sz.first, &sz.second);
return sz;
}
}
应用程序依赖Exampledll,创建一个窗口应用程序,并运用固定管线绘制一个红色的三角形。当然本教程不讲解OpenGL,如果想要学习OpenGL的使用,应该学习更先进的可编程管线。
- main.cpp代码如下:
#include <ExampleDll.hpp>
#if defined _WIN32
//Windows平台使用OpenGL需要包含Windows.h
#include <Windows.h>
//消除窗口程序的控制台界面
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#endif
#include <gl/GL.h>
//导入ExampleDll中的Window类
class _declspec(dllimport) ExDLL::Window;
int main()
{
ExDLL::Window window{ 800, 600, "Hello World!" };
while (!window.shouldClose())
{
window.pollEvents();
//为简单起见,使用了古老的固定管线
glColor3f(1.0, 0.0, 0.0);
glBegin(GL_TRIANGLES);
glVertex2f(-0.5f, -0.5f);
glVertex2f(0.5f, -0.5f);
glVertex2f(0, 0.5f);
glEnd();
window.swapBuffers();
}
return 0;
}
单元测试程序检查创建的窗口大小是否与参数要求的大小一致。
- Test.cpp代码如下:
#define CATCH_CONFIG_MAIN
#include <Catch.hpp>
#include <ExampleDll.hpp>
TEST_CASE("Window tests", "[ExampleDll]")
{
using namespace ExDLL;
Window w{ 600, 400, "Test Window" };
auto size = w.getWindowSize();
REQUIRE(size.first == 600);
REQUIRE(size.second == 400);
}
premake5.lua是生成工程配置文件的核心。
- premake5.lua代码如下:
-- 最终解决方案的名称
workspace "premakeExample"
-- 解决方案与各项目配置文件生成路径
location "prj"
-- 指定语言
language "C++"
-- 指定架构 x64 或 x86 或 x86_64
architecture "x64"
-- 配置类型
configurations {"Debug","Release"}
-- 针对Debug配置类型的参数设置
filter {"configurations:Debug"}
symbols "On"
-- 针对Release配置类型的参数设置
filter {"configurations:Release"}
optimize "On"
-- 重置过滤器的其他设定
filter {}
-- 目标文件生成路径 如%{prj.name}为内置的宏,指项目的名称,如后面的ExampleDll,App等
targetdir ("build/target/%{prj.name}/%{cfg.longname}")
-- 中间文件生成路径
objdir ("build/obj/%{prj.name}/%{cfg.longname}")
-- 编译后命令行,将目标文件拷贝至bin文件夹,注意../bin/是prj文件夹的相对路径
postbuildcommands{
("{COPY} %{cfg.buildtarget.relpath} \"../bin/\"")
}
-- 定义函数,包含glfw三方库头文件,可被其他工程调用
function includeGLFW()
includedirs "3rd/glfw/include"
end
-- 定义函数,链接glfw三方库
function linkGLFW()
-- 指定lib的文件路径
libdirs "3rd/glfw/lib-vc2019"
-- 指定lib文件名,即glfw3dll.lib,此处使用的是动态库
links "glfw3dll"
end
-- ExampleDll项目
project "ExampleDll"
-- 类型为动态库项目
kind "SharedLib"
-- 代码文件,即ExampleDll文件夹下的所有文件
files "src/ExampleDll/**"
-- 包含glfw头文件
includeGLFW()
-- 链接glfw三方库
linkGLFW()
-- 定义函数,链接ExampleDll动态库
function useExampleDLL()
includedirs "src/ExampleDll"
links "ExampleDll"
end
-- App应用程序
project "App"
-- 类型为控制台程序
kind "ConsoleApp"
-- 代码文件
files "src/App/**"
-- 链接ExampleDll动态库
useExampleDLL()
-- windows平台使用OpenGL需链接OpenGL32
filter "system:windows"
links {"OpenGL32"}
-- 定义函数,包含Catch
function includeCatch()
includedirs "3rd/Catch/Include"
-- 预定义宏,C++版本为C++11及以上
defines "CATCH_CPP11_OR_GREATER"
end
-- UnitTests单元测试项目
project "UnitTests"
-- 类型为控制台程序
kind "ConsoleApp"
-- 代码文件
files "src/UnitTests/**"
-- 包含Catch
includeCatch()
-- 链接ExamleDll
useExampleDLL()
premake5.exe运行时时会寻找调用命令路径下的premake5.lua文件,如果想生成工程配置文件,需在premakeExample路径下调用premake5.exe <action>
,其中action
可指定为vs2019
或gmake
,本文以vs2019为例。