今天一个朋友提到以前在DELPHI 7中运行正常的CreateProcess代码在XE2总是报内存地址非法写入错误。当时调试了一下,果真如此,颇感奇怪,于是祭出MSDN宝典一查,才发现其中端倪。MSDN原文部分摘录如下。
BOOL WINAPI CreateProcess( _In_opt_ LPCTSTR lpApplicationName, _Inout_opt_ LPTSTR lpCommandLine, _In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes, _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes, _In_ BOOL bInheritHandles, _In_ DWORD dwCreationFlags, _In_opt_ LPVOID lpEnvironment, _In_opt_ LPCTSTR lpCurrentDirectory, _In_ LPSTARTUPINFO lpStartupInfo, _Out_ LPPROCESS_INFORMATION lpProcessInformation );
其中关于lpCommandLine参数的说明中有一段话。
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.
大致意思如下:
在unicode版本的CreateProcessW中需要修改字符串的内容。所以,这个参数不能为指向只读内存的指针(比如该参数使用了const限定符或者literal string(不知如何翻译了))。如果这个参数是一个常量,函数将产生内存访问错误。
原来如此,这段话就解释了为何CreateProcess会报内存写错误。以下为可以在XE2下运行的代码,功能是将DOS程序运行的结果重定向到TMemo里,该函数可实时读取DOS程序运行结果,理论上还支持输入命令,命令尾部注意加\r\n;
procedure RunDosInMemo(const DosApp: string; AMemo: TRichEdit); const { 设置ReadBuffer的大小 } ReadBuffer = 1024; var Security: TSecurityAttributes; ReadPipe, WritePipe: THandle; start: TStartUpInfo; ProcessInfo: TProcessInformation; Buffer: PAnsiChar; BytesRead: DWORD; Buf: string; AppName: array [0 .. MAX_PATH - 1] of Char; Ret: Integer; begin with Security do begin nlength := SizeOf(TSecurityAttributes); binherithandle := true; lpsecuritydescriptor := nil; end; { 创建一个命名管道用来捕获console程序的输出 } if Createpipe(ReadPipe, WritePipe, @Security, 0) then begin Buffer := AllocMem(ReadBuffer + 1); FillChar(start, SizeOf(start), #0); { 设置console程序的启动属性 } with start do begin cb := SizeOf(start); GetStartupInfo(start); hStdOutput := WritePipe; // 将输出定向到我们建立的WritePipe上 hStdInput := ReadPipe; // 将输入定向到我们建立的ReadPipe上 hStdError := WritePipe; // 将错误输出定向到我们建立的WritePipe上 dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; wShowWindow := SW_HIDE; // 设置窗口为hide end; try StrPCopy(@AppName, DosApp); { 创建一个子进程,运行console程序 } if CreateProcess(nil, @AppName, @Security, @Security, true, NORMAL_PRIORITY_CLASS, nil, nil, start, ProcessInfo) then begin Ret := WaitForSingleObject(ProcessInfo.hProcess, 0); { 当进程没有结束时,循环读取输出数据} while (Ret = WAIT_TIMEOUT) or (Ret = WAIT_OBJECT_0) do begin BytesRead := 0; { 预读管道数据,必须要加,否则部分系统ReadFile会进入挂起状态} if PeekNamedPipe(ReadPipe, nil, 0, nil, @BytesRead, nil) then begin if BytesRead > 0 then begin BytesRead := 0; { 读取管道数据} if not ReadFile(ReadPipe, Buffer[0], ReadBuffer, BytesRead, nil) then Break; if BytesRead = 0 then Break; Buffer[BytesRead] := #0; OemToAnsi(Buffer, Buffer); Buf := Buf + string(Buffer); { 处理分隔符} while pos(#10, Buf) > 0 do begin AMemo.Lines.add(Copy(Buf, 1, pos(#10, Buf) - 1)); Delete(Buf, 1, pos(#10, Buf)); end; end; end; { 进程结束时退出循环} if (Ret = WAIT_OBJECT_0) then Break; { 长时间操作时处理进程消息} Application.ProcessMessages; Ret := WaitForSingleObject(ProcessInfo.hProcess, 0); end; end else AMemo.Lines.add('CreateProcess Last error:' + IntToStr(GetLastError)); finally FreeMem(Buffer); CloseHandle(WritePipe); CloseHandle(ReadPipe); CloseHandle(ProcessInfo.hProcess); CloseHandle(ProcessInfo.hThread); end; end; end;
有不少朋友反映使用CreateProcess执行cmd命令,然后WaitForSingleObject等待不到进程结束,其实很简单,将cmd加上参数/c 执行即可,例:'cmd /c dir'。
作者:空
出处:http://www.cnblogs.com/vikong2012/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。