从Win32程序的主函数WinMain中获取命令行参数

这些参数帮助我们为程序传入命令行参数。"argc"为命令行参数的个数,"argv"则为传入参数的数组列表。但是当我们在Visual Studio中创建Win32 GUI程序的时候,WinMain变成程序的入口函数,而该函数并没有"argc" 和"argv"参数,那我们怎样给Windows程序传入命令行参数呢?Windows程序中又怎样取得这些传入的参数呢?

lpCmdLine 参数

第一个方案就来自WinMain函数自身。让我们看一个典型的WinMain函数声明:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)

如声明所示,WinMain函数有一个类型为"LPSTR(char*)"的参数"lpCmdLine". 这个变量存放着命令行中除程序自身名字外的剩下所有部分。例如,我们有一个名字为"test.exe"的应用程序,当用下面的命令行运行该程序时
test.exe Some arguments here

变量lpCmdLine的值即为"Some arguments here"。尽管该方法不像"argc"和"argv"一样非常方便,但它是获取命令行参数的方法之一。我们需要自己写程序去分析lpCmdLine字符串,这增加了程序的复杂度。

【译注:Windows程序代码同时ANSI版本和UNICODE版本接口。其中WinMain函数为ANSI版本,wWinMain为UNICODE版本。从Visual Studio创建出来的代码主函数命名为_tWinMain。这个函数名会根据当前工程有没有定义_UNICODE宏而在编译时翻译成上面两个不同版本。当翻译成WinMain函数时候,lpCmdLine的类型为LPSTR,而当翻译成wWinMain函数时候,lpCmdLine的类型为 LPWSTR,即宽字符数组】

GetCommandLine()函数

另外一个方法就是使用GetCommandLine() API。这个函数返回整个命令行,它把程序自身名称(包括程序的绝对路径)和所有参数放在一个字符串中。该函数非常类似于对lpCmdLine的直接访问。但它的一个好处是能够根据你当前工程的设置自动映射到GetCommandLineA()或者GetCommandLineW()函数。因此解决了访问Unicode命令行输入的问题。但是它还是既没有提供命令行参数数目,也没有类似argv那样把参数自动分割成独立变量的能力。

CommandLineToArgvW()函数

最后一个我要讨论的方法是CommandLineToArgvW函数。这个函数只有Unicode宽字符版本,没有对应的CommandLineToArgvA函数。它的声明如下:

LPWSTR *CommandLineToArgvW(LPCWSTR lpCmdLine, int *pNumArgs)

该函数和’argc’/'argv’一样简单,但是它并不是在Windows程序中直接访问argc和argv变量。如声明所示,函数接受两个参数,一个是需要解析的Unicode命名行字符串,另外一个是指向整型变量的指针。函数在返回时把参数数目存到这个整型变量中。

函数返回一个类似于’argv’的字符串数组。让我们看一个例子:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, LPSTR lpCmdLine, int nShowCmd)
{
    LPWSTR *szArgList;
    int argCount;

    szArgList = CommandLineToArgvW(GetCommandLine(), &argCount);
    if (szArgList == NULL)
    {
        MessageBox(NULL, L"Unable to parse command line", L"Error", MB_OK);
        return 10;
    }

    for(int i = 0; i < argCount; i++)
    {
        MessageBox(NULL, szArgList[i], L"Arglist contents", MB_OK);
    }

    LocalFree(szArgList);
    return 0;
}

如上所示,通过这个函数,我们可以取得命令行参数的数目(argc)和一个字符串列表(argv)。这里唯一要注意的事情是该函数会给返回参数列表分配一块内存。当我们用完列表后需要手动地释放该内存,否则就会有内存泄露。



扩展:

因为Windows没有给出CommandLineToArgvA的实现,所以我们自己实现一个,如下:

commonLineHelp.cpp

#ifdef _WIN32
#include 
#include 
using namespace std;

string wstr_to_str(const wstring  & wstr, UINT CodePage = CP_OEMCP);

string wstr_to_str(const wstring  & wstr, UINT CodePage)
{
	string str;
	int buffer_size = 0;	
	char* pchar_str = NULL;
	buffer_size = WideCharToMultiByte(CodePage, NULL,wstr.c_str(), -1, 0, 0, NULL, FALSE);
	pchar_str = new char[buffer_size + 1];
	if (pchar_str == NULL)
	{
		return str;
	}
	memset(pchar_str, 0, buffer_size + 1);
	if (WideCharToMultiByte(CodePage, NULL, wstr.c_str(), -1, pchar_str, buffer_size, NULL, FALSE))
	{		
		str = pchar_str;
	}
	delete pchar_str;	
	return str;
}

