さて、前回の方針(注)にそって学習を進めましょう。

注:「ということで(何が「ということ」なのか、分かりませんが)、「GDI+をカバーする」なんて大口はたたかず、GDI+で拡張された種々のイメージファイルの読み書きのみを先ずは当たろうか、ということで考えています。」

 

定石1:GDI+の開始の際のお作法

GDI+を使うには、その為のDLLをアプロードし、その為の定義が入ったヘッダーが必要です。また、それらは専用ネームスペースにあるので使えるようにします。

//GDI+のインクルード
#include <gdiplus.h>
#include <wchar.h>
#pragma comment(lib,"gdiplus.lib")

using namespace Gdiplus;
次にGDI+の初期化に関わる専用の変数と関数を呼びます。

GdiplusStartupInput m_gdiSI;    //GDI+のスタートアップインプット
ULONG_PTR m_gdiToken;            //GDI+のトークン
//GDI+の開始
GdiplusStartup(&m_gdiToken, &m_gdiSI, NULL);
これでGDI+が使えるようになります。なお、新し系のサービスではワイド文字を使っているので"wchar.h"を入れておくのが良いでしょう。またその際には必ずワイド文字-マルチバイト文字変換があるのでロケール設定関数も必要です。

//ワイド文字使用のためロケールの初期化(日本語)
setlocale(LC_CTYPE, "JPN");
 

定石2:GDI+の終了の際のお作法

C++では入り口にお作法(例:コンストラクター)があれば、必ず出口にもお作法(例:デストラクター)があります。GDI+の場合は以下の通りです。

//GDI+の終了
GdiplusShutdown(m_gdiToken);
 

ここまではすべてのGDI+の利用に共通です。掘り下げて学びたい方は↓をご覧ください。

学習用-GDI+ (復習用-Windows GDI

なお、20ものヘッダーファイルリストがありますが、BCC102のインクルードファイルで見ると全てgdiplus.hで済まされそうです。(エラーが出たらその時に考えましょう。)

 

定石3:GDI+のイメージ描画のお作法

今回やるイメージファイルからのイメージの読みこみ、ウィンドウへの表示には、次の二つのクラスがかかわってきます。(というか、老人には荷が重いので、今回はこれだけしか使わないつもりです。)また、GDI+はC++で書かれているようで同一名のメソッド(関数のオーバーロード)がおおく(注)、いちいち確認して進めることが肝要です。

注:例えばGraphicsクラスのイメージを表示するDrawImageメソッドだけで30ものオーバーロード関数があることは前回書きました。

 

Graphics
The Graphics class provides methods for drawing lines, curves, figures, images, and text. A Graphics object stores attributes of the display device and attributes of the items to be drawn.(線、曲線、図形、像や文章を描画するメソッドを規定するクラスで、その実体(インスタンス)は描画デバイスと描画対象の属性を保有します。)

これを使えるようにするにはコンストラクターが必要です。これもオーバーロード関数がいくつかありますが、次が簡単でしょう。

Graphics(HDC)

Graphics(HWND,BOOL)

第2引数のboolは色調整を行うか否かの指示で既定値はFALSEです。Graphics(hWnd)と省略して書く事が出来ます。

Graphics* m_pGraphics = new Graphics(m_hDC);

 

Image
The Image class provides methods for loading and saving raster images (bitmaps) and vector images (metafiles).(ラスターイメージ(ビットマップ)とベクターイメージ(メタファイル)の読み込み、書き込みメソッドを規定するクラスです。)(注)

注:「なんだ、ビットマップだけなんだ」と思わないでください。データとして保有するのはビットマップ形式ですが、読み込み(デコード)と書き込み(エンコード)には多彩な種類があります。これは末尾に確認するコードを載せておきます。また、ビットマップとメタファイル(よくワードやエクセルの画像などで出てきますよね?)については次を参考にしてください。

ベクター画像とラスター画像の違いとは?

コンストラクターは大きく、ファイルからとストリームからの二つあります。

Image(WCHAR*,BOOL)

Image(IStream*,BOOL)

初心者の私は勿論前者のファイルからのものを使います。(ここでwcharが出てきましたね。)

Image* m_Image[m_NoI] = new (Image)(m_WFileName);    //m_WFileNameはワイド文字のフルパス名です。

 

このGraphicsクラスのインスタンスとImageクラスのインスタンスを使った画像表示は次のようになります。(これも↑で書いたように30のオーバーロード関数があります。)

例:m_pGraphics->DrawImage(m_Image[m_Selected], x, y, w, h);    //GraphicsクラスのインスタンスのDrawImageメソッドの引数にImageクラスのインスタンスへのポインターを入れ、x,y座標に幅(w)、高さ(h)指定で表示する。

 

ここまでくれば、GDI+によるファイルの読み込み、表示ができるようになりますね。

 

<ご参考-ご自身のPCで読み書きできるイメージファイルの列挙(MSDNの例を改造)>

#include <windows.h>
#include <gdiplus.h>
#include <stdio.h>
#include <conio.h>

using namespace Gdiplus;

INT main() {

    // Initialize GDI+.
    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
    //Variables to use
    UINT  num;                              // number of image encoders
    UINT  size;                             // size, in bytes, of the image encoder array
    ImageCodecInfo* pImageCodecInfo;    //ImageCodeInfo
    //<<< Encoder List>>>
    // How many encoders are there?
    // How big (in bytes) is the array of all ImageCodecInfo objects?
    GetImageEncodersSize(&num, &size);
    // Create a buffer large enough to hold the array of ImageCodecInfo
    // objects that will be returned by GetImageEncoders.
    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    // GetImageEncoders creates an array of ImageCodecInfo objects
    // and copies that array into a previously allocated buffer. 
    // The third argument, imageCodecInfo, is a pointer to that buffer. 
    GetImageEncoders(num, size, pImageCodecInfo);
    // Display the graphics file format (MimeType)
    // for each ImageCodecInfo object.
    printf("--------------------\r\nEncoder list of GDI+\r\n--------------------\r\n");
    for(UINT j = 0; j < num; ++j) { 
        wprintf(L"%s\n", pImageCodecInfo[j].MimeType);    
    }
    free(pImageCodecInfo);

    //<<< Decoder List>>>
    // How many decoders are there?
    // How big (in bytes) is the array of all ImageCodecInfo objects?
    GetImageDecodersSize(&num, &size);
    // Create a buffer large enough to hold the array of ImageCodecInfo
    // objects that will be returned by GetImageDecoders.
    pImageCodecInfo = (ImageCodecInfo*)(malloc(size));
    // GetImageDecoders creates an array of ImageCodecInfo objects
    // and copies that array into a previously allocated buffer. 
    // The third argument, imageCodecInfo, is a pointer to that buffer. 
    GetImageDecoders(num, size, pImageCodecInfo);
    // Display the graphics file format (MimeType)
    // for each ImageCodecInfo object.
    printf("--------------------\r\nDecoder list of GDI+\r\n--------------------\r\n");
    for(UINT j = 0; j < num; ++j) { 
        wprintf(L"%s\n", pImageCodecInfo[j].MimeType);    
    }
    free(pImageCodecInfo);

    //Finalizing
    GdiplusShutdown(gdiplusToken);
    getch();
    return 0;
}

 

<私のPCの出力結果>

--------------------
Encoder list of GDI+
--------------------
image/bmp
image/jpeg
image/gif
image/tiff
image/png
--------------------
Decoder list of GDI+
--------------------
image/bmp
image/jpeg
image/gif
image/x-emf
image/x-wmf
image/tiff
image/png
image/x-icon

 

前回DirectShowでBGMを聞きながらネタ探しでWEBをさまよっていて、GDI+というものに出会って興味を持った、と書きました。その背景には、

(1)BCC102のみならず、BCC55でも"include"フォールダーにグラフィックインターフェースがあることは知っていた(注1)

(2)実はエスニック系料理(注2)や酒の肴作りに興味があり、自作の料理はスマホ写真を撮っている

(3)その他にも旅行や冠婚葬祭等の記念写真がいっぱいある

という事情があり、静止画(デジカメやスマホは通常jpg形式)をまとめて管理するものがあればいいな(注3)と思っていたからです。

注1:お恥ずかしいのですが、記憶間違いでそれは"GDI+"ではなく、"OpenGL"であり、過去「要すればカラフルな三角形がクルクル廻る、あれでしょ?」程度の思い込みでした。

注2:実は過去NYに5年、Singaporeに5年住んでいたことがあり、北米大陸やアジア・オセアニアをとb廻っていたことがあるアル中なのでこんなことに興味を持っていました。

 

しかし、過去プログラミングした経験があるのはプログラミングツールのBMPやICONだけであり、最近のjpg、png、gif等のファイルは取り扱いがよく分からなかったこともありましたので、「WEB散歩」をしていた次第です。

 

知らないものがあるとまずwikiで概要を調べるのが習わしなので、ちょっと見てみるとGDI+についてこんなことが書いてあります。シンガポールからの帰国で私がプログラミングを中断した20年前の導入、ということで私がよく知らなかった訳です。「フーン」ということで、次はより詳しくMicrosoftのサイトで調べてみます。これだけ見ると「チャチャっとできそう」に見えますが、下に下がってより詳しく見てゆくとその裾野は広大であり、絶望感に襲われます。それはある意味当然のことで、GDI+は旧GDIと同じく、イメージ表示のみならず、様々な、描画をディスプレーのみならず、印刷イメージ迄カバーしており、旧版からさらに拡張されているのですから。

 

ということで(何が「ということ」なのか、分かりませんが)、「GDI+をカバーする」なんて大口はたたかず、GDI+で拡張された種々のイメージファイルの読み書きのみを先ずは当たろうか、ということで考えています。(現在はGDI+のうち、GraphicsクラスとImageクラスについてプロパティとメソッドを学習し、演習としてこれらをを使ったサンプルを組んで動作を確認中です。例えばGraphicsクラスのイメージを表示するDrawImageメソッドだけで30ものオーバーロード関数があります。心底疲れますね。)

 

Vectorから連絡があり、障害が復旧したということで、本日最新のセットをアップロードしました。(すぐにダウンロードしても前の版が更新されるまでは旧いままになるのでご注意ください。因みにzipファイルの更新日付は本日です。)

 

さて、次になにをするかがまた問題となります。

 

(軽いBGMプログラムとして実用的といえる)DirectShowは完成したのですが、(動画と音楽の再生を目的としたので当たり前ですが)現在様々な形式がある、静止画を扱う実用的なソフト、つまり「アルバム」のようなものが無いことに気が付きました。どのような形式の静止画でも読み込めるコントロールとかないかな、とwebを散歩していたら「GDI+」というものがあることに気が付き、調べ始めたら結構面白いのですが、使い方の全貌というか、プロパティ、メソッドの全体がまだ理解できていません。当面はこの調査と学習の上で、簡単に使えるカプシュール化を目指したいと思っています。

 

ファイルがすべて出来上がったので、BCC102用のBatchGoodでコンパイルします。(COMを使っているのでBCC55ではコンパイルできません。)

@ECHO OFF
ECHO ----------------------------------
ECHO  BatchGood - Batch File Generator
ECHO  for Embarcadero "bcc32c.exe"
ECHO  Copyright (c) 2021 by Ysama
ECHO ----------------------------------

cd "C:\Users\(パス)\DirectShow\Debug"
"C:\Borland\BCC102\bin\bcc32c.exe" "..\DirectShow.cpp" -tW -O1 -w- -I"C:\Borland\BCCForm"
del "DirectShow.tds"
"C:\Borland\BCC102\bin\brc32.exe" "..\DirectShow.rc" "DirectShow.exe"
del "..\DirectShow.res"
pause


さて、先ずは本来の目的であった動画を再生してみましょう。
「ファイル」「動画ファイルを開く」メニューよりも、ツールバーの一番左尾ボタンを使うのが手っ取り早いです。ファイルを読み込んだら(ステータスバーの第3区画に表示されます)、「動画」「再生」(ツールバーの右向きの再生ボタン)で再生が開始されます。
スライドコントロールが表示され、摘みが移動してゆきます。また、ステータスバーの第2区画に残再生時間が表示されます。
一時停止するには「動画」「一時停止」か、ツールバーの「一時停止」ボタンで、あるいは画面を左クリックしてください。
完全に中止する場合は「動画」「中止」か、ツールバーの「進入禁止」ボタンで、あるいは画面を右クリックしてポップアップメニューを出してください。
再生位置を変えたい場合には、スライドバーをつまんで移動させます。(再生動作中で行うことは余りお勧めしません。一時停止させて移動する方が安全だし、位置も確実です。実際「一時停止」中のみスライドバー移動を可能にすることも考えたのですが、一応再生中も行えるようにしました。)
以上は勿論オーディオファイルの場合も同じです。「ファイルを開く」ダイアログの右下のドロップダウンメニューで「ビデオファイル」から「オーディオファイル」に切り替えてくださいね。

次はVersion 1.1の為に付加したプレイリストメニューです。
先ず「プレイリスト」メニューで「プレイリストを表示」を選んでください。メッセージボックスは何も表示しませんね。
それでは「プレイリスト」「プレイリストの作成・選択」、またはツールバーの「ダイアログ形状」ボタンでFileListダイアログを開いてください。こいつはWS_EX_TOPMOSTで常に最前面に来るようにしていますので注意してください。
なぜこう言う様にしたかというと、ドラッグアンドドロップを簡単にするためです。ユーザーの一度に鑑賞したいお好みの音楽や動画のファイルをPC中から拾ってこのダイアログに投げ込んでください。複数放り込んでも結構です。再生順序が気に食わなければ「編集」「編集」でNoを変えてソートしなおしてください。
お好みのプレイリストが出来上がったら「ファイル」「リストの保存」(かツールバーのフロッピーディスクボタン)で「指定されたFileListフォールダー」に保存してください。(他の場所に保存してもよいのですが、このFileListフォールダーが仕様上の定位置です。)
ステータスバーの第2区画に選択されたプレイリストファイル名が出ていたら、ダイアログを終了します。この際「ファイル選択最終確認」が出ますので、はい、またはいいえで答えてください。
プレイリストを再生するには、ツールバーのピンク色右向きの再生ボタンを押します。再生中の操作は↑の単発再生と同じです。再生エラーや再生の中止を選択した場合で、再生ファイルがまだ残っている場合(これは「プレイリスト」「プレイリストを表示」で確認できます)、プレイリスト再生を継続するか否か家訓を求めてきますので、またはいいえで答えてください。

如何でしょうか?iTuneを持っていますが、なんか重くてBGMを聞こうなどとは思っていませんでしたが、こいつは簡単なのでプログラミング中などにBGMを流したり、昔撮った動画などをつまみに一杯やるなどして楽しんでいます。いずれにしてもBCCForm and BCCSkeltonをBCC102対応にし、COMを使ったプログラムを作りたい、という希望がやっと叶えられて満足、満足です。

 

今回はウィンドウ関連のメンバー関数の実装をUser.hとDirectShowProc.hで見てゆきます。まずはUser.hからです。

【User.h】
///////////////////////////
//ツールバーのサブクラス化
///////////////////////////
WNDPROC g_TBProc = NULL;
//(解説:ツールバーコントロールの本来のコールバック関数を記録する為の変数です。)

//TOOLBARのメッセージからWM_HSCROLLのみを取得する
LRESULT CALLBACK TOOLBARProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

    int pos;
    switch (uMsg) {
    case WM_HSCROLL:
        if(lParam != (LPARAM)GetDlgItem(hWnd, IDC_SLIDER))
            break;                            //スライダーからの通知でなければ何もしない
        switch(LOWORD(wParam)) {            //スライダーコントロールからの通知
        case SB_THUMBTRACK:                    //スライダーのドラッグ時は何もしない
            return 0;
        case SB_THUMBPOSITION:                //摘みを離した時に
            pos = HIWORD(wParam);            //スライダーの位置を取得
            break;
        default:                            //それ以外の場合、HIWORD(wParam)は使えないので
            pos = g_Slider.GetPosition();    //GetPosition関数で位置を取得する
            break;
        }
        g_dshow.SetCurrentPos((REFTIME)pos);
        return 0;

    default:
        break;
    }
    return CallWindowProc(g_TBProc, hWnd, uMsg, wParam, lParam);
}
//(解説:ツールバーコントロールの本来のコールバック関数をかすめ取ってg_TBProcに入れ、その代わりにこのTOOLBARProcを呼ぶようにします。そしてWM_HSCROLLの処理(スライダーの位置を取得して、DirectShowの再生位置を設定する)だけして、後は本来のコールバック関数に処理を任せます。何故SB_THUMBTRACKだけ何もしないかというと、この摘みのスライドの際に出される(膨大な)通知を拾っていると(DirectShowの設定処理が遅いので)メッセージキューが一杯になるのか、プログラムが落ちてしまうらしい(未検証)ので、負荷を下げる意味でこれはDirectShowの再生位置設定の対象外としました。(現在はほぼ正常に動いています。が、乱暴に摘みを扱うと落ちることもあるので、異常値が渡されることもあるかもしれません。あー、デバッガーがあればな。)なお、このサブクラス化という手法は定番ですので覚えておいてください。)

