这两天一直在搞cmd回显的问题,虽然已经搞定,但还是存在一些问题,这就是CreateProcess的问题

首先问题描述:

CreateProcess这个函数非常熟悉,再熟悉不过了 创建进程 具体说明如下:

BOOL CreateProcess(
  LPCWSTR pszImageName,
  LPCWSTR pszCmdLine,
  LPSECURITY_ATTRIBUTES psaProcess,
  LPSECURITY_ATTRIBUTES psaThread,
  BOOL fInheritHandles,
  DWORD fdwCreate,
  LPVOID pvEnvironment,
  LPWSTR pszCurDir,
  LPSTARTUPINFOW psiStartInfo,
  LPPROCESS_INFORMATION pProcInfo
);

主要就是其参数设置,其中有一半参数都可以设置为NULL,但又一些参数是非常重要的,下面就一一说明比较重要的参数:

第一个参数 LPCWSTR pszImageName   specify the full path and filename of the module to execute or it can specify a partial path and filename
第二个参数 LPCWSTR pszCmdLine  命令行参数的设置

其次就是  BOOL fInheritHandles这个参数是表示此进程能否继承父进程的句柄 true表示能 false表示 不能

然后就是 LPSTARTUPINFOW psiStartInfoLPPROCESS_INFORMATION pProcInfo 这两个参数 需要初始化

那简单的参数介绍完后,问题就出在第一个参数和第二个参数  在vs2005 MSDN中 这两个参数是这样说明的

pszImageName

[in] Pointer to a null-terminated string that specifies the module to execute.

The string can specify the full path and filename of the module to execute or it can specify a partial path and filename.

The lpszImageName parameter must be non-NULL and must include the module name.

也就是说必须是非空的,必须有一个执行文件

pszCmdLine

[in, out] Pointer to a null-terminated string that specifies the command line to execute.

The system adds a null character to the command line, trimming the string if necessary, to indicate which file was used.

The lpszCmdLine parameter can be NULL. In that case, the function uses the string pointed to by lpszImageName as the command line.

If lpszImageName and lpszCmdLine are non-NULL, * lpszImageName specifies the module to execute, and * lpszCmdLine specifies the command line.

 也就是说可以为空

结论是在vs2005下创建进程CreateProcess的第一个参数必须非空

也就是说vs005下不能执行以下程序:

STARTUPINFO si={sizeof(si)};

PROCESS_INFORMATION pi;

CreateProcess(NULL,TEXT("NOTEPAD"),NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);

上面这段程序是《windows 核心编程》里的代码,主要功能就是从命令行执行记事本NOTEPAD,为什么在vs2005下不能运行?不光是vs2005,还有 vs2008,vs2010,都存在这样的问题,那从命令行运行程序到底行不行,在这本书里也说到了一点,但说的有点深不是很理解,请继续往下看:

以下为收集资料仅供方便查看之用(转载的):

vs05里面 项目默认定义了 UNICODE 和 _UNICODE宏
在项目属性 -> C/C++ -> 预处理器 -> 预处理器定义里可以看到有从项目默认继承的UNICODE 和 _UNICODE宏
这导致 CreateProcess 调用的是 CreateProcessW

vc6里面 项目默认没有定义   UNICODE 和 _UNICODE宏
所以 CreateProcess 调用的是 CreateProcessA

而 CreateProcess 的第2个参数 lpCommandLine   MSDN有如下说明:
The Unicode version of this function, CreateProcessW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as a const variable or a literal string). If this parameter is a constant string, the function may cause an access violation.
CreateProcessW.会改变这个字符串的内容.因此,这个参数不能是一个指向只读内存的变量或者是字符串字面值.如果这个参数是一个常量字符串,函数有可能会造成存取违规.

LPlong pointer 的意思   (在32位机中 不再区分near pointer 和 long pointer) 就是说 是一个指针
Cconstant 的意思   就是说是一个常量
WWide character的意思 表明是一个宽字符
STRstring 的意思   表示是一个字符串
TTchar 的意思 表示该类型会根据是否定义 _UNICODE UNICODE 被替换为 非W或者W类型