#endif


LPSTR *  CommandLineToArgvA(LPCWSTR lpCmdLine, __out int* pNumArgs)
{
	LPWSTR *szArgList;
	int argCount;

	if ( NULL ==  lpCmdLine)
	{
		*pNumArgs = 0;
		return NULL;
	}
	 

	szArgList = CommandLineToArgvW(GetCommandLineW(), &argCount);
	if (szArgList == NULL)
	{
		*pNumArgs = 0;
		return NULL;
	}
 
	char  **szArgListA = NULL;

	szArgListA = (char**)LocalLock(LocalAlloc(LMEM_FIXED, sizeof(char*)*argCount));
	for(int i = 0; i < argCount; i++)
	{
		string str =  wstr_to_str(szArgList[i]);
		szArgListA[i] = (char*)LocalLock(LocalAlloc(LMEM_FIXED, str.length()));
		strcpy(szArgListA[i], str.c_str()); 
	}
	
	LocalFree(szArgList);

	*pNumArgs = argCount;
	
	return szArgListA;
}

commonHelp.h

#ifndef __COMMONLINEHELP__

#define __COMMONLINEHELP__

/**
 * @brief 将应用程序的命令行转化为命令行参数列表(返回字符中是窄字节) 
 *
 * @param[in] lpCmdLine 等转化的命令行字符串  
 * @param[out] pNumArgs 转化后的命令行参数个数      
 *
 * @return  命令行参数列表
 * 
 * @note
 *  返回值在使用完后记得调用LocalFree释放掉
 */
LPSTR *  CommandLineToArgvA(LPCWSTR lpCmdLine, __out int* pNumArgs);



#endif

==============================================================================================================
2013-04-17 对commonHelp.cpp文件的改进
改进原因:
上面的函数在VS调试时通过VS的调试指定命令行是没有问题的,可是当另一应用程序通过::CreateProcess给此应用程序传递命令行时就会报错了。
这时因为使用了LocalAlloc,把它改为Alloc后问题就没了。
但最根本的原因是什么呢?我也没搞清楚,有哪位读者知道答案后请告诉我,谢谢


改进后的函数:
LPSTR *  CommandLineToArgvA(LPCWSTR lpCmdLine, __out int* pNumArgs)
{
	LPWSTR *szArgList;
	int argCount;
	BOOL bRet = FALSE;

	if ( NULL ==  lpCmdLine)
	{
		*pNumArgs = 0;
		return NULL;
	}
	 
	szArgList = CommandLineToArgvW(lpCmdLine, &argCount);
	if (szArgList == NULL)
	{
		*pNumArgs = 0;
		return NULL;
	}
 
	char  **szArgListA = NULL;
	HLOCAL hLocalList = NULL, hLocalListItem = NULL;
	
#if 0
	hLocalList = LocalAlloc(LMEM_FIXED, sizeof(char*)*argCount);
	if ( !hLocalList )
	{
		goto T_OUT;
	}
	

	szArgListA = (char**)LocalLock(hLocalList);
#endif
	szArgListA = (char**)malloc(sizeof(char*)*argCount);
	for(int i = 0; i < argCount; i++)
	{
		string str =  wstr_to_str(szArgList[i]);
#if 0
		hLocalListItem = LocalAlloc(LMEM_FIXED, str.length());
		szArgListA[i] = (char*)LocalLock(hLocalListItem);
#endif
		szArgListA[i] = (char*)malloc(str.length());
		strcpy(szArgListA[i], str.c_str());
		//LocalUnlock(hLocalListItem);
		
	}
	
	LocalFree(szArgList);

	*pNumArgs = argCount;
	if ( FALSE == (bRet = LocalUnlock(hLocalList)) )
	{
		
	}

T_OUT:

	return szArgListA;
}

==============================================================================================================
2013-04-18 对commonHelp.cpp文件的改进

之前版本为每个命令行的字符串申请空间(malloc)时,指定大小是str.length(),这是字符串不包括\0的串长,这样会导致字符串的\0不能复制过来,而导致串没有结束,

新生的后果非常严重:

1、打印信息如下:

我在上一版本中加入打印信息,代码如:

