ぷるぷるの雑記

低レイヤーがんばるぞいなブログ. 記事のご利用は自己責任で.

MFCでOpenGLをつかう -その1-

2022年にやることではないとは思いますが、仕事の勉強がてらMFCを使ったグラフィカルなアプリケーションを作りたくなりました. そこでOpenGLを使用したMFCアプリケーションを作ります.

なお、MFCは書籍等も中古でしか見当たらないほど過去のものであり、特別な事情がない限り使用する理由はないです(逆に言えば、今回は仕事で使いそうだという理由からMFCを利用しています).

この記事はその1として、環境構築から矩形を表示するところまでをカバーしています.

2022/8/16 ライブラリに関する記述を一部修正

2022/10/6 内容を修正

環境

項目 バージョン
OS Windows11
Visual Studio 2017 Community Edition (ツールセットv141)
Windows SDK 10.0.17763.0
Glew_static 2.1.0.3
glfw 3.38

プロジェクトの作成

まずは新しいMFCプロジェクトを作成します.ファイル->新規作成->プロジェクトからVisual C++->MFC/ATL->MFCアプリを選択し、好きなプロジェクト名を作成します. 今回はOpenGLという名前にしました. MFCアプリ作成用ウィザードが表示されるので、次の画像のようにプロジェクトを作成します.

MFCアプリケーションプロジェクトを作成

ウィザード:アプリケーションの種類

ウィザード:ドキュメント テンプレート プロパティ

ウィザード:ユーザーインターフェイス機能

ウィザード:高度な機能

ウィザード:生成されたクラス

上記の画像のようにウィザードを進めたら完了を押してプロジェクトの作成は完了です.作成されたばかりのプロジェクトをビルドすると、次のようなフレームが表示されます.

初ビルドの結果

とりあえずコントロールOpenGLで描画

さて、ここから実際にMFCのコントロールOpenGLを用いて描画していきます. 次の記事を参考にさせていただきました.

sourcechord.hatenablog.com

ざっくりとした流れは次になります

  • ピクチャーコントロールをダイアログに追加する
  • CStaticクラスを継承したCGLScreenクラスを作る
  • ダイアログのソースにCGLScreen型のDDX変数を追加
  • CGLScreenのデバイスコンテキストを取得し、OpenGLで矩形を描画する

ピクチャーコントロールの追加

リソースビュー ->OpenGL->OpenGL.rc ->Dialog->IDD_OPENGL_DIALOGを選択し、リソースエディタを開きます. リソースエディタを表示中にツールボックスをクリックすると、様々なコントロールが表示されています. 検索窓でPicture Controlと検索し、Picture Controlをドラッグ&ドロップしてダイアログに追加しましょう. 邪魔なので、中心にあるラベルは削除しておきましょう.

IDD_OPENGL_DIALOG

Picture Control をドラッグアンドドロップしてダイアログに追加

Picture Control追加直後は、コントロールのIDがID_STATICになっているかと思います. 今回はIDC_GLという名前に変更します.

Picture Control のIDをIDC_GLに

CGLScreenクラスの定義

次にPicture Control用の独自クラスを実装するため、ソリューションエクスプローラー->右クリック->追加->クラス からクラスのソースファイルとヘッダーファイルを同時に生成します.

CGLScreenクラスのソースとヘッダーの生成

CGLScreen.h

生成されたCGLScreen.hを以下のようにします.

// CGLScreen.h
#pragma once
#include <afxwin.h>
class CGLScreen : public CStatic
{
    protected:
    HGLRC m_hRC;
    CDC* m_pDC;
    BOOL SetupPixelFormat();
    BOOL InitGLContext();

public:
        virtual void PreSubclassWindow();
    DECLARE_MESSAGE_MAP()
    virtual afx_msg void OnPaint();
    virtual afx_msg void OnDestroy();
};

CGLScreen.cpp

生成されたCGLScreen.cppに下のようなコードを追記します.

// CGLScreen.cpp
#include "CGLScreen.h"
#include <GL/GL.h>
#include <GL/GLU.h>

