今回はFileList.h、User.hに続き、FileListProc.hをやります。通常ですと解説のハイライトはProc.hなんですが、DLLプログラムではcppとバッチファイルがより重要となりますので、本日はサクっとやりますか。

2.リソースとプログラム

(2)プログラム-FileListProc.h

//////////////////////////////////////////
// FileListProc.h
// Copyright (c) 02/08/2022 by BCCSkelton
//////////////////////////////////////////

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

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

    //ツールバー登録-SetHandle(hWnd))
    m_TBar.SetHandle(GetDlgItem(m_hWnd, IDC_TOOLBAR));
    //ツールバーボタン用カスタムビットマップ追加
    m_TBar.AddBmp(m_hInstance, MAKEINTRESOURCE(IDI_TOOLBAR), 9);
    //ツールバーボタン追加
    TBBUTTON tbb[13];
    ZeroMemory(tbb, sizeof(tbb));
    tbb[0].iBitmap = m_TBar.m_id + 0;
    tbb[0].fsState = TBSTATE_ENABLED;
    tbb[0].fsStyle = TBSTYLE_BUTTON;
    tbb[0].idCommand = IDM_OPEN;
    tbb[1].iBitmap = m_TBar.m_id + 1;
    tbb[1].fsState = TBSTATE_ENABLED;
    tbb[1].fsStyle = TBSTYLE_BUTTON;
    tbb[1].idCommand = IDM_SAVE;
    tbb[2].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[3].iBitmap = m_TBar.m_id + 2;
    tbb[3].fsState = TBSTATE_ENABLED;
    tbb[3].fsStyle = TBSTYLE_BUTTON;
    tbb[3].idCommand = IDM_EXIT;
    tbb[4].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[5].iBitmap = m_TBar.m_id + 3;
    tbb[5].fsState = TBSTATE_ENABLED;
    tbb[5].fsStyle = TBSTYLE_BUTTON;
    tbb[5].idCommand = IDM_ADD;
    tbb[6].iBitmap = m_TBar.m_id + 4;
    tbb[6].fsState = TBSTATE_ENABLED;
    tbb[6].fsStyle = TBSTYLE_BUTTON;
    tbb[6].idCommand = IDM_SEARCH;
    tbb[7].iBitmap = m_TBar.m_id + 5;
    tbb[7].fsState = TBSTATE_ENABLED;
    tbb[7].fsStyle = TBSTYLE_BUTTON;
    tbb[7].idCommand = IDM_EDIT;
    tbb[8].iBitmap = m_TBar.m_id + 6;
    tbb[8].fsState = TBSTATE_ENABLED;
    tbb[8].fsStyle = TBSTYLE_BUTTON;
    tbb[8].idCommand = IDM_DELETE;
    tbb[9].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[10].iBitmap = m_TBar.m_id + 7;
    tbb[10].fsState = TBSTATE_ENABLED;
    tbb[10].fsStyle = TBSTYLE_BUTTON;
    tbb[10].idCommand = IDM_SORT;
    tbb[11].fsStyle = TBSTYLE_SEP;    //セパレーター
    tbb[12].iBitmap = m_TBar.m_id + 8;
    tbb[12].fsState = TBSTATE_ENABLED;
    tbb[12].fsStyle = TBSTYLE_BUTTON;
    tbb[12].idCommand = IDM_VERSION;
    m_TBar.AddButtons(13, tbb);

    //ステータスバー登録-SetHandle(hWnd))
    m_SBar.SetHandle(GetDlgItem(m_hWnd, IDC_STATUSBAR));
    //ステータスバー区画設定
    int sec[2] = {100, -1};
    m_SBar.SetSection(2, sec);
    //ステータスバー文字列設定
    m_SBar.SetText(0, "FileList Ver 1.0");
    m_SBar.SetText(1, "プレイリストファイル名(*.plf)");
//(解説:ここまではお馴染みのSkeltonWizard通りのところですね。)

    //リストビューウィンドウハンドル
    m_ListView.m_hWnd = GetDlgItem(m_hWnd, IDC_LISTVIEW);
    //リストビュー親ウィンドウハンドル
    m_ListView.m_hParent = m_hWnd;
    //リストビューインスタンス
    m_ListView.m_hInstance = m_hInstance;
    //リストビューコントロールID
    m_ListView.m_hMenu = (HMENU)IDC_LISTVIEW;
//(解説:ここがリストビューコントロールの初期化部分です。CLISTVIEWクラスはCreate()メソッドを持っているので作ることもできますが、今回はダイアログベースで既にリストビューができているのでCLISTVIEWクラスのインスタンスをそのダイアログのリストビューに「被せ」ます。具体的には自分のハンドル(GetDlgItem(m_hWnd, IDC_LISTVIEW))、親のハンドル(m_hWnd)、インスタンス(m_hInstance)、ID(m_hMenu)に値を設定します。)
    //リストビュー列名と幅の設定
    m_ListView.InsertColumn(0, 30, "No", LVCFMT_CENTER);
    m_ListView.InsertColumn(1, 190, "ファイル名");
    m_ListView.InsertColumn(2, 496, "備考");
    m_ListView.InsertColumn(3, 0, "フルパス名");
//(解説:次にリストビューの列幅と列タイトルの設定です。実際には4列で4列目にフルパス名を入れますが、これは長すぎるので表示せず、3列だけの表示となります。)
    //リストビュー拡張スタイルの設定
    INITCOMMONCONTROLSEX ic;
    ic.dwSize = sizeof(ic);
    ic.dwICC = ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx(&ic);
    DWORD lvStyle = SendItemMsg(IDC_LISTVIEW, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0);
    lvStyle |= LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES;
    SendItemMsg(IDC_LISTVIEW, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES, lvStyle);
//(解説:これは既存のウィンドウスタイルにOR(|)で「一行選択」と「点線グリッド」のスタイルを追加しています。これも定番です。)

    //メニューハンドルの取得
    g_hPopup = GetSubMenu(GetMenu(m_hWnd), 1);
//(解説:右クリックの際のポップアップメニュー用です。)

    //リストビューは項目未選択なので「編集」と「削除」を無効にする
    ChangeMenuStatus(FALSE);
//(解説:引数を使って「編集」と「削除」の有効、無効を切り替えます。

    //ドラッグアンドドロップの受付開始
    DragAcceptFiles(m_hWnd, TRUE);
//(解説:ドラッグアンドドロップを開始するにはこの関数を呼べばよいのです。)

    return TRUE;
}

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

    if((int)wParam == IDC_LISTVIEW) {    //リストビューからのメッセージ
        int n = m_ListView.GetNextItem(-1, LVNI_ALL | LVIS_SELECTED);
        if(n == -1) {    //未選択なら
            //「編集」と「削除」を無効にする
            ChangeMenuStatus(FALSE);
        }
        else {
            //「編集」と「削除」を有効にする
            ChangeMenuStatus(TRUE);
        }
//(解説:行が選択されているか、いないかで「編集」と「削除」の有効、無効を切り替えます。)
        //リストビューの通知メッセージによる処理
        switch(((LPNMLISTVIEW)lParam)->hdr.code) {
        case LVN_COLUMNCLICK:    //列見出しがクリックされたら
            if(sortsubno[((LPNMLISTVIEW)lParam)->iSubItem] == UP)
                sortsubno[((LPNMLISTVIEW)lParam)->iSubItem] = DOWN;
            else
                sortsubno[((LPNMLISTVIEW)lParam)->iSubItem] = UP;
            m_ListView.SortItem(CompProc, ((LPNMLISTVIEW)lParam)->iSubItem);
            break;
//(解説:リストビューの列タイトルの部分がクリックされるとその列のデータを基に昇順、降順と交互にソートします。)
        case NM_DBLCLK:        //左ダブルクリックしたら
            if(n != -1)
                OnEdit();
//(解説:行が選択されている状態でダブルクリックするとメニューの「編集」を実行します。)
            break;
        case NM_RCLICK:        //右クリックしたら
            //ウインドウの位置情報を取得
            RECT rec;
            GetWindowRect(m_hWnd, &rec);
            //ポップアップメニューの表示場所を設定
            POINT pt;
            GetCursorPos(&pt);
            //ポップアップメニューの表示
            TrackPopupMenu(g_hPopup, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_LEFTBUTTON, pt.x, pt.y, 0, m_hWnd, &rec);
            break;
        }
//(解説:右クリックすると「編集」メニューがポップアップします。)
        return TRUE;
    }
    //ツールバーツールチップ
    if(((LPNMHDR)lParam)->code == TTN_NEEDTEXT) {    //TTN_GETDISPINFOと同じ
        ((LPTOOLTIPTEXT)lParam)->hinst = m_hInstance;
        char* tag;
        switch(((LPTOOLTIPTEXT)lParam)->hdr.idFrom) {
        case IDM_OPEN:
            tag = "リストを開く";
            break;
        case IDM_SAVE:
            tag = "リストの保存";
            break;
        case IDM_EXIT:
            tag = "終了";
            break;
        case IDM_ADD:
            tag = "追加";
            break;
        case IDM_SEARCH:
            tag = "検索";
            break;
        case IDM_EDIT:
            tag = "編集";
            break;
        case IDM_DELETE:
            tag = "削除";
            break;
        case IDM_SORT:
            tag = "並び替え";
            break;
        case IDM_VERSION:
            tag = "バージョン情報";
            break;
        }
        ((LPTOOLTIPTEXT)lParam)->lpszText = tag;
        return TRUE;
//(解説:ツールバーツールチップの定番処理です。)
    }
    return FALSE;
}

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

    m_TBar.AutoSize();
    m_SBar.AutoSize();
    //最後の「備考」幅を調整してm_ListViewをクライアント領域に合わせる
    m_ListView.m_x = 0;
    m_ListView.m_Width = LOWORD(lParam);
    m_ListView.m_Height = HIWORD(lParam);
    RECT rec;
    GetWindowRect(m_SBar.GetHandle(), &rec);
    m_ListView.m_Height -= rec.bottom - rec.top;
    GetWindowRect(m_TBar.GetHandle(), &rec);
    m_ListView.m_y = rec.bottom - rec.top;
    m_ListView.m_Height -= m_ListView.m_y;
    m_ListView.Move(m_ListView.m_x, m_ListView.m_y, m_ListView.m_Width, m_ListView.m_Height);
    m_ListView.SetColumn(2, LVCFMT_LEFT, m_ListView.m_Width - 224, "備考");
