2022/09/06(火) [n年前の日記]
#1 [prog][windows] GDIでダブルバッファ描画
Windows10 x64 21H2上で、C/C++ (MinGW) と Windows API を使ってウインドウ描画ができるか実験中。
_昨日、GDI+を使ってpng画像を描画できるのか、 実験していたわけだけど。GDI+ は描画が遅いという話を見かけたので、GDI+ ではなく GDI だけを使って描画ができるならそのほうがいいのかなと…。そんなわけで、GDI でダブルバッファ描画をする方法を試してみた。まあ、png画像の使用を諦めて、bmp画像(bitmap画像)だけを利用することになってしまうけど…。
環境は、Windows10 x64 21H2 + MinGW (gcc 9.2.0)。MSYS2 + MinGW-w64 ではなくて、MinGW (+ MSYS) で実験してる。
以下を参考にさせてもらいました。
_ビットマップのマルチバッファリング【Windowsプログラミング研究所】
今回書いたサンプルの実行結果は以下。ちょっと解像度が荒いけど、雰囲気は伝わるかと…。ダブルバッファで描画しているので、ちらつきは感じられない。
_昨日、GDI+を使ってpng画像を描画できるのか、 実験していたわけだけど。GDI+ は描画が遅いという話を見かけたので、GDI+ ではなく GDI だけを使って描画ができるならそのほうがいいのかなと…。そんなわけで、GDI でダブルバッファ描画をする方法を試してみた。まあ、png画像の使用を諦めて、bmp画像(bitmap画像)だけを利用することになってしまうけど…。
環境は、Windows10 x64 21H2 + MinGW (gcc 9.2.0)。MSYS2 + MinGW-w64 ではなくて、MinGW (+ MSYS) で実験してる。
以下を参考にさせてもらいました。
_ビットマップのマルチバッファリング【Windowsプログラミング研究所】
今回書いたサンプルの実行結果は以下。ちょっと解像度が荒いけど、雰囲気は伝わるかと…。ダブルバッファで描画しているので、ちらつきは感じられない。
◎ リソースファイル。 :
リソースファイルは以下。このファイルは、windres を使って .o に変換する。
_res.rc
_res.rc
BALL_IMG BITMAP "ball.bmp" BALL_MSK BITMAP "ball_mask.bmp" BG_IMG BITMAP "bg2.bmp"
- 3つのbmp画像ファイルを含めるように指定している。
- 先頭に書かれている「BALL_IMG」「BALL_MSK」「BG_IMG」が、C言語のソース内で指定する識別名。
◎ 使用画像。 :
使用画像は以下。
_images.zip
一応、png画像も載せておきます。