//////////////////////////
//プレイリスト再生注フラグ
//////////////////////////
bool g_AutoPlay = FALSE;
//(解説:プレイリスト実行中の際のフラグです。)

次にDirectShowProc.hを解説します。

【DirectShowProc.h】
//////////////////////////////////////////
// DirectShowProc.h
// Copyright (c) 01/31/2022 by BCCSkelton
//////////////////////////////////////////

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////
bool CMyWnd::OnCreate(WPARAM wParam, LPARAM lParam) {

    //コモンコントロールの初期化
    InitCommonControls();

    //ツールバー登録-Init(hWnd, hIinstance, ID, (以下省略可)Style)
    TBar.Init(m_hWnd, m_hInstance, TOOLBAR);
    //ツールバーボタン用カスタムビットマップ追加
    TBar.AddBmp(m_hInstance, MAKEINTRESOURCE(IDI_TOOLBAR), 9);
    //ツールバーボタン追加
    TBBUTTON tbb[13];
    ZeroMemory(tbb, sizeof(tbb));
    tbb[0].iBitmap = TBar.m_id + 0;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = IDM_OPEN;
    tbb[1].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[2].iBitmap = TBar.m_id + 1;
    tbb[2].fsState = TBSTATE_ENABLED;
    tbb[2].fsStyle = TBSTYLE_BUTTON;
    tbb[2].idCommand = IDM_EXIT;
    tbb[3].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[4].iBitmap = TBar.m_id + 2;
    tbb[4].fsStyle = TBSTYLE_BUTTON;
    tbb[4].idCommand = IDM_SHOW;
    tbb[5].iBitmap = TBar.m_id + 3;
    tbb[5].fsStyle = TBSTYLE_BUTTON;
    tbb[5].idCommand = IDM_PAUSE;
    tbb[6].iBitmap = TBar.m_id + 4;
    tbb[6].fsStyle = TBSTYLE_BUTTON;
    tbb[6].idCommand = IDM_STOP;
    tbb[7].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[8].iBitmap = TBar.m_id + 5;
    tbb[8].fsState = TBSTATE_ENABLED;
    tbb[8].fsStyle = TBSTYLE_BUTTON;
    tbb[8].idCommand = IDM_MAKELIST;
    tbb[9].iBitmap = TBar.m_id + 6;
    tbb[9].fsStyle = TBSTYLE_BUTTON;
    tbb[9].idCommand = IDM_AUTOPLAY;
    tbb[10].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[11].iBitmap = TBar.m_id + 7;
    tbb[11].fsState = TBSTATE_ENABLED;
    tbb[11].fsStyle = TBSTYLE_BUTTON;
    tbb[11].idCommand = IDM_ABOUT;
    tbb[12].iBitmap = TBar.m_id + 8;
    tbb[12].fsState = TBSTATE_ENABLED;
    tbb[12].fsStyle = TBSTYLE_BUTTON;
    tbb[12].idCommand = IDM_VERSION;
    TBar.AddButtons(13, tbb);

    //ステータスバー登録-Init(hWnd, hIinstance, ID, (以下省略可)Style)
    SBar.Init(m_hWnd, m_hInstance, STATUSBAR);
    //ステータスバー区画設定
    int sec[3] = {120, 240, -1};
    SBar.SetSection(3, sec);
    //ステータスバー文字列設定
    SBar.SetText(0, "DirectShow Ver 1.1");
    SBar.SetText(1, "再生時間");
    SBar.SetText(2, "ファイル名");
//(解説:ここはSkeltonWizard通りです。今回はステータスバーを3区画としました。)

    //メニュー、ツールバーボタン等初期化
    ChangeMenuStatus(0);    //「再生」、「一時停止」、「中止」
    AllowtoOpen(TRUE);        //「動画ファイルを開く」、「URLから開く」
    AutoPlay(TRUE, FALSE);    //「プレイリストの作成・選択」、「プレイリストの再生」
//(解説:コメントにあるメニュー、ツールバーボタンの状態を変えます。)

    //ヘルプファイル登録
    g_HelpFile = arg.Path();
    g_HelpFile = g_HelpFile + "\\DirectShowHelp.chm";
//(解説:ヘルプファイルのパス、名を記録します。)

    //ファイルリストディレクトリーの作成
    g_FileListDir = arg.Path();
    g_FileListDir = g_FileListDir + "\\FileList";
    if(!PathFileExists(g_FileListDir.ToChar()))
        CreateDirectory(g_FileListDir.ToChar(), NULL);
//(解説:プレイリストファイルのフォールダーをプログラムの下の"FileList"フォールダーとします。)

    //再生用スライダーコントロールの作成
    g_Slider.Create(TBar.GetHandle(), IDC_SLIDER, 340, 0, 300, 24);
    ShowWindow(g_Slider.GetHandle(), SW_HIDE);    //スライダーコントロールを隠す
//(解説:スライダーコントロールは再生時にのみ表示します。)
    //スライダーの親であるツールバーのWM_HSCROLL処理を行う為のサブクラス化
    g_TBProc = (WNDPROC)GetWindowLongPtr(GetDlgItem(m_hWnd, TOOLBAR), GWLP_WNDPROC);
    SetWindowLongPtr(GetDlgItem(m_hWnd, TOOLBAR), GWLP_WNDPROC, (LONG_PTR)TOOLBARProc);
//(解説:↑で見たように、スライダーコントロールの通知を拾うためにツールバーのコールバック関数をサブクラス化します。)

    //ドラッグアンドドロップの受付開始
    DragAcceptFiles(m_hWnd, TRUE);

    //ファイル起動時の処理
    if(arg.c() > 1) {
        if( strstr(arg.v(1), ".avi") || strstr(arg.v(1), ".AVI") ||
            strstr(arg.v(1), ".mp4") || strstr(arg.v(1), ".MP4") ||
            strstr(arg.v(1), ".mov") || strstr(arg.v(1), ".MOV") ||
            strstr(arg.v(1), ".mp3") || strstr(arg.v(1), ".MP3") ||
            strstr(arg.v(1), ".wav") || strstr(arg.v(1), ".WAV") ||
            strstr(arg.v(1), ".mid") || strstr(arg.v(1), ".MID")) {
            g_ByFile = TRUE;
            g_dshow.SetFileName(arg.v(1));
            OnOpen();
        }
        else
            MessageBox(m_hWnd, "再生対象外のファイルです", "エラー", MB_OK | MB_ICONERROR);
    }
//(解説:対象ファイル名か否かをチェックしてOnOpen関数へ渡します。ファイル起動はメディアファイルだけでplfは対象外です。)

    return TRUE;
}

bool CMyWnd::OnTimer(WPARAM wParam, LPARAM lParam) {

    int cp = (int)g_dshow.GetCurrentPos();
    char* tm = GetTimeStr(g_Mtm - cp);
    //スライダーコントロールの表示
    g_Slider.SetPosition(cp);
    //ステータスバー文字列設定
    SBar.SetText(1, tm);
    return TRUE;
}
//(解説:DirectShowの再生状態(経過時間)を取得し、スライダーの位置を変え、残再生時間を表示します。)

bool CMyWnd::OnLButtonDown(WPARAM wParam, LPARAM lParam) {

    //一時停止(1)または再生中(2)(タイムラグ10/1000秒)
    if(g_dshow.GetState(10) == 1 || g_dshow.GetState(10) == 2) {
        OnPause();
        return TRUE;
    }
    return FALSE;
}
//(解説:マウス左ボタンをクリックするとOnPause関数を呼び、一時停止(またはその解除)になります。)

bool CMyWnd::OnRButtonDown(WPARAM wParam, LPARAM lParam) {

    //ポップアップメニューを読みこむ
    HMENU hPopupMenu = GetSubMenu(GetMenu(m_hWnd), 1);
    //ポップアップメニューの表示
    POINT pt;
    GetCursorPos(&pt);
    TrackPopupMenu(hPopupMenu, TPM_LEFTALIGN | TPM_LEFTBUTTON,
                  pt.x, pt.y, 0, m_hWnd, NULL);
    return TRUE;
}
//(解説:マウス右ボタンをクリックすると、動画メニューがポップアップします。)

bool CMyWnd::OnNotify(WPARAM wParam, LPARAM lParam) {

    //ツールバーからのツールチップ情報取得
    if(((LPNMHDR)lParam)->code == TTN_NEEDTEXT) {
        static LPTOOLTIPTEXT lptip;
        lptip = (LPTOOLTIPTEXT)lParam;
        switch (lptip->hdr.idFrom) {
            case IDM_OPEN:
                lptip->lpszText = "動画ファイルを開く";
                break;
            case IDM_EXIT:
                lptip->lpszText = "終了";
                break;
            case IDM_SHOW:
                lptip->lpszText = "再生";
                break;
            case IDM_PAUSE:
                lptip->lpszText = "一時停止";
                break;
            case IDM_STOP:
                lptip->lpszText = "中止";
                break;
            case IDM_MAKELIST:
                lptip->lpszText = "プレイリストの作成・選択";
                break;
            case IDM_AUTOPLAY:
                lptip->lpszText = "プレイリストの再生";
                break;
            case IDM_ABOUT:
                lptip->lpszText = "DirectShowについて";
                break;
            case IDM_VERSION:
                lptip->lpszText = "バージョン情報";
                break;
        }
        return TRUE;
    }
    return FALSE;
//(解説:定番のツールバーツールチップ処理です。)
}