//(解説:最初の2行はSkeltonWizardが書いてくれたツールバーとステータスバーのサイズ調整です。コメント以降はコメントの通り、リストビューの「備考」部分の変更でクライアントエリアに合わせてサイズ調整しす。)
    return TRUE;
}

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

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES) {
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        EndModal((int)g_File.ToChar());    //呼び出し先に返す値
//(解説:まだ解説していませんがFileList.cppを変更し、CFILELISTウィンドウをモーダルダイアログにするので、終了はEndModal関数になります。戻り値はplfファイルパス名となります。)
        return TRUE;
    }
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

//D&D処理
bool CFILELIST::OnDropFiles(WPARAM wParam, LPARAM lParam) {

    //ドラッグアンドドロップされたファイル名取得と処理
    static 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")) {
        g_File = FileName;
        g_ByFile = TRUE;
        OnAdd();
        DragFinish((HDROP)wParam);
        return TRUE;
    }
    MessageBox(m_hWnd, "再生対象外のファイルです", "エラー", MB_OK | MB_ICONERROR);
    return TRUE;
//(解説:再生ファイルの追加でドラッグアンドドロップを許します。この為、拡張子で対象外ファイルを選別します。また、対象ファイルの場合はg_Dataにファイル名を入れ、g_ByFileフラグを立ててOnAdd()関数を呼びます。)
}

//ウィンドウ最小サイズ処理
bool CFILELIST::OnMinMax(WPARAM wPram, LPARAM lParam) {

    //典型的なウィンドウのサイズ制限処理
    MINMAXINFO *pmmi;
    pmmi = (MINMAXINFO*)lParam;
    pmmi->ptMinTrackSize.x = 496;    //クライアントエリア480 + 16
    pmmi->ptMinTrackSize.y = 301;    //クライアントエリア240 + 61
    return FALSE;                    //処理はDefWndProcに任す
//(解説:これは定番の最低サイズ制限処理です。)
}

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

    //plfファイルを選択する
    char* fp = g_Cmndlg.GetFileName(m_hWnd, g_LFlt, TRUE, "plf", g_Path);
    if(!fp) {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    else
        g_File = fp;
    //選択したplfファイルをg_Dataに読み込む
    if(!g_Data.FromFile(g_File.ToChar())) {
        MessageBox(m_hWnd, "plfファイルを読み込めませんでした", "エラー",
                    MB_OK | MB_ICONERROR);
        return FALSE;
    }
//(解説:g_Fileにファイル名をいれ、ファイルデータはg_Dataに読ませます。)
    else {
        //現在の行データをすべて削除
        m_ListView.DeleteAllItems();
        int n = 0;
        while(char* cp = GetDelimtStr(g_Data.ToChar())) {
            //再生番号(999迄、最後のnはLVIF_PARAMをソートする為に追加)
            char buff[4];
            wsprintf(buff, "%d", n);
            m_ListView.InsertItem(n, buff, -1, n);
            //フルパス名(データを保存)
            m_ListView.SetItem(n, 3, cp);
            //ファイル名
            m_ListView.SetItem(n, 1, GetFileName(cp));
            //備考
            cp = GetDelimtStr(g_Data.ToChar());
            m_ListView.SetItem(n, 2, cp);
            n++;
            if(n > 999) {    //念のためのエラー処理
                MessageBox(m_hWnd, "最大再生数(999)を超えました", "エラー", MB_OK | MB_ICONERROR);
                return FALSE;
            }
        }
//(解説:g_Dataの一行二つの""で囲まれた文字列をcpに切り出して読み込んでゆきます。まず行番号nを文字列にして第0列に、次に""を外したフルパス名cpを(表示しない)第3列に、フルパス名からGetFileNameでファイル名だけを取得して第1列に、最後に備考の""外したcpを第2列に入れます。
    }
    m_SBar.SetText(1, g_File.ToChar());        //ステータスバーにフルパス名表示
//    m_SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)g_File.ToChar());    //ToolTipをつける
//(解説:ステータスバーにツールチップを付けると変なところを参照する問題は解決していません。)
    return TRUE;
}

