MFC에서 명령줄(command line) 사용

rss
마이크로소프트 비주얼 스튜디오(Microsoft Visual Studio)에서 MFC로 간단한 다이얼로그 기반 애플리케이션을 하나 생성했는데, 이 프로그램에서 명령줄 인자(command line arguments)를 처리하려면 어떻게 해야 하는지 몰라 찾아 봤습니다.


그에 앞서, 명령줄 인자는 직접 명령 프롬프트에서 전달해도 되고 (D:\MyApp arg1 arg2 이런 식이죠) 아래 그림과 같이 비주얼 스튜디오의 메뉴에서 ProjectProperty PagesConfiguration PropertiesDebuggingCommand Arguments에서 설정해 주어도 됩니다.



Using global variables __argc and __targv
C 프로그래밍 시 main() 함수의 인자를 이용할 때의 argc, argv와 같은 역할을 하는 변수로 MFC에는 __argc__targv가 있습니다. _targv은 유니코드 환경에서는 __wargv로, 그렇지 않은 경우__argv로 변환됩니다.
#include <string>

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

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		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);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here
	std::string arglist;
	for (int i = 0; i < __argc; ++i) {
		arglist += __targv[i];
		arglist += " ";
	}
	AfxMessageBox( arglist.c_str() );

	return TRUE;  // return TRUE  unless you set the focus to a control
}
예를 들어 실행 파일의 이름이 CommandLine이라 가정하고 명령줄에서 CommandLine -L -P이라 입력하면 다음과 같은 팝업을 볼 수 있습니다.



GetCommandLine()
GetCommandLine() 함수는 현재 프로세스의 프로그램 이름을 포함한 명령줄 인자 전체를 넘겨 줍니다.
#include <string>
#include <sstream>
#include <vector>

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

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		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);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here
	std::string inputString( GetCommandLine() );
	std::string arglist;
	std::string buf;
	std::stringstream ss(inputString);

	std::vector<std::string> tokens;

	while (ss >> buf) {
		tokens.push_back(buf);
	}

	for (int i = 0; i < tokens.size(); ++i) {
		arglist += tokens[i];
		arglist += " ";
	}
	AfxMessageBox( arglist.c_str() );

	return TRUE;  // return TRUE  unless you set the focus to a control
}
실행 결과는 위 __argc, __targv 때와 동일합니다.



m_lpCmdLine
CWinApp 클래스에는 m_lpCmdLine이라는 멤버 변수가 있습니다. 이를 그대로 불러 사용할 수 있습니다.
#include <string>
#include <sstream>
#include <vector>

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

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		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);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here
	std::string inputString( theApp.m_lpCmdLine );
	std::string arglist;
	std::string buf;
	std::stringstream ss(inputString);

	std::vector<std::string> tokens;

	while (ss >> buf) {
		tokens.push_back(buf);
	}

	for (int i = 0; i < tokens.size(); ++i) {
		arglist += tokens[i];
		arglist += " ";
	}
	AfxMessageBox( arglist.c_str() );

	return TRUE;  // return TRUE  unless you set the focus to a control
}
GetCommandLine() 함수 대신 직접 CWinApp 클래스에 접근해 theApp.m_lpCmdLine를 사용(또는 AfxGetApp->m_lpCmdLine)한 결과는 다음과 같습니다. 차이가 있다면, GetCommandLine() 함수는 프로그램명을 포함한 결과를 보여 주지만 theApp.m_lpCmdLine을 사용하면 프로그램명은 제외한 결과를 보여 준다는 것입니다.



CommandLineToArgvW()
앞서서 GetCommandLine() 함수나 m_lpCmdLine 멤버 변수를 이용할 때에는 명령줄 인자를 std::stringstd::stringstream 클래스를 이용하여 일일이 분해해서 사용했습니다. 하지만 CommandLineToArgvW() 함수를 사용하면 그런 수고로움을 덜 수 있습니다. 다만, 이름에서도 유추할 수 있듯이 이 함수는 유니코드 환경에서만 정상적으로 동작합니다. 또한 사용 후에는 CommandLinetoArgvW() 함수를 위해 할당했던 메모리를 해제해 주어야 합니다.
BOOL CCommandLineDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		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);
		}
	}

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

	// TODO: Add extra initialization here
	std::wstring arglist;
	LPWSTR* szArglist;
	int nArgs;

	szArglist = CommandLineToArgvW( GetCommandLineW(), &nArgs );
	if (NULL == szArglist) {
		AfxMessageBox(_T("CommandLineToArgvW failed"));
	} else {
		for (int i = 0; i < nArgs; ++i) {
			arglist += szArglist[i];
			arglist += _T(" ");
		}
	}
	AfxMessageBox( arglist.c_str() );

	// Free memory allocated for CommandLineToArgvW arguments.
	LocalFree(szArglist);

	return TRUE;  // return TRUE  unless you set the focus to a control
}