bool CMyWnd::OnSize(WPARAM wParam, LPARAM lParam) {

    TBar.AutoSize();
    SBar.AutoSize();
    //スライダーコントロールの自動位置調整
    g_Slider.AutoSize(1);        //右上隅にmarginxは0、marginyは2で表示
    //クライアントエリアサイズ
    int w = LOWORD(lParam);        //ウインドウ幅
    int h = HIWORD(lParam);        //ウインドウ高さ
    //TBarとSBarの高さを控除
    RECT rec;
    GetWindowRect(SBar.GetHandle(), &rec);
    h -= rec.bottom - rec.top;
    GetWindowRect(TBar.GetHandle(), &rec);
    int barh = rec.bottom - rec.top;
    h -= barh;
    g_dshow.Move(0, barh, w, h);
    return TRUE;
//(解説:ツールバー、ステータスバーの調整の後、IMVideoWindowをクライアントエリアに広げます。)
}

bool CMyWnd::OnClose(WPARAM wParam, LPARAM lParam) {

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES)
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        return TRUE;
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

bool CMyWnd::OnMinMax(WPARAM wParam, LPARAM lParam) {

    //典型的なウィンドウのサイズ制限処理
    MINMAXINFO *pmmi;
    pmmi = (MINMAXINFO*)lParam;
    pmmi->ptMinTrackSize.x = 656;    //クライアントエリア640 + 16
    pmmi->ptMinTrackSize.y = 541;    //クライアントエリア480 + 61
    return FALSE;                    //処理はDefWndProcに任す
}
//(解説:私の場合、最小サイズを懐かしのVGAサイズ、640 x 480にしました。)

bool CMyWnd::OnGraphNotify(WPARAM wParam, LPARAM lParam) {

    long evnt;                    //EC_COMPLETE - 再生終了、EC_USERABORT - 再生中止、EC_ERRORABORT - エラー
    if(!g_dshow.IsOver(evnt))    //再生開始前や再生中は何もしない
        return FALSE;
    //終了処理
    WhenComplete();
    //プレイリスト再生中
    if(g_AutoPlay) {
        if(*m_FileList.ToChar())    //まだ完了していない場合
            OnAutoPlay();
        else {                        //完了した場合
            g_AutoPlay = FALSE;
            AutoPlay(TRUE, FALSE);    //プレイリストの作成・選択、再生
        }
    }    
    return TRUE;
}
//(解説:これがユーザー定義通知の処理です。再生終了時(eventに終了事由が入りますが、どのような終了事由であっても今回は関係ないので使いません)のみ終了処理を行い、プレイリスト再生中はプレイリストに再生ファイルが残っている間はプレイリスト再生を続けます。)

bool CMyWnd::OnDropFiles(WPARAM wParam) {

    //ドラッグアンドドロップされたファイル名取得と処理
    char FileName[MAX_PATH];
    DragQueryFile((HDROP)wParam, 0, FileName, MAX_PATH);
    if( strstr(FileName, ".avi") || strstr(FileName, ".AVI") ||
        strstr(FileName, ".mp4") || strstr(FileName, ".MP4") ||
        strstr(FileName, ".mov") || strstr(FileName, ".MOV") ||
        strstr(FileName, ".mp3") || strstr(FileName, ".MP3") ||
        strstr(FileName, ".wav") || strstr(FileName, ".WAV") ||
        strstr(FileName, ".mid") || strstr(FileName, ".MID")) {
        g_ByFile = TRUE;
        g_dshow.SetFileName(FileName);
        OnOpen();
        DragFinish((HDROP)wParam);
        return TRUE;
    }
    MessageBox(m_hWnd, "再生対象外のファイルです", "エラー", MB_OK | MB_ICONERROR);
    return FALSE;
//(解説:定番のドラッグアンドドロップ処理です。再生対象リストか否かの処理を行っています。)
}

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////
bool CMyWnd::OnOpen() {

    if(g_ByFile)
        g_ByFile = FALSE;
    else {
        //動画ファイルを読む
        if(!g_dshow.SetFileName(""))
            return FALSE;
    }
    //メニュー、ボタンの状態変更
    ChangeMenuStatus(1);
    //ステータスバー文字列設定
    SBar.SetText(2, g_dshow.m_FileName);
    SBar.SendMsg(SB_SETTIPTEXT, 2, (LPARAM)g_dshow.m_FileName);    //ToolTipをつける
    return TRUE;
//(解説:DirectShowにメディアファイルをセットします。D&Dの場合(g_ByFile == TRUE)は既にセット済でそうでない場合は「ファイルを開く」ダイアログで指定します。)

bool CMyWnd::OnUrl() {

    urldlg.DoModal(m_hWnd, "IDD_URL", urldlgProc, m_hInstance);
    //メニュー、ボタンの状態変更
    ChangeMenuStatus(1);
    //ステータスバー文字列設定
    SBar.SetText(2, g_dshow.m_FileName);
    SBar.SendMsg(SB_SETTIPTEXT, 2, (LPARAM)g_dshow.m_FileName);    //ToolTipをつける
    return TRUE;
//(解説:DirectShowにダイアログを使ってメディアファイル(URL)パス、名をセットします。}
}

bool CMyWnd::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);
    return TRUE;
//(解説:定番の終了処理です。}
}

bool CMyWnd::OnShow() {

    //再生開始
    if(g_dshow.Show(m_hWnd)) {
        g_Mtm = (int)g_dshow.GetDuration();
        char* tm = GetTimeStr(g_Mtm);
        //ステータスバー文字列設定
        SBar.SetText(1, tm);
        SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)tm);    //ToolTipをつける
        //メニュー、ボタンの状態変更
        ChangeMenuStatus(2);
        AllowtoOpen(FALSE);        //ファイル読み込みを不能にする
        //再生用スライダーコントロールの設定
        g_Slider.SetRange(0, (int)g_Mtm);
        g_Slider.SetPosition(0);
        //スライダーコントロールを表示する
        ShowWindow(g_Slider.GetHandle(), SW_SHOW);
        //タイマー開始(毎1秒)
        g_tmr.Begin(m_hWnd, 1, 1000);
        return TRUE;
//(解説:コメントにある通り、再生を開始し、再生時間の文字列を取得してステータスバーに表示し、ファイル読み込みを不可にして「一時停止」「中止」を有効にします。また、スライダーを表示し、その後の再生時間とスライダーの処理をタイマー割り込みに任せます。}
    }
    else
        return FALSE;
}

bool CMyWnd::OnPause() {

    static bool toggle = FALSE;
    if(toggle) {
        //再生再開(Run()メソッド)
        g_dshow.Continue();
        //メニュー、ボタンの状態変更
        ChangeMenuStatus(2);
        g_tmr.Begin(m_hWnd, 1, 1000);    //タイマー開始(毎1秒)
    }
    else {
        //再生一時停止
        g_dshow.Pause();
        //メニュー、ボタンの状態変更
        ChangeMenuStatus(3);
        g_tmr.Stop();                    //タイマー中止
    }
    toggle = !toggle;
    return TRUE;
//(解説:「一時停止」の場合、メニュー、ボタン状態を変更してタイマーを止めます。再度「一時停止」ボタンを押すと再生を再開し、メニュー、ボタン状態を再変更してタイマーを復活させます。)
}

bool CMyWnd::OnStop() {

    //再生中止
    g_dshow.Stop();
    //終了処理
    WhenComplete();
    //プレイリスト再生中
    if(g_AutoPlay) {
        if(*m_FileList.ToChar()) {        //まだ完了していない場合
            if(MessageBox(m_hWnd, "プレイリスト再生を続けますか?", "プレイリスト再生確認",
                        MB_YESNO | MB_ICONQUESTION) == IDYES)
                OnAutoPlay();
            else {                        //プレイリスト再生の中止
                m_FileList = "";
                g_AutoPlay = FALSE;
                AutoPlay(TRUE, FALSE);    //プレイリストの作成・選択、再生
            }
        }
        else {                            //完了した場合
            g_AutoPlay = FALSE;
            AutoPlay(TRUE, FALSE);        //プレイリストの作成・選択、再生
        }
    }    
    return TRUE;
//(解説:再生を中止し、終了後処理を行います。プレイリスト再生中は、まだ再生ファイルがある場合、再生を継続するか否か確認し、再生継続または完全中止にします。)
}

bool CMyWnd::OnMakelist() {

    //FileList.dllからダイアログを呼ぶ
    char* fn = GetFileList("プレイリストファイル(*.plf)\0*.plf\0\0", FILTER, g_FileListDir.ToChar());
    if(!*fn) {
        MessageBox(m_hWnd, "plfファイルが選択されませんでした", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    m_FileList.FromFile(fn);
    //プレイリストの再生メニューとツールボタンを有効化する
    AutoPlay(FALSE, TRUE);
    return TRUE;
//(解説:FileList.dllを使ってファイルリスト(プレイリスト)を作成、指定します。指定があればプレイリストをm_FileListに読み込み、「プレイリストの作成、指定」メニューを無効として、「プレイリスト再生」メニューを有効にします。)
}

bool CMyWnd::OnAutoPlay() {

    CSTR fn, comment;
    if(!m_FileList.Next(fn)) {        //ファイルリストから再生ファイルのフルパス名を切り出す
        g_AutoPlay = FALSE;
        AutoPlay(TRUE, FALSE);
    }
    else {                            //ファイルリストに再生ファイルがあったならば
        m_FileList.Next(comment);    //ファイルリストから不要な「備考」を切り出す
        //DirectShowに再生ファイルを登録
        g_dshow.SetFileName(fn.ToChar());
        //ステータスバー文字列設定
        SBar.SetText(2, g_dshow.m_FileName);
        SBar.SendMsg(SB_SETTIPTEXT, 2, (LPARAM)g_dshow.m_FileName);    //ToolTipをつける
        //プレイリストの作成・選択、再生メニューを無効化する
        AutoPlay(FALSE, FALSE);
        //プレイリスト再生状態
        g_AutoPlay = TRUE;
        //再生
        if(!OnShow())                    //プレイリストの再生に失敗した場合
            OnStop();
    }
    return TRUE;
//(解説:「プレイリスト再生」が指定されると、メニュー、ボタン状態を変更し、プレイリスト(m_FileList)が空になるまで再生を継続します。再生でエラーが生じた場合、再生を中止し、再生継続の確認をします。(ここで結構考えました。)再生ファイルが無くなればプレイリスト再生を終了します。)
}

bool CMyWnd::OnPlayList() {

    CSTR List, FileList, FileName;
    CARG arg;
    FileList = m_FileList;
    while(FileList.Next(FileName)) {
        arg = FileName.ToChar();
        List = List + arg.FileName();
        List = List + "\r\n";
        FileList.Next(FileName);        //備考をスキップする
    }
    MessageBox(m_hWnd, List.ToChar(), "プレイリスト", MB_OK | MB_ICONINFORMATION);
    return TRUE;
//(解説:m_FileListに残っている再生ファイルをメッセージボックスで表示します。)
}

bool CMyWnd::OnAbout() {

    CSTR cmd = "hh ";
    cmd = cmd + g_HelpFile;
    WinExec(cmd.ToChar(), SW_SHOWNORMAL);
    return TRUE;
//(解説:ヘルプファイルをHTMLHelpで表示します。)
}

bool CMyWnd::OnVersion() {

    versiondlg.DoModal(m_hWnd, "IDD_VERSION", versiondlgProc, m_hInstance);
    return TRUE;
//(解説:バージョンダイアログを表示します。)
}

////////////////////////////////////////////////////////
//ユーザー定義関数
//スライダー非表示とタイマー、再生時間メニュー等の初期化
////////////////////////////////////////////////////////
void CMyWnd::WhenComplete() {

    //タイマー中止
    g_tmr.Stop();
    //再生時間の初期化
    g_Mtm = 0;
    //スライダーコントロールを隠す
    ShowWindow(g_Slider.GetHandle(), SW_HIDE);
    //再生終了後、ファイル読み込み後状態にする(再生可)
    ChangeMenuStatus(1);
    //ファイル読み込みを可能にする
    AllowtoOpen(TRUE);
//(解説:ファイル毎の終了処理です。)
}

///////////////////////////////////////////////////
//ユーザー定義関数
//初期状態・終了時0
//ファイル読込み時1、再生時2、一時停止時3(トグル)
///////////////////////////////////////////////////
void CMyWnd::ChangeMenuStatus(int flag) {

    HMENU hMenu = GetSubMenu(GetMenu(m_hWnd), 1);    //メニューハンドルの取得
    switch(flag) {
    case 0:        //全て無効化する
        //メニューアイテム
        EnableMenuItem(hMenu, 0, MF_BYPOSITION | MF_GRAYED);
        EnableMenuItem(hMenu, 1, MF_BYPOSITION | MF_GRAYED);
        EnableMenuItem(hMenu, 2, MF_BYPOSITION | MF_GRAYED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_SHOW, MAKELONG(FALSE, 0));
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_PAUSE, MAKELONG(FALSE, 0));
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_STOP, MAKELONG(FALSE, 0));
        break;
    case 1:        //再生のみ有効化する
        //メニューアイテム
        EnableMenuItem(hMenu, 0, MF_BYPOSITION | MF_ENABLED);
        EnableMenuItem(hMenu, 1, MF_BYPOSITION | MF_GRAYED);
        EnableMenuItem(hMenu, 2, MF_BYPOSITION | MF_GRAYED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_SHOW, MAKELONG(TRUE, 0));
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_PAUSE, MAKELONG(FALSE, 0));
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_STOP, MAKELONG(FALSE, 0));
        break;
    case 2:        //再生を無効化し、一時停止、中止を有効化する
        //メニューアイテム
        EnableMenuItem(hMenu, 0, MF_BYPOSITION | MF_GRAYED);
        EnableMenuItem(hMenu, 1, MF_BYPOSITION | MF_ENABLED);
        EnableMenuItem(hMenu, 2, MF_BYPOSITION | MF_ENABLED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_SHOW, MAKELONG(FALSE, 0));
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_PAUSE, MAKELONG(TRUE, 0));
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_STOP, MAKELONG(TRUE, 0));
        break;
    case 3:        //再生、中止を共に無効化する
        //メニューアイテム
        EnableMenuItem(hMenu, 0, MF_BYPOSITION | MF_GRAYED);
        EnableMenuItem(hMenu, 2, MF_BYPOSITION | MF_GRAYED);
        //ツールバーボタン
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_SHOW, MAKELONG(FALSE, 0));
        SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_STOP, MAKELONG(FALSE, 0));
        break;
    default:
        return;
    }
    DrawMenuBar(m_hWnd);    //Re-draw
