MFCでDBテーブルを操作したい話 3

前回までの話

別アプローチ

さて、前回なぜかDB接続ができちゃった(?)って話でしたが。

今度はMySQL Connector C++を使わない方法でやってみます。

もともと、この記事を書く予定でしたので、こちらを先に。

手順としては、

  1. sqlファイルを作成する
  2. バッチファイルを作成する
  3. バッチ起動させる
  4. DB操作を行う
  5. 作ったファイルは削除

という感じです。

では早速。

プロジェクト新規作成

前回作ったMySQLTestソリューションに、新たにプロジェクトを追加します。

ファイルメニューから新規作成→プロジェクトを選択。
MFCアプリを選択後、プロジェクト名を決めます。

今回はMySQLTest2。

ソリューションは再び作る必要はないので、「ソリューションに追加」を選択します。

アプリケーションの種類は同じくダイアログベースにして完了。

プロジェクトが新規追加されました。

ボタンと3つとエディットボックス2つを追加しておきます。

左のボタンはIDが上から順に

  • IDC_BUTTON_INSERT
  • IDC_BUTTON_SELECT
  • IDC_BUTTON_DELETE

エディットボックスは

  • IDC_EDIT_NAME
  • IDC_EDIT_AGE

です。

年齢のエディットボックスはプロパティで「数字」をTrueにし、
数字のみ入力可にします。

スタティックテキストはわかりやすさのため追加してるだけで、
今回重要な意味は持ちません。

最後に、追加したボタン3つをそれぞれダンブルクリックし、
ボタンを押したときに呼ばれる関数を追加しておきます。

処理作成

ヘッダーMySQLTest2Dlg.hに、関数を定義します。

private:
	void SQLOperator(int iMode);						// SQL処理
	void CreateDeleteBatFile(BOOL bMode);				// バッチファイル作成削除
	void CreateDeleteSQLFile(BOOL bMode, int iMode);	// sqlファイル作成削除
    CString strCurrentDir();							// 現在実行exeのパス

関数 CreateDeleteBatFile

バッチファイルを作成・削除する関数です。

引数はenumが望ましいと思いますが、
今回はTRUE=作成、FALSE=削除で処理を進めます。

/**
 * @fn
 * @brief	バッチファイル作成・削除
 * @param	BOOL	TRUE	作成
 *					FALSE	削除
 * @return	なし
 */
void CMySQLTest2Dlg::CreateDeleteBatFile(BOOL bMode)
{
	// 現在実行しているMySQLTest2.exeのパス
	CString strPath = strCurrentDir();
	//ファイル名
	strPath += _T("run_sql.bat");

	if (bMode)
	{
		// バッチファイル作成します
		
		CString strUser = _T("root");		// ユーザ名
		CString strPass = _T("password");	// パスワード
		CString strHost = _T("localhost");	// ホスト名
		CString strDB = _T("testschema1");	// DB名
		// ※パスワードは自分のやつを

		// コマンド
		CString strBatCommand = _T("@echo off\r\n");

		CString strTemp = _T("");
		strTemp.Format(_T("set MYSQL_CMD=\"C:\\Program Files\\MySQL\\MySQL Server 8.0\\bin\\mysql.exe\" -u %s -p%s -h %s %s\r\n"),
			(LPCTSTR)strUser, (LPCTSTR)strPass, (LPCTSTR)strHost, (LPCTSTR)strDB);
		strBatCommand += strTemp;

		// sqlファイルを実行
		strBatCommand += _T("%MYSQL_CMD% < commands.sql");


		// ファイル作成
		CStdioFile* pStdioFile = new CStdioFile(strPath, (CFile::modeWrite | CFile::modeCreate));
		pStdioFile->Seek(0, CFile::end);
		pStdioFile->WriteString(strBatCommand);
		delete pStdioFile;	//後処理
		pStdioFile = NULL;	//後処理
	}
	else
	{
		// バッチファイル削除します
		DeleteFile(strPath);
	}
	
}

