从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’的字符串数组。让我们看一个例子:

  1. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, LPSTR lpCmdLine, int nShowCmd)
  2. {
  3. LPWSTR *szArgList;
  4. int argCount;
  5.  
  6. szArgList = CommandLineToArgvW(GetCommandLine(), &argCount);
  7. if (szArgList == NULL)
  8. {
  9. MessageBox(NULL, L"Unable to parse command line", L"Error", MB_OK);
  10. return 10;
  11. }
  12.  
  13. for(int i = 0; i < argCount; i++)
  14. {
  15. MessageBox(NULL, szArgList[i], L"Arglist contents", MB_OK);
  16. }
  17.  
  18. LocalFree(szArgList);
  19. return 0;
  20. }

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

 

扩展:

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

commonLineHelp.cpp

 

  1. #ifdef _WIN32
  2. #include <windows.h>
  3. #include <string>
  4. using namespace std;
  5.  
  6. string wstr_to_str(const wstring & wstr, UINT CodePage = CP_OEMCP);
  7.  
  8. string wstr_to_str(const wstring & wstr, UINT CodePage)
  9. {
  10. string str;
  11. int buffer_size = 0;
  12. char* pchar_str = NULL;
  13. buffer_size = WideCharToMultiByte(CodePage, NULL,wstr.c_str(), -1, 0, 0, NULL, FALSE);
  14. pchar_str = new char[buffer_size + 1];
  15. if (pchar_str == NULL)
  16. {
  17. return str;
  18. }
  19. memset(pchar_str, 0, buffer_size + 1);
  20. if (WideCharToMultiByte(CodePage, NULL, wstr.c_str(), -1, pchar_str, buffer_size, NULL, FALSE))
  21. {
  22. str = pchar_str;
  23. }
  24. delete pchar_str;
  25. return str;
  26. }
  27.  
  28. #endif
  29.  
  30.  
  31. LPSTR * CommandLineToArgvA(LPCWSTR lpCmdLine, __out int* pNumArgs)
  32. {
  33. LPWSTR *szArgList;
  34. int argCount;
  35.  
  36. if ( NULL == lpCmdLine)
  37. {
  38. *pNumArgs = 0;
  39. return NULL;
  40. }
  41.  
  42.  
  43. szArgList = CommandLineToArgvW(GetCommandLineW(), &argCount);
  44. if (szArgList == NULL)
  45. {
  46. *pNumArgs = 0;
  47. return NULL;
  48. }
  49.  
  50. char **szArgListA = NULL;
  51.  
  52. szArgListA = (char**)LocalLock(LocalAlloc(LMEM_FIXED, sizeof(char*)*argCount));
  53. for(int i = 0; i < argCount; i++)
  54. {
  55. string str = wstr_to_str(szArgList[i]);
  56. szArgListA[i] = (char*)LocalLock(LocalAlloc(LMEM_FIXED, str.length()));
  57. strcpy(szArgListA[i], str.c_str());
  58. }
  59.  
  60. LocalFree(szArgList);
  61.  
  62. *pNumArgs = argCount;
  63.  
  64. return szArgListA;
  65. }

commonHelp.h

 

 

  1. #ifndef __COMMONLINEHELP__
  2.  
  3. #define __COMMONLINEHELP__
  4.  
  5. /**
  6. * @brief 将应用程序的命令行转化为命令行参数列表(返回字符中是窄字节)
  7. *
  8. * @param[in] lpCmdLine 等转化的命令行字符串
  9. * @param[out] pNumArgs 转化后的命令行参数个数
  10. *
  11. * @return 命令行参数列表
  12. *
  13. * @note
  14. * 返回值在使用完后记得调用LocalFree释放掉
  15. */
  16. LPSTR * CommandLineToArgvA(LPCWSTR lpCmdLine, __out int* pNumArgs);
  17.  
  18.  
  19.  
  20. #endif

