Open imuncle opened 4 years ago
对于Microsoft Windows程序来说,图标、光标、菜单等都是Windows“资源”的类型。资源是数据,它们通常被保存在一个程序的.exe文件中,但它们不在可执行程序的数据区,换句话说,资源不能再程序代码中用变量直接寻址,相反,Windows提供了函数来显示或隐式地把程序资源加载到内存中以供使用。
.exe
使用资源的一个好处是可以把程序的很多组建都绑定到程序的.exe文件中,如果没有资源的概念,一个诸如图标图像的二进制文件将不得不作为一个单独的文件来保存,.exe文件需要将该文件读入内存再能使用。或者,图标需要在程序中定义为一个字节的数组(这会使得我们难以将实际图标图像可视化),而作为一种资源,图标在开发者的机器上被存为一个单独的可编辑文件,然后在程序编译的过程中被绑定到.exe文件内。
不管是什么教程,基本都是通过IDE来添加资源,并且教程中都强烈建议不要修改IDE自动生成的resource.h和resource.rc文件,编译也是通过IDE自动将资源和代码文件链接起来,目前的教程里基本都是基于Visual Studio的,更老一点的教程可能基于Visual C++ Developer Studio,而我追求的是去IDE化,想使用命令行直接编译。
resource.h
resource.rc
我的工程文件有:
代码里实现的是一个顶部菜单栏,具体的菜单选项在menudemo.rc里面。我按照之前的编译方法编译:
menudemo.rc
gcc -mwindows menudemo.c -o menudemo.exe
然而生成出来的可执行文件里并没有顶部菜单,说明编译时并没有将资源文件包含进来,这让我一度很困惑。
后来在网上查找,发现需要先使用windres工具将资源文件编译为.o文件,最后再用gcc将.o文件和代码文件链接起来,所以步骤如下:
windres
.o
gcc
编译资源文件
windres menudemo.rc resource.o
链接生成可执行文件
gcc -mwindows resource.o menudemo.c -o menudemo.exe
最终生成的程序就有菜单项了:
现在编译代码需要敲的命令越老越长了,每次重复敲有点麻烦,是时候基础CMake自动化脚本了。
其实我最开始有想过使用CMake的,但是又想着这么简单的工程犯不着用CMake吧,于是就打算自己写编译脚本。另外微软提供了一个CMake的Windows版本NMake,算是微软专门为Windows程序设计的,也没用过,看资料估计跟CMake差不多。
其实CMake的基本思想就是从可执行文件反推查找,缺什么就编译什么,最后链接起来。每次编译前会对比目标文件与被编译文件的修改时间,如果被编译文件的修改时间更靠后,说明它被程序员修改过,需要重新编译。
现在我先实现一个最简单的自动化脚本,不管三七二十一全部从头编译:
windres menudemo.rc resource.o gcc -mwindows menudemo.c menudemo.o -o menudemo.exe del resource.o
保存为make.bat,之后要编译就直接在命令行敲make命令就行了:
make.bat
make
可以看到,输出make指令后,命令行就依次执行编译语句了,最后我把生成的中间资源文件删除了(反正每次都要从头编译)。
后续打算使用自动化脚本模拟CMake,等以后工程文件多起来再说。
最后附上我的测试小程序:
/* menudemo.c */ #include <windows.h> #include "resource.h" #define ID_TIMER 1 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("MenuDemo") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("Menu Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static int idColor [5] = { WHITE_BRUSH, LTGRAY_BRUSH, GRAY_BRUSH, DKGRAY_BRUSH, BLACK_BRUSH } ; static int iSelection = IDM_BKGND_WHITE ; HMENU hMenu ; switch (message) { case WM_COMMAND: hMenu = GetMenu (hwnd) ; switch (LOWORD (wParam)) { case IDM_FILE_NEW: case IDM_FILE_OPEN: case IDM_FILE_SAVE: case IDM_FILE_SAVE_AS: MessageBeep (0) ; return 0 ; case IDM_APP_EXIT: SendMessage (hwnd, WM_CLOSE, 0, 0) ; return 0 ; case IDM_EDIT_UNDO: case IDM_EDIT_CUT: case IDM_EDIT_COPY: case IDM_EDIT_PASTE: case IDM_EDIT_CLEAR: MessageBeep (0) ; return 0 ; case IDM_BKGND_WHITE: // Note: Logic below case IDM_BKGND_LTGRAY: // assumes that IDM_WHITE case IDM_BKGND_GRAY: // through IDM_BLACK are case IDM_BKGND_DKGRAY: // consecutive numbers in case IDM_BKGND_BLACK: // the order shown here. CheckMenuItem (hMenu, iSelection, MF_UNCHECKED) ; iSelection = LOWORD (wParam) ; CheckMenuItem (hMenu, iSelection, MF_CHECKED) ; SetClassLong (hwnd, GCL_HBRBACKGROUND, (LONG) GetStockObject (idColor [LOWORD (wParam) - IDM_BKGND_WHITE])) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case IDM_TIMER_START: if (SetTimer (hwnd, ID_TIMER, 1000, NULL)) { EnableMenuItem (hMenu, IDM_TIMER_START, MF_GRAYED) ; EnableMenuItem (hMenu, IDM_TIMER_STOP, MF_ENABLED) ; } return 0 ; case IDM_TIMER_STOP: KillTimer (hwnd, ID_TIMER) ; EnableMenuItem (hMenu, IDM_TIMER_START, MF_ENABLED) ; EnableMenuItem (hMenu, IDM_TIMER_STOP, MF_GRAYED) ; return 0 ; case IDM_APP_HELP: MessageBox (hwnd, TEXT ("Help not yet implemented!"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; case IDM_APP_ABOUT: MessageBox (hwnd, TEXT ("Menu Demonstration Program\n"), szAppName, MB_ICONINFORMATION | MB_OK) ; return 0 ; } break ; case WM_TIMER: MessageBeep (0) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
// menudemo.rc #include "resource.h" MENUDEMO MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New", ID_MENUITEM40020 MENUITEM "&Open", IDM_FILE_OPEN MENUITEM "&Save", IDM_FILE_SAVE MENUITEM "Save &As...", IDM_FILE_SAVE_AS MENUITEM SEPARATOR MENUITEM "E&xit", IDM_APP_EXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo", IDM_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "C&ut", IDM_EDIT_CUT MENUITEM "&Copy", IDM_EDIT_COPY MENUITEM "&Paste", IDM_EDIT_PASTE MENUITEM "De&lete", IDM_EDIT_CLEAR END POPUP "&Background" BEGIN MENUITEM "&White", IDM_BKGND_WHITE, CHECKED MENUITEM "&Light Gray", IDM_BKGND_LTGRAY MENUITEM "&Gray", IDM_BKGND_GRAY MENUITEM "&Dark Gray", IDM_BKGND_DKGRAY MENUITEM "&Black", IDM_BKGND_BLACK END POPUP "&Timer" BEGIN MENUITEM "&Start", IDM_TIMER_START MENUITEM "S&top", IDM_TIMER_STOP, GRAYED END POPUP "&Help" BEGIN MENUITEM "&Help...", IDM_APP_HELP MENUITEM "&About MenuDemo...", IDM_APP_ABOUT END END
// resource.h #define IDM_FILE_NEW 40001 #define IDM_FILE_OPEN 40002 #define IDM_FILE_SAVE 40003 #define IDM_FILE_SAVE_AS 40004 #define IDM_APP_EXIT 40005 #define IDM_EDIT_UNDO 40006 #define IDM_EDIT_CUT 40007 #define IDM_EDIT_COPY 40008 #define IDM_EDIT_PASTE 40009 #define IDM_EDIT_CLEAR 40010 #define IDM_BKGND_WHITE 40011 #define IDM_BKGND_LTGRAY 40012 #define IDM_BKGND_GRAY 40013 #define IDM_BKGND_DKGRAY 40014 #define IDM_BKGND_BLACK 40015 #define IDM_TIMER_START 40016 #define IDM_TIMER_STOP 40017 #define IDM_APP_HELP 40018 #define IDM_APP_ABOUT 40019 #define ID_MENUITEM40020 40020
资源简介
对于Microsoft Windows程序来说,图标、光标、菜单等都是Windows“资源”的类型。资源是数据,它们通常被保存在一个程序的
.exe
文件中,但它们不在可执行程序的数据区,换句话说,资源不能再程序代码中用变量直接寻址,相反,Windows提供了函数来显示或隐式地把程序资源加载到内存中以供使用。使用资源的一个好处是可以把程序的很多组建都绑定到程序的
.exe
文件中,如果没有资源的概念,一个诸如图标图像的二进制文件将不得不作为一个单独的文件来保存,.exe
文件需要将该文件读入内存再能使用。或者,图标需要在程序中定义为一个字节的数组(这会使得我们难以将实际图标图像可视化),而作为一种资源,图标在开发者的机器上被存为一个单独的可编辑文件,然后在程序编译的过程中被绑定到.exe
文件内。遇到的问题
不管是什么教程,基本都是通过IDE来添加资源,并且教程中都强烈建议不要修改IDE自动生成的
resource.h
和resource.rc
文件,编译也是通过IDE自动将资源和代码文件链接起来,目前的教程里基本都是基于Visual Studio的,更老一点的教程可能基于Visual C++ Developer Studio,而我追求的是去IDE化,想使用命令行直接编译。我的工程文件有:
代码里实现的是一个顶部菜单栏,具体的菜单选项在
menudemo.rc
里面。我按照之前的编译方法编译:然而生成出来的可执行文件里并没有顶部菜单,说明编译时并没有将资源文件包含进来,这让我一度很困惑。
编译资源文件
后来在网上查找,发现需要先使用
windres
工具将资源文件编译为.o
文件,最后再用gcc
将.o
文件和代码文件链接起来,所以步骤如下:编译资源文件
链接生成可执行文件
最终生成的程序就有菜单项了:![image](https://user-images.githubusercontent.com/35989223/68537130-41ae2480-039a-11ea-9031-306826680bd9.png)
自动化编译
现在编译代码需要敲的命令越老越长了,每次重复敲有点麻烦,是时候基础
CMake自动化脚本了。其实我最开始有想过使用CMake的,但是又想着这么简单的工程犯不着用CMake吧,于是就打算自己写编译脚本。另外微软提供了一个CMake的Windows版本NMake,算是微软专门为Windows程序设计的,也没用过,看资料估计跟CMake差不多。
其实CMake的基本思想就是从可执行文件反推查找,缺什么就编译什么,最后链接起来。每次编译前会对比目标文件与被编译文件的修改时间,如果被编译文件的修改时间更靠后,说明它被程序员修改过,需要重新编译。
现在我先实现一个最简单的自动化脚本,不管三七二十一全部从头编译:
保存为![image](https://user-images.githubusercontent.com/35989223/68537379-7f607c80-039d-11ea-8a5e-ac75948cbf0f.png)
make.bat
,之后要编译就直接在命令行敲make
命令就行了:可以看到,输出
make
指令后,命令行就依次执行编译语句了,最后我把生成的中间资源文件删除了(反正每次都要从头编译)。后续打算使用自动化脚本模拟CMake,等以后工程文件多起来再说。
程序源码
最后附上我的测试小程序:
参考