/**
 * @fn
 * @brief	現在実行パス取得
 * @param	なし
 * @return	CString		パス
 */
CString CMySQLTest2Dlg::strCurrentDir()
{
	// 返却値
	CString strRtn = (LPCTSTR)nullptr;
	// パス、ドライブ名、ディレクトリ名、ファイル名、拡張子
	TCHAR path[_MAX_PATH], drive[_MAX_PATH], dir[_MAX_PATH], file[_MAX_PATH], ext[_MAX_PATH];

	// フルパスを取得
	if (::GetModuleFileName(NULL, path, _MAX_PATH) != 0)
	{
		// ファイルパスを分割
		::_tsplitpath_s(path, drive, _MAX_PATH, dir, _MAX_PATH, file, _MAX_PATH, ext, _MAX_PATH);
		// ドライブ名とディレクトリ名を結合
		strRtn = ::PathCombine(path, drive, dir);
	}

	return strRtn;
}

13行目でファイル名を決定しています。
26~34行目まではバッチファイルの中身です。
29行目は接続しており、strTempで変数化しています。
34行目はsqlファイルを実行。
そこから下はファイル出力処理です。
バッチファイル削除処理は1文で済みます。

strCurrentDir関数の方は、毎度おなじみのブログを参考にさせていただいています。

https://untitled-note.com/vcpp-module-path

ボタン押下時の処理内に、一時的にこのような処理を作ってみます。
後ですぐ消します。

// INSERT処理
void CMySQLTest2Dlg::OnBnClickedButtonInsert()
{
    // INSERTを押すとバッチファイルが作られる
	CreateDeleteBatFile(TRUE);
}

// SELECT処理
void CMySQLTest2Dlg::OnBnClickedButtonSelect()
{
    // SELECTを押すとバッチファイルが削除
	CreateDeleteBatFile(FALSE);
}

一度ビルドして試してみます。

INSERTボタンを押して、MySQLTest2.exeがあるフォルダを覗くと。

バッチファイルができています。
メモ帳で開いて見ると、

ちゃんと処理がかけてます。

後はこのバッチファイルが動いてくれればいいので、
それはSQLOperator関数内で書きます。

SELECTボタンを押すと、バッチファイルが削除されました。

ボタンを押したときの処理を元に戻しておきます。

// INSERT処理
void CMySQLTest2Dlg::OnBnClickedButtonInsert()
{

}

// SELECT処理
void CMySQLTest2Dlg::OnBnClickedButtonSelect()
{

}

関数 CreateDeleteSQLFile

sqlファイルを作成・削除します。

こちらも引数はenumが望ましいと思いますが、
同じくTRUE=作成、FALSE=削除で処理を進めます。
また、iModeは0(=INSERT)か1(=SELECT)か2(=DELETE)しかないとします。

/**
 * @fn
 * @brief	sql作成・削除
 * @param	BOOL	TRUE	作成
 *					FALSE	削除
 *			int		0	INSERT
 *					1	SELECT
 *					2	DELETE
 * @return	なし
 */