BOOL CGLScreen::SetupPixelFormat()
{
    PIXELFORMATDESCRIPTOR pfd = {
        sizeof(PIXELFORMATDESCRIPTOR),
        1,
        PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
        PFD_TYPE_RGBA,
        24,
        0, 0, 0, 0, 0, 0,
        0,
        0,
        0,
        0,  0, 0, 0,
        16,
        0,
        0,
        PFD_MAIN_PLANE,
        0,
        0, 0, 0
    };

    int pixelformat;
    if (0 == (pixelformat = ::ChoosePixelFormat(m_pDC->GetSafeHdc(), &pfd))) {
        return FALSE;
    }

    if (FALSE == ::SetPixelFormat(m_pDC->GetSafeHdc(), pixelformat, &pfd)) {
        return FALSE;
    }

    return TRUE;
}

BOOL CGLScreen::InitGLContext()
{
    m_pDC = new CClientDC(this);

    if (NULL == m_pDC) {
        return FALSE;
    }
    if (!SetupPixelFormat()) return FALSE;
    if (0 == (m_hRC = ::wglCreateContext(m_pDC->GetSafeHdc()))) {
        return FALSE;
    }
    if (FALSE == ::wglMakeCurrent(m_pDC->GetSafeHdc(), m_hRC)) {
        return FALSE;
    }

    return TRUE;
}

void CGLScreen::PreSubclassWindow()
{
    LONG  style = GetWindowLong(this->m_hWnd, GWL_STYLE);
    style |= WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
    SetWindowLong(this->m_hWnd, GWL_STYLE, style);


    InitGLContext();
    CStatic::PreSubclassWindow();

}

BEGIN_MESSAGE_MAP(CGLScreen, CStatic)
    ON_WM_PAINT()
    ON_WM_DESTROY()
END_MESSAGE_MAP()


void CGLScreen::OnPaint(){
    CPaintDC dc(this); // device context for painting

    ::glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //とりあえず、四角形を描画してみる。
    ::glPushMatrix();
    ::glColor3f(1.0f, 1.0f, 0.0f);
    
    glColor3d(1.0, 0.0, 0.0);
    glBegin(GL_POLYGON);
    glVertex2d(-0.9, -0.9);
    glVertex2d(0.9, -0.9);
    glVertex2d(0.9, 0.9);
    glVertex2d(-0.9, 0.9);
    glEnd();

    ::glPopMatrix();
    ::glFinish();

    if (FALSE == ::SwapBuffers(m_pDC->GetSafeHdc())) {}
}

void CGLScreen::OnDestroy()
{
    CStatic::OnDestroy();

    // TODO: ここにメッセージ ハンドラ コードを追加します。
    if (FALSE == ::wglMakeCurrent(NULL, NULL)) {
        // 必要に応じてエラーハンドリング
    }

    if (FALSE == ::wglDeleteContext(m_hRC)) {
        // 必要に応じてエラーハンドリング
    }

    if (m_pDC) delete m_pDC;
}

ここでOnPaint()とOnDestroy()の解説をば. MFCアプリケーションはメッセージによるイベント駆動を基本に動作します. つまり、イベントハンドラを書き足してアプリケーションを作るのですが、その際にどのメッセージを有効にするかということを明示しなければなりません. メッセージを有効にする場合、BEGIN_MESSAGE_MAP()~END_MESSAGE_MAP()で囲まれた部分に有効にしたいメッセージとそのメッセージに対するハンドラを登録します. JavaScriptでいうところのaddEventListenerみたいなものですね. 今回の場合はWM_PAINTとWM_DESTROYというメッセージに対するイベントハンドラを登録していて、これらのメッセージを受信したときにハンドラが自動的に実行されるようにしています.

PreSubclassWindow()は自動的に呼び出されるという点ではイベントハンドラに似ていますが、フレームワークによって呼び出される関数です. なので、対応するメッセージなどは存在せず、メッセージマップに登録する必要はありません.

DDX変数のダイアログへの追加