==============================================================================================================
2013-04-17 对commonHelp.cpp文件的改进
改进原因:
上面的函数在VS调试时通过VS的调试指定命令行是没有问题的,可是当另一应用程序通过::CreateProcess给此应用程序传递命令行时就会报错了。
这时因为使用了LocalAlloc,把它改为Alloc后问题就没了。
但最根本的原因是什么呢?我也没搞清楚,有哪位读者知道答案后请告诉我,谢谢!
 
 
改进后的函数:
  1. LPSTR * CommandLineToArgvA(LPCWSTR lpCmdLine, __out int* pNumArgs)
  2. {
  3. LPWSTR *szArgList;
  4. int argCount;
  5. BOOL bRet = FALSE;
  6.  
  7. if ( NULL == lpCmdLine)
  8. {
  9. *pNumArgs = 0;
  10. return NULL;
  11. }
  12.  
  13. szArgList = CommandLineToArgvW(lpCmdLine, &argCount);
  14. if (szArgList == NULL)
  15. {
  16. *pNumArgs = 0;
  17. return NULL;
  18. }
  19.  
  20. char **szArgListA = NULL;
  21. HLOCAL hLocalList = NULL, hLocalListItem = NULL;
  22.  
  23. #if 0
  24. hLocalList = LocalAlloc(LMEM_FIXED, sizeof(char*)*argCount);
  25. if ( !hLocalList )
  26. {
  27. goto T_OUT;
  28. }
  29.  
  30.  
  31. szArgListA = (char**)LocalLock(hLocalList);
  32. #endif
  33. szArgListA = (char**)malloc(sizeof(char*)*argCount);
  34. for(int i = 0; i < argCount; i++)
  35. {
  36. string str = wstr_to_str(szArgList[i]);
  37. #if 0
  38. hLocalListItem = LocalAlloc(LMEM_FIXED, str.length());
  39. szArgListA[i] = (char*)LocalLock(hLocalListItem);
  40. #endif
  41. szArgListA[i] = (char*)malloc(str.length());
  42. strcpy(szArgListA[i], str.c_str());
  43. //LocalUnlock(hLocalListItem);
  44.  
  45. }
  46.  
  47. LocalFree(szArgList);
  48.  
  49. *pNumArgs = argCount;
  50. if ( FALSE == (bRet = LocalUnlock(hLocalList)) )
  51. {
  52.  
  53. }
  54.  
  55. T_OUT:
  56.  
  57. return szArgListA;
  58. }

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

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

新生的后果非常严重:

1、打印信息如下:

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

 

  1. char** CommandLineToArgvA(LPCWSTR lpCmdLine, __out int* pNumArgs)
  2. {
  3. LPWSTR *szArgList;
  4. int argCount;
  5. BOOL bRet = FALSE;
  6.  
  7. if ( NULL == lpCmdLine)
  8. {
  9. *pNumArgs = 0;
  10. return NULL;
  11. }
  12. #if 1
  13. OutputDebugString("==========Conver String.Original Status\n");
  14. OutputDebugStringW(lpCmdLine);
  15. #endif
  16. szArgList = CommandLineToArgvW(lpCmdLine, &argCount);
  17. if (szArgList == NULL)
  18. {
  19. *pNumArgs = 0;
  20. return NULL;
  21. }
  22.  
  23. char **szArgListA = NULL;
  24. HLOCAL hLocalList = NULL, hLocalListItem = NULL;
  25.  
  26. szArgListA = (char**)malloc(sizeof(char*)*argCount);
  27. memset(szArgListA, 0x0, sizeof(char*)*argCount);
  28. OutputDebugString("==========Conver String ...\n");
  29. for(int i = 0; i < argCount; i++)
  30. {
  31. string str = wstr_to_str(szArgList[i]);
  32.  
  33. szArgListA[i] = (char*)malloc(str.length());
  34. memset(szArgListA[i], 0x0, str.length());
  35. strncpy(szArgListA[i], str.c_str(), str.length());
  36. szArgListA[i][str.length()] = '\0';
  37. #if 1
  38.  
  39. OutputDebugString(str.c_str());
  40. OutputDebugString(szArgListA[i]);
  41. //L_TRACE("len=%d\n", strlen(szArgListA[i]));
  42. #endif
  43. //LocalUnlock(hLocalListItem);
  44.  
  45. }
  46.  
  47. #if 1
  48. OutputDebugString("==========Convert String result:\n");
  49. for (int i = 0; i < argCount; i++)
  50. {
  51. OutputDebugString(szArgListA[i]);
  52. }
  53. #endif // 0
  54.  
  55.  
  56. LocalFree(szArgList);
  57.  
  58. *pNumArgs = argCount;
  59.  
  60.  
  61. if ( FALSE == (bRet = LocalUnlock(hLocalList)) )
  62. {
  63.  
  64. }
  65.  
  66. T_OUT:
  67.  
  68. return szArgListA;
  69. }


 

 

 

后得到的打印如下:

 

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);
 
原因:自然不用说了,很明显。!!!!
至于为什么会影响到其他内存的空间时,可能是因为我们申请的空间过小,导致此串没有结束,而我们可能会对串进行操作,如进行字符成员访问,

 

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

posted @ 2021-11-10 16:42  CharyGao  阅读(62)  评论(0)    收藏  举报