bool CFILELIST::OnSave() {

    char* fp = g_Cmndlg.GetFileName(m_hWnd, g_LFlt, FALSE, "plf", g_Path);
    if(!fp) {
        MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
    g_File = fp;                //ファイル名を保存
    //行数だけ文字列化を繰り返す
    int n = m_ListView.GetItemCount();
    //初期化
    g_Data = "";                //g_Dataを初期化
    char buff[MAX_PATH * 4];    //「備考」の最大文字数は約1KB
    CSTR row;
    for(int i = 0; i < n; i++) {
        row = "\"";                //rowを初期化(フルパスは「""」で囲む)
        //ListViewの2列のデータを読み出し、コンマ区切の文字列一行とする
        m_ListView.GetItemText(i, 3, buff, MAX_PATH);
        row = row + buff;
        row = row + "\",\"";
        m_ListView.GetItemText(i, 2, buff, MAX_PATH * 4);
        row = row + buff;        //最終項目も文字列中に'\n'があるので
        row = row + "\"\n";        //「""」で囲う
         //一行データをg_Dataへ足しこんでゆく
        g_Data = g_Data + row;
    }
    g_Data.ToFile(g_File.ToChar());        //ファイルを保存
    m_SBar.SetText(1, g_File.ToChar());    //ステータスバーにフルパス名表示
//    m_SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)g_File.ToChar());    //ToolTipをつける
    return TRUE;
//(解説:これはOnOpenの反対に、ListViewから読み込んだ第3列のフルパス名と第2列の備考を「カンマ区切り文字列 + 改行」にしてCSTR変数rowに入れて、それをg_Dataに詰め込んで行き、最後にファイル名g_Fileで保存します。)
}

bool CFILELIST::OnExit() {

    SendMsg(WM_CLOSE, 0, 0);
    return TRUE;
//(解説:定番です。)
}

bool CFILELIST::OnAdd() {

    char* fn;
    if(g_ByFile)
        g_ByFile = FALSE;
    else {
        //ファイルを選択する
        fn = g_Cmndlg.GetFileName(m_hWnd, g_MFlt, TRUE);
        if(!fn) {
            MessageBox(m_hWnd, "キャンセルされました", "エラー", MB_OK | MB_ICONERROR);
            return FALSE;
        }
        g_Data = fn;
//(解説:D&Dは既にg_Dataにファイル名が入っていますのでg_ByFileを偽にするだけです。通常は「ファイルを開く」ダイアログを使います。)
    }
    //リストの最後尾を確認
    int n = m_ListView.GetItemCount();
    //念のためのエラー処理
    if(n > 999) {
        MessageBox(m_hWnd, "最大再生数(999)を超えました", "エラー", MB_OK | MB_ICONERROR);
        return FALSE;
    }
//(解説:999にあまり意味はありませんが、これが現実的限界だろうと考えました。)
    //再生番号(999迄、最後のnはLVIF_PARAMをソートする為に追加)
    char buff[4];
    wsprintf(buff, "%d", n);
    m_ListView.InsertItem(n, buff, -1, n);
    //フルパス名(データを保存)
    m_ListView.SetItem(n, 3, g_Data.ToChar());
    //ファイル名
    m_ListView.SetItem(n, 1, GetFileName(g_Data.ToChar()));
    return TRUE;
}
//(解説:「追加」では「備考」データはありません。)

bool CFILELIST::OnSearch() {

    static CSTR ToFind;                    //検索文字列保存用変数
    static int found = 0;                //初期値は最初から
    char buff[MAX_PATH];                //リストボックス文字列取得用バッファ
    int n = m_ListView.GetItemCount();    //リストボックスの行数確認
    //検索文字列取得ダイアログ
    searchdlg.DoModal(m_hWnd, "IDD_SEARCH", searchdlgProc);
    //前回の検索と同じ検索文字列であれば検索を継続
    if(g_Data != ToFind) {    //異なれば
        ToFind = g_Data;        //検索文字列を更新し
        found = 0;            //最初から検索する
        m_ListView.SetItemState(-1, 0, LVIS_SELECTED);    //全行の選択状態の解除
    }
    for(int i = found; i < n; i++) {        //行
        for(int j = 0; j < 3; j++) {        //列
            m_ListView.GetItemText(i, j, buff, MAX_PATH);    //文字列取得
            if(strstr(buff, ToFind.ToChar())) {                //検索文字列が含まれれば
                found = i + 1;                                //次の検索のために、見つかった行の次を指す
                SetFocus(m_ListView.m_hWnd);                //m_ListViewにフォーカスがないと選択状態が表示されない
                m_ListView.SetItemState(i, LVIS_SELECTED, LVIS_SELECTED);    //選択状態にする
                return TRUE;
            }
        }
    }
    MessageBox(m_hWnd, "検索文字は見当たりませんでした", "検索結果", MB_OK | MB_ICONINFORMATION);
    m_ListView.SetItemState(-1, 0, LVIS_SELECTED);    //全行の選択状態の解除
    return TRUE;
}
//(解説:IDListのときとおなじく、すべてのセルデータを調べてゆきます。)

bool CFILELIST::OnEdit() {

    int n = m_ListView.GetNextItem(-1, LVNI_ALL | LVNI_SELECTED);
    if (n == -1) {
        MessageBox(m_hWnd, "項目が選択されていません", "警告", MB_OK);
        return FALSE;
    }
    //CSTR g_DataにNo、ファイル名とカンマ区切り「備考」を格納
    char buff[MAX_PATH * 4];
    //No
    m_ListView.GetItemText(n, 0, buff, MAX_PATH);
    g_Data = buff;
    g_Data = g_Data + ",";
    //ファイル名
    m_ListView.GetItemText(n, 1, buff, MAX_PATH);
    g_Data = g_Data + buff;
    g_Data = g_Data + ",\"";
    //備考
    m_ListView.GetItemText(n, 2, buff, MAX_PATH * 4);
    g_Data = g_Data + buff;    //最終項目は文字列中に'\n'
    g_Data = g_Data + "\"";    //があるので「""」で囲う
    //編集用ダイアログを呼び出す
    if(!editdlg.DoModal(m_hWnd, "IDD_EDIT", editdlgProc))
        return FALSE;
    //CSTR g_Dataに格納されたNoとカンマ区切り「備考」を格納
    char* cp = GetDelimtStr(g_Data.ToChar());
    m_ListView.SetItem(n, 0, cp);
    m_ListView.SetItem(n, 2, GetDelimtStr(g_Data.ToChar()));
    return TRUE;
}
//(解説:「No」(再生順)、「ファイル名」(これは編集できないようにします)、「備考」をg_Dataにいれてダイアログに渡します。)

bool CFILELIST::OnDelete() {

    //複数選択に対応
    int n = m_ListView.GetNextItem(-1, LVNI_ALL | LVIS_SELECTED);
    while(n != -1) {
        m_ListView.DeleteItem(n); 
        n = m_ListView.GetNextItem(n - 1, LVNI_ALL | LVIS_SELECTED);
    }
    m_SBar.SetText(1, "プレイリストファイル名(*.plf)");    //ステータスバー表示変更
    return TRUE;
}
//(解説:選択行を削除します。複数行でも削除できます。)

bool CFILELIST::OnSort() {

    sortdlg.DoModal(m_hWnd, "IDD_SORT", sortdlgProc);
    return TRUE;
}
//(解説:ソートダイアログへ行くだけです。)

bool CFILELIST::OnVersion() {

    versiondlg.DoModal(m_hWnd, "IDD_VERSION", versiondlgProc);
    return TRUE;
}
//(解説:バージョン情報はダイアログに既に入れています。)

//////////////////////////////////////
//ユーザー定義関数
//IDM_EDITとIDM_DELETEの状態を変更する
//////////////////////////////////////
bool CFILELIST::ChangeMenuStatus(bool flag) {

    EnableMenuItem(g_hPopup, IDM_EDIT, (flag ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
    EnableMenuItem(g_hPopup, IDM_DELETE, (flag ? MF_ENABLED : MF_GRAYED) | MF_BYCOMMAND);
    SendMessage(m_TBar.GetHandle(), TB_ENABLEBUTTON, IDM_EDIT, flag);
    SendMessage(m_TBar.GetHandle(), TB_ENABLEBUTTON, IDM_DELETE, flag);
    return TRUE;
}
//(解説:行が選ばれていなければ使えない「編集」と「削除」のメニュー、ツールバーボタンの状態を変更します。)

////////////////////////////////////
//ユーザー定義関数
//フルパス名からファイル名だけを取得
////////////////////////////////////
char* CFILELIST::GetFileName(char* pathname) {

    static char fn[MAX_PATH];    //データをスタックに置かない為
    lstrcpy(fn, pathname);        //引数の文字列のコピーを作る
    //cpをpassnameの終端(NULL)のひとつ前に進める
    char* cp = fn + lstrlen(fn) - 1;
    if(*cp == '\"' && *fn == '\"')
        *cp = NULL;    //「""」で囲まれていれば外す
    //文字列の後ろから最初の'\'を(保険でfnの先頭迄)探す
    while(*cp != '\\' && cp > fn) {
        --cp;
    }
    //*cpが「\」、「"」(fn)の何れでも一つ進める("ファイル名"の場合はそのまま)
    if(*cp == '\\' || *cp == '\"')
        cp++;
    return cp;                //ファイル名の先頭アドレスを返す
}
//(解説:フルパスファイル名の最後尾から先頭まで、'\'が見つかるまで戻ります。「"ファイル名"」だけであっても大丈夫です。)

///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////
bool EDITDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //GetDelimitStrはソースを短縮してしまうのでg_Dataは保護する
    CSTR Data = g_Data;
    //CSTR g_Dataのカンマ区切り文字列をEdit1~3までに格納
    char* cp = GetDelimtStr(Data.ToChar());
    SendItemMsg(IDC_EDIT1, WM_SETTEXT, NULL, (LPARAM)cp); 
    cp = GetDelimtStr(Data.ToChar());
    SendItemMsg(IDC_EDIT2, WM_SETTEXT, NULL, (LPARAM)cp); 
    cp = GetDelimtStr(Data.ToChar());
    SendItemMsg(IDC_EDIT3, WM_SETTEXT, NULL, (LPARAM)cp); 
    return TRUE;
}
//(解説:No、ファイル名、備考を順にエディットボックスにセットします。)

bool EDITDLG::OnIdok() {

    char buff[MAX_PATH * 4];    //文字列格納用バッファ
    //g_DataにNoを格納
    SendItemMsg(IDC_EDIT1, WM_GETTEXT, (WPARAM)MAX_PATH, (LPARAM)buff); 
    g_Data = buff;
    //g_Dataにカンマ区切りで「備考」を格納
    g_Data = g_Data + ",\"";
    SendItemMsg(IDC_EDIT3, WM_GETTEXT, (WPARAM)MAX_PATH * 4, (LPARAM)buff); 
    g_Data = g_Data + buff;
    g_Data = g_Data + "\"";    //「備考」は文字列中に'\n'があるので「""」で囲う
    EndModal(TRUE);
    return TRUE;
}
//(解説:No、備考のみデータを取り、""で囲い、g_Dataへ格納します。)

bool EDITDLG::OnIdcancel() {

    EndModal(FALSE);
    return TRUE;
}
//(解説:Escボタン対応です。)

///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////
bool SEARCHDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    SendItemMsg(IDC_EDIT1, WM_SETTEXT, 0, (LPARAM)m_ToFind);
    return TRUE;
}
//(解説:既に検索したことがある場合、前の検索文字列をセットします。)

bool SEARCHDLG::OnIdok() {

    SendItemMsg(IDC_EDIT1, WM_GETTEXT, MAX_PATH, (LPARAM)m_ToFind);
    g_Data = m_ToFind;
    EndModal(TRUE);
    return TRUE;
}
//(解説:新しい検索文字列を保存し、g_Dataで返します。)

bool SEARCHDLG::OnIdcancel() {

    EndModal(FALSE);
    return TRUE;
}

///////////////////////////////
//ユーザーダイアログの関数定義
//コントロール関数
///////////////////////////////
bool SORTDLG::OnInit(WPARAM wParam, LPARAM lParam) {

    //コンボボックスに文字列を登録
    SendItemMsg(IDC_COMBOBOX1, CB_ADDSTRING, 0, (LPARAM)"No");
    SendItemMsg(IDC_COMBOBOX1, CB_ADDSTRING, 0, (LPARAM)"ファイル名");
    SendItemMsg(IDC_COMBOBOX1, CB_ADDSTRING, 0, (LPARAM)"備考");
    SendItemMsg(IDC_COMBOBOX1, CB_SETCURSEL, 0, 0);    //一番目を選択状態にする
    SendItemMsg(IDC_COMBOBOX2, CB_ADDSTRING, 0, (LPARAM)"昇順");
    SendItemMsg(IDC_COMBOBOX2, CB_ADDSTRING, 0, (LPARAM)"降順");
    SendItemMsg(IDC_COMBOBOX2, CB_SETCURSEL, 0, 0);    //一番目を選択状態にする
    return TRUE;
}
//(解説:ソート条件をこのボックスにセットします。)

bool SORTDLG::OnIdok() {

    //選択番号の取得
    int col = SendItemMsg(IDC_COMBOBOX1, CB_GETCURSEL, 0, 0);
    int dir = SendItemMsg(IDC_COMBOBOX2, CB_GETCURSEL, 0, 0);
    //ソート対象項目に昇順、降順のデータを書込む
    sortsubno[col] = (bool)dir;
    //ソート処理
    FileList.m_ListView.SortItem(CompProc, col);
    EndModal(TRUE);
    return TRUE;
}
//(解説:ここからダイレクトにリストビューのソートを行います。)

bool SORTDLG::OnIdcancel() {

    EndModal(FALSE);
    return TRUE;
}

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

    EndModal(TRUE);
    return TRUE;
}

既にIDListでやった内容のおさらいでした。次回はいよいよSkeltonWizardでつくられたコードをDLL化する核心に入ります。

2.リソースとプログラム

前回の(1)リソースの続きです。

(2)プログラム-FileList.h

いくつか手を加えたところがあるので(解説:)します。

//////////////////////////////////////////
// FileList.h
// Copyright (c) 02/08/2022 by BCCSkelton
//////////////////////////////////////////
//BCCSkeltonのヘッダー-これに必要なヘッダーが入っている
#include    "BCCSkelton.h"
//リソースIDのヘッダー
#include    "ResFileList.h"

/////////////////////////////////////////////////////////////////////
//CMyWndクラスをCDLGクラスから派生させ、メッセージ用の関数を宣言する
/////////////////////////////////////////////////////////////////////
class CFILELIST : public CDLG
//(解説:まず、SkeltonWizardでつくられたコードはメインウィンドウをCMyWndクラスにするのですが、DLLにするので、一応これをCFILELISTに替えます。エディターの「一括置換」で"CMyWnd"→"CFILELIST"でFileList.hとFileListProc.hを舐めます。)