void CMySQLTest2Dlg::CreateDeleteSQLFile(BOOL bMode, int iMode)
{
	// 現在実行しているMySQLTest2.exeのパス
	CString strPath = strCurrentDir();
	//ファイル名
	strPath += _T("commands.sql");

	if (bMode)
	{
		// sqlファイル作成します

		CString strName = _T("");
		GetDlgItemText(IDC_EDIT_NAME, strName);	// 名前エディットボックスから取得
		CString strAge = _T("");
		GetDlgItemText(IDC_EDIT_AGE, strAge);	//年齢エディットボックスから取得

		CString strCommand = _T("");
		
		if (iMode == 0)
		{
			// INSERT文
			strCommand.Format(_T("INSERT INTO testtable1 (name, age) VALUES ('%s', %s);"), (LPCTSTR)strName, (LPCTSTR)strAge);
		}
		else if (iMode == 1)
		{
			// SELECT文
			if (strAge == _T(""))
			{
				// 年齢に何も入力されていない場合は全件
				strCommand.Format(_T("SELECT * FROM testtable1;"));
			}
			else
			{
				// 年齢に入力されている場合はwhere句で条件付き
				strCommand.Format(_T("SELECT * FROM testtable1 WHERE age = %s;"), (LPCTSTR)strAge);
			}
			
		}
		else
		{
			// DELETE文
			strCommand.Format(_T("DELETE FROM testtable1 WHERE name = '%s' AND age = %s;"), (LPCTSTR)strName, (LPCTSTR)strAge);
		}

		// ファイル作成
		CFile file;
		if (file.Open(strPath, CFile::modeWrite | CFile::modeCreate | CFile::typeBinary))
		{
			// UTF-8 BOMを書き込む
			BYTE bom[3] = {0xEF,  0xBB, 0xBF };
			file.Write(bom, sizeof(bom));

			// CStringをUTF-8に変換して書き込む
			CT2A pszConvertedAnsiString(strCommand, CP_UTF8);
			std::string strUtf8(pszConvertedAnsiString);
			file.Write(strUtf8.c_str(), strUtf8.length());
			file.Close();
		}
	}
	else
	{
		// sqlファイル削除します
		DeleteFile(strPath);
	}
}

やってることはバッチファイル作成削除とほぼ同じです。

一つだけ、56行目以降、先ほどとファイル出力処理を変えました。。

エディットボックスに全角文字が入った場合、
先ほどの処理だと正しくファイル出力ができない場合があります。

具体的には、commands.sqlファイルを開くと
INSERT INTO table01 (name, age) VALUES (
となります。
半角文字は問題ないんですけどね。

これは文字セットがUnicode文字セットが原因。
マルチバイト文字セットを使用すると問題なく出力できるのですが。

Unicode文字セットで進めたかったため、処理を変えました。

まぁそもそも、CreateDeleteBatFile関数内で使ったCStdioFileは、
Unicode文字を正しく書き込むための方法を提供していないらしいので、
そちらも変更した方がいいんでしょうがね…。

面倒なのd

これまた、ボタン押下時の処理に一時的な処理を入れます。

void CMySQLTest2Dlg::OnBnClickedButtonInsert()
{
	CreateDeleteSQLFile(TRUE, 0);
}

// SELECT処理
void CMySQLTest2Dlg::OnBnClickedButtonSelect()
{
	CreateDeleteSQLFile(TRUE, 1);
}

// DELETE処理
void CMySQLTest2Dlg::OnBnClickedButtonDelete()
{
	CreateDeleteSQLFile(TRUE, 2);
}

ビルドして、ファイルを作成してみます。

これで各ボタンを押します。
出来上がったファイルは

こちらも問題なさげです。

ボタン押下時の処理を削除しておきます。

関数 SQLOperator

ボタンが押下されたときに共通の関数を使いたいので、
引数でどのボタンが押されたかを判別し、この共通関数に進ませます。


引数のiModeは0(CREATE)、1(SELECT)、2(DELETE)で考えます。

ここで作る処理は

  1. バッチファイル作成処理呼応(CreateDeleteBatFile(TRUE))
  2. sqlファイル作成処理呼応(CreateDeleteSQLFile(TRUE))
  3. バッチ実行
  4. ファイル削除(CreateDeleteBatFile(FALSE)、CreateDeleteSQLFile(FALSE))

です。

ボタン押下時の処理も含め、一気に書いてみます。

// INSERT処理
void CMySQLTest2Dlg::OnBnClickedButtonInsert()
{
	SQLOperator(0);
}

// SELECT処理
void CMySQLTest2Dlg::OnBnClickedButtonSelect()
{
	SQLOperator(1);
}

// DELETE処理
void CMySQLTest2Dlg::OnBnClickedButtonDelete()
{
	SQLOperator(2);
}

/**
 * @fn
 * @brief	SQL実行
 * @param	int		0	INSERT
 *					1	SELECT
 *					2	DELETE
 * @return	なし
 */
void CMySQLTest2Dlg::SQLOperator(int iMode)
{
	// バッチファイル作成
	CreateDeleteBatFile(TRUE);
	// sqlファイル作成
	CreateDeleteSQLFile(TRUE, iMode);

	// バッチ実行
	// バッチファイルとsqlファイルのありか
	CString strBatPath = strCurrentDir();
	strBatPath += _T("run_sql.bat");

	// STARTUPINFOとPROCESS_INFORMATION構造体を宣言
	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	// 構造体をゼロ初期化
	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);  // cbメンバに構造体のサイズを設定
	ZeroMemory(&pi, sizeof(pi));

	// バッチファイルを実行するためのコマンドライン文字列を作成
	CString strCommandLine = _T("cmd.exe /C \"") + strBatPath + _T("\"");

	// CreateProcess関数を使ってプロセスを起動
	if (!CreateProcess(
		nullptr,             // 実行するアプリケーション名
		strCommandLine.GetBuffer(),  // コマンドライン文字列
		nullptr,             // プロセスのセキュリティ属性
		nullptr,             // スレッドのセキュリティ属性
		FALSE,               // 子プロセスに親プロセスのハンドルを継承させるか
		0,                   // 作成オプション
		nullptr,             // 環境ブロック
		nullptr,             // 作業ディレクトリ
		&si,                 // STARTUPINFO構造体
		&pi                  // PROCESS_INFORMATION構造体
	))
	{
		// エラー処理
		AfxMessageBox(_T("バッチファイルの実行に失敗しました。"));
		return;
	}

	// プロセスが終了するのを待機する
	WaitForSingleObject(pi.hProcess, INFINITE);

	// ハンドルを閉じる
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	// バッチファイル削除
	CreateDeleteBatFile(FALSE);

	// sqlファイル削除
	CreateDeleteSQLFile(FALSE, iMode);
}