//(解説:「再生」、「一時停止」、「中止」のメニュー、ボタンの状態を変化させます。)
}

/////////////////////////////////////////////////////
//ユーザー定義関数
//「ファイルを開く」と「URLから開く」の状態を変更する
/////////////////////////////////////////////////////
void CMyWnd::AllowtoOpen(bool flag) {

    HMENU hMenu = GetSubMenu(GetMenu(m_hWnd), 0);    //メニューハンドルの取得
    //メニューアイテム
    EnableMenuItem(hMenu, 0, MF_BYPOSITION | (flag ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem(hMenu, 1, MF_BYPOSITION | (flag ? MF_ENABLED : MF_GRAYED));
    //ツールバーボタン
    SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_OPEN, MAKELONG(flag, 0));
    SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_URL, MAKELONG(flag, 0));
//(解説:「動画ファイルを開く」、「URLから開く」のメニュー、ボタンの状態を変化させます。)
}

//////////////////////////////////////////////////////////////////////
//ユーザー定義関数
//「プレイリストの作成・選択」と「プレイリストの再生」の状態を変更する
//////////////////////////////////////////////////////////////////////
void CMyWnd::AutoPlay(bool flag1, bool flag2) {

    HMENU hMenu = GetSubMenu(GetMenu(m_hWnd), 2);    //メニューハンドルの取得
    //メニューアイテム
    EnableMenuItem(hMenu, 0, MF_BYPOSITION | (flag1 ? MF_ENABLED : MF_GRAYED));
    EnableMenuItem(hMenu, 1, MF_BYPOSITION | (flag2 ? MF_ENABLED : MF_GRAYED));
    //ツールバーボタン
    SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_MAKELIST, MAKELONG(flag1, 0));
    SendMessage(TBar.GetHandle(), TB_ENABLEBUTTON, IDM_AUTOPLAY, MAKELONG(flag2, 0));
//(解説:プレイリストの「プレイリストの作成・選択」と「プレイリスト再生」のメニュー、ボタンの状態を変化させます。)
}

////////////////////////////////////////////
//ユーザー定義関数
//整数の秒数を「時間分秒」の文字列にして返す
////////////////////////////////////////////
char* CMyWnd::GetTimeStr(int tm) {

    static CSTR tmstr;
    int hour, min, sec;
    hour = tm / 3600;
    min = sec = tm % 3600;
    min /= 60;
    sec %= 60;
    tmstr = "残り";
    if(hour) {
        tmstr = tmstr + hour;
        tmstr = tmstr + "時間";
    }
    tmstr = tmstr + min + "分";
    tmstr = tmstr + sec + "秒";
    return tmstr.ToChar();
//(解説:整数の秒を「残り(H時間)M分S秒」形式の文字列にして返します。)
}

///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////
bool URLDLG::OnIdok() {

    char buff[MAX_PATH];
    SendItemMsg(IDC_URL, WM_GETTEXT, MAX_PATH, (LPARAM)buff);
    if(g_dshow.SetFileName(buff)) {
        EndModal(TRUE);
        return TRUE;
    }
    else {
        EndModal(FALSE);
        return FALSE;
    }
//(解説:エディットボックスに入力された文字列をDirectShowにファイル名としてセットします。手を抜いて、入力文字列のエラーチェックはしていません。)
}

bool URLDLG::OnCancel() {

    EndModal(FALSE);
    return FALSE;
}

///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////
bool VERSIONDLG::OnIdok() {

    EndModal(TRUE);
    return TRUE;
//(解説:バージョンダイアログを閉じるだけです。)
}

以上でDirectShow Version 1.1のプログラム解説を終わります。次回はバッチファイルと開発後記にしようと思います。
 

本来今日はブログではなく、「プログラミング」の「【DirectShow】DirectShowプログラム(3)」を書く予定でしたが、ふとした契機で矢鱈デバッグにはまってしまいました。

 

実は完成してから安定して動いていたVersion 1.1でしたが、「プレイリスト再生」中に、「例えば(昔の私にとってのmp4やmovファイルのような)有効な再生用のフィルターグラフが無いファイルがリストに入っていた場合(注1)、どうなるのか?」という疑問が天から降りてきました。(注2)

注1:CDSHOW.hの179行のメッセージボックスで「フィルターグラフを作成できませんでした」エラーが発生します。

注2:その後延々とデバグを行い、大変疲れましたが、公表してからバグを出すよりは1000倍ラッキーだというべきでしょう。

 

この為、私はMIDI.plfというプレイリストを使って、中の1曲のファイル名を変えて故意に「フィルターグラフを作成できませんでした」エラーを出すようにしました。

 

その結果、思うような動作をしないどころか、曲を早く終了させるためにスライダーコントロールを動かしていたら「プログラムが落ちる」という新しいトラブル現象が発生し、目が点に。

 

こういう時はパニクってドツボにはまるので夜眠れなくなるのですが、歳をとって知恵が付き(というよりも衰えて気力、体力が尽き)先ずは一晩おいて考えようということにしました。それでもやはり夜中に目が覚めてコードを思い出しながら、あそこはこういう現象で、こうなっているからこうすべきでは、などと考え、興奮してまた眠れなくなります。

 

いずれにせよ、本日はまたまたデバッグに精を出して、

 

(1)問題点を整理してひとつづくつぶしてゆく。

(2)もう一度頭を白紙にしてコードを点検してゆく。

(3)実際にトラブル現象を発生させ、その工程にトラップをかけて何が起こっているのかを確認する。

(4)最適解を検討する。

 

を続けました。その結果、

 

(1)トラックバー問題は、最初トラックバーを極端に引っ張ると異常値が出るのでは?と疑っていたのですが、何度もエラーを出していくと、どうもウィンドウズのメッセージキューが過大になって落ちているのではないか、と感じられ、トラックバーの摘まみをドラッグする際のDirectShowへの再生位置変更が問題ではないか、とあたりを付け、「トラックバーの摘まみをドラッグする際の通知は処理しない」という負荷軽減策で何とか凌いだように感じます。(それでも手荒な操作で時々落ちる可能性がありますが。→そろそろエラー処理を入れようかな?)

 

(2)プレイリスト再生でエラーが出た際の復帰処理を色々と試してみて、矢張り「中止」状態にして「プレイリストの再生を続けますか?」という確認を出すことが一番自然に感じ、そのようにしました。

 

(3)見直しついでに本日既に公表したCDSHOW.hの内容も少し修正し、プレイリスト再生は黙って終わっていたのですが、「プレイリストの再生を完了しました」というメッセージを出すことにしました。

 

結果としてDirectShowやスライダーについてまた知見が増え、プログラムも一段とまともなにはなったと思いますが、今回のことで「どっかにまだ気づかないバグが残っているのでは?」という不安も完全にぬぐえません。ということで、(Vectorもまだ復旧していないことから)何日かプログラムを使ってみて安定性を確認してから最後のDirectShowProc.hを公開することにします。

 

いずれにせよ、疲れましたが、勉強にもなった一日でした。

 

