C++ Builder XE4 > 起動中のソフトウェアのうち、同じフォルダに含まれる実行ファイルと関連があるものだけ見つける > v0.1:EnumWindows実装, v0.2: ビルトイン?EnumWindows()使用

```txt:動作環境
C++ Builder XE4
```

### 処理概要

- 前提
    - 実行ファイルが特定のフォルダに用意されている
        - 例
            - C:\Work\Project1.exe
            - C:\Work\Project2.exe
    - 各ソフトは実行ファイル名の文字列がキャプションに含まれる
        - 例: 「Project1 (v0.1.0) - This software is ...」
- 仕様
    - 起動中のソフトウェアのうち、同じフォルダに含まれる実行ファイルと関連があるものだけ見つける
        - Project1からProject2の実行を見つける

### 関連

- [C++ Builder XE4 > 起動しているソフトのキャプションを取得 v0.1](https://qiita.com/7of9/items/0b6904523e0692fd8d09)
- [C++ > unique_ptr > ポインタを取得する > .get()](https://qiita.com/7of9/items/0a56c851fe3c76192a6d)

### 実装 > v0.1 > EnumetareWindows()の再帰処理

```Unit1.h
//---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TButton *B_listup;
	TMemo *Memo1;
	void __fastcall B_listupClick(TObject *Sender);
private:	// ユーザー宣言
	void __fastcall listFileNamesFromFolder(TStringList *dstPtr);
	void __fastcall listSoftCaption(TStringList *dstPtr);
	void EnumetareWindows(HWND hWndParent, int depth, TStringList *dstPtr);
	void __fastcall removeNonTargetSoft(TStringList *fileNameSL, TStringList *softCapsSL);
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
```

```Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <IOUtils.hpp>
#include <memory>
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
	: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::B_listupClick(TObject *Sender)
{
	// 1. ファイルリストアップ
	std::unique_ptr<TStringList> fileNames(new TStringList);
	listFileNamesFromFolder(fileNames.get());

	// 2. 起動ソフトのキャプションをリストアップ
	std::unique_ptr<TStringList> softCaps(new TStringList);
	listSoftCaption(softCaps.get());

	// 3. フィルター
	removeNonTargetSoft(fileNames.get(), softCaps.get());

	// 4. 結果の表示
	Memo1->Lines->Clear();
	for(int idx=0; idx < softCaps->Count; idx++) {
		Memo1->Lines->Add(softCaps->Strings[idx]);
	}
}
//---------------------------------------------------------------------------
void __fastcall TForm1::listFileNamesFromFolder(TStringList *dstPtr)
{
	// .exeのあるフォルダ内の.exeファイルをリストアップする

	if (dstPtr == NULL) {
		return; // error
	}

	String curDir = GetCurrentDir();

	TStringDynArray fileList;
	TSearchOption option = TSearchOption::soTopDirectoryOnly;

	fileList = TDirectory::GetFiles(curDir, L"*.exe", option);

	for(int idx=0; idx<fileList.Length; idx++) {
		String filnam = ExtractFileName(fileList[idx]);
		//Memo1->Lines->Add(filnam);
		dstPtr->Add(filnam);
	}
}

void __fastcall TForm1::listSoftCaption(TStringList *dstPtr)
{
	// (階層1で)起動中のソフトキャプションをリストアップする

	if (dstPtr == NULL) {
		return; // error
	}

	HWND hWndParent = GetDesktopWindow();
	EnumetareWindows(hWndParent, /*depth=*/0, dstPtr); // 0: 探索開始階層(トップ)
}

static const int kMaxDepth = 1; // 起動ソフトをチェックする最大階層

void TForm1::EnumetareWindows(HWND hWndParent, int depth, TStringList *dstPtr)
{
	if (dstPtr == NULL) {
		return; // error
	}

	wchar_t szBuf[512] = {0};
	GetWindowText(hWndParent, szBuf, 512);

	if (szBuf[0] != 0x00) {
		if (depth <= kMaxDepth) {
			String msg = IntToStr(depth) + L":" + String(szBuf);
			dstPtr->Add(String(szBuf));
			//Memo1->Lines->Add(msg);

		}
	}

	HWND hWndChild = FindWindowEx(hWndParent, NULL, NULL, NULL);
	if (hWndChild == NULL) {
		return;
	}

	do {
		EnumetareWindows(hWndChild, (depth+1), dstPtr);
	} while ( (hWndChild = FindWindowEx(hWndParent, hWndChild, NULL, NULL)) != NULL );
}

void __fastcall TForm1::removeNonTargetSoft(TStringList *fileNameSL, TStringList *softCapsSL)
{
	// 1. 非対象ソフトの確認
	for(int si=0; si < softCapsSL->Count; si++){ // si: software index
		int cnt=0;
		for(int fi=0; fi < fileNameSL->Count; fi++) { // fi: filename index
			String filnam = fileNameSL->Strings[fi];
			// .exeの削除
			filnam = StringReplace(filnam, L".exe", L"", TReplaceFlags()<<rfReplaceAll);

			//
			if (softCapsSL->Strings[si].Pos(filnam) > 0) { // 実行ファイル名がキャプションに含まれる
				cnt++;
			}
		}
		if (cnt == 0) {
			softCapsSL->Strings[si] = L""; // Delete()で削除すると位置が変わるため、ここではゼロ長さとする
		}
	}

	// 2. 非対象ソフトのリストからの削除
	//    (ずれを考慮して、後ろから消す)
	for(int si = (softCapsSL->Count - 1); si >= 0; si--) { // si: software index
		if (softCapsSL->Strings[si].Length() == 0) {
			softCapsSL->Delete(si);
		}
	}

}
```

### 結果

1. Project2を起動する
2. Project1を起動する
3. Project1上のボタンを押す

![2018-10-24_09h26_24.png](https://qiita-image-store.s3.amazonaws.com/0/32870/0bb5ef46-212b-ae80-ab46-c8cbc5bb6902.png)

### 備考

- Proejct1(自分)が2つ見つかる
    - 自分の情報は別の方法で取得できるので使わない方法を検討する
- Project2.exeとProject27182818_napier.exeが同居すると探索に失敗する
    - **将来のソフトウェアアップデート時の名前付けを考慮しておくこと**
        - XXX.exe
        - XXX2.exe
            - XXX.exeとXXX2.exeが同時に起動している場合に問題となる
- .exeのショートカットファイルにも対応すると便利かもしれない
    - v0.1では未対応

### 改善点

「FindWindowEx()を使えば、もっとシンプルになるかもしれない」というのが人と話していく中で出てきた。

- [C++ Builder XE4, 10.2 Tokyo > HWNDの取り方 > リファクタリング対応](https://qiita.com/7of9/items/39f448b7d5df4692f135)
    - `HWND subHwnd = FindWindowEx(NULL, NULL, NULL, fullCaption.c_str());`
    - ==> 試したところだめだった。Captionの文字列の部分一致検索をFindWindowEx()でできないというのが原因。

## 実装 > v0.2 > EnumWindows()使用

### 参考

- [Get the handle of a window with not fully known title. (C#)](https://stackoverflow.com/questions/637652/get-the-handle-of-a-window-with-not-fully-known-title-c)
    - EnumWindowsの使用について回答有り
- [EnumWindows function](https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-enumwindows)

### 実装

```Unit1.h
 //---------------------------------------------------------------------------

#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE で管理されるコンポーネント
	TButton *B_listup;
	TMemo *Memo1;
	void __fastcall B_listupClick(TObject *Sender);
private:	// ユーザー宣言
	void __fastcall listFileNamesFromFolder(TStringList *dstPtr);
	String __fastcall findWindowCaption_hasStringOf(String partStr);
public:		// ユーザー宣言
	__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
```

```Unit1.cpp
//---------------------------------------------------------------------------

#include <vcl.h>
#pragma hdrstop

#include <IOUtils.hpp>
#include <memory>
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::B_listupClick(TObject *Sender)
{
    // 1. ファイルリストアップ
	std::unique_ptr<TStringList> fileNames(new TStringList);
    listFileNamesFromFolder(fileNames.get());

    // 2. 関係ソフトの探索と表示
    Memo1->Lines->Clear();
    for(int idx=0; idx<fileNames->Count; idx++) {
        String acap = findWindowCaption_hasStringOf(fileNames->Strings[idx]);
        Memo1->Lines->Add(acap);
    }
}
//---------------------------------------------------------------------------
void __fastcall TForm1::listFileNamesFromFolder(TStringList *dstPtr)
{
    // .exeのあるフォルダ内の.exeファイルをリストアップする

    if (dstPtr == NULL) {
        return; // error
    }

    String curDir = GetCurrentDir();

    TStringDynArray fileList;
	TSearchOption option = TSearchOption::soTopDirectoryOnly;

    fileList = TDirectory::GetFiles(curDir, L"*.exe", option);

    for(int idx=0; idx<fileList.Length; idx++) {
        String filnam = ExtractFileName(fileList[idx]);
        //Memo1->Lines->Add(filnam);
        dstPtr->Add(filnam);
    }
}

static String s_partStr; // 探索用文字列
static bool FindWindowWithExpr(HWND hWnd, LPARAM lParam)
{
	TCHAR szText[256] = {0};
	GetWindowText(hWnd, szText, 255);

	if (String(szText).Pos(s_partStr) > 0) {
		*((HWND*) lParam) = hWnd;
		return false;
	}

	return true;
}

String __fastcall TForm1::findWindowCaption_hasStringOf(String partStr)
{
	// 戻り値: ウィンドウのキャプション

	HWND hWnd = NULL; // root から探索

	// .exeの削除した文字列で検索 (例: Project1)
	s_partStr = StringReplace(partStr, L".exe", L"", TReplaceFlags()<<rfReplaceAll);
	EnumWindows((WNDENUMPROC)&FindWindowWithExpr, (LPARAM)&hWnd); // top-level windowsのみ探索
	if (hWnd == NULL) {
		return L"";
	}

	// ウィンドウキャプションの取得
	wchar_t winCap[256] = {0};
	GetWindowText(hWnd, winCap, 256);
	return String(winCap);
}
```

### 結果

1. Project2を起動する
2. Project1を起動する
3. Project1上のボタンを押す

![2018-10-24_13h17_18.png](https://qiita-image-store.s3.amazonaws.com/0/32870/b9effb2a-ebef-12e8-285b-55b4f8958f09.png)

### 備考

`static String s_partStr; // 探索用文字列`
は多重処理時には別の方法が必要になる。