バッチファイルを単に実行するだけなら、
CreateProcessではなくShellExecuteでもいいかもしれません。

今回、使ったファイルは削除するようにしているので、
バッチ実行前にファイル削除されては困ります。

なので、バッチ処理が終わるまで処理を進めないという
少し詳細な制御が欲しいため、CreateProcessを使いました。

今思うと、削除する必要あるのかとも思ってきましたが…

因みにこれ、SELECTってどうやってデータ表示するんだ問題ありますが、
とりあえず、これでビルド。

実行

先ほどと同じデータで、INSERTを実行してみます。

実行しているフォルダを観察すると一瞬、
ファイルが2つ出て、CMDが立ち上がり、ファイルが削除されたのを
確認しました。

せっかくなのでA5:SQL Mk-2で確認。

テーブルを表示させると

なんでできてないんだい
⊂⌒~⊃。Д。)⊃

原因調査

CreateDeleteSQLFile関数に少しだけ手を加えます。

//...略
// コマンド
CString strBatCommand = _T("@echo off\r\n");

CString strTemp = _T("");
strTemp.Format(_T("set MYSQL_CMD=\"C:\\Program Files\\MySQL\\MySQL Server 8.0\\bin\\mysql.exe\" -u %s -p%s -h %s %s\r\n"),
	(LPCTSTR)strUser, (LPCTSTR)strPass, (LPCTSTR)strHost, (LPCTSTR)strDB);
strBatCommand += strTemp;

// sqlファイルを実行
strBatCommand += _T("%MYSQL_CMD% < commands.sql");

//調査用に追加
strBatCommand += _T("\r\npause");


// ファイル作成
//...略

14行目を追加しました。
pauseでbat処理を一時停止させます。

再度ビルドして同じデータで確認。

接続はできていますね。
問題はsql文の読み込み…?

調べると、
ERROR 1366 at line 1: Incorrect string value …
のエラー対処法結構出てきますね。

my.iniを修正するのもありましたが、
自分の場合、my.iniを変更しちゃうと
サービスでMySQL80が起動できなくなっちゃいました