今回はこのプログラムの核心、DirectShowのクラスCDSHOWを(解説:)します。(

注:このブログをアップした後、DirectShowのエラー発生時対応を改良した為、以下のファイルを2か所修正しました。一つはGetDuration()関数にREFTIMEのローカス変数を使っていましたが、メンバー変数のm_Lenにしたこと(というか、完全に忘れていました、この変数!)、もう一つはIsOver()関数が真偽(bool)の戻り値でしたが、正確な終了自由を取りたいユーザーがいるはずなので「参照型long引数(long&)」を与え、IsOver(evnt)のように使うことで、再生終了、再生中止、再生エラーの終了事由を取ることができるようにしました。以下は既に修正されています。

【CDSHOW.h】
///////////////////////////
//CDSHOWクラス定義ファイル
///////////////////////////
#include <DShow.h>                    //DirectShowヘッダー
//(解説:DirectShowを使う場合には、このヘッダーファイルを必ず読み込みます。)
#include <wchar.h>
//(解説:COMの文字列はワイド文字が標準であり、wchar.hは必ず読み込みます。)

//フィルター定義(利用者のフィルターの環境によって適宜変更が必要
#define    FILTER    "ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オーディオファイル(*.mp3;*.wav;*.mid)\0*.mp3;*.wav;*.mid\0\0"
//(解説:これは私の利用したいファイル種類を列挙しただけで、DirectShowはこれ以上の対応幅があります。)

//親ウィンドウへの通知メッセージ
#define WM_GRAPHNOTIFY  WM_APP + 1
//(解説:ユーザー定義メッセージです。)

class CDSHOW {

protected:
    //DirectShow親ウィンドウ
    HWND m_hWnd;
    //DirectShow関連変数
    IGraphBuilder *m_pGraph;        //フィルターグラフビルダークラスポインター
    IMediaControl *m_pControl;        //メディアコントロールインターフェースポインター
    IMediaEventEx *m_pEvent;        //メディアイベントインターフェースポインター
    IVideoWindow *m_pVideoWindow;    //ビデオウィンドウポインター
    IMediaPosition *m_pMPos;        //メディア再生位置インターフェースポインター
    //結果判定用変数
    HRESULT m_hr;
//(解説:内部で処理する関連の変数なのでprotectedとしました。)
public:
    //ファイル名取得用変数
    char m_FileName[MAX_PATH];
    WCHAR m_WFileName[MAX_PATH];
//(解説:ファイル名はユーザーからアクセスすることを許します。通常はマルチバイトのchar*を使います。)
    //ファイル再生時間変数(double)
    REFTIME m_Len;
//(解説:再生時間取得用で、REFTIMEはd実際はdoubleです。)
    //メンバー関数
    CDSHOW();                        //コンストラクター
    ~CDSHOW();                        //デストラクター
    void Init();                    //メンバー関数の初期化
    bool Move(int, int, int, int);    //ビデオウィンドウの位置設定(x, y, w, h)
    bool SetFileName(char*, char*);    //ビデオファイル名を設定する
    bool Show(HWND);                //ビデオウィンドウの初期化とビデオの再生
    REFTIME GetDuration();            //ビデオ再生時間を返す
    OAFilterState GetState(int);    //フィルターグラフの再生状態を取得(0-Stopped、1-Paused、2-Running)
    bool Pause();                    //ビデオの一時停止
    bool Continue();                //ビデオの再開
    void Stop();                    //ビデオの中止
    REFTIME GetCurrentPos();        //現在の再生時間を返す
    bool SetCurrentPos(REFTIME);    //再生位置(時間-秒)を設定する
    bool IsOver(long&);                //親ウィンドウに置く終了モニタリング関数
    void CleanUp();                    //ビデオウィンドウ、フィルターグラフの終了処理
};

//コンストラクター
CDSHOW::CDSHOW() {

    //ワイド文字使用のためロケールの初期化(日本語)
    setlocale(LC_CTYPE, "JPN");
    //メンバー変数の初期化
    Init();
    //COMの初期化
    m_hr = CoInitialize(NULL);
    if(FAILED(m_hr))
        MessageBox(m_hWnd, "COMを初期化できませんでした", "エラー", MB_OK | MB_ICONERROR);
//(解説:マルチバイト文字のワイド文字変換にはロケールが不可欠です。メンバー変数の初期化はInit関数にまとめ、一回だけ行うCOM初期化も実行します。)
}

//デストラクター
CDSHOW::~CDSHOW() {

    //フィルターグラフの終了処理
    CleanUp();
//(解説:メモリー解放を行うべきものはCleanUpで纏めて実施します。)
    //COMの終了処理
    CoUninitialize();
//(解説:COMの必須終了処理です。)
}

//メンバー関数の初期化
void CDSHOW::Init() {

    //親ウィンド    ウハンドル
    m_hWnd = NULL;
    //DirectShow関連変数
    m_pGraph = NULL;
    m_pControl = NULL;
    m_pEvent = NULL;
    m_pVideoWindow = NULL;
    m_pMPos = NULL;
    m_Len = 0;
//(解説:メンバー変数の初期化です。再生のたびに実施します。)
}

//ビデオウィンドウの位置設定
bool CDSHOW::Move(int x, int y, int w, int h) {

    //ビデオウィンドウが無ければ何もしない
    if(!m_pVideoWindow)
        return FALSE;
    if(m_pVideoWindow) {
        //描画先ウィンドウ内の描画位置の設定
        m_hr = m_pVideoWindow->SetWindowPosition(x, y, w, h);
        if(m_hr != S_OK && m_hr != E_NOINTERFACE) {    //VFW_E_NOT_CONNECTEDではなく、E_NOINTERFACEが返される
//(解説:例の「画像フィルターが不要でビデオウィンドウが作れてしまう場合」の仕様外のエラーメッセージが出る件です。)
            MessageBox(m_hWnd, "ビデオウィンドウの位置を設定できませんでした", "エラー", MB_OK | MB_ICONERROR);
            return FALSE;
        }
        return TRUE;
    }
    else
        return FALSE;
}

//ビデオファイル名を設定する
bool CDSHOW::SetFileName(char* fn, char* ftr = FILTER) {

//(解説:DirectShowに読ませるワイド文字ファイルパス、名です。マルチバイト文字のファイルパス、名を変換します。)
    if(*fn) {
//(解説:ファイル名指定がある場合です。)
        if(lstrlen(fn) > MAX_PATH - 1) {
            MessageBox(m_hWnd, "ファイルパス、名またはURLが長すぎます", "エラー", MB_OK | MB_ICONERROR);
            *m_FileName = NULL;
            *m_WFileName = L'\0';    //16bitの終端文字
            return FALSE;
        }
        else {
            //ファイル名の記録
            lstrcpy(m_FileName, fn);
            //ファイル名のワイド文字化
            mbstowcs(m_WFileName, m_FileName, MAX_PATH);
            return TRUE;
        }
    }
    else {
//(解説:ファイル名指定が無い("")場合です。cmndlgを使わずWin32APIのGetOpenFileNameで書いてみました。)
        OPENFILENAME ofn;                //オープンファイルダイアログ構造体
        ZeroMemory(&ofn, sizeof(ofn));    //ゼロ初期化
        ofn.lStructSize        = sizeof(ofn);
        ofn.hwndOwner        = m_hWnd;
        ofn.lpstrFilter        = ftr;        //引数でフィルタリングする
        ofn.nFilterIndex    = 1;
        ofn.lpstrFile        = m_FileName;
        ofn.nMaxFile        = MAX_PATH;
        ofn.lpstrTitle        = NULL;
        ofn.lpstrInitialDir = ".";
        ofn.Flags            = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
        ofn.lpstrDefExt        = NULL;
        bool success = GetOpenFileName(&ofn);
        if(success) {
            //ファイル名のワイド文字化
            mbstowcs(m_WFileName, m_FileName, MAX_PATH);
            return TRUE;
        }
        else {
            *m_FileName = NULL;
            *m_WFileName = L'\0';    //16bitの終端文字
            return FALSE;
        }
    }
}

//ビデオウィンドウの初期化とビデオの再生
bool CDSHOW::Show(HWND hWnd) {

//(解説:ワイド文字ファイルパス、名をセットし、再生の都度インスタンスを作成し、終了時に開放します。)
    //親ウィンドウの記録
    m_hWnd = hWnd;
    //フィルターグラフマネージャーを作成し、IGraphBuilderインターフェイスを取得
    m_hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
                            IID_IGraphBuilder, (void **)&m_pGraph);
    if(FAILED(m_hr)) {
        MessageBox(m_hWnd, "フィルターグラフマネージャーを作成できませんでした",
                    "エラー", MB_OK | MB_ICONERROR);
        goto exit;

//(解説:多くのチェックがあるのでエラー処理をまとめて、久々のgotoを使いました。。)

    }

    //IGraphBuilderからIMediaControlインターフェイスを取得
    m_hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pControl);
    if(FAILED(m_hr)) {
        MessageBox(m_hWnd, "メディアコントロールインターフェースを取得できませんでした",
                    "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //IGraphBuilderからIMediaEventインターフェイスを取得
    m_hr = m_pGraph->QueryInterface(IID_IMediaEvent, (void **)&m_pEvent);
    if(FAILED(m_hr)) {
        MessageBox(m_hWnd, "メディアイベントインターフェースを取得できませんでした",
                    "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //指定されたファイルを再生できるフィルターグラフを作成する(存在しない場合、エラー)
    m_hr = m_pGraph->RenderFile(m_WFileName, NULL);
    if(FAILED(m_hr)) {
        MessageBox(m_hWnd, "フィルターグラフを作成できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
//(解説:特定のPC環境で再生できるフィルターが無い場合、ここでエラーとなります。)
    //再生位置インターフェースを取得する
    m_pGraph->QueryInterface(IID_IMediaPosition, (LPVOID *)&m_pMPos);

//(解説:再生状態の音とロールに必要です。)

    //動画ファイルの長さ(秒-double)を取得
    m_pMPos->get_Duration(&m_Len);

//(解説:ファイルの再生時間(管理上の分母)を取得します。)

    //親ウィンドウへの通知を開始する
    m_pEvent->SetNotifyWindow((OAHWND)m_hWnd, WM_GRAPHNOTIFY, 0);

//(解説:イベントメッセージを親に転送し、状態を把握します。以上は再生状況監視、コントロール用です。)
    //ビデオコントロールを取得する
    m_hr = m_pGraph->QueryInterface(IID_IVideoWindow, (LPVOID*)&m_pVideoWindow);
    if(FAILED(m_hr)) {
        MessageBox(m_hWnd, "ビデオウィンドウを取得できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //親ウィンドウの設定
    m_hr = m_pVideoWindow->put_Owner((OAHWND)m_hWnd);
    if(m_hr != S_OK && m_hr != E_NOINTERFACE) {    //VFW_E_NOT_CONNECTEDではなく、E_NOINTERFACEが返される
//(解説:以降は例の「画像フィルターが不要でビデオウィンドウが作れてしまう場合」の仕様外のエラーメッセージが出る件です。FAILED()マクロは使えません。)
        MessageBox(m_hWnd, _T("親ウィンドウを設定できませんでした"), _T("エラー"), MB_OK | MB_ICONERROR);
        goto exit;
    }
    //ウィンドウスタイルを設定する
    m_hr = m_pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
    if(m_hr != S_OK && m_hr != E_NOINTERFACE) {    //VFW_E_NOT_CONNECTEDではなく、E_NOINTERFACEが返される
        MessageBox(m_hWnd, "ウィンドウスタイルを設定できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //描画先ウィンドウを640 x 480の表示が行える最小サイズにする
    SetWindowPos(m_hWnd, HWND_TOP, 0, 0, 656, 541, SWP_NOMOVE);
//(解説:クライアント領域サイズ640x480のウィンドウサイズはPCによって異なる可能性があります。)
    //サイズ変更が無い場合の為に親ウィンドウへWM_SIZEを送る
    SendMessage(m_hWnd, WM_SIZE, 0, (LPARAM)MAKELONG(640, 480));
//(解説:既に最小サイズになっている場合、SetWindowPosではWM_SIZEが出ないので、強制的に出します。)
    //指定したウィンドウへIVideoWindowの通知を流す
    m_hr = m_pVideoWindow->put_MessageDrain((OAHWND)m_hWnd);
//(解説:この再生中、ビデオウィンドウの通知は親に流されます。マウスクリックやキー入力などで必要です。)
    //ウィンドウを表示する
    m_hr = m_pVideoWindow->put_Visible(OATRUE);
    if(m_hr != S_OK && m_hr != E_NOINTERFACE) {    //VFW_E_NOT_CONNECTEDではなく、E_NOINTERFACEが返される
        MessageBox(m_hWnd, "ビデオウィンドウを表示できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //動画表示を開始する。
    m_hr = m_pControl->Run();
    if(SUCCEEDED(m_hr))
        return TRUE;
    //以上が正常終了で、エラーがあると以下を実行する
exit:
    CleanUp();
//(解説:終了処理です。)
    return FALSE;
}

//ビデオ再生時間を返す
REFTIME CDSHOW::GetDuration() {

    m_pMPos->get_Duration(&m_Len);
    return m_Len;
//(解説:再生時間(秒単位)を返します。)
}

//フィルターグラフの再生状態を取得(0-Stopped、1-Paused、2-Running)
OAFilterState CDSHOW::GetState(int delay) {

    OAFilterState fs = 0;
    if(m_pControl)
        m_pControl->GetState(delay, &fs);    //処理遅延の為、delay(ミリ秒)後に取得
//(解説:再生終了を監視する目的で使い、delayミリ秒後の、コメントに書かれた状態を返します。ビデオコントロールインスタンスが無いと落ちますので注意してください。)
    return fs;
}

//ビデオの一時停止
bool CDSHOW::Pause() {

    //動画表示を一時停止する。
    m_hr = m_pControl->Pause();
    if(SUCCEEDED(m_hr))
        return TRUE;
    else
        return FALSE;
//(解説:これもビデオコントロールインスタンスが無いと落ちますが、メインウィンドウでメニュー、ボタン状態を有無効処理をしてしのいでいます。)
}

//ビデオの再開
bool CDSHOW::Continue() {
    //動画表示を一時停止する。
    m_hr = m_pControl->Run();
    if(SUCCEEDED(m_hr))
        return TRUE;
    else
        return FALSE;
}

//ビデオの中止
void CDSHOW::Stop() {

    //動画表示を中止し、終了処理を行う
    CleanUp();
}
//(解説:再生、一時停止、中止のコントロールに使います。)

//現在の再生時間を返す
REFTIME CDSHOW::GetCurrentPos() {

    REFTIME tm;
    m_pMPos->get_CurrentPosition(&tm);
    return tm;
}
//(解説:経過時間、残再生時間の取得の為に使います。)

//再生位置(時間-秒)を設定する
bool CDSHOW::SetCurrentPos(REFTIME tm) {

    m_pMPos->put_CurrentPosition(tm);
}
//(解説:スライダーで元に戻したり、先に進める為に使います。)

//親ウィンドウに置く終了モニタリング関数
bool CDSHOW::IsOver(long& evtCode) {

    //IMediaEvent *m_pEventが設定されていなければFALSEで返る
    if(!m_pEvent)
        return FALSE;
//(解説:これもインスタンスが無いのに実行すると落ちます。)

    //イベント処理
    LONG_PTR Param1, Param2;
    while(SUCCEEDED(m_pEvent->GetEvent(&evtCode, &Param1, &Param2, 0))) {
        m_pEvent->FreeEventParams(evtCode, Param1, Param2);
        switch(evtCode) {
        case EC_COMPLETE:    //再生終了
        case EC_USERABORT:    //再生中止
        case EC_ERRORABORT:    //エラー
            CleanUp();
            return TRUE;
        }
    }
    return FALSE;
//(解説:WaitForCompletion関数を使うとウィンドウが無効化するので使わず、ユーザー定義のWM_GRAPHNOTIFYメッセージを親に送ってこの関数で監視します。)
}

//ビデオウィンドウ、フィルターグラフの終了処理
void CDSHOW::CleanUp() {

    //メモリーの開放
    if(m_pVideoWindow) {
        //表示ウィンドウを消す
        //m_pVideoWindow->put_Visible(OAFALSE);
        //親をデスクトップとする
        //m_pVideoWindow->put_Owner(NULL);
        m_pVideoWindow->Release();
    }
    if(m_pMPos)
        m_pMPos->Release();
    if(m_pControl) {
        //動画表示を中止する。
        m_pControl->Stop(); 
        m_pControl->Release();
    }
    if(m_pEvent) {
        //親ウィンドウへの通知を終了する
        m_pEvent->SetNotifyWindow((OAHWND)NULL, 0, 0);
        m_pEvent->Release();
    }
    if(m_pGraph)
        m_pGraph->Release();
    //再初期化
    Init();
//(解説:「後入れ先出し」で、先にインターフェースからメモリーを解放して、親玉のグラフマネージャーを最後に開放します。全て解放したら、メンバー変数を初期化します。)
}

DirectShowは必要な処理をすべてメソッドで纏められているので、ユーザーのラッピングは非常に簡単です。これでメインウィンドウの処理が簡素化できます。

【CSLIDER.h】
/////////////////////////////////////////
//スライダー(CSLIDER)クラス定義ファイル
/////////////////////////////////////////

class CSLIDER : public CCTRL
{
public:
    //メンバー関数
    bool Create(HWND, int, int,
                int, int, int, DWORD);    //コントロール作成関数
    void AutoSize(int, int, int);        //親のサイズ変更に合わせて位置(0-3)を変更
    void SetSize(int, int, int, int);    //位置と大きさを変更する
    void SetRange(int, int);            //スライダーの範囲設定
    void SetPageSize(int);                //ページサイズ(マウスクリック時の移動量)設定
    void SetLineSize(int);                //ラインサイズ(矢印キーによる移動量)設定
    int  GetPosition();                    //ポジションの取得
    void SetPosition(int);                //ポジションの設定
    void SetTic(int);                    //目盛りサイズの設定
};
//(解説:大体何をするのかはコメントでわかると思います。)

//コントロール作成関数
bool CSLIDER::Create(HWND hParent, int cID, int x, int y, int w, int h,
                    DWORD Style = WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS) {

    m_hWnd = CreateWindowEx(m_dwExStyle = WS_EX_TOPMOST,
                            m_lpClassName = TRACKBAR_CLASS,
                            m_lpWindowName = "",
                            m_dwStyle = Style,
                            m_x = x,
                            m_y = y,
                            m_Width = w,
                            m_Height = h,
                            m_hParent = hParent,
                            m_hMenu = (HMENU)cID,
                            m_hInstance = (HINSTANCE)GetWindowLong(hParent, GWL_HINSTANCE),
                            m_lpParam = NULL
    );
    if(m_hWnd == NULL) {
        MessageBox(0, "スライダーコントロールを作れませんでした", "エラー",
                    MB_OK | MB_ICONEXCLAMATION);
        return FALSE;
    }
    //コントロールの表示と再描画
    ShowWindow(m_hWnd, SW_SHOWNORMAL);
    UpdateWindow(m_hWnd);
    return TRUE;
//(解説:スライダーコントロールの作成関数です。)
}

//親のサイズ変更に合わせて位置(時計回りに0-3の隅)を変更
void CSLIDER::AutoSize(int pos, int marginx = 0, int marginy = 0) {

    RECT rec;
    GetClientRect(m_hParent, &rec);
    switch(pos) {
    case 0:        //左上隅
        m_x = marginx;
        m_y = marginy;
        break;
    case 1:        //右上隅
        m_x = (rec.right - rec.left) - m_Width - marginx;
        m_y = marginy;
        break;
    case 2:        //右下隅
        m_x = (rec.right - rec.left) - m_Width - marginx;
        m_y = (rec.bottom - rec.top) - m_Height - marginy;
        break;
    case 3:        //左下隅
        m_x = marginx;
        m_y = (rec.bottom - rec.top) - m_Height - marginy;
        break;
    default:
        return;
    }
    MoveWindow(m_hWnd, m_x, m_y, m_Width, m_Height, TRUE);
//(解説:これは今回ツールバーの中に置くことを考えて作りましたが、他の応用も効くはずです。親ウィンドウの四隅にmarginx, yだけ離してスライダーを置きます。)
}

//位置と大きさを変更する
void CSLIDER::SetSize(int x, int y, int w, int h) {

    m_x = x;
    m_y = y;
    m_Width = w;
    m_Height = h;
//    SetWindowPos(m_hWnd, HWND_TOP, m_x, m_y, m_Width, m_Height, SWP_SHOWWINDOW);
    MoveWindow(m_hWnd, m_x, m_y, m_Width, m_Height, TRUE);
//(解説:AutoSizeでは高さ幅が決められないので、これも併用します。)
}

//スライダーの範囲設定
void CSLIDER::SetRange(int min, int max) {

    SendMessage(m_hWnd, TBM_SETRANGE, TRUE, (LPARAM)MAKELONG(min, max));
//(解説:まんま、です。)
}

//ページサイズ(マウスクリック時の移動量)設定
void CSLIDER::SetPageSize(int dst) {

    SendMessage(m_hWnd, TBM_SETPAGESIZE, 0, (LPARAM)dst);
//(解説:スライダーノブをつかまない場合の移動量です。)
}

//ラインサイズ(矢印キーによる移動量)設定
void CSLIDER::SetLineSize(int dst) {

    SendMessage(m_hWnd, TBM_SETLINESIZE, 0, (LPARAM)dst);
//(解説:キーの場合です。)
}

//ポジションの取得
int CSLIDER::GetPosition() {

    return SendMessage(m_hWnd, TBM_GETPOS, 0, 0);
//(解説:スライダーノブを移動させられた場合の位置取得用です。)
}

//ポジションの設定
void CSLIDER::SetPosition(int pos) {

    SendMessage(m_hWnd, TBM_SETPOS, TRUE, (LPARAM)pos);
//(解説:処理振興に合わせてスライダーを動かすために使います。)
}

//目盛りサイズの設定
void CSLIDER::SetTic(int tic) {

    //目盛りのサイズを設定 
    SendMessage(m_hWnd, TBM_SETTICFREQ, tic, 0);
//(解説:まんま、です。)
}

以上です。COMの扱いの定石をクラス化で省略することができ、簡単にCOMやスライダーコントロールが使えるようになります。

 

まだVectorの障害が続いていて、DirectShowを使ったDirectShowプログラムとFileListがアップロードできていませんが、プログラムDirectShowの解説を先行させていこうと思います。

 

解説するDirectShowプログラムはVersion 1.1で、前の「【DirectShow】ブログ」で書いたVersion 1.0よりも進化し、FileListを使ったプレイリストが使用できて「実用プログラム」となっています。(実際今もMIDIの音楽をBGMとして聴きながら書いてます。)一方マウス右クリックでサンプルプログラムを起動するVersion 1.0の機能は(ウザいので)外しています。(アップロードするファイルにはノスタルジーとしてVersion 1.0のファイルを圧縮して同梱しておきますよ。)

 

DirectShowプログラムがウィンドウプログラムとしては単純なので、今回もポイントを絞った解説にします。

 

【DirectShow.rc】

リソースファイルです。内容はURL入力用、バージョン表示用のダイアログとメニューだけの単純なものです。

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2002 by ysama
//-----------------------------------------
#include    "ResDirectShow.h"

//----------------------------------
// ダイアログ (IDD_URL)
//----------------------------------
IDD_URL DIALOG DISCARDABLE 0, 0, 270, 72
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | DS_MODALFRAME | DS_3DLOOK | DS_CENTER
CAPTION "URLの入力"
FONT 9, "Times New Roman"
{
 CONTROL "", IDC_URL, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_LEFT, 3, 7, 262, 45, WS_EX_CLIENTEDGE
 CONTROL "OK", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 115, 56, 40, 12
}

//----------------------------------
// ダイアログ (IDD_VERSION)
//----------------------------------
IDD_VERSION DIALOG DISCARDABLE 0, 0, 160, 40
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | DS_MODALFRAME | DS_3DLOOK | DS_CENTER
CAPTION "バージョン情報"
FONT 9, "Times New Roman"
{
 CONTROL IDI_ICON, 0, "STATIC", SS_SUNKEN | SS_ICON | WS_CHILD | WS_VISIBLE, 12, 10, 32, 32
 CONTROL "DirectShow Version 1.1\nCopyright 2022 by Ysama", 0, "STATIC", SS_CENTER | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 42, 8, 80, 24
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 130, 14, 20, 12
}

//-------------------------
// メニュー(IDM_MAIN)
//-------------------------
IDM_MAIN MENU DISCARDABLE
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "動画ファイルを開く(&O)", IDM_OPEN
        MENUITEM "URLから開く(&U)", IDM_URL
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)", IDM_EXIT
//(解説:「動画ファイルを開く」とありますが、実際にはmp3等音楽ファイルを多く開いています。(笑)これは「ファイルを開く」ダイアログでファイルを指定するのですが、次の「URLから開く」はダイアログに手入力するものです。URLは最後にメディアファイル名が指定されていなければ開けません。これらは単一のファイル(動画や音楽)を聴くための物です。なお、私の環境(と趣味)に合わせてファイルフィルターは「"ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オーディオファイル(*.mp3;*.wav;*.mid)\0*.mp3;*.wav;*.mid\0\0"」となっています。)

    }
    POPUP "動画(&M)"
    {
        MENUITEM "再生(&R)", IDM_SHOW
        MENUITEM "一時停止(&P)", IDM_PAUSE
        MENUITEM "中止(&S)", IDM_STOP
//(解説:動画(音楽も)ファイルの基本的なコントロールです。以下のプレイリストでも同じです。)

    }
    POPUP "プレイリスト(&P)"
    {
        MENUITEM "プレイリストの作成・選択(&M)", IDM_MAKELIST
        MENUITEM "プレイリストの再生(&P)", IDM_AUTOPLAY
        MENUITEM "プレイリストを表示(&S)", IDM_PLAYLIST

//(解説:複数の動画や音楽のファイルを連続再生する場合のメニューです。FileListダイアログでファイルリスト(プレイリスト)を作成選択し、再生します。プレイリストの表示は未再生のファイルを表示します。)

    }
    POPUP "ヘルプ(&H)"
    {
        MENUITEM "DirectShowについて(&A)", IDM_ABOUT
        MENUITEM "バージョン情報(&V)", IDM_VERSION
//(解説:「DirectShowについて」はブログ内容と参考資料やリンクが入っています。)

    }
}
 

//--------------------------
// イメージ(IDI_ICON)
//--------------------------
IDI_ICON    ICON    DISCARDABLE    "C:\Users\(パス)\DirectShow\Movie.ico"

//--------------------------
// イメージ(IDI_TOOLBAR)
//--------------------------
IDI_TOOLBAR    BITMAP    DISCARDABLE    "C:\Users\(パス)\DirectShow\ToolBar.bmp"

 

なお、ResDirectShow.hのコントロールIDファイルは省略します。

 

【DirectShow.cpp】

メインのcppファイルですが、ほぼSkeltonWizardが作成した通りのままです。

//////////////////////////////////////////
// DirectShow.cpp
//Copyright (c) 01/31/2022 by BCCSkelton
//////////////////////////////////////////
#include    "DirectShow.h"
#include    "User.h"
//(解説:これはマニュアルで追加しています。また、User.hはhファイルとProc.hファイルの間においてください。)

#include    "DirectShowProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    //2重起動防止    //(解説:多重起動も考えましたが、音声が被ったりするので止めました。)
    if(!DirectShow.IsOnlyOne()) {
        //ここに2重起動時の処理を書く(下記は1例)
        HWND hWnd = FindWindow("MainWnd", "DirectShow");
        if(IsIconic(hWnd))
            ShowWindow(hWnd, SW_RESTORE);
        SetForegroundWindow(hWnd);
        return 0L;
    }

    //ウィンドウ登録 - Init(ClassName, hInstance, WndProc, "IDM_MAIN",
    //                        (以下省略可)MAKEINTRESOURCE(IDI_ICON), IDC_ARROW, Brush)
    DirectShow.Init("MainWnd", hInstance, SDIPROC, "IDM_MAIN", MAKEINTRESOURCE(IDI_ICON));

    //ウィンドウ作成と表示-Create(WindowTitle, (以下省略可)Style,
    //                        ExStyle, hParent, hMenu, x, y, w, h)
    if(!DirectShow.Create("DirectShow"))
        return 0L;

    //メッセージループに入る
    return DirectShow.Loop();
}

 

【DirectShow.h】

メインウィンドウ等の定義ファイルです。ここは少し説明が必要です。

//////////////////////////////////////////
// DirectShow.h
// Copyright (c) 01/31/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResDirectShow.h"
//CDSHOWのヘッダー
#include    "CDSHOW.h"

//(解説:DirectShowのラッパー(大袈裟ですが)クラスです。別途説明します。)
//ShellAPIヘッダー(PathFileExist関数の為)
#include    "shlwapi.h"
//(解説:PathFileExist関数一つの為ですが、ヘッダーが必要です。)

//再生用スライダーコントロール関連
#include    "CSLIDER.h"
#define        IDC_SLIDER    1000

//(解説:再生状況表示の為のスライダー(トラックバー)のラッパークラスとID定数です。)

//FileList.dllの静的ロード
#include    "DLL.h"

#pragma comment(lib, ".\\Debug\\FileList.lib") 
IMPORT char* GetFileList(char*, char*, char*);

//(解説:FileListでやったDLL呼び込みの為のヘッダーファイル、ロード指示、関数プロタイプ宣言です。)


/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCSDIクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CMyWnd : public CSDI
{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CMyWnd(char* UName) : CSDI(UName) {}
    //OnAutoPlay用プレイリスト変数
    CSTR m_FileList;

//(解説:これは追加したメンバー変数で*.plfファイルの記録用です。)

    //メニュー項目、ダイアログコントロール関連
    bool OnOpen();
    bool OnUrl();
    bool OnExit();
    bool OnShow();
    bool OnPause();
    bool OnStop();
    bool OnMakelist();
    bool OnAutoPlay();
    bool OnPlayList();
    bool OnAbout();
    bool OnVersion();

//(解説:メニュー通りです。)

    //ウィンドウメッセージ関連
    bool OnCreate(WPARAM, LPARAM);
    bool OnTimer(WPARAM, LPARAM);
    bool OnLButtonDown(WPARAM, LPARAM);
    bool OnRButtonDown(WPARAM, LPARAM);
    bool OnNotify(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnMinMax(WPARAM, LPARAM);

//(解説:最小サイズ用ですね。)

    bool OnGraphNotify(WPARAM, LPARAM);        //DirectShow空の通知処理

//(解説:これがDirectShowからのメッセージを受けます。)

    bool OnDropFiles(WPARAM);

//(解説:ドラッグアンドドロップ用です。)

    //ユーザー定義関数
    void WhenComplete();

//(解説:1ファイルが再生終了した際の処理です。)

    void ChangeMenuStatus(int);

//(解説:再生と一時停止、中止メニュー、ボタンの有効、無効状態を変えます。)

    void AllowtoOpen(bool);

//(解説:「ファイルを開く」「URLから開く」メニュ―と「ファイルを開く」ボタンの有効、無効状態を変えます。)

   void AutoPlay(bool, bool);

//(解説:プレイリストによる自動再生の処理です。)

   char* GetTimeStr(int);

//(解説:残再生時間の文字列化関数です。)

};

////////////////////////////////////////////////////////////////////////
//派生させたCMyWndクラスのインスタンスとコールバック関数(マクロ)の作成
//主ウィンドウはダイアログと違い、コールバック関数は一つしか作れない
////////////////////////////////////////////////////////////////////////
CMyWnd DirectShow("DirectShow");    //ウィンドウクラスインスタンスの生成

BEGIN_SDIMSG(DirectShow)    //ダイアログと違い、コールバック関数名を特定しない
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(DirectShow, IDM_OPEN, OnOpen())
    ON_COMMAND(DirectShow, IDM_URL, OnUrl())
    ON_COMMAND(DirectShow, IDM_EXIT, OnExit())
    ON_COMMAND(DirectShow, IDM_SHOW, OnShow())
    ON_COMMAND(DirectShow, IDM_PAUSE, OnPause())
    ON_COMMAND(DirectShow, IDM_STOP, OnStop())
    ON_COMMAND(DirectShow, IDM_MAKELIST, OnMakelist())
    ON_COMMAND(DirectShow, IDM_AUTOPLAY, OnAutoPlay())
    ON_COMMAND(DirectShow, IDM_PLAYLIST, OnPlayList())
    ON_COMMAND(DirectShow, IDM_ABOUT, OnAbout())
    ON_COMMAND(DirectShow, IDM_VERSION, OnVersion())
    //ウィンドウメッセージ関連
    ON_CREATE(DirectShow)
    ON_TIMER(DirectShow)
    ON_LBUTTONDOWN(DirectShow)
    ON_RBUTTONDOWN(DirectShow)
    ON_NOTIFY(DirectShow)
    ON_SIZE(DirectShow)
    ON_CLOSE(DirectShow)
    ON_(DirectShow, WM_GETMINMAXINFO, OnMinMax(wParam, lParam))

//(解説:最小サイズメッセージです。)

    ON_(DirectShow, WM_GRAPHNOTIFY, OnGraphNotify(wParam, lParam))    //DirectShow通知

//(解説:DirectShowからのメッセージです。)

    ON_(DirectShow, WM_DROPFILES, OnDropFiles(wParam))

//(解説:ドラッグアンドドロップのメッセージです。)

END_WNDMSG

///////////////////
//ツールバーの作成
///////////////////
CTBAR TBar;

///////////////////////
//ステータスバーの作成
///////////////////////
CSBAR SBar;

/////////////////////////////////
//CTIMERクラスインスタンスの作成
/////////////////////////////////
CTIMER g_tmr;

//(解説:残再生時間の表示用にタイマーを使用します。)


///////////////////////
//CDSHOWクラス関連変数
///////////////////////
CDSHOW g_dshow;            //CDSHOWインスタンス
int g_Mtm;                //メディアファイルの再生時間(REFTIME→unsigned int)

//(解説:これがDirectShowのCDSHOWクラスインスタンスと再生時間記録用変数です。)


//////////////////////////////
//再生用スライダーコントロール
//////////////////////////////
CSLIDER g_Slider;

//(解説:スライダーのインスタンスです。)


////////////////////////
//CARGクラスインスタンス
////////////////////////
CARG arg;
bool g_ByFile = FALSE;

//(解説:CARGクラスインスタンスとファイル起動やドラッグアンドドロップ用のフラグです。)


////////////////////////
//ヘルプファイルパス、名
////////////////////////
CSTR g_HelpFile;

//(解説:ヘルプファイルのパス、名記録用変数です。)


/////////////////////
//ファイルリストパス
/////////////////////
CSTR g_FileListDir;

//(解説:*.plf保存用フォールダーへのパスで、DirectShowのあるフォールダーの下に置いています。)


///////////////////////////////////////////
// CDLGクラスからURLDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class URLDLG : public CDLG {
public:
    bool OnIdok();
    bool OnCancel();
};

////////////////////////////////////////////////////////////////////////////
// VERSIONDLGクラスダイアログ変数の生成とそのコールバック関数(マクロ)を定義
// 複数同一クラスのダイアログを作成することを予期してコールバック関数を明記
////////////////////////////////////////////////////////////////////////////
URLDLG urldlg;

BEGIN_MODALDLGMSG(urldlgProc, urldlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(urldlg, IDOK, OnIdok())
    ON_COMMAND(urldlg, IDCANCEL, OnCancel())
END_DLGMSG

///////////////////////////////////////////
// CDLGクラスからVERSIONDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class VERSIONDLG : public CDLG {
public:
    bool OnIdok();
};

////////////////////////////////////////////////////////////////////////////
// VERSIONDLGクラスダイアログ変数の生成とそのコールバック関数(マクロ)を定義
// 複数同一クラスのダイアログを作成することを予期してコールバック関数を明記
////////////////////////////////////////////////////////////////////////////
VERSIONDLG versiondlg;

BEGIN_MODALDLGMSG(versiondlgProc, versiondlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(versiondlg, IDOK, OnIdok())
END_DLGMSG

 

ここら辺は大きな特記事項はありません。この後、CDSHOWクラス、CSLIDERクラスの宣言と定義、メンバー関数等の実装(User.h、DirectShowProc.h)の順で解説する予定です。

 

今回はFileList.dllの最終回として、BCC102用のBatchGoodを使ったコンパイルの仕方とFileList.dllのテストを行うサンプルプログラムを紹介します。

仮にFileListというフォールダーに(1)~(4)までに紹介したファイルが入っているとして、BatchGoodを起動しましょう。

【FileList.dllのコンパイル手順】
1.メニュー「ファイル」の「ファイルを開く」、「開くボタン」あるいはD&DでFileList.cppを開いてください。FileListはリソースがあるのでFileList.rcファイルも同様にツリービュ―に加えてください。

2.次に「bcc32c-オプション」の右の「詳細」ボタンを押してダイアログを出します。

3.実行ファイル形式は「Dynamic Link Liabrary」、出力ディレクトリーは「Debug」のまま(「Release」でもよいですが)、最適化はブランクのままでもよいですが「サイズ最適化」にしましょうか。呼び出し規約はブランクのままでデフォルトとし、インクルードパスはBCCSkelton.hのあるフォールダーを指定します。警告文はチェック無しで結構です。これで「終了」ボタンを押しましょう。

4.後は「メニュー」の「ツール」「バッチファイルの実行」、または「早送りボタン」を押してください。(「実行しますか?」とメッセージボックスが訊いてきても、DLLなので無視してください。)

5.指定した「Debug」(または「Release」)フォールダーに「FileList.dll」、「FileList.lib」ができていることでしょう。

 

6.一応FileList.batを見てみましょう。

@ECHO OFF
ECHO ----------------------------------
ECHO  BatchGood - Batch File Generator
ECHO  for Embarcadero "bcc32c.exe"
ECHO  Copyright (c) 2021 by Ysama
ECHO ----------------------------------

cd "C:\Users\(パス)\FileList\DLL\Debug"
"C:\Borland\BCC102\bin\bcc32c.exe" "C:\Users\(パス)\FileList\DLL\FileList.cpp" -tD -O1 -w- -I"C:\Borland\BCCForm"
del "C:\Users\(パス)\FileList\DLL\Debug\FileList.tds"
"C:\Borland\BCC102\bin\brc32.exe" "C:\Users\(パス)\FileList\DLL\FileList.rc" "C:\Users\(パス)\FileList\DLL\Debug\FileList.dll"
del "C:\Users\(パス)\FileList\DLL\FileList.res"
"C:\Borland\BCC102\bin\implib.exe" "C:\Users\(パス)\FileList\DLL\Debug\FileList.lib" "C:\Users\(パス)\FileList\DLL\Debug\FileList.dll"
pause


これが本当に動くかどうか、「静的コンパイル」「動的コンパイル」いずれでも確認できるようにしています。

【サンプルプログラム-TestFileList.cpp】
////////////////////////////////
//FileList.dllサンプルプログラム
////////////////////////////////

#include    <windows.h>
#include    "DLL.h"

#define        STATIC    1        //これをコメントアウトすると動的ロードのテストになる

//////////////////
//DLLの静的ロード
//////////////////

#ifdef STATIC
    #pragma comment(lib, ".\\Debug\\FileList.lib") 
    IMPORT char* GetFileList(char*, char*, char*);
#endif
//(解説:静的ロードの場合のリンクすべきlibファイルの指定と、IMPORTを使ったDLLのGetFileList関数のプロトタイプ宣言)

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {

#ifdef STATIC
    MessageBox(NULL, "DLLの静的ロードです", "GetBitmapOfIcon関数", MB_OK);
    char* cp =
GetFileList("プレイリストファイル(*.plf)\0*.plf\0\0",
                            "ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オーディオファイル(*.mp3;*.wav;)\0*.mp3;*.wav\0\0",
                            "C:\\Users\\(パス)\\DirectShow\\Debug\\FileList");

    MessageBox(NULL, cp, "GetFileName実行結果(戻り値)", MB_OK | MB_ICONINFORMATION);

//(解説:静的ロードの場合はそのまま関数を使用できます。)
#endif

///////////////////////////////////////////////////////
//DLLの動的ロード
//(冒頭の"#define STATIC"をコメントアウトしてください)
///////////////////////////////////////////////////////

#ifndef STATIC
    //以下はDLLの動的呼び出し
    typedef char*
(*LPGETFILELIST)(char*, char*, char*);
//(解説:DLLのGetFileList関数へのポインターを定義します。)

    LPGETFILELIST lpGetFileList;
//(解説:GetFileList関数へのポインター変数)
    HMODULE hDll = LoadLibrary("FileList.dll");
//(解説:DLLをロードする関数です。覚えておいてください。)
    if(!hDll) {
        MessageBox(NULL, "FileList.dllが見つかりません。", "エラー", MB_OK | MB_ICONWARNING);
        return 0;
    }
    
lpGetFileList = (LPGETFILELIST)GetProcAddress(hDll, "GetFileList");
//(解説:GetFileList関数へのポインターを取得)
    if(!lpGetFileList) {
        MessageBox(NULL, "関数がエクスポートされていません。", "エラー", MB_OK | MB_ICONWARNING);
        
FreeLibrary(hDll);
//(解説:DLLを開放する関数です。覚えておいてください。このようなエラー処理の中にも必ず書いてください。)
        return 0;
    }
    MessageBox(NULL, "DLLの動的ロードです", "GetFileName関数", MB_OK);
    char* cp =
(*lpGetFileList)("プレイリストファイル(*.plf)\0*.plf\0\0",
                            "ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オーディオファイル(*.mp3;*.wav;)\0*.mp3;*.wav\0\0",
                            "C:\\Users\\(パス)\\DirectShow\\Debug\\FileList");

//(解説:ポインターに*を付けることでGetFileList関数を実行することができます。)
    //DLLが解放される前にファイル名を保存しておく必要がある
    char fn[MAX_PATH];
    lstrcpy(fn, cp);
    cp = fn;
    MessageBox(NULL, cp, "GetFileName実行結果(戻り値)", MB_OK | MB_ICONINFORMATION);
    if(
FreeLibrary(hDll)) {
        MessageBox(NULL, "FreeLiabrary関数成功", "通知", MB_OK | MB_ICONINFORMATION);
    }
    else
        MessageBox(NULL, "FreeLiabrary関数失敗", "通知", MB_OK | MB_ICONINFORMATION);

#endif

    return 0;
}

 

ついでにこれのバッチファイルも紹介します。

 

@ECHO OFF
ECHO ----------------------------------
ECHO  BatchGood - Batch File Generator
ECHO  for Embarcadero "bcc32c.exe"
ECHO  Copyright (c) 2021 by Ysama
ECHO ----------------------------------

cd "C:\Users\(パス)\FileList\DLL\Debug"
"C:\Borland\BCC102\bin\bcc32c.exe" "C:\Users\(パス)\FileList\DLL\TestFileList.cpp" -tW -w-
del "C:\Users\(パス)\FileList\DLL\Debug\TestFileList.tds"
pause

 

如何でしたか?BCCForm and BCCSkeltonでつくったexeファイルを簡単にDLLにすることができましたね?これでモジュールの部品化を図り、アプリケーションの幅を増やしてください。

 

(追補)なお、静的ロード、動的ロード共に、テストプログラムのフォールダーからFileList.dllを移して起動するとどうなるか(エラーの出方)も確認してみてください。

 

さて、今までの(1)~(3)までで、FileList.cppを除くFileList.h、FileListProc.hと追加noUser.hはダイアログベースのexeファイルのまんまでよいことが分かりましたが、最後のFileList.cppはDLLとなる為にマニュアルでの変更が必要になります。このマニュアルでの変更はやや技術的な話も入りますが、それを知らなくても簡単にDLLは作れます。そんな訳で本文ではプラクティカルに「何をどうすればよい」かだけを書き、その意味や背景、根拠等は注書きするつもりですので、そのようにお読みください。(まぁ、私も完全に理解しているとはいいがたいのであまり信用しないでくださいね...笑)

まず、BCCForm and BCCSkeltonのプログラム作法をおさらいします。それは、

(1)(完成仕様を考えて)BCCForm(やイメージツール)でリソースを作る
(2)出来上がったrcファイル(とRes.hファイル)をもとにSkeltonWizardでプロジェクト名.h、プロジェクト名Proc.hとプロジェクト名.cppファイル(加えてBCCDeveloper用のbdpファイル)でドンガラウィンドウスケルトンを作る。(必要に応じてウィンドウクラスにメンバー変数や関数を追加する等、プロジェクト.hファイルを加工する。)
(3)ウィンドウメッセージ、メニュー、コントロールに対応したメインウィンドウやダイアログのメンバー関数、ユーザー適宜関数をProc.h(やUser.h)ファイルに実装する。
(4)SDIやMDIウィンドウまたはダイアログベースのアプリケーションが完成する。
というものです。事実今回のFileListでも(1)~(3)のブログでダイアログベースのスタンドアローンアプリケーションのFileList.exeが動きます。

これをDLLにするには以下の(5)を行います。
(5)プロジェクト名.cppを、以下の点でDLL用に改造します。
①WinMainの代わりに、"DLLMain"という関数を使用する。(注1
②DLL化の為の"DLL.h"ファイルを、呼び出されるDLL側のみならず、呼び出しプログラム側でもインクルードする。(注2
③DLL側で公開する関数を「輸出(Export)」する一方、呼び出しプログラム側でもその関数を「輸入(Import)」する。(注3

今回のFileListで言えば、

(a)当初SkeltonWizardが作るcppファイルはモードレスダイアログで記述されますが、

(b)まずそれをモーダルダイアログベースにし、

(c)更にDLL化する

というステップを踏みました。(DLL化にモーダルダイアログが必須ということではありません。FileListはそれを使っている間に呼び出したDirectShowを止めたいためにモーダルダイアログにしただけです。DLL化はモードレスダイアログでもウィンドウベースでも全く変わりません。)

(a)<SkeltonWizardによるオリジナル-モードレスダイアログのexeファイル>
//////////////////////////////////////////
// FileList.cpp
//Copyright (c) 02/08/2022 by BCCSkelton
//////////////////////////////////////////
#include    "FileList.h"
#include    "FileListProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    //2重起動防止
    if(!FileList.IsOnlyOne()) {
        //ここに2重起動時の処理を書く(下記は1例)
        //HWND hWnd = FindWindow("MainWnd", "FileList");
        //if(IsIconic(hWnd))
        //    ShowWindow(hWnd, SW_RESTORE);
        //SetForegroundWindow(hWnd);
        //SendMessage(hWnd, WM_COMMAND, IDM_OPEN, 0);
        //return 0L;
    }
    //モードレスダイアログを作成Create(hParent, DlgName, DlgProc);
    if(!FileList.Create(NULL, hInstance, "IDD_MAIN", ModelessProc))
        return 0L;
    //メッセージループに入る
    return FileList.Loop();
}
//(解説:このように最初はモードレスダイアログで動くようになっていました。)

(b)<モーダルダイアログ化-モーダルダイアログのexeファイル>
//////////////////////////////////////////
// FileList.cpp
//Copyright (c) 02/08/2022 by BCCSkelton
//////////////////////////////////////////
#include    "FileList.h"
#include    "User.h"            //(解説:これはマニュアルで挿入
#include    "FileListProc.h"

////////////////
// WinMain関数
////////////////
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    LPSTR lpCmdLine, int nCmdShow) {

    //ファイルフィルターの初期化
    g_LFlt = "プレイリストファイル(*.plf)\0*.plf\0\0";
    g_MFlt = "ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オーディオファイル(*.mp3;*.wav;)\0*.mp3;*.wav\0\0";
    //ファイルパス
    g_Path = "C:\\Users\\ysama\\Programing\\Borland C++\\DirectShow\\Debug\\FileList";

//(解説:これらの文字列ポインター変数は関数の引数とするために、スタンドアローンのexeプログラムではここで初期化するようにしました。)
    //モーダルダイアログを作成DoModal(HWND, LPCTSTR, DLGPROC, HINSTANCE);
    char* cp = (char*)FileList.DoModal(NULL, "IDD_MAIN", ModalProc, hInstance);
    MessageBox(NULL, g_File.ToChar(), "戻り値", MB_OK | MB_ICONINFORMATION);

//(解説:モードレスダイアログをモーダルダイアログに変え、戻り値をcpで受け止めるようにしてその確認用のメッセージボックスを付加しました。また(3)で書いた通り、も―^ダルダイアログ化に伴い、OnClose関数にEndModal関数を付加しました。)
    return 0L;
}
//(解説:アプリケーションプログラムから呼ばれるDLLの関数に渡される引数と返す戻り値を考えてモーダルダイアログ化しました。)

(c)<DLL化-最終形-モーダルダイアログのDLL>
//////////////////////////////////////////
// FileList.cpp
//Copyright (c) 02/08/2022 by BCCSkelton
//////////////////////////////////////////
#include    "DLL.h"
//(解説:説明の通り、DLL化の為のヘッダーファイルをインクルードします)
#include    "FileList.h"
#include    "User.h"
#include    "FileListProc.h"

////////////////
// DLLMain関数
////////////////
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpReserved)
{
//(解説:説明の通り、WinMain関数に代わり、DLLMain関数に替えます。この関数はDLLが呼ばれる度に呼ばれるだけの「エントリーポイント」です。)
    //CFILELISTクラスメンバー変数の初期化
    FileList.m_hInstance = hInstance;

//(解説:DLLMain関数で引き渡すインスタンスハンドルをメインダイアログのインスタンスに保存します。)
    return TRUE;
}

EXPORT char* GetFileList(char* Filter1, char* Filter2, char* FilePath) {
//(解説:このDLL.hで定義された"EXPORT"が「C言語ベースで__stdcall規約の呼び出し関数を外部の利用の為に公開する」という意味を持ちます。)
    //ファイルフィルターの初期化
    g_LFlt = Filter1;
    g_MFlt = Filter2;
    //ファイルパス
    g_Path = FilePath;

//(解説:予定していた通り、モーダルダイアログ化で初期化していた変数の値に引数を使います。)
    //モーダルダイアログとして起動
    return (char*)FileList.DoModal(NULL, "IDD_MAIN", ModalProc, FileList.m_hInstance);
//(解説:モーダルダイアログを呼ぶことはexeファイルと変わりません。唯一この関数の戻り値として直接plfファイルのパス、名を返します。)
}

このように変えてゆくだけでSkeltonWizardで作成されたBCCSkeltonプログラムを簡単にDLL化することができます。
「習うより慣れろ」といいます。まずは簡単なダイアログのプログラムを作り、それをDLL化してコツをつかんでほしいと思います。

次回はBatchGoodによるDLL化したFileListのコンパイル方法と共に、FileList.dllを呼び出すサンプルを紹介します。

<<<以下はややめんどーくさい話ですが、気が向いた方は読んでください>>>
 

注1:「WinMainの代わりに、"DLLMain"という関数を使用」
実行型ファイルの開始アドレスをエントリーポイントというようで、それがウィンドウベースのexeファイルだとWinMain関数になることはご理解の通りです。これがDLLになるとエントリーポイントであることは変わりませんが、DLLプログラムを起動する(DLLファイルがLoadLibraryでロードされる)際に呼ばれる関数となります。(従って、公開されて使用する関数ではありません。)

DLLMainは、
bool WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpReserved)
定義されていますが、

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
と書かれていることもあります。但し実際にはWINAPI == APIENTRY、HINSTANCE == HMODULEで、場合によっては、
int WINAPI DllEntry(HINSTANCE hInstance, DWORD fdwReason, LPVOID lpReserved)

と書いてもコンパイルされます。
DLLMain関数は、DLLが正常にロードされた場合に真を返します。
"return TRUE;  // Successful DLL_PROCESS_ATTACH."
参照先:DLLエントリーポイント関数
 

次に変数の型宣言でもない"WINAPI"や"APIENTRY"とは何でしょうか?

これらは「呼び出し規約」といわれるもので、C言語やC++言語の特徴である「関数の値渡し("call by value")」(注1-1)の方法の違いを表します。

注1-1:これの反対概念は「関数の参照渡し("call by reference")」と呼ばれ、前者が引数を「先入れ後出し(FILO)」でメモリーのスタックコピーで渡すのに対して、後者はアドレスを渡します。その為、前者は呼び出し側の引数が呼び出し先の影響を受けませんが、後者は影響を受ける点が異なります。

CやC++で代表的な「呼び出し規約」(呼び出し方法)として以下の3つがあります。

(1) C__cdecl→bcc32cの"-pc"オプションで指定)

(2) stdcall__stdcall→bcc32cの"-ps"オプションで指定)

「関数(引数1, 引数2, 引数3)」を例にとると、これらは引数を右から左に引数3、引数2、引数1の順にスタックに積んで関数を呼び出します。(1)と(2)の違いはスタック処理規則の違いで、__cdeclでは呼び出し元の関数によって、スタックから引数がポップされますが、__stdcallでは呼び出された関数が、自分の引数をスタックからポップします。従って、異なる呼び出し規約でDLLが呼ばれると暴走することが理解できると思います。

(3) fastcall__fastcall→bcc32cの"-pm"オプションで指定)

これは引数をスタックではなくCPUのレジスターに入れて関数を呼びますので高速です。DelphiやBCBではお馴染みですよね。
これらの呼び出し規約が特定の必要性から別名(Alias)を受けたのが"WINAPI"や"APIENTRY"で、例えばbcc55のwindef.hでは次のように定義されています。(BCC102ではwindef.h、winbase.h、windows.hには見当たりませんね。どこ行ったのでしょう?)
【bcc55 - windef.hから抜粋】
#define CALLBACK    __stdcall
#define WINAPI      __stdcall
#define WINAPIV     __cdecl
#define APIENTRY    WINAPI
#define APIPRIVATE  __stdcall
#define PASCAL      __stdcall

注2:「DLL化の為の"DLL.h"ファイルをインクルード」
先ずは私のDLL.hの中身とコメントにつけた「名前修飾(Name mangling)」の参照先を見てください。
【DLL.h】
/////////////////////////////////////////////////////////////////////////////////////////
// DLL.h for DLL Programs

//名前修飾(Name mangling)
//<Wiki> https://ja.wikipedia.org/wiki/%E5%90%8D%E5%89%8D%E4%BF%AE%E9%A3%BE
//<QIITA> https://qiita.com/nomunomu0504/items/722a2771fef7d8038ceb
//<Visual Studio> https://docs.microsoft.com/ja-jp/cpp/cpp/definitions-and-declarations-cpp?view=msvc-170

/////////////////////////////////////////////////////////////////////////////////////////
#define    
EXPORT extern "C" WINAPI __declspec(dllexport)
#define    
IMPORT extern "C" WINAPI __declspec(dllimport)
赤字のEXPORTとIMPORTextern "c"以降と同義を謂う意味ですが、最初のextern "C"を理解するには前提となる、C++コンパイラーの「名前修飾(Name mangling)」を理解しなければなりません。

この「名前修飾(Name mangling)」とは、C言語のコンパイラーが関数のリンクIDを単純な関数名に基づく参照名称にするのに対し、C++言語のコンパイラーでは関数のオーバーロード(注2-1)が可能であり、「同じ関数名が異なる関数に使用される」ことからコンパイラーがユニークな参照名称を作成することを言います。
注2-1:関数のオーバーロードとは、同一名称の関数でも、引数や戻り値が異なると異なる関数として認識され、コンパイルされることを謂います。以下の架空の関数はすべて別物で、内部的には"@foo$qizc"、"@foo$qv"というような名称で区別されて管理されます。
void foo(int);

char* foo(int);
int foo(int);

int foo(int, int);

int foo(char*);

「extern "C"」宣言は、(C++ではコンパイラーが勝手に名前修飾をするので参照名をうかがい知れないので)本来C言語では外部変数や関数の存在を宣言する"extern"予約語を使い、「(唯一無二の関数名しか許さない)C言語の方式で外部に公開する」ことを意味しているといえます。
参考:extern "C"とは
しかし、これってDLLで外部公開する関数が↑のfooのようにオーバーロードされた関数である場合には「extern "C"」を使ってはならず、C++でファイルリンケージにしなければならないことを意味します。

参考:CとC++が混在したプログラムでの注意点

 

次の"WINAPI"については「呼び出し規約」で__stdcallのことだと既に説明しました。


最後が"__delspec(dellexport, (または)dllimport)"です。これは簡単に説明がしにくいもののようで、多くのwebサイトでも「おまじない」として使えと言っています。実際これを説明しているのはMicrosoftの英文サイトしかなく、「文法(Grammer)」のところもよく分からないのですが、

The extended attribute syntax for specifying storage-class information uses the __declspec keyword, which specifies that an instance of a given type is to be stored with a Microsoft-specific storage-class attribute listed below.

ということで、「保存するインスタンスのマイクロソフトのスト―レージクラスを特定する拡張属性構文」のようです。まぁ、知っていてもプログラミング上得るものはなく「おまじない」として理解するのが正しいのでしょう。

注3:「DLL側で公開する関数を「輸出(Export)」、「輸入(Import)」」
DLL.hのEXPORTIMPORTが大体何をしているか、が分かったところで、これをどう使うかになります。

まず、DLLの中で外部からの利用の為に公開したい関数があれば、

EXPORT (戻り値の型宣言) (関数名)(引数...) {

...

}
のように記述します。

【FileList.dllの場合】

EXPORT char* GetFileList(char* Filter1, char* Filter2, char* FilePath) {

...

}

この関数を使用する場合、呼び出し側も相対する関数のプロトタイプ宣言を行う必要があります。

【FileList.dllの場合】

IMPORT char* GetFileList(char*, char*, char*);