Picture Controlと先ほど定義したクラスを紐づけるため、DDX変数を追加します. より正確に言えば、IDC_GLとバインドさせたCGLScreen型の変数m_GLScreenを、OpenGLDlgクラスに追加します.

Picture Control を追加したときのようにリソースエディタを開き、DDX変数を追加したいコントロールを右クリック->変数の追加からウィザードを開きます.

リソースエディタからPicture Control のDDXを追加する

コントロール変数の追加ウィザードのコントロールタブを次のようにします. なお、その他タブはデフォルトのままで大丈夫です.

ウィザード:コントロール変数の追加

ウィザードを完了した後にOpenGLDlg.hを見ると、CGLScreen型の変数m_GLScreenが追加されていることが分かります. ただし、CGLScreen.hをインクルードしていないので、赤い破線が表示されています.

DDX変数追加直後のOpenGLDlg.h

なので、OpenGLDlg.h内でCGLScreen.hをインクルードしましょう.

OpenGLDlg.h

// OpenGLDlg.h
// m_GLScreenが追加された

#pragma once
#include "CGLScreen.h"

class COpenGLDlg : public CDialogEx
{
public:
    COpenGLDlg(CWnd* pParent = nullptr);

// ダイアログ データ
#ifdef AFX_DESIGN_TIME
    enum { IDD = IDD_OPENGL_DIALOG };
#endif

    protected:
    virtual void DoDataExchange(CDataExchange* pDX);  // DDX/DDV サポート


// 実装
protected:
    HICON m_hIcon;

    // 生成された、メッセージ割り当て関数
    virtual BOOL OnInitDialog();
    afx_msg void OnPaint();
    afx_msg HCURSOR OnQueryDragIcon();
    DECLARE_MESSAGE_MAP()
public:
    CGLScreen m_GLScreen;

};

以上でファイルの編集は完了です.

いざビルド

ビルド結果
以上のように無事MFCのコントロールOpenGLを使って描画することが出来ました.

まとめ

Visual Studio 2017でもMFCOpenGLを連携して使用することが出来ました. VC++でイベント駆動アプリケーションを作るのは抵抗がありましたが、やってみるとほかの言語とそこまで変わらなくて驚きました.

次回は頂点バッファオブジェクトと頂点配列オブジェクトを使った矩形の描画をしていきます.

参考

stackoverflow.com sourcechord.hatenablog.com

docs.microsoft.com

docs.microsoft.com

docs.microsoft.com

docs.microsoft.com

ヘッダファイルの役割

今まで見てきたことがあるパターンを書き出してみた.

関数のプロトタイプ宣言

  • おそらく一番オーソドックスな使い方.
  • 関数本体が書いてあるオブジェクトファイルor静的ライブラリor動的ライブラリがあるはず.
  • ヘッダーとオブジェクトファイルが一対一対応とは限らない?

グローバル変数のextern宣言

  • 研究室のプログラムでよく見た
  • 本来であればソースファイルごとにそのモジュールで使用するグローバス変数のみをextern宣言する方が礼儀よさそう
  • 変数の実体を定義するソースファイルを用意するのを忘れずに

インライン関数

  • 呼び出し回数が多くかつ短い関数はオーバーヘッドが関数呼び出しになってしまうので、インライン展開するとよい
  • コンパイラによっては必ずインライン展開される保証はないと聞く
  • システムコールとかをインラインにしておくことが多い?

構造体やtypedefやマクロ

  • これもよく見る
  • 関数のプロトタイプ宣言とともに書かれていることが多い.
  • マクロは変数だけでなく関数マクロも.
  • ペリフェラルレジスタをいじるような操作はマクロにしておくとよいらしい

子ヘッダーのインクルード

  • 数の子ヘッダーをインクルードしているヘッダーをマスターヘッダと言うらしい
  • windows.hやafxwin.hがまさにこの使い方

参考

ja.wikipedia.org

Windows.hとafxwin.hの場所

C:\Program Files(x86)以下で検索すれば簡単に見つかるが、たまに知りたくなるのでメモを残しておく.