なのでこの方法はなし。

あ、ERROR 1366の説明については他のブログや公式を参考にしてください。

対処方法

バッチファイルに書く処理を修正します。

void CMySQLTest2Dlg::CreateDeleteBatFile(BOOL bMode)
{
	// 現在実行しているMySQLTest2.exeのパス
	CString strPath = strCurrentDir();
	//ファイル名
	strPath += _T("run_sql.bat");

	if (bMode)
	{
		// バッチファイル作成します
		
		CString strUser = _T("root");		// ユーザ名
		CString strPass = _T("password");	// パスワード
		CString strHost = _T("localhost");	// ホスト名
		CString strDB = _T("testschema1");		// DB名
		// ※パスワードは自分のやつを

		// コマンド
		CString strBatCommand = _T("@echo off\r\n");

		CString strTemp = _T("");
		strTemp.Format(_T("set MYSQL_CMD=\"C:\\Program Files\\MySQL\\MySQL Server 8.0\\bin\\mysql.exe\" --default-character-set=utf8mb4 -u %s -p%s -h %s %s\r\n"),
			(LPCTSTR)strUser, (LPCTSTR)strPass, (LPCTSTR)strHost, (LPCTSTR)strDB);
		strBatCommand += strTemp;

		// 後は同じ 略
	}
	else
	{
		// バッチファイル削除します
		DeleteFile(strPath);
	}
	
}

変更したのは22行目、
–default-character-set=utf8mb4
このオプションを挿入しました。

エンコーディングの指定を行います。

とりあえずpauseは残したまま、再度ビルド。

INSERTボタンを押します。

これはうまくいったのでは!!??

A5:SQL Mk-2で確認してみます。
Workbenchでももちろんいいですよ。

╭( ・ㅂ・)و  ヨシャイ!
(idが6なのは、途中試しに色々挿入していたのを削除したためです。)

因みにDELETEボタンを押すと挿入した行が消えてくれました。
作ったDELETE文ですが、同姓同名同年齢の人がいた場合、
当然両方削除されてしまいます。

本来の処理であれば生年月日を入れるなどもありますが、
今回はテーブルの操作をしたいだけなのでそこまではしません。

課題

問題(?)のSELECTですが。

とりあえずselect allしたいので、エディットボックスはすべてからにします。

SELECTボタンを押すと

なるほど。
出てきてくれましたね。

年齢を入力するとそこ行だけ出てくることも確認済みです。

問題は、
コマンドプロンプト上に出現する結果をどうやってexe側に渡すか。

この辺は、また別の機会に考えたいと思います。
もしかしたら、前回作成しちゃった処理の方を使えばできるのか…?

とりあえず、自分がやりたかった操作はできました。

いったん、調査用に挿入したpauseの箇所は削除して再ビルドしておきます。

終わりに

バッチファイルでSQL操作をしてみる話でした。

やってないですけど、この感じだとUPDATEも問題なくできそうです。

後、本来の処理であれば
テーブル名変数化とかエディットボックス制御も必要ですが、
今回は省略しました。

さて、Connector C++を使う方法、ちょっとだけ試してみます。
失敗で終わる可能性は高いですが。

今回作ったMySQLTest2プロジェクトのソースを載せて終わりです。

