● 無料のC/C++コンパイラ
ボーランド社のホームページから「Borland C++ Compiler 5.5」がダウンロードできる。契約条件に沿って使う限りは、無料で利用できる、信頼できるC/C++コンパイラである。
最近、たまたまCQ出版社からの新刊、「TRY!PC 2002年秋号―C++プログラミングのポイント(1,980円)」を読んでみた。
最初のほうにBorland C++ 5.5 (以下BCC55と略記)の使い方が書いてあるのだが、意外にややこしい。
私がpolyhed等のソースを公開しているのは、BCC55などで簡単にコンパイルできると思い込んでいたからである。実際、少し触った程度では、そのように思えたのであった。
私のように、プログラミングが半ば職業のようになっている者なら、サポートのない状況であれこれ調べるよりも、製品版を買ったほうが全体として安上がりなような気がする。しかし、趣味でプログラミングする方に、いきなり購入をお勧めするのは心苦しい点があるし、何よりいろいろ調べるのは勉強にもなる。
ということで、ここではBCC55でOpenGLがとにかく動かせるまでのやり方を紹介する。
● Borland C++ Compiler 5.5 の導入
単に導入するだけなら簡単である。
Webからダウンロードもできるし、上述の雑誌のCD-ROMを使っても良い。導入後のフォルダにreadme.txtというテキストファイルがあるから、指示に従う。コンソールプログラムの「hello,
world」プログラム等は簡単にコンパイル・実行できるはずだから、そこまでは到達している前提で、以下、話を進める。
● 問題点はどこにあるのか
とにもかくにも、Windowsプログラムを書いて実行するだけなら、
d:\user\hellowin>bcc32 -tW hellowin.cpp
などと-tWオプションを付ければ済む話である。
ところが、メニューを付けたりダイアログを表示させたりと、リソースを書かねばならなくなると、たちまち困難になる。
(1) ビジュアルなリソースエディタやアイコンエディタが付属しない
これはどうしようもない。Windowsの無料のリソースエディタが他にあるのかどうか、知らない。であるから、以下で述べるリソースファイルというテキストファイル(.rc)でボタンの配置等を指示しないといけない。
アイコンエディタはどこかにあるとは思う。無料で使えるアイコン(.ico)は多量に出回っているから、それを使うのもよいだろう。
(2) なぜかリンカが素直にリソースファイルを受け付けない
上述の雑誌で指摘されている。たしかに、当方の環境でも素直に動作しない。
雑誌のいう通り、#pragmaという技を使わないとリンクされない。以下のgl1.cppの最初のほうに出てくる。
● 必要な知識
Windowsプログラミング自身については、いくらでも入門書がある状態であろう。BCC55単独だとオブジェクト指向的には書けず、WndProc()という応答ルーチンの中身を延々と書かないといけないのだが、これはある程度慣れの問題であり、事態が混迷する訳ではない。
OpenGLの入門書は、大きな書店では店頭で何とか手に入る。システムとしては、解説書は手に入りやすい部類に入る、と思う。
● サンプルプログラム
以下の3つのテキストファイルを用意する。専用のフォルダを作っておけば便利。
上述の雑誌と関数の定義等が若干違うのだが、私の書きなれたやり方なので、ご容赦いただきたい。
▼ gl1.cpp
C言語部分。かなり長大に見えるが、ワンパターンの部分が多い。単にOpenGLで絵を描くだけなら、関数drawGL()の中身を書き換えるだけで、あれこれ試せると思う。
//gl1.cpp; yokada; 021005
// OpenGL practice program
#include <windows.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <gl/gl.h>
#include <gl/glu.h>
#include "gl1.h"
#pragma resource "gl1.res"
// globals
HINSTANCE ghInst;
TCHAR szTitle[100];
HDC ghDC;
HGLRC ghRC;
GLfloat aspect;
// function definitions
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK About(HWND, UINT, WPARAM, LPARAM);
// drawing
GLvoid drawGL(GLvoid) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(45.0, aspect, 1.0, 20.0);
glMatrixMode(GL_MODELVIEW);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glTranslatef(0.0f, 0.0f,-5.0f);
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glBegin(GL_TRIANGLES);
glColor3f(1.0f, 1.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.625f, 0.0f);
glVertex3f(0.75f, 0.0f, 0.0f);
glColor3f(1.0f, 0.5f, 0.5f);
glVertex3f(-0.75f,-0.75f,-0.75f);
glVertex3f(0.75f, 0.5f, 0.25f);
glVertex3f(-0.5f, 0.0f, 0.0f);
glColor3f(0.5f, 0.5f, 1.0f);
glVertex3f(-0.5f, 0.5f, 0.2f);
glVertex3f(0.625f, 0.375f, 0.625f);
glVertex3f(0.75f, 0.25f,-0.5f);
glEnd();
glPopMatrix();
SwapBuffers(ghDC);
}
BOOL bSetupPixelFormat() {
PIXELFORMATDESCRIPTOR pfd;
int pixelformat;
pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL
| PFD_DOUBLEBUFFER;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cDepthBits = 32;
pfd.cAccumBits = 0;
pfd.cStencilBits = 0;
if ((pixelformat = ChoosePixelFormat(ghDC, &pfd)) == 0)
{
MessageBox(NULL, "ChoosePixelFormat failed",
"Error", MB_OK);
return FALSE; }
if (SetPixelFormat(ghDC, pixelformat, &pfd) == FALSE) {
MessageBox(NULL, "SetPixelFormat failed",
"Error", MB_OK);
return FALSE; }
return TRUE;
}
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE /*hPrevInst*/,
LPSTR /*lpCmdParam*/, int nCmdShow) {
static char szWindowClass[] = "gl1";
HWND hWnd;
MSG msg;
WNDCLASSEX wcex;
ghInst = hInst;
LoadString(hInst, IDS_WINTITLE, szTitle, 100);
// register the class
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = (WNDPROC)WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInst;
wcex.hIcon = LoadIcon(hInst, (LPCTSTR)IDI_GL1);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = (LPCSTR)IDC_GL1;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = NULL;
RegisterClassEx(&wcex);
// initialize the instance
hWnd = CreateWindow(szWindowClass, szTitle,
WS_OVERLAPPEDWINDOW, 0, 0,
512 + GetSystemMetrics(SM_CXFRAME) * 2,
512 + GetSystemMetrics(SM_CYFRAME) * 2 +
GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYMENU),
NULL, NULL, hInst, NULL);
if (!hWnd) {return -1;}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg); }
return msg.wParam;
}
GLvoid initializeGL(GLsizei width, GLsizei height) {
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClearDepth(1.0);
glEnable(GL_DEPTH_TEST);
aspect = (GLfloat)width / (GLfloat)height;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message,
WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT ps;
RECT rect;
switch (message) {
case WM_CREATE:
ghDC = GetDC(hWnd);
if (!bSetupPixelFormat()) PostQuitMessage(0);
ghRC = wglCreateContext(ghDC);
wglMakeCurrent(ghDC, ghRC);
GetClientRect(hWnd, &rect);
initializeGL(rect.right, rect.bottom);
break;
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDM_ABOUT:
DialogBox(ghInst, (LPCTSTR)IDD_ABOUTBOX,
hWnd, (DLGPROC)About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
}
break;
case WM_PAINT:
BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
drawGL();
break;
case WM_SIZE:
GetClientRect(hWnd, &rect);
if (rect.bottom == 0) break;
glViewport(0, 0, rect.right, rect.bottom);
aspect = (GLfloat)rect.right / rect.bottom;
break;
case WM_DESTROY:
if (ghRC) {wglDeleteContext(ghRC); ghRC = NULL;
}
if (ghDC) {ReleaseDC(hWnd, ghDC); ghDC = NULL;
}
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
LRESULT CALLBACK About(HWND hDlg, UINT message,
WPARAM wParam, LPARAM /*lParam*/) {
switch (message) {
case WM_INITDIALOG:
return TRUE;
case WM_COMMAND:
if ((LOWORD(wParam) == IDOK) || (LOWORD(wParam)
== IDCANCEL)) {
EndDialog(hDlg, LOWORD(wParam));
}
break;
}
return FALSE;
}
▼ gl1.h
ヘッダファイル。リソースエディタがないので、リソース番号は自分で管理しないといけない。
// gl1.h; yokada; 021005
#define IDC_STATIC -1
#define IDS_WINTITLE 10000
#define IDC_GL1 10001
#define IDI_GL1 10002
#define IDM_EXIT 10003
#define IDM_ABOUT 10004
#define IDC_MYICON 10005
#define IDD_ABOUTBOX 10006
▼ gl1.rc
リソースの定義ファイル。アイコンファイル、ここではpolyhed.ico、があれば、三行目の//を外すとアイコンが表示される。
他に、メニューと文字列テーブル、簡単なダイアログの定義が書かれている。
// gl1.rc; yokada; 021005
#include "gl1.h"
// IDI_GL1 ICON DISCARDABLE "POLYHED.ICO"
IDC_GL1 MENU DISCARDABLE
BEGIN
POPUP "ファイル(&F)"
BEGIN
MENUITEM "終了(&X)", IDM_EXIT
END
POPUP "ヘルプ(&H)"
BEGIN
MENUITEM "バージョン情報(&A)...",
IDM_ABOUT
END
END
STRINGTABLE DISCARDABLE
BEGIN
IDS_WINTITLE "OpenGL test 1"
END
IDD_ABOUTBOX DIALOG DISCARDABLE 22, 17, 200, 40
STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU
CAPTION "About 'gl1'"
FONT 8, "System"
BEGIN
ICON IDI_GL1, IDC_MYICON, 14, 9, 20, 20
LTEXT "gl1 version 1.0", IDC_STATIC, 49, 10, 101,
8
LTEXT "著作物 岡田好一 2002", IDC_STATIC, 49, 20,
101, 8
DEFPUSHBUTTON "OK", IDOK, 160, 10, 30, 11
END
● サンプルプログラムのコンパイルと実行
まずリソースコンパイラで、.rcファイルから.resファイルを作成する。
brcc32 gl1.rc
この操作で、gl1.resファイルが作成されるはずである。
次に、C/C++コンパイラで.cppファイルから.objファイルを経由して、.exeファイルを作成する。
bcc32 -tW gl1.cpp
この操作で、目的のgl1.exeが作成される。意外にも、OpenGLのライブラリファイルを明示する必要はない。
リソースを書き換えたら、brcc32をその都度実行する。.exeに結果を反映させるには、bcc32も実行する。
.cppを書き換えただけなら、bcc32を実行するだけでよい。
2002年10月6日 岡田好一