{
public:    //以下はコールバック関数マクロと関連している
    //2重起動防止用のMutex用ID名称
    CFILELIST(char* UName) : CDLG(UName) {}
    //ツールバー
    CTBAR m_TBar;
    //ステータスバー
    CSBAR m_SBar;
    //リストビュー
    CLISTVIEW m_ListView;

//(解説:ツールバー、ステータスバー、リストビュー等コントロールはメンバー変数化しました。)

    //メニュー項目、ダイアログコントロール関連
    bool OnOpen();
    bool OnSave();
    bool OnExit();
    bool OnAdd();
    bool OnSearch();
    bool OnEdit();
    bool OnDelete();
    bool OnSort();
    bool OnVersion();
    //ウィンドウメッセージ関連
    bool OnInit(WPARAM, LPARAM);
    bool OnNotify(WPARAM, LPARAM);
    bool OnSize(WPARAM, LPARAM);
    bool OnClose(WPARAM, LPARAM);
    bool OnDropFiles(WPARAM, LPARAM);
    bool OnMinMax(WPARAM, LPARAM);

//(解説:手作業で追加したのはOnDropFileとOnMinMaxです。)

    //ユーザー定義関数
    bool ChangeMenuStatus(bool);
    char* GetFileName(char*);

//(解説:処理によりメニューやツールバーボタンの状態を変える処理、および文字列からカンマやCRで区切られた文字列を切り出す関数です。)

};

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

BEGIN_MODALDLGMSG(ModalProc, FileList)    //コールバック関数名は主ウィンドウの場合ModalProcにしている
    //メニュー項目、ダイアログコントロール関連
    ON_COMMAND(FileList, IDM_OPEN, OnOpen())
    ON_COMMAND(FileList, IDM_SAVE, OnSave())
    ON_COMMAND(FileList, IDM_EXIT, OnExit())
    ON_COMMAND(FileList, IDM_ADD, OnAdd())
    ON_COMMAND(FileList, IDM_SEARCH, OnSearch())
    ON_COMMAND(FileList, IDM_EDIT, OnEdit())
    ON_COMMAND(FileList, IDM_DELETE, OnDelete())
    ON_COMMAND(FileList, IDM_SORT, OnSort())
    ON_COMMAND(FileList, IDM_VERSION, OnVersion())
    //ウィンドウメッセージ関連
    //自動的にダイアログ作成時にOnInit()、終了時にOnClose()を呼びます
    ON_SIZE(FileList)
    ON_(FileList, WM_DROPFILES, OnDropFiles(wParam, lParam))
    ON_(FileList, WM_GETMINMAXINFO, OnMinMax(wParam, lParam))

//(解説:手作業で追加したのはWM_DROPFILESとWM_GETMINMAXINFOです。)
END_DLGMSG

///////////////////////////////////////////
// CDLGクラスからEDITDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class EDITDLG : public CDLG {
public:
    //コントロール関連
    bool OnIdok();
    bool OnIdcancel();
//(解説:IDCANCELはEscキー対応です。)
    //ダイアログメッセージ関連
    bool OnInit(WPARAM, LPARAM);
};

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

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

///////////////////////////////////////////
// CDLGクラスからSEARCHDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class SEARCHDLG : public CDLG {
public:
    char m_ToFind[MAX_PATH];
//(解説:ダイアログで入力する検索文字列の保存よです。)
    //コントロール関連
    bool OnIdok();
    bool OnIdcancel();
//(解説:IDCANCELはEscキー対応です。)
    //ダイアログメッセージ関連
    bool OnInit(WPARAM, LPARAM);
};

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

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

///////////////////////////////////////////
// CDLGクラスからSORTDLGクラスを派生
// 複数の同一ダイアログ変数とダイアログも作
// れるが、一つのダイアログに一つの派生ダイ
// アログクラスを作成するのが基本
///////////////////////////////////////////
class SORTDLG : public CDLG {
public:
    //コントロール関連
    bool OnIdok();
    bool OnIdcancel();
//(解説:IDCANCELはEscキー対応です。)
    //ダイアログメッセージ関連
    bool OnInit(WPARAM, LPARAM);
};

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

BEGIN_MODALDLGMSG(sortdlgProc, sortdlg)    //第1引数がコールバック関数の名前
    ON_COMMAND(sortdlg, IDOK, OnIdok())
    ON_COMMAND(sortdlg, IDCANCEL, OnIdcancel())
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

(2)プログラム-User.h
FileListに特有の定数や外部変数、関数を定義するファイルです。

//////////////////////////////////////////
// User.h
// Copyright (c) 02/09/2022 by Ysama
//////////////////////////////////////////
////////////
//定数の定義
////////////
#define UP                FALSE
#define DOWN             TRUE
#define NO_OF_SUBITEM    3

//(解説:これらはソート用です。)

//////////////////
//コモンダイアログ
//////////////////
CMNDLG g_Cmndlg;
//(解説:「ファイルを開く」ダイアログの為に使用します。)

///////////////
//ファイルパス
///////////////
char* g_Path;
//(解説:「ファイルを開く」ダイアログの初期フォールダー設定用です。FileListに与えられる引数を使う予定です。)

////////////////////
//ファイルフィルター
////////////////////
char* g_LFlt;        //リストファイルフィルターへのポインター
char* g_MFlt;        //メディアファイルフィルターへのポインター
//(解説:「ファイルを開く」ダイアログの為のフィルターです。前者は*.plfファイル、後者はAVメディアファイルを予定しています。)

///////////////////////////////
//ファイルリストファイル(plf)
///////////////////////////////
CSTR g_File;
//(解説:*.plfファイルを読み込むCSTRファイルです。)

//////////////////////
//データ処理用CSTR変数
//////////////////////
CSTR g_Data;
//(解説:作業用変数です。)

////////////////////////////////
//ポップアップメニュ-のハンドル
////////////////////////////////
HMENU g_hPopup;
//(解説:右マウスボタンクリックで表示するポップアップメニューのハンドル用です。)

/////////////
//D&D用フラグ
/////////////
bool g_ByFile = FALSE;
//(解説:ドラッグアンドドロップでAVメディアファイルを追加することができるようにします。)

//////////////////////////
//サブアイテムのソート関連
//////////////////////////
//列とソート方法の配列
bool sortsubno[NO_OF_SUBITEM] = {FALSE};
//ソートコールバック関数
int CALLBACK CompProc(LPARAM lp1, LPARAM lp2, LPARAM lpSort) {

    static int nItem1, nItem2;
    static char buf1[MAX_PATH], buf2[MAX_PATH];
    //アイテムの検索
    nItem1 = FileList.m_ListView.FindItem(-1, NULL, lp1);
    nItem2 = FileList.m_ListView.FindItem(-1, NULL, lp2);
    //アイテム情報の取得
    FileList.m_ListView.GetItemText(nItem1, (int)lpSort, buf1, MAX_PATH);
    FileList.m_ListView.GetItemText(nItem2, (int)lpSort, buf2, MAX_PATH);
    if(sortsubno[(bool)lpSort])
        return(strcmp(buf1, buf2));
    else
        return(strcmp(buf1, buf2) * -1);
}
//(解説:これはListViewコントロールのソート用コールバック関数(文字列用)の定番です。)

////////////////////////////
//カンマ区切り文字切り出し
//★str 文字列は切り出した文
//字列だけ短縮されるので注意
////////////////////////////
char* GetDelimtStr(char* str) {

    static char buff[MAX_PATH * 4];    //取り敢えず1K bytes確保
    char *cp = str;
    int len = 0;
    switch(*cp) {
    case 0:
            return 0;            //NULL終端を指していれば単に帰る(FALSE)
    case '\"':                    //「""」処理
        cp++;                    //'\"'の次に進める
        while(*cp != '\"') {
            buff[len] = *cp;     //buffへのコピー
            cp++;
            len++;
        }
        cp++;                    //cpは'\"'の次の','へ進める
        buff[len] = '\0';        //buffのNULL終端処理
        break;
    default:                    //「""」以外
        //','でも'\n'でもNullでもなければ
        while(*cp != ',' && *cp != '\n' && *cp) {
            buff[len] = *cp;    //strデータをbuffにコピーしてゆく
            cp++;
            len++;
        }
        buff[len] = '\0';        //buffのNULL終端処理
        break;
    }
    if(*cp){                    //cpがNULL終端を指していなければ
        cp++;                    //','や'\n'を指しているので一つ進め
    }
    lstrcpy(str, cp);            //残余の文字列を文字列先頭に移動
    return buff;                //切り取った文字列を返す(TRUE)
}
//(解説:これは','または'(CR)'で区切られた文字列を切り出す関数です。""で囲まれた文字列は','や'(CR)'があってもそのまま切り出されます。CSTRにもNext()という同様のメンバー関数がありますが、復讐のために敢えてコーディングしてみました。)

次回はFileListProc.hをやりましょう。
 

ファイうアップロードはできていませんが、DirectShowのバージョンアップに使ったFileListを例として、BCCForm and BCCSkeltonでDLLを作る過程を説明してみましょう。

 

まず、DLLの中身ですが、小さくは一つの関数から大きくは一連の処理を行える一つのプログラムまで色々です。簡単な関数を使ったDLLの解説はWEBで結構ありますから、ここではダイアログを使ったDLLの開発を、BCCForm and BCCSkeltonの特徴を踏まえて解説します。具体的にはFileListというダイアログベースのスタンドアロンのプログラムをBCCDeveloper(STLとかCOMとか使わないので、bcc32で行けます)でつくり、それを丸ごと(今の人は「まるっと」というんですか?馴染めないなぁ)DLL化します。

 

1.仕様

(1)メニュー、ツールバー、ステータスバーを備えたダイアログベースで、リストビューをクライアントエリアに張り付ける。

(2)データはファイル入出力を行え、データ形式は、「一行が『"(ファイルのフルパス名)","(コメント文字列)"(CRLF)』というテキストファイル」にする。

(3)上記一行データは「追加」、「検索」、「編集」、「削除」が行え、再生順等で並び替えができるようにする。