MFCでDBテーブルを操作したい話 4(最終回)
前回までの話再度挑戦2話目において実践した、MySQL Connector C++を利用してテーブル操作を行いたいです。再度、ボタンを押した際のソース掲示。void CMySQLTest1Dlg::OnBnClickedButton1(){...

MySQLTest2Dlg.h


// MySQLTest2Dlg.h : ヘッダー ファイル
//

#pragma once


// CMySQLTest2Dlg ダイアログ
class CMySQLTest2Dlg : public CDialogEx
{
// コンストラクション
public:
	CMySQLTest2Dlg(CWnd* pParent = nullptr);	// 標準コンストラクター

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

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


// 実装
protected:
	HICON m_hIcon;

	// 生成された、メッセージ割り当て関数
	virtual BOOL OnInitDialog();
	afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
	afx_msg void OnPaint();
	afx_msg HCURSOR OnQueryDragIcon();
	DECLARE_MESSAGE_MAP()
public:
	afx_msg void OnBnClickedButtonInsert();
	afx_msg void OnBnClickedButtonSelect();
	afx_msg void OnBnClickedButtonDelete();

private:
	void SQLOperator(int iMode);						// SQL処理
	void CreateDeleteBatFile(BOOL bMode);				// バッチファイル作成削除
	void CreateDeleteSQLFile(BOOL bMode, int iMode);	// sqlファイル作成削除
	CString strCurrentDir();							// 現在実行exeのパス
};

MySQLTest2Dlg.cpp


// MySQLTest2Dlg.cpp : 実装ファイル
//

#include "pch.h"
#include "framework.h"
#include "MySQLTest2.h"
#include "MySQLTest2Dlg.h"
#include "afxdialogex.h"
#include <string>

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// アプリケーションのバージョン情報に使われる CAboutDlg ダイアログ

class CAboutDlg : public CDialogEx
{
public:
	CAboutDlg();

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

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

// 実装
protected:
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX)
{
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx)
END_MESSAGE_MAP()


// CMySQLTest2Dlg ダイアログ



CMySQLTest2Dlg::CMySQLTest2Dlg(CWnd* pParent /*=nullptr*/)
	: CDialogEx(IDD_MYSQLTEST2_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CMySQLTest2Dlg::DoDataExchange(CDataExchange* pDX)
{
	CDialogEx::DoDataExchange(pDX);
}

BEGIN_MESSAGE_MAP(CMySQLTest2Dlg, CDialogEx)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BUTTON_INSERT, &CMySQLTest2Dlg::OnBnClickedButtonInsert)
	ON_BN_CLICKED(IDC_BUTTON_SELECT, &CMySQLTest2Dlg::OnBnClickedButtonSelect)
	ON_BN_CLICKED(IDC_BUTTON_DELETE, &CMySQLTest2Dlg::OnBnClickedButtonDelete)
	ON_BN_CLICKED(IDOK, &CMySQLTest2Dlg::OnBnClickedOk)
	ON_BN_CLICKED(IDCANCEL, &CMySQLTest2Dlg::OnBnClickedCancel)
END_MESSAGE_MAP()


// CMySQLTest2Dlg メッセージ ハンドラー

BOOL CMySQLTest2Dlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// "バージョン情報..." メニューをシステム メニューに追加します。

	// IDM_ABOUTBOX は、システム コマンドの範囲内になければなりません。
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != nullptr)
	{
		BOOL bNameValid;
		CString strAboutMenu;
		bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
		ASSERT(bNameValid);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// このダイアログのアイコンを設定します。アプリケーションのメイン ウィンドウがダイアログでない場合、
	//  Framework は、この設定を自動的に行います。
	SetIcon(m_hIcon, TRUE);			// 大きいアイコンの設定
	SetIcon(m_hIcon, FALSE);		// 小さいアイコンの設定

	// TODO: 初期化をここに追加します。

	return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

void CMySQLTest2Dlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialogEx::OnSysCommand(nID, lParam);
	}
}

// ダイアログに最小化ボタンを追加する場合、アイコンを描画するための
//  下のコードが必要です。ドキュメント/ビュー モデルを使う MFC アプリケーションの場合、
//  これは、Framework によって自動的に設定されます。

void CMySQLTest2Dlg::OnPaint()
{
	if (IsIconic())
	{
		CPaintDC dc(this); // 描画のデバイス コンテキスト

		SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// クライアントの四角形領域内の中央
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// アイコンの描画
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialogEx::OnPaint();
	}
}

// ユーザーが最小化したウィンドウをドラッグしているときに表示するカーソルを取得するために、
//  システムがこの関数を呼び出します。
HCURSOR CMySQLTest2Dlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}