CreateProceesA 第2参数类型是 LPSTR 就是说一个指向 单字节字符串的指针   (现在Windows版本下 原型就是是 char *)
CreateProceesW 第2个参数类型是 LPWSTR 就是说是一个指向宽字节字符串的指针   (原型 wchar_t *)
CreateProcees 第2个参数类型是 LPTSTR 就是说 会根据 U宏 决定是 LPSTR 还是 LPTSTR (原型TCHAR*)

就是说 该函数将第2个参数视为可读写的 该函数不保证该字符串内容不被修改
而CreateProceesW 确实修改了其内容.... (关于如何修改的... 有个实验 呆会说....)

MessageBoxA 跟字符串有关的参数 都是 LPCSTR ( const char*)
MessageBoxW LPCWSTR (const wchar_t*)
MessageBox LPCTSTR (const TCHAR* )

该函数将这2个参数视为不可读写的 并且保证不会修改其内容
所以用 MessageBox(NULL,TEXT("Text"),TEXT("Caption"),0); 就不会有问题

最下面有一段代码将用一个线程 不断监视传给CreateProcess的第2个参数的内容

对CreateProcess的第2个参数 传入的是
TCHAR szCmd[] = TEXT("this is a cmd line");

首先是W版
以下是程序的输出
(每次输出内容都不一定相同 因为主线程和监视线程的调度情况不可预知 这次输出是最能反应实质的输出)
116 104 105 115 0 105 115 32 97 32 99 109 100 32 108 105 110 101
116 104 105 115 32 105 115 0 97 32 99 109 100 32 108 105 110 101
116 104 105 115 32 105 115 32 97 0 99 109 100 32 108 105 110 101
116 104 105 115 32 105 115 32 97 32 99 109 100 0 108 105 110 101
116 104 105 115 32 105 115 32 97 32 99 109 100 32 108 105 110 请按任意键继续. ..

联想到什么了吗?
传入的参数类容是 TEXT("this is a cmd line")
't'=116
'h'=104
'i'=105
's'=115
' '=32 (空格)
...

也就是说 在CreatProcessW执行过程中
监视线程执行了5次 并且检测到 "this is a cmd line" 中的4个空格被分别替换成了 '/0'=0

再联想 cmdline 被认作是一种以空格分隔的(space-delimited ) 不定项目的字符串
所以 CreatProcessW对第2个参数干的事情就是 依次将空格替换成 '/0'(字符串终结符)
this is a cmd line
在5次监视线程插足的时候CreatProcessW将第2参数分别被当作
this
this is
this is a
this is a cmd
this is a cmd line
5个字符串处理

当然 它们都不是合法的应用程序名称
(第1参数为NULL时 将把第2参数的第一个空格前——即是第一个项目   认作应用程序名称)
所以在本例中CreateProcessW 最终会失败
但是也反应出了问题本质~~
虽然函数执行完毕后 会恢复第2参数的内容 但是它曾经是被修改过的! 所以不能传入只读存储区地址!

如果去掉UNICODE _UNICODE宏定义 重新编译 程序输出如下
(同样 只是多次输出中的一次)
116 104 105 115 32 105 115 32 97 32 99 109 100 32 108 105 110 101
116 104 105 115 32 105 115 32 97 32 99 109 100 32 108 105 110 101
116 104 105 115 32 105 115 32 97 32 99 109 100 32 108 105 110 101
116 104 105 115 32 105 115 32 97 32 99 109 100 32 108 105 110 101
116 104 105 115 32 105 115 32 97 32 99 109 100 32 108 105 110 101
116 104 105 115 32 105 115 32 97 32 99 109 100 32 108 105 110 101 101 请按任意键
继续. . .

可以看出 CreateProcessA 没有对第2参数进行修改

以下是代码

