1. VS2022를 사용한 MFC 다중 문서 프로젝트 생성
테스트를 간소화하기 위해 모든 기능을 애플리케이션 클래스(CWinApp 파생 클래스) 내에서 구현한다. 디버깅 출력은 메인 프레임의 하단에 위치한 출력 창(OutputWnd)을 통해 수행된다.
2. 프로젝트 속성 설정
- 문자 집합: 다중 바이트 문자 집합(Multi-Byte Character Set)으로 설정하여 CString 및 C 스타일 문자열 처리를 단순화한다.
- MFC 사용: 정적 라이브러리(Static Library)로 설정하여 외부 DLL 의존성을 제거하고 배포를 용이하게 한다.
3. 기본 구조 수정
애플리케이션 클래스에 ID_FILE_OPEN 명령에 대한 메시지 핸들러 OnFileOpen()을 추가하고, 기존의 기본 구현은 주석 처리한다. 이 함수 내에서 SQLite 테스트 코드를 직접 실행한다.
디버그 출력을 위한 두 가지 오버로드된 PrintDebug 함수를 앱 클래스에 구현하고, 이를 전역 매크로 PRINT로 정의하여 쉽게 접근할 수 있도록 한다. 실제 출력 로직은 COutputWnd 클래스가 소유하므로, CMainFrame을 통해 중계하는 간단한 래퍼 함수가 필요하다.
4. wxSQLite3 통합 방법
기존의 컴파일된 wxSQLite3 정적 라이브러리를 사용하면 MFC와의 함수 충돌이 발생하므로, 대신 소스 코드 기반으로 통합한다.
- wxsqlite3 소스 패키지 내
wxsqlite3/sqlite3secure/src폴더의 모든 파일을 프로젝트 디렉터리에 복사한다. sqlite3.h와sqlite3secure.c를 프로젝트에 추가한다.sqlite3secure.c파일의 속성에서 "미리 컴파일된 헤더 사용 안 함"으로 설정하여 PCH 오류를 방지한다.- 프로젝트 속성 → 구성 속성 → C/C++ → 전처리 → 전처리기 정의에 다음 항목들을 추가하여 암호화 및 확장 기능을 활성화한다:
SQLITE_ENABLE_FTS3 SQLITE_ENABLE_FTS3_PARENTHESIS SQLITE_ENABLE_RTREE SQLITE_ENABLE_COLUMN_METADATA SQLITE_HAS_CODEC=1 SQLITE3ENCRYPT_EXPORTS SQLITE_SECURE_DELETE SQLITE_SOUNDEX CODEC_TYPE=CODEC_TYPE_AES256
codec.c파일에서 안전하지 않은 함수 경고(C4996)가 발생하므로, 파일 상단 또는 관련 함수 내부에#pragma warning(disable: 4996)지시자를 삽입하여 경고를 억제한다.
5. 핵심 코드 구현
5.1 애플리케이션 클래스 헤더 (MFCwxSqliteTest.h)
SQLite 헤더 포함과 함께, 출력 및 콜백용 멤버 함수를 선언한다.
#pragma once
#include "resource.h"
#include "sqlite3.h"
#define PRINT theApp.PrintDebug
class CMFCwxSqliteTestApp : public CWinAppEx
{
public:
CMFCwxSqliteTestApp() noexcept;
int PrintDebug(TCHAR* fmt, ...);
int PrintDebug(CString str);
static int callback(void* data, int argc, char** argv, char** colNames);
static int callback2(void* data, int argc, char** argv, char** colNames);
virtual BOOL InitInstance();
DECLARE_MESSAGE_MAP()
afx_msg void OnFileOpen();
};
extern CMFCwxSqliteTestApp theApp;
5.2 애플리케이션 클래스 구현 (MFCwxSqliteTest.cpp)
OnFileOpen() 함수 내에서 파일 대화상자를 열어 DB 경로를 선택하고, SQLite 데이터베이스를 열고 암호를 설정한 후 CRUD 작업을 수행한다.
void CMFCwxSqliteTestApp::OnFileOpen()
{
sqlite3* db = nullptr;
char* errorMsg = nullptr;
int result;
CFileDialog fileDlg(TRUE, _T("db"), _T("test.db"),
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
_T("Database Files (*.db)|*.db|All Files (*.*)|*.*||"));
if (fileDlg.DoModal() == IDOK)
{
CString dbPath = fileDlg.GetPathName();
PRINT(_T("Opening database: %s"), dbPath);
// 데이터베이스 연결
result = sqlite3_open(T2A(dbPath), &db);
if (result != SQLITE_OK)
{
PRINT(_T("Failed to open database."));
return;
}
// AES256 암호 설정 (또는 해제)
result = sqlite3_key(db, "password", 8);
PRINT(_T("Key setup result: %d"), result);
// 테이블 생성
const char* createSql =
"CREATE TABLE IF NOT EXISTS EMPLOYEE("
"EMP_ID INTEGER PRIMARY KEY NOT NULL,"
"FULL_NAME TEXT NOT NULL,"
"YEARS_OLD INTEGER,"
"LOCATION TEXT,"
"WAGE REAL"
");";
result = sqlite3_exec(db, createSql, nullptr, nullptr, &errorMsg);
if (result != SQLITE_OK)
{
PRINT(_T("SQL Error: %S"), errorMsg);
sqlite3_free(errorMsg);
}
// 데이터 삽입
const char* insertSql =
"INSERT INTO EMPLOYEE (EMP_ID, FULL_NAME, YEARS_OLD, LOCATION, WAGE) VALUES "
"(1, 'John Doe', 30, 'Seoul', 50000.0);"
"INSERT INTO EMPLOYEE (EMP_ID, FULL_NAME, YEARS_OLD, LOCATION, WAGE) VALUES "
"(2, 'Jane Smith', 28, 'Busan', 55000.0);";
result = sqlite3_exec(db, insertSql, nullptr, nullptr, &errorMsg);
if (result != SQLITE_OK)
{
PRINT(_T("Insert failed: %S"), errorMsg);
sqlite3_free(errorMsg);
}
// 데이터 조회 및 콜백을 통한 출력
const char* selectSql = "SELECT * FROM EMPLOYEE";
const char* context = "Query Result";
result = sqlite3_exec(db, selectSql, callback2, (void*)context, &errorMsg);
if (result == SQLITE_OK)
{
PRINT(_T("Query executed successfully."));
}
else
{
PRINT(_T("Query error: %S"), errorMsg);
sqlite3_free(errorMsg);
}
// 연결 종료
sqlite3_close(db);
}
}
// 일반 콜백: 각 행의 컬럼 이름과 값을 출력
int CMFCwxSqliteTestApp::callback(void* /*data*/, int colCount, char** values, char** colNames)
{
for (int i = 0; i < colCount; ++i)
{
PRINT(_T("%S = %S"), colNames[i], values[i] ? values[i] : "NULL");
}
return 0;
}
// 컨텍스트를 포함한 콜백
int CMFCwxSqliteTestApp::callback2(void* context, int colCount, char** values, char** colNames)
{
PRINT(_T("%S:"), (const char*)context);
for (int i = 0; i < colCount; ++i)
{
PRINT(_T(" %S = %S"), colNames[i], values[i] ? values[i] : "NULL");
}
return 0;
}
5.3 출력 창 구현 (COutputWnd.cpp)
출력 항목이 많아질 경우 자동 정리되며, 새로운 메시지가 항상 보이도록 스크롤을 조정한다.
void COutputWnd::PrintDebug(TCHAR* message)
{
CListBox& outputBox = m_wndOutputBuild;
int itemCount = outputBox.GetCount();
// 최대 100개 항목 유지
if (itemCount > 100)
{
for (int i = 0; i < 10; ++i)
{
outputBox.DeleteString(0);
}
}
outputBox.AddString(message);
outputBox.SetCurSel(outputBox.GetCount() - 1); // 마지막 항목으로 스크롤
}
6. 실행 결과
프로그램 실행 후 파일 → 열기를 선택하면 파일 대화상자가 나타나며, 선택한 .db 파일에 대해 테이블 생성, 데이터 삽입, 조회 쿼리가 순차적으로 실행된다. 모든 출력은 하단의 출력 패널에 실시간으로 표시되며, 암호화된 데이터베이스도 성공적으로 접근하는 것을 확인할 수 있다.