char**  CommandLineToArgvA(LPCWSTR lpCmdLine, __out int* pNumArgs)
{
	LPWSTR *szArgList;
	int argCount;
	BOOL bRet = FALSE;

	if ( NULL ==  lpCmdLine)
	{
		*pNumArgs = 0;
		return NULL;
	}
#if 1
	OutputDebugString("==========Conver String.Original Status\n");
	OutputDebugStringW(lpCmdLine);
#endif
	szArgList = CommandLineToArgvW(lpCmdLine, &argCount);
	if (szArgList == NULL)
	{
		*pNumArgs = 0;
		return NULL;
	}
 
	char  **szArgListA = NULL;
	HLOCAL hLocalList = NULL, hLocalListItem = NULL;
	
	szArgListA = (char**)malloc(sizeof(char*)*argCount);
	memset(szArgListA, 0x0, sizeof(char*)*argCount);
	OutputDebugString("==========Conver String ...\n");
	for(int i = 0; i < argCount; i++)
	{
		string str =  wstr_to_str(szArgList[i]);

		szArgListA[i] = (char*)malloc(str.length());
		memset(szArgListA[i], 0x0, str.length());
		strncpy(szArgListA[i], str.c_str(), str.length());
		szArgListA[i][str.length()] = '\0';
#if 1
		
		OutputDebugString(str.c_str());
		OutputDebugString(szArgListA[i]);
		//L_TRACE("len=%d\n", strlen(szArgListA[i]));
#endif
		//LocalUnlock(hLocalListItem);
		
	}
	
#if 1
	OutputDebugString("==========Convert String result:\n");
	for (int i = 0; i < argCount; i++)
	{
		OutputDebugString(szArgListA[i]);
	}
#endif  // 0


	LocalFree(szArgList);

	*pNumArgs = argCount;


	if ( FALSE == (bRet = LocalUnlock(hLocalList)) )
	{
		
	}

T_OUT:

	return szArgListA;
}




后得到的打印如下:

00001088	2.38102412	[1180] ==========Conver String.Original Status	
00001089	2.38104796	[1180] "D:\Company_Centerm\linux\modules\xred\thunk\base\client\src\WindowsCode\Release\XredClient.exe" -g 1080*768 -u xred -p xred 192.168.4.151	
00001090	2.38108373	[1180] ==========Conver String ...	
00001091	2.38111877	[1180] D:\Company_Centerm\linux\modules\xred\thunk\base\client\src\WindowsCode\Release\XredClient.exe	
00001092	2.38114262	[1180] D:\Company_Centerm\linux\modules\xred\thunk\base\client\src\WindowsCode\Release\XredClient.exe	
00001093	2.38116908	[1180] -g	
00001094	2.38119221	[1180] -g	
00001095	2.38121915	[1180] 1080*768	
00001096	2.38124251	[1180] 1080*768	
00001097	2.38126826	[1180] -u	
00001098	2.38129187	[1180] -u	
00001099	2.38131785	[1180] xred	
00001100	2.38134098	[1180] xred	
00001101	2.38136673	[1180] -p	
00001102	2.38138986	[1180] -p	
00001103	2.38141584	[1180] xred	
00001104	2.38143897	[1180] xred	
00001105	2.38146520	[1180] 192.168.4.151	
00001106	2.38148880	[1180] 192.168.4.151	
00001107	2.38151193	[1180] ==========Convert String result:	
00001108	2.38153529	[1180] D:\Company_Centerm\linux\modules\xred\thunk\base\client\src\WindowsCode\Release\XredClient.exe	
00001109	2.38155818	[1180] -g	
00001110	2.38158154	[1180] 1080*768]	
00001111	2.38160467	[1180] -u	
00001112	2.38162780	[1180] xred	
00001113	2.38165069	[1180] -p	
00001114	2.38167381	[1180] xred	
00001115	2.38169694	[1180] 192.168.4.151	

从打印信息看,在转化时串都是正确的;可是都转化完后再打印一次,发现有一个字符串多了个  ‘]’   。


这样导致了我们解析字符中出错而已。


2、除1、中所讲的危害外,还会引起整个进程的意常发生,时不时会提示“地址XXXX不能被读”。

(1)在CreateThread时不进入线程回调函数了,且弹出系统访问内存错误码的提示。

(2)



修改方法:

只修改一行代码:

szArgListA[i] = (char*)malloc(str.length());

改为:

szArgListA[i] = (char*)malloc(str.length()+1);
原因:自然不用说了,很明显。!!!!
至于为什么会影响到其他内存的空间时,可能是因为我们申请的空间过小,导致此串没有结束,而我们可能会对串进行操作,如进行字符成员访问,

因为没有结束符,所以可能访问到不是我们申请的空间,这时没有报错。而这块空间可能后来被别人申请了,这样就会导致别人的内存被我们修改了。






你可能感兴趣的:(从Win32程序的主函数WinMain中获取命令行参数)