#include <windows.h>
#include <process.h>
#include <tchar.h>
#include <stdio.h>

typedef struct tagUserData
{
       const TCHAR * pctstr;
       int len;
} UserData;

unsigned int __stdcall Monitor(void *pvParam);

int main()
{
       STARTUPINFO si;
       ZeroMemory(&si,sizeof(si));
       si.cb = sizeof(si);
       //和楼主代码有点区别
       //照理说应该在ZeroMemory之后
       //设置 cb 为该结构体的大小

PROCESS_INFORMATION pi;
       ZeroMemory(&pi,sizeof(pi));

si.dwFlags = STARTF_USESHOWWINDOW;
       si.wShowWindow = TRUE;

TCHAR        szCmd[] = TEXT("this is a cmd line");

UserData param;
       param.pctstr = szCmd;
       param.len = static_cast<int>(   _tcslen(szCmd) );
       //        _tcslen 是 strlen 的"T" 版本
      
       //        建立监视线程
       HANDLE hThread =
            reinterpret_cast<HANDLE> ( _beginthreadex(NULL,0,Monitor,static_cast<void*>(& param),0,NULL) );
       //调高其优先权   利于监视
       SetThreadPriority(hThread,THREAD_PRIORITY_HIGHEST);

BOOL bRet = ::CreateProcess (
            NULL,                     
            szCmd,            
            NULL,                     
            NULL,                     
            FALSE,                     
            CREATE_NEW_CONSOLE,       
            NULL,            
            NULL,                     
            &si,
            &pi);

return 0;
}

unsigned int __stdcall Monitor(void *pvParam)
{
       for (;;)
       {
            UserData *pData = static_cast<UserData*>(pvParam);
            for (int i=0;i<pData->len;++i)
                  _tprintf(TEXT("%d "),pData->pctstr[ i ] );
            _tprintf(TEXT("/n"));
       }
       return 0;
}

问题得以解决,总结一下,关键还是字符集的问题,哈哈 ,从内部来讲,CreateProcess会修改传递给它的命令行字符串,如果命令行字符串包含在文件映像的只读部分,那么函数就会出现违规访问的问题,例如上文在《windows核心编程》中的那段代码就是典型的例子。当CreateProcess函数试图修改这个字符串时就会发生违规访问(较早的Visual C++版本中将字符串放入读/写内存,因此调用CreateProcess不会出现违规访问)。解决这个问题的最佳方法是,在调用CreateProcess之前,将常量字符串复制到临时缓存中,程序代码如下:

STARTUPINFO si={sizeof(si)};

PROCESS_INFORMATION pi;

TCHAR cmd[]=TEXT("NOTEPAD");

CreateProcess(NULL,cmd,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi);

就这么简单,也可以使用Visual C++的/GF和/GF编译器开关,这些开关可以控制重复字符串的删除,并确定是否将这些字符串放入只读内存部分(注意,/ZI开关包含了/GF开关的功能,允许使用Visual Studio的Edit&Continue调试特性)。最好是使用/GF编译器开关和临时缓存。Microsoft公司能够做到的是设置好CreateProcess,让函数来生成一个字符串的临时副本,简化我们的工作。调用windows 2000以上版本的系统上CreateProcess的ANSI版本不会产生违规访问(即CreateProcessA),因为调用一个函数,系统就已经生成了一个命令行字符串的历史副本。详细的请看《windows核心编程》。

但太深的原理还不是很理解,需要以后在学习中继续摸索。如果有哪位牛人对此比较熟悉,请不吝赐教。在下不胜感激