이처럼 CommandLineToArgvW() 함수를 쓰면 또 한 가지 편리한 점이 있습니다. 예를 들어, 명령 프롬프트에서 D:\MyApp -L -P "This is my argument"라는 명령을 입력했다고 하겠습니다. 앞서 std::stringstd::stringstream 클래스를 이용하여 명령줄 인자를 파싱(parsing)하는 경우에는 화이트스페이스 문자(whitespace characters)를 기준으로 문자열을 나누기 때문에 "This is my argument"를 통째로 인식하지 못하고 "This, is, my, argument"와 같이 각각을 따로 인식합니다. 따라서 이를 별도로 처리해 주어야 합니다. 하지만 CommandLineToArgvW() 함수는 따옴표 사이의 문자열을 하나로 묶어 주기 때문에 이러한 번거로움을 덜어 줍니다.


CCommandLineInfo
검색을 해 보니 위에서 이야기했던 방법들 외에 CCommandLineInfo 클래스를 이용해서 처리하는 방법이 있습니다. 우선 헤더 파일에서 CCommandLineInfo 클래스를 상속하여 구현합니다.
class CMyCommandLineInfo : public CCommandLineInfo
{
private:
	bool m_bL;
	bool m_bT;
public:
	CMyCommandLineInfo() : m_bL(false), m_bT(false) {}

	bool IsL() { return m_bL; }
	bool IsT() { return m_bT; }
private:
	void ParseParam(const TCHAR* pszParam, BOOL bFlag, BOOL bLast)
	{
		if (TRUE == bFlag) {
			if (0 == _tcsicmp(pszParam, _T("l"))) {
				m_bL = true;
				return;
			}
			if (0 == _tcsicmp(pszParam, _T("t"))) {
				m_bT = true;
				return;
			}
		}
		CCommandLineInfo::ParseParam(pszParam, bFlag, bLast);
	}
};
위 구현에서 ParseParam() 함수의 매개변수로 bFlag가 있는데, 이는 명령줄 인자에서 -/ 문자가 있으면 이를 플래그로 인식하는 역할을 합니다.

다음으로 CWinApp::InitInstance() 내에 이를 처리하는 부분을 추가합니다.
BOOL CCommandLineApp::InitInstance()
{
	// InitCommonControlsEx() is required on Windows XP if an application
	// manifest specifies use of ComCtl32.dll version 6 or later to enable
	// visual styles.  Otherwise, any window creation will fail.
	INITCOMMONCONTROLSEX InitCtrls;
	InitCtrls.dwSize = sizeof(InitCtrls);
	// Set this to include all the common control classes you want to use
	// in your application.
	InitCtrls.dwICC = ICC_WIN95_CLASSES;
	InitCommonControlsEx(&InitCtrls);

	CWinApp::InitInstance();

	if (!AfxSocketInit())
	{
		AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
		return FALSE;
	}


	AfxEnableControlContainer();

	// Create the shell manager, in case the dialog contains
	// any shell tree view or shell list view controls.
	CShellManager *pShellManager = new CShellManager;

	// Standard initialization
	// If you are not using these features and wish to reduce the size
	// of your final executable, you should remove from the following
	// the specific initialization routines you do not need
	// Change the registry key under which our settings are stored
	// TODO: You should modify this string to be something appropriate
	// such as the name of your company or organization
	SetRegistryKey(_T("Local AppWizard-Generated Applications"));


	// Parse command line for standard shell commands, DDE, file open
	CMyCommandLineInfo cmdInfo;
	ParseCommandLine(cmdInfo);

	if (cmdInfo.IsL()) {
		AfxMessageBox("-L selected");
	}
	if (cmdInfo.IsT()) {
		AfxMessageBox("-T selected");
	}


	CCommandLineDlg dlg;
	m_pMainWnd = &dlg;
	INT_PTR nResponse = dlg.DoModal();
	if (nResponse == IDOK)
	{
		// TODO: Place code here to handle when the dialog is
		//  dismissed with OK
	}
	else if (nResponse == IDCANCEL)
	{
		// TODO: Place code here to handle when the dialog is
		//  dismissed with Cancel
	}

	// Delete the shell manager created above.
	if (pShellManager != NULL)
	{
		delete pShellManager;
	}

	// Since the dialog has been closed, return FALSE so that we exit the
	//  application, rather than start the application's message pump.
	return FALSE;
}


Posted by EXIFEEDI
,