// INSERT処理
void CMySQLTest2Dlg::OnBnClickedButtonInsert()
{
	SQLOperator(0);
}

// SELECT処理
void CMySQLTest2Dlg::OnBnClickedButtonSelect()
{
	SQLOperator(1);
}

// DELETE処理
void CMySQLTest2Dlg::OnBnClickedButtonDelete()
{
	SQLOperator(2);
}

/**
 * @fn
 * @brief	SQL実行
 * @param	int		0	INSERT
 *					1	SELECT
 *					2	DELETE
 * @return	なし
 */
void CMySQLTest2Dlg::SQLOperator(int iMode)
{
	// バッチファイル
	CreateDeleteBatFile(TRUE);
	// sqlファイル
	CreateDeleteSQLFile(TRUE, iMode);

	// バッチ実行
	// バッチファイルとsqlファイルのありか
	CString strBatPath = strCurrentDir();
	strBatPath += _T("run_sql.bat");

	// STARTUPINFOとPROCESS_INFORMATION構造体を宣言
	STARTUPINFO si;
	PROCESS_INFORMATION pi;

	// 構造体をゼロ初期化
	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);  // cbメンバに構造体のサイズを設定
	ZeroMemory(&pi, sizeof(pi));

	// バッチファイルを実行するためのコマンドライン文字列を作成
	CString strCommandLine = _T("cmd.exe /C \"") + strBatPath + _T("\"");

	// CreateProcess関数を使ってプロセスを起動
	if (!CreateProcess(
		nullptr,             // 実行するアプリケーション名
		strCommandLine.GetBuffer(),  // コマンドライン文字列
		nullptr,             // プロセスのセキュリティ属性
		nullptr,             // スレッドのセキュリティ属性
		FALSE,               // 子プロセスに親プロセスのハンドルを継承させるか
		0,                   // 作成オプション
		nullptr,             // 環境ブロック
		nullptr,             // 作業ディレクトリ
		&si,                 // STARTUPINFO構造体
		&pi                  // PROCESS_INFORMATION構造体
	))
	{
		// エラー処理
		AfxMessageBox(_T("バッチファイルの実行に失敗しました。"));
		return;
	}

	// プロセスが終了するのを待機する
	WaitForSingleObject(pi.hProcess, INFINITE);

	// ハンドルを閉じる
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

	// バッチファイル削除
	CreateDeleteBatFile(FALSE);

	// sqlファイル削除
	CreateDeleteSQLFile(FALSE, iMode);
}

/**
 * @fn
 * @brief	バッチファイル作成・削除
 * @param	BOOL	TRUE	作成
 *					FALSE	削除
 * @return	なし
 */
void CMySQLTest2Dlg::CreateDeleteBatFile(BOOL bMode)
{
	// 現在実行しているMySQLTest2.exeのパス
	CString strPath = strCurrentDir();
	//ファイル名
	strPath += _T("run_sql.bat");

	if (bMode)
	{
		// バッチファイル作成します
		
		CString strUser = _T("root");		// ユーザ名
		CString strPass = _T("password");	// パスワード
		CString strHost = _T("localhost");	// ホスト名
		CString strDB = _T("testschema1");		// DB名
		// ※パスワードは自分のやつを

		// コマンド
		CString strBatCommand = _T("@echo off\r\n");

		CString strTemp = _T("");
		strTemp.Format(_T("set MYSQL_CMD=\"C:\\Program Files\\MySQL\\MySQL Server 8.0\\bin\\mysql.exe\" --default-character-set=utf8mb4 -u %s -p%s -h %s %s\r\n"),
			(LPCTSTR)strUser, (LPCTSTR)strPass, (LPCTSTR)strHost, (LPCTSTR)strDB);
		strBatCommand += strTemp;

		// sqlファイルを実行
		strBatCommand += _T("%MYSQL_CMD% < commands.sql");

		//調査用に追加
		//strBatCommand += _T("\r\npause");


		// ファイル作成
		CStdioFile* pStdioFile = new CStdioFile(strPath, (CFile::modeWrite | CFile::modeCreate));
		pStdioFile->Seek(0, CFile::end);
		pStdioFile->WriteString(strBatCommand);
		delete pStdioFile;	//後処理
		pStdioFile = NULL;	//後処理
	}
	else
	{
		// バッチファイル削除します
		DeleteFile(strPath);
	}
	
}