(4)このダイアログ終了時に「データファイルのフルパス名の文字列ポインター」(従ってダイアログ消滅時に独立したデータ領域を持ち、それが書き換えられる前にコピーする必要があります)を返し、選択されていない場合はヌルポインター(注)を返します。

注:C++11ではstd::nullptr_tという型ができたそうですが、私はいい加減なので値が"NULL(オブジェクトが何もない)"を指しているポインターのみならず、ポインターの値がNULL(0)とかもそういうことがありますので注意してくださいね。

 

2.リソースとプログラム

内容が似ていたので、前に開設したIDListを流用して作ります。

(1)リソース

//-----------------------------------------
//             BCCForm Ver 2.41
//    An Easy Resource Editor for BCC
//  Copyright (c) February 2002 by ysama
//-----------------------------------------
#include    "ResFileList.h"
#define        TBSTYLE_TOOLTIPS    0x0100
//#define        SBT_TOOLTIPS        0x0800                //【ステータスバーの怪】SB_SETTIPTEXT参照

//----------------------------------
// ダイアログ (IDD_MAIN)
//----------------------------------
IDD_MAIN DIALOG DISCARDABLE 0, 0, 480, 270
EXSTYLE WS_EX_DLGMODALFRAME | WS_EX_TOPMOST
STYLE WS_POPUP | WS_THICKFRAME | WS_CAPTION | WS_SYSMENU | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | DS_SETFONT | DS_CENTER
CAPTION "FileList"
MENU IDM_FORM_MENU
FONT 9, "MS 明朝"
{
// CONTROL "", IDC_STATUSBAR, "MSCTLS_STATUSBAR32", WS_CHILD | WS_VISIBLE | SBT_TOOLTIPS | CCS_TOP | CCS_NOMOVEY, 0, 0, 480, 12                //同じくコメントアウトしています
 CONTROL "", IDC_STATUSBAR, "MSCTLS_STATUSBAR32", WS_CHILD | WS_VISIBLE | CCS_TOP | CCS_NOMOVEY, 0, 0, 480, 12
 CONTROL "", IDC_TOOLBAR, "TOOLBARWINDOW32", WS_CHILD | WS_VISIBLE | WS_BORDER |TBSTYLE_SEP | TBSTYLE_TOOLTIPS, 0, 0, 480, 19
 CONTROL "", IDC_LISTVIEW, "SYSLISTVIEW32", WS_CHILD | WS_VISIBLE | WS_BORDER | LVS_REPORT, 0, 21, 480, 235, WS_EX_CLIENTEDGE
}
//(解説:これはIDListでSDIウィンドウであったものをダイアログにしたものです。サイズ変更枠を使い、システムメニューも付けていますのでウィンドウと変わりません。)
 

//----------------------------------
// ダイアログで使用するメニュー
//----------------------------------
IDM_FORM_MENU MENU DISCARDABLE
{
    POPUP "ファイル(&F)"
    {
        MENUITEM "リストを開く(&O)", IDM_OPEN
        MENUITEM SEPARATOR
        MENUITEM "リストの保存(&S)", IDM_SAVE
        MENUITEM SEPARATOR
        MENUITEM "終了(&X)", IDM_EXIT
    }
    POPUP "編集(&E)"
    {
        MENUITEM "追加(&A)", IDM_ADD
        MENUITEM "検索(&S)", IDM_SEARCH
        MENUITEM "編集(&E0", IDM_EDIT
        MENUITEM "削除(&D)", IDM_DELETE
    }
    POPUP "表示(&V)"
    {
        MENUITEM "並び替え(&S)", IDM_SORT
    }
    POPUP "ヘルプ(&H)"
    {
        MENUITEM "バージョン情報(&V)", IDM_VERSION
    }
}
//(解説:名前が違うほか、ほとんどIDListと変わりません。)

//----------------------------------
// ダイアログ (IDD_EDIT)
//----------------------------------
IDD_EDIT DIALOG DISCARDABLE 0, 0, 360, 132
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | WS_DLGFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_CENTER
CAPTION "リストの編集"
FONT 9, "MS 明朝"
{
 CONTROL "", IDC_EDIT1, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_AUTOHSCROLL | ES_LEFT | ES_NUMBER, 87, 9, 264, 12, WS_EX_CLIENTEDGE
 CONTROL "", IDC_EDIT2, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | ES_AUTOHSCROLL | ES_LEFT | ES_READONLY, 87, 24, 264, 12, WS_EX_CLIENTEDGE
 CONTROL "", IDC_EDIT3, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_MULTILINE | ES_LEFT | WS_VSCROLL | ES_WANTRETURN, 87, 39, 264, 66
 CONTROL "編集終了", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 288, 110, 60, 18
 CONTROL "再生順", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN, 6, 9, 75, 12
 CONTROL "ファイル名", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN, 6, 24, 75, 12
 CONTROL "備考", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY | SS_SUNKEN, 6, 39, 75, 15
}
//(解説:これは変更しています。まず"フルパスファイル名"は編集させないので表示せず、「No(再生順位)」(編集可)、「(ファイルパス無し)ファイル名」(編集不可)、「備考(自由なコメント)」(編集可)の3つにしています。)

//----------------------------------
// ダイアログ (IDD_SEARCH)
//----------------------------------
IDD_SEARCH DIALOG DISCARDABLE 0, 0, 180, 48
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_CENTER
CAPTION "リストの検索"
FONT 9, "MS 明朝"
{
 CONTROL "", IDC_EDIT1, "EDIT", WS_CHILD | WS_BORDER | WS_VISIBLE | WS_TABSTOP | ES_AUTOHSCROLL | ES_LEFT, 3, 15, 171, 12, WS_EX_CLIENTEDGE
 CONTROL "OK", IDOK, "BUTTON", WS_CHILD | WS_VISIBLE | WS_TABSTOP | BS_DEFPUSHBUTTON, 66, 30, 45, 15
 CONTROL "検索する文字列", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 6, 3, 123, 9
}
//(解説:IDListと全く同じです。)

//----------------------------------
// ダイアログ (IDD_SORT)
//----------------------------------
IDD_SORT DIALOG DISCARDABLE 0, 0, 189, 87
EXSTYLE WS_EX_DLGMODALFRAME
STYLE WS_POPUP | DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT | DS_CENTER
CAPTION "リストの並べ替え"
FONT 9, "MS 明朝"
{
 CONTROL "", IDC_COMBOBOX1, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 12, 18, 165, 60
 CONTROL "", IDC_COMBOBOX2, "COMBOBOX", WS_CHILD | WS_VISIBLE | WS_TABSTOP | CBS_DROPDOWNLIST | WS_VSCROLL, 12, 48, 165, 60
 CONTROL "サブアイテムの選択", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 15, 6, 93, 9
 CONTROL "並べ替え方法", 0, "STATIC", WS_CHILD | WS_VISIBLE | SS_NOTIFY, 15, 36, 93, 9
 CONTROL "完了", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 75, 66, 39, 15
}
//(解説:IDListと全く同じです。)

//----------------------------------
// ダイアログ (IDD_VERSION)
//----------------------------------
IDD_VERSION DIALOG DISCARDABLE 0, 0, 186, 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, 6, 6, 26, 26
 CONTROL "FileList Ver 1.0\r\n(c) Copyright by Ysama 2022", 0, "STATIC", SS_CENTER | SS_SUNKEN | WS_CHILD | WS_VISIBLE, 40, 6, 102, 24
 CONTROL "OK", IDOK, "BUTTON", BS_PUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 150, 12, 32, 16
}
//(解説:アイコン、表示名以外はIDListと全く同じです。)

//----------------------------------
// ダイアログで使用するイメージ
//----------------------------------
IDI_ICON    ICON    DISCARDABLE    "Icon.ico"

//--------------------------
// イメージ(IDI_TOOLBAR)
//--------------------------
IDI_TOOLBAR    BITMAP    DISCARDABLE    "ToolBar.bmp"

//(解説:完全にメニューに倣ったビットマップにしています。)


 

定数は100から700まで順に並べただけなのでResFileList.hは省略します。

 

色々すったもんだありましたが、本日Version 1.1ができました。

まぁ、バージョンアップなどというたいそうなものではなく、PCで作業をする時にBGMを聞きたいためにプレイリスト対応にしたいな、ということなんです。(何故か、今まではiTuneがあっても聞きませんでしたが、DirectShowを作ってサンプルの動画や音楽を聴いていたら、BGMが欲しくなりました。)

 

その為に、元々「作らなくちゃ」と思っていたファイルのリスト化ツール(FileList)を、前に作ったIDListを再利用(改造)してダイアログの汎用DLLとして作り、DirectShowに合体させて実用化第1号となりました。(従ってDirectShowの実行にはFileList.dllが、また静的ロードでコンパイルするにはFileList.libが必要となります。サンプルのパックに入れておきます。)

前は↓こんな風だったのですが、

拡張子の「区分」なんか使わないだろうと廃止し、逆に再生順のように順番にはこだわりたい人もいるかもと「No」を追加しました。(ステータスバーはやはりウィンドウスタイルにSBT_TOOLTIPS(0x0800)を入れるとおかしな動作になるので、ツールチップは与えていません。)

これでPCの中に散在する様々なファイルをリスト化してコメント付きで管理することができます。今回はプレイリスト(音楽だけじゃなく、MVなど動画も一緒にプレイリストで再生しますよ)にしましたが、プログラミングで再利用できるクラスや関数のヘッダーファイル等を管理するにも良いかもしれませんね。

 

なお、現在Vectorでは新規アップロードや更新アップロードに障害が出ており、Version 1.1を上げられません。障害が片付いてからVersion1.1を入れたBCCForm and BCCSkeltonパックを上げるつもりですのでお待ちください。

 

ご無沙汰です。

DirectShow Ver 1.0を仕上げてから、矢張り習作ではなく一応実用にしたいと使い勝手の改善とプレイリストによる再生の追加を行っていて怪談に会いました。

 