比較的古い場合(少なくともVS6)

ヘッダー名 場所
windows.h C:\Program Files(x86)\Microsoft SDKs\Windows\v.70A\include
afxwin.h C:\Program Files(x86)\Microsoft Visual Studio\VC98\MFC\include

比較的新しい場合(少なくともVS2017)

ヘッダー名 場所
Windows.h C:\Program Files (x86)\Windows Kits\10\Include\10.0.17763.0\um
afxwin.h C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Tools\MSVC\14.16.27023\atlmfc\include

補足

  • 数字の部分はバージョンによって異なる
  • 昔はMicrosoft SDKsだったものがWindows Kitsに
  • MFC用のヘッダーは一貫してVisual Studioフォルダ以下にある

Arduino UNO R3 の 素子 まとめ

Arduino UNO R3 の実物を回路図と照らし合わせてみました. といっても電子回路はからっきしなので、主要な素子だけのまとめになります.

型番は一部省略してるのであしからず.

Arduino UNO R3 の IC (+α)

パッケージ上の表記 回路図上の表記 説明
1117M3... NCP1117ST50T3G 電圧レギュレータ
LPFG LP2985 電圧レギュレータ
IE FDN340P 電圧降下防止p-MOSFET
M7 1N4007 汎用整流ダイオード
SGM8542... LMV358 x 2個 CMOSオペアンプ
読めない CSTCE16MOV53-RO 16MHzセラミック振動子
T16.000 記載なし 16MHz水晶振動子
8 MF-MSMF050-2 回路保護ヒューズ
記載なし POWERSUPPLY_DC21MMX 電源ジャック
MEGA16U2 ATMEGA16U2-MU(R) ファームウェア書き込み済みコントローラー
ATMEGA328P ATMEGA328P-PU 8-bit MCU

写真と一緒に

Arudino上の主要な素子

参考

https://www.arduino.cc/en/uploads/Main/Arduino_Uno_Rev3-schematic.pdf

ASCIIコードにも種類がある?

バイナリエディタのASCIIコードの欄を見ていて不思議に思うことがあったので、その際に調べたことについてのメモ.

ASCIIコードと拡張ASCIIコードとは

ASCIIコードは7bit(0x00~0x7F)を使って数字やアルファベットなどを表しますが、0x80~0xFFの値に対応した文字は存在しません.

ですが、英語圏以外の人間からすると、余った1bit(0x80~0xFF)も有効活用したいところです. そこでこの余った1bitに好きな文字を対応させた拡張ASCIIコードが地域ごとに誕生しました.

例えば、日本語環境のための拡張ASCIIコードは0x80~0xFFに半角カタカナを対応させています. ヨーロッパの方ではアクセント付きアルファベットなどを対応させています.

拡張ASCIIコードも広義のASCIIコードとみなすと、ASCIIコードにもいくつかの種類が存在し、0x00~0x7Fの範囲は互換性があるが、0x80~0xFFの範囲は環境によって対応する文字が異なっているといえます.

バイナリエディタを使って確かめる

環境

項目 内容
OS Windows11
バイナリエディタ STM32 Cube Programmer 2.11.0 内のバイナリエディタ

ASCII文字を見てみる

以下の図はSTM32マイコン用にビルドしたプログラムをフラッシュに書き込んだ後のフラッシュのダンプ結果になります. よくわからないラテン文字がASCII文字として表示されているのがわかります. 例えば、0x08000000の行を見ると 0xCD=Í、 0xD3=ÓとしてASCII文字が表示されています. しかし、ASCII文字は本来(0x00~0x7F)で定義されているため、0xCDや0xD3の値には文字が割り振られていないはずです.

このことから、このバイナリエディタはバイナリをヨーロッパ用の拡張ASCIIで表示しているようです.

バイナリエディタ上の表記

参考

gallery-code.blogspot.com

Uncaught SyntaxError: Unexpected token < in JSON at position 0の解決方法

javascriptのfetchAPIでphpプログラムと通信すると以下のようなエラーが出てくる時があります.

