プログラミングへ戻る

Borland C++でOpenGL

● 無料の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日 岡田好一