1.背景

ウィンドウズソフトはツールバーやステータスバーを付けてユーザーインターフェースを充実させることができます。過去のBCCForm and BCCSkeltonでも、例えばBCCMaker等、多くのソフトがツールバー、ステータスバーを実装していました。特にステータスバーはその方法がとても簡単です。

(1)ステータスバーのウィンドウスタイルに"SBT_TOOLTIPS"を加える。その為には"#define        SBT_TOOLTIPS        0x0800"を追加する。

(2)WM_CREATEメッセージでは、ユーザー指定の区画数と初期表示文字列に基づきSkeltonWizardがステータスバー作成のコードを作るので、ユーザーはステータスバーの区画幅だけ修正する。

<例-問題となる今回のコード>

    //ステータスバー登録-SetHandle(hWnd))
    m_SBar.SetHandle(GetDlgItem(m_hWnd, IDC_STATUSBAR));
    //ステータスバー区画設定
    int sec[2] = {100, -1};
    m_SBar.SetSection(2, sec);
    //ステータスバー文字列設定
    m_SBar.SetText(0, "FileList Ver 1.0");
    m_SBar.SetText(1, "プレイリストファイル名(*.plf)");
勿論ユーザーが紫字部分のコードをいじって修正することもできます。

(3)後は、必要に応じてステータスバーの表示を変更する際に、ウィンドウサイズの変更等で表示文字が完全に表示できない場合、ツールチップを付けます。

<例-問題となる今回のコード>

    m_SBar.SetText(1, g_File.ToChar());        //ステータスバーにフルパス名表示
    m_SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)g_File.ToChar());    //ToolTipをつける

ちょっと説明すると、m_SBarはステータスバーをカプセル化するCSBARクラスのインスタンスで、メンバー関数(メソッド)として区画に文字列を書き込む"SetText(int 区画指定、char* 文字列)"と"SendMessage(m_SBar.m_hWnd(ステータスバーのウィンドウハンドル), SB_SETTIPTEXT, (WPARAM)区画指定, (LPARAM)文字列)"と等価なSendMsg(SB_SETTIPTEXT, (WPARAM)区画指定, (LPARAM)文字列)を使っています。

 

2.問題

いつもと同じように上記のコードを記載し、(特別な処理が無いので)BCC5.5のbcc32でコンパイルすると、ステータスバーの区画1の文字が"U駆SVW▯急♠~"といういかにも文字化け(おそらくシステム領域を指しているから)表示が出ます。試しにBCC102のbcc32cでコンパイルすると暴走して落ちます。

この症状はコンスタンスで、毎回完全に再現できるので偶発要因の関与ではないと判断できます。

 

3.原因追及

今までのプログラムで正常に作動していたものが今回異常なのは、今回のプログラムの特異性に由来する可能性があるので、それを考えます。

(1)メイヌィンドウがダイアログ

DirectShowのようなSDIウィンドウと異なり、今回はモーダルダイアログベースでつくっていますが、それとステータスバーとはリモートと考えられます。事実同じ設定のBCCMakerでは全く問題がありません。

(2)コード記述ミス

これも何度も見直しましたが、もともと他のプログラムからのコピペなので間違えようはずもありません。

(3)LPARAM引数のCSTR変数の異常値

これも一応考えましたが、直前のSetText関数で正常にステータスバーの区画1に表示されていますし、直後にMessageBoxを置いて確認しても何ら異常値はありませんでした。試しにchar配列(char[] と const char*)でコンパイル実行しましたが、いずれも同じ"U駆SVW▯急♠~"という文字列を表示しました。

(4)問題現象から考える

m_SBar.SendMsg(SB_SETTIPTEXT, 1, (LPARAM)g_File.ToChar());の実行によりツールチップが出るので、動作(SB_SETTIPTEXT)としては正常です。問題は正常なLPARAM引数がツールチップ処理部で参照されず、関係のないアドレスを参照していることです。

 

 

何故なんでしょう?

 

分からない。

 

歳をとるのは嫌なものです。この重要な話を忘れていました。

 

GraphEditというのはマイクロソフトの提供するフィルターグラフ編集ツールです。(Wikiは英語版ですね。)

元々は「分家」のDirectXのSDK一部であったものが、コンピューティングのAV化に伴い、「本家」のWindows SDKに出世された由。

 

WEBでは「現在はWindows SDKをダウンロードして入手」とありますが、Microsoftのサイトは広すぎてわからず、なんとかこれだろうというWin10のSDK最新版を入手しましたが、なんか、見つかりませんでした。(Microsoftは既にWin11のSDKを最新として、売る気満々ですね!Win10はレガシー扱いです。)ということで、いつものようにweb上の親切な方のサイトからダウンロードします。

 

結構旧いソフトですが、内容は秀逸ですね。(大体、DirectShowの学習中にこれを見つけて入手したら、自分でプログラムを書くのが嫌になっちゃいましたよ。)日本語化はされていないので、敬遠される方もいらっしゃると思いますので、是非是非「食わず嫌い」をせずに一度試してみてください。日本語の使い方記事は結構ありますので参考になりますよ。

DirectShow GraphEdit
その4 GraphEditツールでフィルタを知ろう


将に遊んで学習できるツールとして高く評価できるのではないでしょうか?視覚的にフィルターグラフもわかりやすいと思います。

 

本日午前中かかって、"DirectShow"のBCCForm and BCCSkelton版プログラムを終了いたしました。前回のサンプルプログラムからの発展としては、

 

(1)CDSHOWクラス(CDSHOW.h)

DirectShowを簡単に利用できるように、CDSHOWクラスのインスタンスを作り、ファイル名を登録したら、再生、一時停止、中止を行えるというシンプルので、宣言部分を載せます。

 

///////////////////////////////
//CDSHOWクラス定義ファイル
///////////////////////////////
#include <DShow.h>                    //DirectShowヘッダー
#include <wchar.h>

//フィルター定義(利用者のフィルターの環境によって適宜変更が必要)
#define    FILTER    "ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オ-ディオファイル(*.mp3;*.wav;*.)\0*.mp3;*.wav\0\0"

//親ウィンドウへの通知メッセージ
#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;
public:
    //ファイル名取得用変数
    char m_FileName[MAX_PATH];
    WCHAR m_WFileName[MAX_PATH];
    //ファイル再生時間変数(double)
    REFTIME m_Len;
    //メンバー関数
    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();                    //親ウィンドウに置く終了モニタリング関数
    
void CleanUp();                    //ビデオウィンドウ、フィルターグラフの終了処理
};

 

開始時と大きく使用が異なったのは、フィルターグラフとインターフェースをメディアファイル毎に生成、解放していることです。(まぁ、その方が安心できますが。)

動画や音楽の再生だけならば、↑の赤字関数だけで足ります。再生位置の変更や再生状態などをいじりたい場合も紫字関数しか使いません。プログラマーが使うのは、ウィンドウを固めちゃう悪名高い「WaitForCompletion関数」を使わずにウィンドウズプログラムでIsOver関数を親ウィンドウに置き、終わったら通知させるようにし、WM_SIZEの場合のMove関数、再生終了または中止の際の再初期化CleanUp関数程度です。

 

(2)CSLIDER.h

ウィンドウズプログラムでは「残再生時間」をステータスバーで表示させていたのですが、矢張りスライダー(トラックバー)コントロールで位置を変えられるようにしたいのと、ファイル起動やD&D処理を追加したいのでそれらを追加しました。スライダーコントロールはSDKで書いてもよかったのですが、本プログラム用にCSLIDERクラスを作ってみました。しかし、余り汎用性はなさそうです。(笑;)参考までに宣言部分を載せます。このスライダーは、置き場所が無いので、ToolBarの中に置くこととし、スライダーからのメッセージはツールバーをサブクラス化して盗み取ることにしました。せっかくクラス化したのですが、使ったのは以下の赤字関数だけでした。

 

/////////////////////////////////////////
//スライダー(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);                    //目盛りサイズの設定
};

 

いずれにしても「素人プログラマーが踏み込める範囲はここまで」と潔く認めて、BCC102によるCOMプログラミング第一弾のDirectShowは完結します。この開発記録はDirectShowのヘルプに入れておきます。

 

 

(このプログラムの解説は、腰痛が完全に治ってからにしましょう。)

 

DirectShowでmp4とmovが再生できるようになればこっちのもの。さっそく(昨年のBCC102対応とBatchGoodを踏まえての、念願の)COMプログラミングとしてのDirectShowのBCCForm and BCCSkeltonサンプルプログラム開発を目指します。(2月5日現在の現状は、いくつかのトラブルやハードルを乗り越えて、やっとサンプルプログラムから"CDSHOW"というクラスを作り、それでBCCForm and BDDSkeltonのウィンドウズ版プログラム"DirectShow"(まんまやんか!)を完成させ、ヘルプファイルを書いているところです。)

 

先ずは、ウィンドウ版の構想に基づき、昨年書いたサンプルプログラムを発展させます。

 

【発展形サンプルプログラム-ウィンドウズ版の"DirectShow"にも入れています】

//必要となるであろうヘッダーファイルは次の通り。

//#include    <windows.h>

//#include <DShow.h>                        //DirectShowヘッダー
//#include <wchar.h>
//予め定義しておいた方がよいと思われるフィルター定義(これは飽くまで私の環境に基づいています)
//#define    FILTER    "ビデオファイル(*.avi;*.mp4;*.mov)\0*.avi;*.mp4;*.mov\0オ-ディオファイル(*.mp3;*.wav;*.)\0*.mp3;*.wav\0\0"
//ワイド文字を使う場合、mbstowcs関数を使う際に必要なロケール処理(これをしないと正しく変換されません-私もこれで失敗しました)

//ワイド文字使用のためロケールの初期化(日本語)
//  setlocale(LC_CTYPE, "JPN");
////////////////////////////////////////
//DirectShowの動画再生サンプルプログラム
//エラーの場合は久々のgotoによる終了処理
////////////////////////////////////////

