views:

1427

answers:

3

Hi there!

I am trying to write a routine which will execute a DOS batch program from within a Delphi 2010 application. My old routine which works in Delphi 6 keeps giving me the error message:-

"Project1.exe raised exception class EAccessViolation with message 'Access violation at address 7C82F29C in module 'kernel32.dll'. Write of address 004A3B82".

Here is my old routine that works in Delphi 6:-

Procedure TForm1.BatchProgramCall;  
var  
    StartInfo: TStartUpInfo;  
    ProcInfo: TProcessInformation;  
    createOK: Boolean;  
begin  
     FillChar(StartInfo, SizeOf(TStartUpInfo), #0);  
     FillChar(ProcInfo, SizeOf(TProcessInformation), #0);  
     StartInfo.cb := SizeOf(TStartUpInfo);  
     StartInfo.dwFlags := STARTF_USESHOWWINDOW;  
     StartInfo.wShowWindow := SW_SHOWMINIMIZED;  

     createOK := CreateProcess(Nil,PCHAR('SOMEBATCHPROGRAM.BAT'),Nil, Nil, false,
                               CREATE_NEW_PROCESS_GROUP+HIGH_PRIORITY_CLASS,
                               NIL, NIL, STARTINFO, PROCINFO);
     if createOK then
        waitForSingleObject(PROCINFO.HPROCESS, Infinite);
end;

Please let me know what I am doing wrong or there is much better way to go about this... Thanks a lot.

A: 

I do a similar thing in Delphi 6, using much of your code but slightly different, I wonder if it will work for you?

function WinExecAndWait32(FileName: String; Visibility: integer): integer;
var
   zAppName: array[0..512] of char;
   zCurDir: array[0..255] of char;
   WorkDir: String;
   StartupInfo: TStartupInfo;
   ProcessInfo: TProcessInformation;
   Res: UINT;
begin
     StrPCopy(zAppName, FileName);
     GetDir(0, WorkDir);
     StrPCopy(zCurDir, WorkDir);
     FillChar(StartupInfo, Sizeof(StartupInfo), #0);
     StartupInfo.cb := Sizeof(StartupInfo);
     StartupInfo.dwFlags := STARTF_USESHOWWINDOW;
     StartupInfo.wShowWindow := Visibility;

     if not (CreateProcess(nil,
       zAppName,             { pointer to command line string }
       nil,                  { pointer to process security attributes}
       nil,                  { pointer to thread security attributes }
       false,                { handle inheritance flag }
       CREATE_NEW_CONSOLE or { creation flags }
       NORMAL_PRIORITY_CLASS,
       nil,                  { pointer to new environment block }
       nil,                  { pointer to current directory name }
       StartupInfo,          { pointer to STARTUPINFO }
       ProcessInfo)) then     { pointer to PROCESS_INF }
       Result := -1
     else
     begin
          WaitforSingleObject(ProcessInfo.hProcess, INFINITE);
          GetExitCodeProcess(ProcessInfo.hProcess, Res);
          {Added v2.4.4 (JS)}
          CloseHandle(ProcessInfo.hProcess);
          CloseHandle(ProcessInfo.hThread);
          Result := Res;
     end;
end;

To use:

WinExecAndWait32(sExtractProgramName, SW_SHOWNORMAL);
_J_
+8  A: 

you can read these articles about CreateProcess and the unicode issues.

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.

You can use the UniqueString function as workaround to fix the problem.

Procedure TForm1.BatchProgramCall;  
var
    StartInfo: TStartUpInfo;
    ProcInfo: TProcessInformation;
    createOK: Boolean;
    sMyBat: string;

begin
     FillChar(StartInfo, SizeOf(TStartUpInfo), #0);
     FillChar(ProcInfo, SizeOf(TProcessInformation), #0);
     StartInfo.cb := SizeOf(TStartUpInfo);
     StartInfo.dwFlags      := STARTF_USESHOWWINDOW;
     StartInfo.wShowWindow := SW_SHOWMINIMIZED;

     sMyBat  :='SOMEBATCHPROGRAM.BAT';
     UniqueString(sMyBat); //this make the magic.
     createOK := CreateProcess(Nil,pchar(sMyBat),Nil, Nil, false,
                               CREATE_NEW_PROCESS_GROUP+HIGH_PRIORITY_CLASS,
                               NIL, NIL, STARTINFO, PROCINFO);
     if createOK then
        waitForSingleObject(PROCINFO.HPROCESS, Infinite);
end;
RRUZ
Thanks a lot... the UniqueString sure performed the magic.
Snackmoore
+1 For linking to the CreateProcess Declaration article, people please leave your reaction/vote so we make the changes to Jedi Apilib!
Remko
+6  A: 

The reason that your function fails in Delphi 2010 but works in Delphi 6 is that CreateProcessW() must not be called with a read-only lpCommandLine parameter. To quote the MSDN documentation:

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.

The reason that it works with Delphi 6 is that all Windows functions are really wide string internally, and the Ansi versions do nothing but convert string parameters to their wide string counterpart, and then call the wide version. You call the function with a constant, and with Delphi 6 Windows internally creates a writeable buffer for you. With Delphi 2010 you experience the AV.

Note that your program has another bug, as the documentation does also state:

To run a batch file, you must start the command interpreter; set lpApplicationName to cmd.exe and set lpCommandLine to the following arguments: /c plus the name of the batch file.

mghie
Thanks for the mention of the commandLine or I will run into a brick wall again.
Snackmoore