Uncaught SyntaxError: Unexpected token < in JSON at position 0  

「<」の部分は場合によっては異なる文字だったりしますが、たいてい1文字です.

このエラーはjavascript側ではjson形式を想定しているのに、phpプログラムがjson形式をechoしていないときに生じます.例えば、以下のようなコードでは上記のエラーが生じます.

<!-- index.html -->
<html>
    <body>
        <button class="test">hoge</button>
    </body>
</html>

<script>
let btn = document.querySelector('.test')
btn.onclick = function(){
    const url="test.php"
    fetch(url)
    .then(res => res.json())  // javascript側ではjsonが返ってくると思っている
    .then(data => console.log(data))
}
</script>
<?php 
    echo 'hello';   // phpはjsonではなくstringを返している
    exit;

解決方法は単純で、php側をきちんとjson形式にするか、javascriptのres.json()をres.text()にすれば良いです.

番外編になりますが、自分は以下のようなコードを書いてよくこのエラーに出くわします.

<!-- index.html -->
<html>
    <body>
        <button class="test">hoge</button>
    </body>
</html>

<script>
let btn = document.querySelector('.test')
btn.onclick = function(){
    const url="test.php"
    fetch(url)
    .then(res => res.json())  // javascript側ではjsonが返ってくると思っている
    .then(data => console.log(data))
}
</script>
<?php 
    ...
/*
データベースと通信してデータを取得し、$results[]という連想配列にpush
*/
    echo var_dump($results);       // デバッグ用にvar_dump. こいつが原因
    echo json_encode($results);   // phpはjsonではなくstringを返している
    exit;

要はphp側でechoしたものがすべてjavascript側に送られるので、php側から送るデータの形式は統一しないとだめってことですね.

pngファイルの種類を右クリックから判別する方法

いきなりですがpngファイルと言っても何種類かあるのですね.ImageMagickに入っているidentifyコマンドを利用すればどの種類のpngファイルなのか区別できるということですが、いちいちプロンプトを開いてコマンドを打つのも面倒なので、右クリックのメニューからidentifyコマンドを実行できるようにしました.

三行まとめ

  • Windows11にImageMagickをインストール
  • identifyコマンドの結果を表示するバッチファイルを作成
  • 右クリックの送るの欄に表示されるようにする

1. ImageMagickのインストール

ImageMagickのダウンロードページから環境にあったバイナリをダウンロードします. dll形式と.exe形式があるっぽいですが、とりあえずImageMagick-7.1.0-portable-Q16-HDRI-x64.zip というポータブル形式のzipファイルをダウンロードした後解凍しました.

解凍したフォルダをC:\Program Filesに置きましょう.

2. パスを通す

解凍後のフォルダを開くと、binフォルダが置かれておらず直下にコマンドが鎮座しています.なので通すべきパスはC:\Program Files\ImageMagick-7.1.0-portable-Q16-HDRI-x64 までになります.

ImageMagickの一部のコマンドはWindowsのコマンドと同名であるので、下記のブログを参考に先ほど通したパスを一番上に持ってきましょう.

higuma.github.io

3. バッチファイルを書く

以下のようなバッチファイルを用意してidentify.batという名前を付けて保存します. 名前は正直なんでもいいです.

@echo off

identify -verbose %1 | findstr "Alpha: color_type:"  

cmd /k

4. 送るに追加

先ほど作成したバッチファイルを C:\Users\ユーザー名\AppData\Roaming\Microsoft\Windows\SendTo フォルダに移動、あるいはコピーします.

これでidentify.batが右クリックの送るという項目に表示されます. Win11では、右クリック->その他のオプションを表示->送る になります.

5. 実行してみた

右クリックの送るからスクリプト実行

実行結果

要らない部分も表示されてしまいますが、許してください.

また、 環境によってはAlphaではなくalpha と表示されるようなので、自分の環境での表記に従いましょう.

参考

imagemagick.biz

iwb.jp

piyopiyocs.blog115.fc2.com

jj-blues.com