bool SampleDirectShow(HWND hWnd) {

    //DirectShow関連変数
    IGraphBuilder *pGraph = NULL;
//(解説:これがDirectShowサービスを受ける際に親玉となるオブジェクトのポインターです。)

    IMediaControl *pControl = NULL;

//(解説:親玉の子分であるメディアファイルのコントロールに関するインターフェースのポインターです。)

    IMediaEvent *pEvent = NULL;

//(解説:親玉の子分であるメディアイベントに関するインターフェースのポインターです。)

    IVideoWindow *pVideoWindow = NULL;

//(解説:親玉の子分であるビデオ出力に関するインターフェースのポインターです。)

    IMediaPosition *pMPos = NULL;

//(解説:親玉の子分であるメディアの出力プロセスに関するインターフェースのポインターです。)

    REFTIME len;    //Pointer to a variable that receives the total stream length, in seconds.

//(解説:IMediaPositionインターフェースのプロセス管理用の時間を記録する変数です。実態はdoubleです。)

    //結果判定用変数
    HRESULT hr;
//(解説:COMプログラミングの際には必ず使う結果判定用変数です。実態はLONGです。)

    bool success;

//(解説:単なる結果判定用bool変数です。コモンダイアログのところで使用します。)

   //再生時間表示用変数
    int min;
    int sec;
    char buff[128];

//(解説:doubleのREFTIME変数を整数にキャストして残再生時間を表示する為に使います。)

   //COMの初期化
    hr = CoInitialize(NULL);

//(解説:エラー処理をしていませんが、COMを使う際に絶対必要なおまじない、とでも覚えてください。CoUninitialize関数と対になっています。分かってきたら素人プログラマーに適当なSTA(Single THread Apartment)とか勉強してください。今はまだよいと思います。)

   //フィルターグラフマネージャーを作成し、IGraphBuilderインターフェイスを取得
    hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&pGraph);
    if(FAILED(hr)) {
        MessageBox(hWnd, "フィルターグラフマネージャーを作成できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }

//(解説:これがDirectShowを利用する際の親玉の生成です。一応Microsoft Docsなどで(分からなくとも)関数の説明を確認してください。ざっくりというと、最初のCLSIDが付いた奴がDirectShow(= FilterGraphマネージャー)のことで、PCのレジストリーの中のクラスIDを示しています。次の赤字はそのオブジェクトを「どこから工面してくるか」の定数で意味は「DLLとして実装されたCOMサーバを検索」するということです。最後のIIDというのがCLSID_FilterGraphにアクセスするインターフェースのIDという意味です。全体としてはDirectShowのフィルターの組織体 = FilterGraphを管理するマネージャーにアクセスるインスタンスを作っています。なお、エラーの際にメモリー内に作ってしまったインスタンスを解放する必要があるので纏めてリリースする為に「禁断(注)のジャンプ」を使っています。注:昔はBASIC言語のジャンプを使う奴はくそプログラマーと罵られ、構造化プログラミングが最良と信じられていました。

    //IGraphBuilderからIMediaControlインターフェイスを取得
    hr = pGraph->QueryInterface(IID_IMediaControl, (void **)&pControl);
    if(FAILED(hr)) {
        MessageBox(hWnd, "pControlを取得できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
//(解説:今度は親玉へのインターフェースであるIID_IMediaControlを取得しています。FAILED()SUCCEEDED()と同じくマクロで、単なる成功、失敗の他、失敗の内容も伝えてくれます。しかし、boolのTURE(!= 0)、FALSE(== 0)ほど単純ではなく、SUCCEEDEDはHRESULTが >= 0 の場合、FAILEDは < 0 の場合なので、注意を要します。)

    //IGraphBuilderからIMediaEventインターフェイスを取得
    hr = pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent);
    if(FAILED(hr)) {
        MessageBox(hWnd, "pEventを取得できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
//(解説:これも親玉へのインターフェースであるIID_MediaEventインターフェースを取得しています。)

    //以上でフィルタ グラフを作成することができる
    //ファイルを選択し、ファイル名、パスをワイド文字にする

    static char FileName[MAX_PATH];
    static wchar_t WFileName[MAX_PATH];
    OPENFILENAME ofn;                //オープンファイルダイアログ構造体
    ZeroMemory(&ofn, sizeof(ofn));    //ゼロ初期化
    ofn.lStructSize        = sizeof(ofn);
    ofn.hwndOwner        = hWnd;
    ofn.lpstrFilter        = FILTER;
    ofn.nFilterIndex    = 1;
    ofn.lpstrFile        = FileName;
    ofn.nMaxFile        = MAX_PATH;
    ofn.lpstrTitle        = NULL;
    ofn.lpstrInitialDir = ".";
    ofn.Flags            = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
    ofn.lpstrDefExt        = NULL;
    success = GetOpenFileName(&ofn);
//(解説:Win32APIのGetOpenFileName関数を使ったファイル取得です。BCCSkeltonではCMNDLGクラスで簡単に処理できますが、ここではあえてSDKでコーディングしてみました。)

    if(success)
        mbstowcs(WFileName, FileName, MAX_PATH);
//(解説:COMサービスでは文字列がワイド文字を使うので、このマルチバイト文字列(mbs)Toワイド文字列(wcs)への変換関数はよく使いますが、国際的なワイド文字は言語が異なると違う文字を返すので、これを使う前にsetlocale関数でどの国の言語を使うのか指定することが必須です。(実はこの関数を使うのはTextToSpeech以来で今回忘れてしまい、ファイルパス、名に漢字が入るとエラーが出るのでやっと思い出した次第です。)

    else

        goto exit;

    //フィルターグラフの作成
    hr = pGraph->RenderFile(WFileName, NULL);
    if(FAILED(hr)) {
        MessageBox(hWnd, "フィルターグラフを作成できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
//(解説:与えられたファイルをDirectShowが分析し、手持ちのフィルターグラフに適合するものがあるか検索し、無ければエラーになります。なお、ファイルパス、名の誤り<上記のmbstowcs変換不良を含む>も同じエラーになるので、原因を複合的に検討することが重要です。→前回の「【DirectShow】一条の光、LAV Splitter(2)」参照。)

    //ビデオコントロールを取得する
    hr = pGraph->QueryInterface(IID_IVideoWindow, (LPVOID*)&pVideoWindow);
    //成功(S_OK-0x00000000)、失敗(E_NOINTERFACE-0x80004002、E_POINTER-0x80004003、その他)
    if(FAILED(hr)) {
        MessageBox(hWnd, "ビデオウィンドウを取得できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }

    //ここではオーディオファイルでもS_OK(0x00000000)が戻る

//(解説:これは親玉のインターフェースの一つで、ビデオ出力先のウィンドウを作り、コントロールします。今回赤字部分でえらい目に会いましたので、いずれ説明します。)
    //再生位置インターフェースを取得する
    pGraph->QueryInterface(IID_IMediaPosition, (LPVOID *)&pMPos);
    //動画ファイルの長さ(秒-double)を取得
    pMPos->get_Duration(&len);
    //ファイル名と再生時間の表示
    min = (int)len / 60;
    sec = (int)len % 60;
    wsprintf(buff, "(再生時間%d分%d秒)", min, sec);
    lstrcat(FileName, buff);
    MessageBox(hWnd, FileName, "データファイル(ワイド文字)", MB_OK | MB_ICONINFORMATION);
//(解説:親玉のインターフェースとしてメディアファイルの進行状況をREFTIMEで管理することができます。まずIID_IMediaPositionインターフェースを取得し、get_Duration(&len)関数でreftime(double)のlenに再生秒数を取り、これを(スピードの点もあって)分、秒の整数にキャストし、それを文字列にしてファイル名の末尾に括弧書きで追加しています。)

    //////////////////////////// トラブル ////////////////////////////
    //以下ビデオウィンドウ関連のメソッドがビデオファイルでは成功するが、
    //(ビデオ関連フィルターのない)オーディオファイル(mp3)では、全て
    //失敗となり、hr は0x80004002(E_NOINTERFACE)を示す。
    //////////////////////////// 私の推測 ////////////////////////////
    //推定として、IVideoWindowのインスタンスは作成させるが、ビデオフィ
    //ルターがピン接続させていないので、ビデオウィンドウが作成されない
    //のではないか?
    //このような場合、仕様では"VFW_E_NOT_CONNECTED(0x80040209) -
    //No video renderer filter nor pins are connected."が戻るべきでは?
    ////////////////////////////////////////////////////////////////////

//(解説:これがえらい目に会った、という内容です。事実としてビデオウィンドウインターフェース(IID_IVideoWindow)インスタンスは正常(S_OK)に取得され、動画はきれいに映るのですが、メディアファイルがオーディオファイルの場合、ビデオウィンドウ生成は正常でもそれに対して何らかのメソッド(最初はput_Ownerメソッドで、その後のメソッド全てエラーになります)を実行するとエラー(if(FAILED(hr)))になります。「どうして?オーディオファイルでビデオウィンドウが作成されなかったからか?」と考えてhrを調べると仕様の"VFW_E_NOT_CONNECTED(0x80040209)"ではなく、これらメソッドの仕様にない(QuryInterface関数で返すべき)"E_NOINTERFACE(0x80004002)"が返ります。これには頭を抱えましたが、現実にそのエラーコードが返ってくるので、仕方なくそれを避けるコーディング(if(hr != S_OK && hr != E_NOINTERFACE))に替えました↓。この件を調べていて偶然この記事を見つけました。この人もカメラの「ビデオ入力フィルター」は使っていても、「ビデオ出力フィルター」を使っていなければ、ビデオウィンドウは不要となり、私と同じ状況になり得ますね。)

    //親ウィンドウの設定
    hr = pVideoWindow->put_Owner((OAHWND)hWnd);
    if(hr != S_OK && hr != E_NOINTERFACE) {
//    if(FAILED(hr)) {
        MessageBox(hWnd, _T("親ウィンドウを設定できませんでした"), _T("エラー"), MB_OK | MB_ICONERROR);
        goto exit;
    }
    //ウィンドウスタイルを設定する
    hr = pVideoWindow->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);
    if(hr != S_OK && hr != E_NOINTERFACE) {
//    if(FAILED(hr)) {
        MessageBox(hWnd, "ウィンドウスタイルを設定できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //ウィンドウを表示する
    hr = pVideoWindow->put_Visible(OATRUE);
    if(hr != S_OK && hr != E_NOINTERFACE) {
//    if(FAILED(hr)) {
        MessageBox(hWnd, "ビデオウィンドウを表示できませんでした", "エラー", MB_OK | MB_ICONERROR);
        goto exit;
    }
    //動画の再生
    hr = pControl->Run();
    if(SUCCEEDED(hr)) {
        // 完了するまで待機する。
        long evCode;
        pEvent->WaitForCompletion(INFINITE, &evCode);
        // 注 : 実際のアプリケーションでは INFINITE を使用しないこと。
        // 無期限にブロックする場合がある。
    }
//(解説:最後にメディアファイルをコントロールインターフェースのRunメソッドで再生します。Runメソッドは別スレッドを立てるのですが、プログラム本体が終了すると終わってしまうので、イベントインターフェースのWaitForCompletionメソッドで再生を継続するようにします。しかし、このWaitForCompletionメソッドはウィンドウを凍結し、全く操作ができなくなってしまうことに注意してください。再生しながらウィンドウ操作を継続するには、ウィンドウサイドで再生状況を監視し、必要に応じて一時停止、中止を行えるようにすべきでしょう。)

exit:
    //メモリーの開放
    if(pVideoWindow)
        pVideoWindow->Release();
    if(pMPos)
        pMPos->Release();
    if(pEvent)
        pEvent->Release();
    if(pControl)
        pControl->Release();
    if(pGraph)
        pGraph->Release();
    CoUninitialize();
//(解説:ここがエラーの終着駅、メモリー解放処理です。メモリー割り当てが無くて解放しようとするとエラーになるのでポインターの値で管理します。また、リリースする順番は基本的にFILO(先入れ後出し)になります。これはpGraphが先に解放されると、そこから派生させたインターフェースの行き場所がなくなるからです、否、じゃないか?と考えたからです。(検証していないのでわかりません...汗;))
    return TRUE;
}

 

簡単な再生サンプルプログラムの発展形でした。何故これを残しているかというと、"DirectShow"プログラムではWaitForCompletionメソッドを使わず、タイマーで1秒毎に終了したか否かを確認に行き、その際に取得したファイルの位置情報から残再生時間を表示するようにしたので、それと対比させようと思ったからです。

 

将に一気呵成にサンプルの説明を行いました。DirectShowはソフトウェアの裾野が広く、その全てを理解するのは非常に困難ですが、「テレビやPCを見るのに、電波放送、インターネットや映像撮影、データ化、再生、映写の原理や理論を知らなくても問題はない」というのと同じく、DirectShowサービスを使うのに(素人プログラマーが)その全てを理解しなくても大丈夫であることを強調しておきます。(とはいえ、まぁ、知っておいて為にはなるけどね。)

 

NimもWindows版が完成し、その後のネタがなくなりました。そんな時に今回の腰痛となり、ベッドですることもないので、スマホをいじりながら「今一度DirectShowのことをもっと調べてみよう」と考え、先ずは手っ取り早くWikiのDirectShowの記事を調べ、コーデックに関わる認識が正しかったことを確認しました。
標準では一般的なメディア形式のフィルターしか導入されない。例えば、コーデックとしてMPEG-1, MP3, Windows Media Audio, Windows Media Video, MIDI、コンテナフォーマットではAVI, ASF, WAV、それ以外には分離・多重化・入力元・出力先(シンク)・静的な画像のフィルターなどである。これらの特許はWindowsにライセンスされており、別途ライセンスを受ける必要はない。現在、評判が良く標準が制定された現代的なMPEG-4 ASP, AAC, H.264, VorbisのコーデックやOgg, MOV, MP4などのコンテナなどが含まれていないことに注意が必要である。使用には、別途コーデックを得る必要がある。

次にDirectShowの仕組みの基礎として(恥ずかしながら今まではそれすら知らなかった)
(1)フィルター(Source Filter、Transform Filter、Renderer FilterというAVデータ処理)
(2)ピン(各種フィルターの入出力の接続)
(3)グラフ(管理、同期された、ピンで接続される複数のフィルター組織)

のことを、「その3 DirectShowの根っこ(基礎編)」という分かりやすい記事で学習しました。

ではffdshowを導入(注)したのに何故再生できないのでしょうか?
注:ffdshowの導入にはffdshowと共に、Microsoftのフィルターとの優先調整の為にWin7DSFilterTweakerが必要です。

この謎を解くために"DirectShow、mp4、mov"をキーワードとして更に調べます。すると、

MP4ファイルをWindowsで再生する方法」で「Haali Media Splitter or MP4スプリッタ」という記述に目が留まります。「スプリッターて何?

ステレオスライドショー/ステレオムービープレーヤーでMP4動画を再生する方法」では更に次の記述で「スプリッター」が「映像、音声の分離フィルタ」であることを知ります。
ステレオスライドショー/ステレオムービープレーヤーは、DirectShowを使って動画再生します。
AVIファイルやWMVファイルは標準でサポートしていますが、
MP4ファイルを再生するためには、MP4用のSplitter(映像、音声分離)フィルタをインストールする必要があります。但し、他の動画アプリをインストールしたときに、MP4用のSplitterがインストールされていれば、インストール不要ですので、MP4動画が再生できない場合のみ、インストールして下さい。
更にmovファイルでも同様のことがいえるようです。
DirectShowでMOVファイルを再生するために必要なモノ
スプリッターについての私の理解は次のサイトで確信に変わります。
DirectShow Splitter
参照サイトのMicrosoftのお墨付きです。
DirectShow フィルタについて

ということで、既にコーデックはffdshowは導入していたので、スプリッターだけ入れようと思い、スプリッターソフトの評価を調べ、最終的に
(1)MP4 Splitter
(2)LAV Filters
の二択となり、movも再生できる(2)にしました。(注)
注:(2)はコーデックを含んでいるのですが、ffdshowとのコンフリクトが嫌なので、インストールする際にLAV Splitter、LAV Video、LAV Audioを選択することができることから、LAVSplitterのみ導入しています。

そしてその結果ですが、.....なーんとmp4、mov共に難なく再生できるようになりました!

 

<プロローグ>

昨年末に開発したBatchGoodでEmbarcadero社のBCC102(bcc32c.exe)でBCCForm and BCCSkeltonプログラムがコンパイル可能になったことから、bcc32c.exeでしかコンパイルできないCOM等に取り組もうと考えましたが、その後長い茨の道(今回の腰痛を含む)があり、何度もギブアップしそうになりましたが、本日BCCForm and BCCSkeltonでDirectShowを使ったサンプルプログラムを完成させる目途が見えましたので、今までの道行きを振り返る意味で、「ブログ」と「プログラミング」の両建てで【DirectShow】シリーズを進めてゆこうか、と考えています。

 

<経緯>
まず、昨年12月29日に次のように書いています。

(略)これでBCC102を使った小規模開発は楽になる、ということで、前に温めていた構想(ビデオコントロールを使った家庭アプリかなんか)を発展させようと、動画を再生できるコントロールを検討していました。

<MicrosoftのDirectShow>

最初はMicrosoftのDirectShow。これはCOMで簡単に実装できるのですが、何せオリジナルで対応するビデオ形式がAVIやWMVなどでmp4などで読み込もうとするとエラーが出ます。これはコーデックを増やす方法があるか検討しましょう。(略)

 

その後元旦1月1日のブログにDirectShowのサンプルプログラムを紹介しているのですが、*.aviしか再生できず、私の場合全て*.mp4か*.movなので全てエラーとなり、「落胆したこと」として書いています。

bcc32(ver 5.5)がメインだった私は、余りCOMも動画関係もプログラミングしたことが無く、エラーの原因が何か、どのように対処すればよいかの手掛かりが全く得られず、ヒントもない状態でした。しかし生半可の知識ですが「これはMP4やMOVファイルを処理できる所謂CODEC(圧縮動画ファイルの解凍、圧縮処理ソフトウェア)が無い所為ではないか?」と思い、WEBでMicrosoftがライセンスが無い為にDirectShowに加えられなかったCODECの補完として名高いというffdshowというユーティリティを探し出し、これを導入しました。そして「これでもう大丈夫だろう」と思ってテストプログラムを実行したのですが、結果は矢張りaviは再生できるのに、mp4とmovは相変わらずエラーのままでした。

 

刀折れ、矢つきた私は、1月4日のブログで次のように書き、完全にギブアップしました。
1.動画再生コントロールの件
これは進展がありません。
DirectShowのRenderFileメソッドのVFW_E_NOT_FOUND (0X80040216) エラーについて、再度ファイルパスを入念にチェック(ファイルパス誤りも同じエラーが出るので)しましたが、矢張りAVIファイルは表示し、MP4やMOVはエラーという構図は変わりません。ウェブで見てもMicrosoftはこのエラーの対策等示唆してくれないので、とりあえずお蔵入りかと。


その後、他に良い動画コントロールは無いかとWindows Media Playerに浮気してATLが正常に動かずに振られ、VCLのコアになるlibVCLというライブラリーがあると知り、入手するも中身を見て私の知見では高根の花であることを知ります。更にその後Intelのフリーライブラリー、OpenCVというものがあることも知り、ダウンロードしますが、画像のみで音声はなく、パス設定等で躓いたことから投げ出してしまいます。

そんなこんなで「もう動画コントロールはいいかな?」という気になり、Nim開発に集中していたこと、ご高承の通りです。