关于CreateProcess的问题相关推荐

  1. ndk-build: CreateProcess error=193

    为什么80%的码农都做不了架构师?>>>    问题:ndk-build": CreateProcess error=193 解决:该问题表明,调用了非windows程序, ...

  2. CreateProcess failed: The system cannot find the file specified.

    在此网站https://www.cnblogs.com/sandeepin/p/ninja.html?ivk_sa=1024320u学习ninja构建项目时 将GCC = D:\MinGW\bin\g ...

  3. 用CreateProcess()在MFC中执行批处理命令

    //封装CreateProcess BOOL CFXNAdhocDemoDlg::ProcessCmd(LPTSTR cmd) {     ASSERT(cmd!=NULL); STARTUPINFO ...

  4. 使用c#调用API函数CreateProcess来运行外部程序

    c#调用CreateProcess函数比较麻烦的地方就是该函数的那些参数用c#的语法如何定义,CreateProcess函数位于Kernel32.dll中,原形如下: BOOL CreateProce ...

  5. 使用CreateProcess创建进程

    // TestCreateProcess.cpp : Defines the entry point for the console application. //#include "std ...

  6. delphi 运行外部程序函数winexec WinExecAndWait32 CreateProcess

    转载地址:http://blog.sina.com.cn/s/blog_7fdf737901013vgh.html 运行外部程序但不需等待winexec 运行外部程序并等待外部程序结束才返回结果的函数 ...

  7. vc中调用其他应用程序的方法(函数) winexec,shellexecute ,createprocess

    三个SDK函数: WinExec,ShellExecute ,CreateProcess可以实现调用其他程序的要求,其中以WinExec最为简单,ShellExecute比WinExec灵活一些,Cr ...

  8. ShellExecute, WinExec, CreateProcess区别

    ShellExecute ShellExecute的功能是运行一个外部程序(或者是打开一个已注册的文件.打开一个目录.打印一个文件等等),并对外部程序有一定的控制. 有几个API函数都可以实现这些功能 ...

  9. 等待CreateProcess进程退出

    STARTUPINFO si; //启动信息 memset(&si, 0, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); si.dwFl ...

  10. 使用CreateProcess和wcscat_s容易出错的地方

    由于没有仔细阅读MSDN,导致使用函数CreateProcess和wcscat_s出错,简单的问题,耽误不少时间. 1. CreateProcess的第二个参数不能指向只读的一段内存空间. 2. er ...

最新文章

  1. 一文看懂物联网完整产业链条
  2. 密度图的密度估计_箱形图、小提琴图、直方图……统统可以卡通化!
  3. oracle数据库没有选项,创建oracle数据库时,出现ORA-00922: 选项缺失或无效
  4. Unity手机摇一摇
  5. (转)oracle表分区详解
  6. Mac 电脑下搭建DOSBox汇编环境
  7. 计算机七年级下册课件ppt课件ppt,七年级音乐下册
  8. CustomValidator 的客户端验证
  9. 《Spring揭秘》读后感
  10. 我的世界服务器怎么弄vip系统,我的世界vip插件怎么用?我的世界vip插件使用方法...
  11. 什么软件测试情侣头像,扫图查情侣头像软件
  12. 英尺英寸和厘米的换算_C语言中关于英尺、英寸、厘米的换算
  13. 小学生怎么做PPT?
  14. 静态后台管理系统的搭建
  15. PLC1200 模拟量采集
  16. Java 内存模型如何保证多线程安全
  17. 大数据系列(一)之hadoop介绍及集群搭建
  18. 如何判断视频的比例(4:3/16:9)和分辨率?
  19. Python:Turtle图形绘制
  20. Lecture4 反向传播(Back Propagation)

热门文章

  1. 【Leetcode】644. Maximum Average Subarray II
  2. 淘宝,1688,京东店铺所有商品接口分享
  3. 【Python入门】Python的Dict容器一
  4. 启发式与元启发式算法
  5. PCIEBXMCx4板卡
  6. 搜网页显示未连接上服务器是什么,【科普君】网页搜索时,出现“404”到底是什么意思?...
  7. GOPS2018 | 华为云运维最佳CP引领AIOps新风向
  8. 古希腊神话故事3 :丢卡利翁和皮拉
  9. poi-导出excel表格
  10. pair用法(给元素赋值)