/**
 * @fn
 * @brief	sql作成・削除
 * @param	BOOL	TRUE	作成
 *					FALSE	削除
 *			int		0	INSERT
 *					1	SELECT
 *					2	DELETE
 * @return	なし
 */
void CMySQLTest2Dlg::CreateDeleteSQLFile(BOOL bMode, int iMode)
{
	// 現在実行しているMySQLTest2.exeのパス
	CString strPath = strCurrentDir();
	//ファイル名
	strPath += _T("commands.sql");

	if (bMode)
	{
		// sqlファイル作成します

		CString strName = _T("");
		GetDlgItemText(IDC_EDIT_NAME, strName);	// 名前エディットボックスから取得
		//strName = _T("\'") + strName + _T("\'");	// シングルクォーテーションで囲む
		CString strAge = _T("");
		GetDlgItemText(IDC_EDIT_AGE, strAge);	//年齢エディットボックスから取得

		CString strCommand = _T("");
		
		if (iMode == 0)
		{
			// INSERT文
			strCommand.Format(_T("INSERT INTO testtable1 (name, age) VALUES ( '%s', %s);"), (LPCTSTR)strName, (LPCTSTR)strAge);
		}
		else if (iMode == 1)
		{
			// SELECT文
			if (strAge == _T(""))
			{
				// 年齢に何も入力されていない場合は全件
				strCommand.Format(_T("SELECT * FROM testtable1;"));
			}
			else
			{
				// 年齢に入力されている場合はwhere句で条件付き
				strCommand.Format(_T("SELECT * FROM testtable1 WHERE age = %s;"), (LPCTSTR)strAge);
			}
			
		}
		else
		{
			// DELETE文
			strCommand.Format(_T("DELETE FROM testtable1 WHERE name = '%s' AND age = %s;"), (LPCTSTR)strName, (LPCTSTR)strAge);
		}

		// ファイル作成
		CFile file;
		if (file.Open(strPath, CFile::modeWrite | CFile::modeCreate | CFile::typeBinary))
		{
			// UTF-8 BOMを書き込む
			BYTE bom[3] = {0xEF,  0xBB, 0xBF };
			file.Write(bom, sizeof(bom));

			// CStringをUTF-8に変換して書き込む
			CT2A pszConvertedAnsiString(strCommand, CP_UTF8);
			std::string strUtf8(pszConvertedAnsiString);
			file.Write(strUtf8.c_str(), strUtf8.length());
			file.Close();
		}
	}
	else
	{
		// sqlファイル削除します
		DeleteFile(strPath);
	}
}

/**
 * @fn
 * @brief	現在実行パス取得
 * @param	なし
 * @return	CString		パス
 */
CString CMySQLTest2Dlg::strCurrentDir()
{
	// 返却値
	CString strRtn = (LPCTSTR)nullptr;
	// パス、ドライブ名、ディレクトリ名、ファイル名、拡張子
	TCHAR path[_MAX_PATH], drive[_MAX_PATH], dir[_MAX_PATH], file[_MAX_PATH], ext[_MAX_PATH];

	// フルパスを取得
	if (::GetModuleFileName(NULL, path, _MAX_PATH) != 0)
	{
		// ファイルパスを分割
		::_tsplitpath_s(path, drive, _MAX_PATH, dir, _MAX_PATH, file, _MAX_PATH, ext, _MAX_PATH);
		// ドライブ名とディレクトリ名を結合
		strRtn = ::PathCombine(path, drive, dir);
	}

	return strRtn;
}

コメント

タイトルとURLをコピーしました