_images.zip
- ball.bmp : 赤いボール画像。透明にしたい部分は黒(#000000)にしておく。
- ball_mask.bmp : ボール画像のマスク画像。不透明部分は黒(#000000)、透明部分は白(#ffffff)。アルファチャンネルを反転した感じの画像。
- bg2.bmp : 背景用画像。タイル状に描画することを前提にしている。
一応、png画像も載せておきます。



◎ C言語のソースファイル。 :
C言語のソースファイルは以下。今回は GDI+ を使ってないので、C++ ではなく C言語で書くことができる。
_01_dblbuf.c
_01_dblbuf.c
/* GDI Double buffer sample */
#include <windows.h>
#define SCRW 1280
#define SCRH 720
#define FPS 60
#define TM_COUNT1 101
#define LPCLASSNAME TEXT("DoubleBufferSample")
#define WINDOW_TITLE TEXT("Double Buffer Sample")
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
static HBITMAP hBitmap;
static HBITMAP hBitmap_mask;
static HBITMAP hBitmap_bg;
static HBITMAP offScrnBitmap;
static int wdw_w, wdw_h;
static int imgw, imgh;
static int bgw, bgh;
static int bgx, bgy;
static float x, y;
static float dx, dy;
switch (msg)
{
case WM_CREATE:
// get window size
{
RECT rc;
GetClientRect(hwnd, &rc);
wdw_w = rc.right - rc.left;
wdw_h = rc.bottom - rc.top;
}
// create off screen bitmap
{
HDC hdc;
hdc = GetDC(hwnd);
offScrnBitmap = CreateCompatibleBitmap(hdc, wdw_w, wdw_h);
ReleaseDC(hwnd, hdc); // GetDC() -> ReleaseDC()
}
// load bitmaps from resource
hBitmap = LoadBitmap(((LPCREATESTRUCT)lp)->hInstance, TEXT("BALL_IMG"));
hBitmap_mask = LoadBitmap(((LPCREATESTRUCT)lp)->hInstance, TEXT("BALL_MSK"));
hBitmap_bg = LoadBitmap(((LPCREATESTRUCT)lp)->hInstance, TEXT("BG_IMG"));
// get images size
{
BITMAP bp;
// get ball image size
GetObject(hBitmap, sizeof(BITMAP), &bp);
imgw = bp.bmWidth;
imgh = bp.bmHeight;
// get bg image size
GetObject(hBitmap_bg, sizeof(BITMAP), &bp);
bgw = bp.bmWidth;
bgh = bp.bmHeight;
}
// init work
x = wdw_w / 2;
y = wdw_h / 2;
dx = (float)wdw_w / (float)FPS;
dy = dx * 0.2;
bgx = 0;
bgy = 0;
SetTimer(hwnd, TM_COUNT1, (int)(1000 / FPS), NULL);
return 0;
case WM_TIMER:
// main loop
x += dx;
y += dy;
if (x <= 0 || (x + imgw) >= wdw_w) dx *= -1;
if (y <= 0 || (y + imgh) >= wdw_h) dy *= -1;
bgx -= 1;
bgy -= 1;
if (bgx <= -(bgw)) bgx += bgw;
if (bgy <= -(bgh)) bgy += bgh;
if (0)
{
// clear background
InvalidateRect(hwnd, NULL, TRUE);
}
else
{
// not clear background
InvalidateRect(hwnd, NULL, FALSE);
}
break;
case WM_PAINT:
// draw window
{
HDC hdc, hBmpDC, hMemDC;
PAINTSTRUCT ps;
hdc = BeginPaint(hwnd, &ps);
// hdc = GetDC(hwnd);
// set off-screen bitmap
hMemDC = CreateCompatibleDC(hdc);
SelectObject(hMemDC, offScrnBitmap);
// draw bg to off-screen
hBmpDC = CreateCompatibleDC(hdc);
SelectObject(hBmpDC, hBitmap_bg);
{
int by = bgy;
while (by <= wdw_h)
{
int bx = bgx;
while (bx <= wdw_w)
{
BitBlt(hMemDC, bx, by, bgw, bgh, hBmpDC, 0, 0, SRCCOPY);
bx += bgw;
}
by += bgh;
}
}
DeleteDC(hBmpDC);
// draw mask to off-screen
hBmpDC = CreateCompatibleDC(hdc);
SelectObject(hBmpDC, hBitmap_mask);
BitBlt(hMemDC, (int)x, (int)y, imgw, imgh, hBmpDC, 0, 0, SRCAND);
DeleteDC(hBmpDC);
// draw source image to off-screen
hBmpDC = CreateCompatibleDC(hdc);
SelectObject(hBmpDC, hBitmap);
BitBlt(hMemDC, (int)x, (int)y, imgw, imgh, hBmpDC, 0, 0, SRCPAINT);
DeleteDC(hBmpDC);
// drawing to off-screen is finished
// draw off-screen bitmap to window
BitBlt(hdc, 0, 0, wdw_w, wdw_h, hMemDC, 0, 0, SRCCOPY);
DeleteDC(hMemDC); // Create*() -> DeleteDC()
EndPaint(hwnd, &ps);
}
return 0;
case WM_DESTROY:
KillTimer(hwnd, TM_COUNT1); // kill timer
DeleteObject(offScrnBitmap);
DeleteObject(hBitmap);
DeleteObject(hBitmap_mask);
DeleteObject(hBitmap_bg);
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wp, lp);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR lpCmdLine, int nCmdShow)
{
HWND hwnd;
MSG msg;
WNDCLASS winc;
winc.style = CS_HREDRAW | CS_VREDRAW;
winc.lpfnWndProc = WndProc;
winc.cbClsExtra = winc.cbWndExtra = 0;
winc.hInstance = hInstance;
winc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
winc.hCursor = LoadCursor(NULL, IDC_ARROW);
winc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
winc.lpszMenuName = NULL;
winc.lpszClassName = LPCLASSNAME;
if (!RegisterClass(&winc)) return -1;
hwnd = CreateWindow(
LPCLASSNAME, WINDOW_TITLE,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, CW_USEDEFAULT,
SCRW, SCRH,
NULL, NULL,
hInstance, NULL
);
if (hwnd == NULL) return -1;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
- SetTimer() で指定したミリ秒が経過すると、その都度、WM_TIMER というメッセージが Windows から送られてくる。
- WM_TIMER が来た時に、ボールやBG画像の表示位置を更新することで、アニメーションをさせている。
- InvalidateRect() を呼べば、ウインドウの再描画を要求できる。(WM_PAINTメッセージが送られてくる。)
- InvalidateRect(hwnd, NULL, TRUE); なら、背景を消去しつつ、ウインドウを再描画(するように要求)。
- InvalidateRect(hwnd, NULL, FALSE); なら、背景を消去せずに、ウインドウを再描画(するように要求)。ダブルバッファ描画をする時はこちらを指定する。
- WM_PAINT が来た時に、描画処理を行う。
- WM_DESTROY が来たら、終了処理を行う。タイマーを殺して、bitmap を全部削除しておく。
◎ Makefile。 :
Makefile は以下。.c や .rc から .o を生成後、.o群をリンクして .exe を生成する。
_Makefile
_Makefile
01_dblbuf.exe: 01_dblbuf.o res.o
gcc 01_dblbuf.o res.o -o 01_dblbuf.exe -mwindows
01_dblbuf.o: 01_dblbuf.c
gcc -c 01_dblbuf.c
res.o: res.rc ball.bmp ball_mask.bmp bg2.bmp
windres res.rc res.o
.PHONY: clean
clean:
rm -f *.exe
rm -f *.o
◎ コンパイル/ビルド。 :
全部で以下のファイルになる。
ちなみに、make clean と打てば、*.o や *.exe を削除してくれる。
- 01_dblbuf.c
- ball.bmp
- ball_mask.bmp
- bg2.bmp
- Makefile
- res.rc
ちなみに、make clean と打てば、*.o や *.exe を削除してくれる。
◎ 余談。 :
DeleteDC() と ReleaseDC() の使い分けが分からなくて悩んだけれど、以下のページを眺めたら分かってきた。
_API 関数解説
_API 関数解説
- GetDC() を使って取得したデバイスコンテキストは、ReleaseDC() を使って解放する。
- Create*() を使って取得したデバイスコンテキストは、DeleteDC() を使って解放する。
◎ 余談その2。 :
今回のソースは、中括弧(?)を利用して、変数のスコープを極力狭めつつ書いてみた。
_変数のスコープ(C言語) - 超初心者向けプログラミング入門
_C/C++ 中括弧でローカルスコープを作る - 【はてな】ガットポンポコ
_変数のスコープ(C言語) - 超初心者向けプログラミング入門
_C/C++ 中括弧でローカルスコープを作る - 【はてな】ガットポンポコ
[ ツッコむ ]
#2 [prog][windows] ビットマップ画像を描画するスクリーンセーバを書いてみた
せっかく C/C++ と GDI を使って、bitmap画像を描画、ダブルバッファ描画、アニメーションができたので…。以前、MinGW を使ってビルドが通ることを確認できた、Windows用スクリーンセーバのサンプルソースにも処理を反映させてみたい。
_スクリーンセーバのビルドの実験中
_Windows10にmingwをインストールし直した
環境は、Windows10 x64 21H2 + MinGW (gcc 9.2.0)。
ちなみに、MSYS2 + MinGW-w64 (gcc 12.2.0) では、Windows用スクリーンセーバはビルドできない。
_msys2 + mingw-w64でスクリーンセーバをビルドしようとしてハマった
さておき。今回書いたサンプルの実行結果は以下。解像度が荒くてアレだけど、雰囲気は伝わるかと…。
_スクリーンセーバのビルドの実験中
_Windows10にmingwをインストールし直した
環境は、Windows10 x64 21H2 + MinGW (gcc 9.2.0)。
ちなみに、MSYS2 + MinGW-w64 (gcc 12.2.0) では、Windows用スクリーンセーバはビルドできない。
- C/C++ を使ってWindows用スクリーンセーバを作る場合、scrnsave.h と関連ライブラリファイルが必要になる。
- Microsoft Visual C++ なら scrnsave.lib、または scrnsavw.lib が必要。
- MinGW系なら libscrnsave.a、または libscrnsavw.a が必要。
- MinGW-w64 に付属している libscrnsave.a や libscrnsavw.a は中身が空っぽなので、リンクエラーになる。
- MinGW に付属している libscrnsave.a や libscrnsavw.a には、ちゃんと中身が入っているので、スクリーンセーバが作れる。
_msys2 + mingw-w64でスクリーンセーバをビルドしようとしてハマった
さておき。今回書いたサンプルの実行結果は以下。解像度が荒くてアレだけど、雰囲気は伝わるかと…。
◎ リソースファイル。 :
リソースファイルは以下。以前のサンプルの最初のほうに、bitmap画像群を追記した。このファイルは、windres を使って、.rc から .o に変換することになる。
_resource.rc
ヘッダーファイルは以下。でも、今回コレは使ってない気がする…。
_resource.h
_resource.rc
/* resource.rc */
#include <windows.h>
#include <scrnsave.h>
#define IDC_STATIC_ICON 2300
#define IDC_STATIC_TEXT 2301
/* bitmap */
IDI_BALL BITMAP "ball.bmp"
IDI_BALL_MSK BITMAP "ball_mask.bmp"
IDI_BG BITMAP "bg2.bmp"
IDI_PREVIEW BITMAP "scrsav_preview.bmp"
/* Config dialog */
DLG_SCRNSAVECONFIGURE DIALOG DISCARDABLE 0, 0, 220, 50
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Setting Sample Screensaver"
FONT 9, "Segoe UI"
BEGIN
DEFPUSHBUTTON "OK", IDOK, 85, 30, 50, 14
ICON IDI_WARNING, IDC_STATIC_ICON, 6, 5, 20, 20
LTEXT "This is the template for the configuration dialog.",
IDC_STATIC_TEXT, 35, 12, 230, 12
END
※ このWeb日記システムの関係で、FONT指定行がおかしな状態で表示されてます。先頭の空白は削除してもらえればと…。ヘッダーファイルは以下。でも、今回コレは使ってない気がする…。
_resource.h
#ifndef RESOURCE_H_ #define RESOURCE_H_ #define IDC_STATIC_ICON 2300 #define IDC_STATIC_TEXT 2301 #define IDI_BALL 2401 #define IDI_BALL_MSK 2402 #define IDI_BG 2403 #endif
◎ 使用画像。 :
使用画像は以下。4つのbmp画像を使っている。
_images.zip
一応、png画像も載せておきます。




_images.zip
- ball.bmp : 赤いボール画像。透明部分は黒(#000000)にしておく。
- ball_mask.bmp : ボールのマスク画像。不透明部分は黒(#000000)、透明部分は白(#ffffff)。
- bg2.bmp : bg画像(背景画像)。タイル状に描画することを前提にしている。
- scrsav_preview.bmp : プレビュー画像。スクリーンセーバの設定画面で表示されてる小さい窓の中に表示するための画像。サイズは 152 x 112 ドット。
一応、png画像も載せておきます。




◎ C言語のソースファイル。 :
_mgscrsv2.c
/* mingwscr.c */
#include <windows.h>
#include <tchar.h>
#include <scrnsave.h>
#include <time.h>
#include <stdlib.h>
#define FPS 60
#define ID_TIMER 101
/* screensaver main processing */
LRESULT WINAPI ScreenSaverProc(HWND hWnd, UINT msg,
WPARAM wParam, LPARAM lParam)
{
static HBITMAP hBitmap;
static HBITMAP hBitmap_mask;
static HBITMAP hBitmap_bg;
static HBITMAP hBitmap_preview;
static HBITMAP offScrnBitmap;
static int wdw_w, wdw_h;
static int imgw, imgh;
static int bgw, bgh;
static int pvw, pvh;
static float x, y, dx, dy, bgx, bgy;
int bx, by;
switch (msg)
{
case WM_CREATE:
/* initialize */
// get window size
{
RECT rc;
GetClientRect(hWnd, &rc);
wdw_w = rc.right - rc.left;
wdw_h = rc.bottom - rc.top;
}
// create off-screen bitmap
{
HDC hdc;
hdc = GetDC(hWnd);
offScrnBitmap = CreateCompatibleBitmap(hdc, wdw_w, wdw_h);
ReleaseDC(hWnd, hdc);
}
// load bitmaps
hBitmap = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance, TEXT("IDI_BALL"));
hBitmap_mask = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance, TEXT("IDI_BALL_MSK"));
hBitmap_bg = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance, TEXT("IDI_BG"));
hBitmap_preview = LoadBitmap(((LPCREATESTRUCT)lParam)->hInstance, TEXT("IDI_PREVIEW"));
// get images size
{
BITMAP bp;
// get ball image size
GetObject(hBitmap, sizeof(BITMAP), &bp);
imgw = bp.bmWidth;
imgh = bp.bmHeight;
// get bg image size
GetObject(hBitmap_bg, sizeof(BITMAP), &bp);
bgw = bp.bmWidth;
bgh = bp.bmHeight;
// get preview image size
GetObject(hBitmap_preview, sizeof(BITMAP), &bp);
pvw = bp.bmWidth;
pvh = bp.bmHeight;
}
// init work
x = wdw_w / 2;
y = wdw_h / 2;
dx = (float)wdw_w / (float)FPS;
dy = dx * 0.5;
bgx = 0;
bgy = 0;
// create timer
SetTimer(hWnd, ID_TIMER, (int)(1000 / FPS), NULL);
break;
case WM_TIMER:
/* main loop */
// update ball position
x += dx;
y += dy;
if (x <= 0 || x + imgw >= wdw_w) dx *= -1;
if (y <= 0 || y + imgh >= wdw_h) dy *= -1;
// update bg position
bgx -= 2;
bgy -= 2;
if (bgx <= -bgw) bgx += bgw;
if (bgy <= -bgh) bgy += bgh;
// draw bitmap
if (wdw_w >= 160)
{
// draw fullscreen
HDC hdc, hBmpDC, hMemDC;
hdc = GetDC(hWnd);
// set off-screen bitmap
hMemDC = CreateCompatibleDC(hdc);
SelectObject(hMemDC, offScrnBitmap);
// draw bg to off-screen
hBmpDC = CreateCompatibleDC(hdc);
SelectObject(hBmpDC, hBitmap_bg);
{
int by = bgy;
while (by <= wdw_h)
{
int bx = bgx;
while (bx <= wdw_w)
{
BitBlt(hMemDC, bx, by, bgw, bgh, hBmpDC, 0, 0, SRCCOPY);
bx += bgw;
}
by += bgh;
}
}
DeleteDC(hBmpDC);
// draw ball mask to off-screen
hBmpDC = CreateCompatibleDC(hdc);
SelectObject(hBmpDC, hBitmap_mask);
BitBlt(hMemDC, (int)x, (int)y, imgw, imgh, hBmpDC, 0, 0, SRCAND);
DeleteDC(hBmpDC);
// draw ball source to off-screen
hBmpDC = CreateCompatibleDC(hdc);
SelectObject(hBmpDC, hBitmap);
BitBlt(hMemDC, (int)x, (int)y, imgw, imgh, hBmpDC, 0, 0, SRCPAINT);
DeleteDC(hBmpDC);
// drawing to off-screen is finished
// draw off-screen bitmap to window
BitBlt(hdc, 0, 0, wdw_w, wdw_h, hMemDC, 0, 0, SRCCOPY);
DeleteDC(hMemDC); // Create*() -> DeleteDC()
ReleaseDC(hWnd, hdc); // GetDC() -> ReleaseDC()
}
else
{
// draw preview
HDC hdc, hBmpDC;
hdc = GetDC(hWnd);
hBmpDC = CreateCompatibleDC(hdc);
SelectObject(hBmpDC, hBitmap_preview);
BitBlt(hdc, 0, 0, pvw, pvh, hBmpDC, 0, 0, SRCCOPY);
DeleteDC(hBmpDC); // Create*() -> DeleteDC()
ReleaseDC(hWnd, hdc); // GetDC() -> ReleaseDC()
}
break;
case WM_DESTROY:
KillTimer(hWnd, ID_TIMER); // kill timer
// delete bitmap
DeleteObject(offScrnBitmap);
DeleteObject(hBitmap);
DeleteObject(hBitmap_mask);
DeleteObject(hBitmap_bg);
DeleteObject(hBitmap_preview);
PostQuitMessage(0);
break;
default:
break;
}
return DefScreenSaverProc(hWnd, msg, wParam, lParam);
}
/* Processing of dialog boxes for configuration */
BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT msg,
WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_INITDIALOG:
MessageBox(
hDlg,
_T("This screen saver has no configuration options."),
_T("Sample Screensaver by using MinGW"),
MB_OK | MB_ICONWARNING);
EndDialog(hDlg, IDOK);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
EndDialog(hDlg, IDOK);
return TRUE;
case IDCANCEL:
EndDialog(hDlg, IDCANCEL);
return TRUE;
}
return FALSE;
}
return FALSE;
}
/* Register non-standard window classes required by dialog boxes for configuration */
BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
return TRUE;
}
◎ Makefile。 :
Makefile は以下。
_Makefile
_Makefile
mgscrsv2.scr: mgscrsv2.o resource.o
gcc mgscrsv2.o resource.o -o mgscrsv2.scr -mwindows -lscrnsave
mgscrsv2.o: mgscrsv2.c
gcc -c mgscrsv2.c
resource.o: resource.rc resource.h ball.bmp ball_mask.bmp bg2.bmp scrsav_preview.bmp
windres resource.rc -o resource.o
.PHONY: clean
clean:
rm -f *.scr
rm -f *.o
- Windows用プログラムなので、-mwindows を渡している。
- スクリーンセーバを作るので、-lscrnsave を渡して「libscrnsave.a (or libscrnsavw.a) をリンクせよ」と指定している。
◎ コンパイル/ビルド。 :
全部で以下のファイルになる。
ちなみに、make clean と打てば、*.o や *.scr を削除できる。
- ball.bmp
- ball_mask.bmp
- bg2.bmp
- Makefile
- mgscrsv2.c
- resource.h
- resource.rc
- scrsav_preview.bmp
ちなみに、make clean と打てば、*.o や *.scr を削除できる。
◎ 動作確認。 :
エクスプローラ等で mgscrsv2.scr を右クリック。
正しく動作しているようなら、スクリーンセーバを本来置くべき場所に .scr をコピーして、スクリーンセーバとして動作するのか確認する。
今回は MinGW を使ってビルドしたので、生成された .scr は32bit版プログラム。
Windows10 64bit + 32bit版プログラムなので ―― C:\Windows\SysWOW64\ に .scr をコピーして動作確認することになる。
試してみたところ、スクリーンセーバとして、それらしく動いてくれた。
ちなみに、作業に使ったメインPCとは別のPC(Windows10 x64 21H2)にコピーして動作確認したけれど、そちらでもちゃんと動いてくれた。
- 「Test」を選べば、フルスクリーン表示時の動作確認ができる。
- 「構成」を選べば、スクリーンセーバの設定画面で「設定」をクリックした時の動作を確認できる。
正しく動作しているようなら、スクリーンセーバを本来置くべき場所に .scr をコピーして、スクリーンセーバとして動作するのか確認する。
- Windows が 64bit版、スクリーンセーバが32bit版なら、C:\Windows\SysWOW64\ に .scr をコピー。
- Windows が 64bit版、スクリーンセーバが64bit版なら、C:\Windows\System32\ に .scr をコピー。
- Windows が 32bit版なら、C:\Windows\System32\ 以下に .scr をコピー。
今回は MinGW を使ってビルドしたので、生成された .scr は32bit版プログラム。
Windows10 64bit + 32bit版プログラムなので ―― C:\Windows\SysWOW64\ に .scr をコピーして動作確認することになる。
試してみたところ、スクリーンセーバとして、それらしく動いてくれた。
ちなみに、作業に使ったメインPCとは別のPC(Windows10 x64 21H2)にコピーして動作確認したけれど、そちらでもちゃんと動いてくれた。
◎ 覚書。 :
Windows用のプログラムをC/C++で書く場合、まずはメイン関数が必要になる。
GUIアプリの場合、えてして他にも、WndProc() とか WindowProc() という名前の関数も書くことになるらしい。これはウィンドウプロシージャと呼ばれる関数で、Windowsから送られてきたメッセージを処理する関数なのだとか。
そして、スクリーンセーバはGUIアプリに分類される。だから本来は、WinMain() という関数を書かなきゃいけない。
ところが、前述のスクリーンセーバのサンプルソースを眺めても、WinMain関数なんて書いてない。…どうなってるの?
なんでも、「スクリーンセーバの処理って大体似た感じになるよね」「WinMain() や WndProc() ってフツーは同じ処理を書くよね」ということで、そのあたりは scrnsave.lib (または scrnsavw.lib。MinGW の場合は libscrnsave.a or libscrnsavw.a) の中に既に入っているらしい。そして、「そこから呼び出される3つの関数だけ書けばスクリーンセーバを作れますよ」ということになっている模様。
そんなわけで、scrnsave.h と scrnsav*.lib (libscrnsav*.a) を使う場合は、以下の3つの関数の中身だけ書けばスクリーンセーバが作れてしまう。
とは言え、GDI / GDI+ / DirectX / OpenGL等々を使わないと画面の描画はできないので、そのあたりの知識が必要になってしまうあたり、まだハードルが高い感じはする…。もっと簡単に作れるようにできないものか…。
- コンソールプログラム(DOS窓上で使う感じのプログラム)なら、main() という名前の関数が必要。
- GUIアプリなら、WinMain() という名前の関数が必要。
GUIアプリの場合、えてして他にも、WndProc() とか WindowProc() という名前の関数も書くことになるらしい。これはウィンドウプロシージャと呼ばれる関数で、Windowsから送られてきたメッセージを処理する関数なのだとか。
そして、スクリーンセーバはGUIアプリに分類される。だから本来は、WinMain() という関数を書かなきゃいけない。
ところが、前述のスクリーンセーバのサンプルソースを眺めても、WinMain関数なんて書いてない。…どうなってるの?
なんでも、「スクリーンセーバの処理って大体似た感じになるよね」「WinMain() や WndProc() ってフツーは同じ処理を書くよね」ということで、そのあたりは scrnsave.lib (または scrnsavw.lib。MinGW の場合は libscrnsave.a or libscrnsavw.a) の中に既に入っているらしい。そして、「そこから呼び出される3つの関数だけ書けばスクリーンセーバを作れますよ」ということになっている模様。
そんなわけで、scrnsave.h と scrnsav*.lib (libscrnsav*.a) を使う場合は、以下の3つの関数の中身だけ書けばスクリーンセーバが作れてしまう。
- ScreenSaverProc()
- ScreenSaverConfigureDialog()
- RegisterDialogClasses()
とは言え、GDI / GDI+ / DirectX / OpenGL等々を使わないと画面の描画はできないので、そのあたりの知識が必要になってしまうあたり、まだハードルが高い感じはする…。もっと簡単に作れるようにできないものか…。
◎ 余談。プレビューモードのウインドウサイズ。 :
今回書いたサンプルでは、プレビューモード(スクリーンセーバ設定画面の小窓に描画する処理)をどのように処理するかで悩んでしまった。
これがもし、座標を指定して線を描画する系のスクリーンセーバなら、フルスクリーン表示だろうと、プレビューモードだろうと、ウインドウサイズに合わせて座標値を計算して描画すればいいだけだから対応するのは難しくなさそうだけど…。
しかし、bitmap画像を描画する場合、bitmapは等倍で描画されて拡大縮小してくれないわけで…。フルスクリーン表示に合わせて座標値等を決めてしまうと、プレビューモード時は変な表示になってしまう。
一応、GDIにも、処理速度はさておいて拡大縮小描画する関数があるらしいので…。あらかじめ描画サイズを決めておいて、空のbitmapにアレコレ描いてから、それをディスプレイ一杯に拡大描画、もしくはプレビューモードの小さいウインドウに縮小描画すれば対応できるだろうけど…。
そのあたり面倒臭くなってしまったので、今回は、プレビューモード用のbitmap画像を一枚用意して、おそらくプレビューモードだろうと思われる時は、そのbitmap画像だけ描画してお茶濁しすることにした。
ちなみに、Windows10上で、スクリーンセーバ設定画面の小窓のサイズを測ってみたら、152 x 112 だった。巷のスクリーンショット等を調べた感じでは、Windows95 の頃からそのサイズだった模様。
そんなわけで、ウインドウサイズを取得して、もし横幅が160ドット以下なら「これはプレビューモードで呼び出されているのだろう」と判断して処理をするようにしてみた。こういうやり方が妥当なのかどうかは分からんけど…。
これがもし、座標を指定して線を描画する系のスクリーンセーバなら、フルスクリーン表示だろうと、プレビューモードだろうと、ウインドウサイズに合わせて座標値を計算して描画すればいいだけだから対応するのは難しくなさそうだけど…。
しかし、bitmap画像を描画する場合、bitmapは等倍で描画されて拡大縮小してくれないわけで…。フルスクリーン表示に合わせて座標値等を決めてしまうと、プレビューモード時は変な表示になってしまう。
一応、GDIにも、処理速度はさておいて拡大縮小描画する関数があるらしいので…。あらかじめ描画サイズを決めておいて、空のbitmapにアレコレ描いてから、それをディスプレイ一杯に拡大描画、もしくはプレビューモードの小さいウインドウに縮小描画すれば対応できるだろうけど…。
そのあたり面倒臭くなってしまったので、今回は、プレビューモード用のbitmap画像を一枚用意して、おそらくプレビューモードだろうと思われる時は、そのbitmap画像だけ描画してお茶濁しすることにした。
ちなみに、Windows10上で、スクリーンセーバ設定画面の小窓のサイズを測ってみたら、152 x 112 だった。巷のスクリーンショット等を調べた感じでは、Windows95 の頃からそのサイズだった模様。
そんなわけで、ウインドウサイズを取得して、もし横幅が160ドット以下なら「これはプレビューモードで呼び出されているのだろう」と判断して処理をするようにしてみた。こういうやり方が妥当なのかどうかは分からんけど…。
[ ツッコむ ]
以上、1 日分です。