SpawnClient.pas 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. unit SpawnClient;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2007 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Spawn client
  8. NOTE: These functions are NOT thread-safe. Do not call them from multiple
  9. threads simultaneously.
  10. $jrsoftware: issrc/Projects/SpawnClient.pas,v 1.5 2007/09/05 02:07:35 jr Exp $
  11. }
  12. interface
  13. uses
  14. Windows, SysUtils, Messages, InstFunc;
  15. procedure InitializeSpawnClient(const AServerWnd: HWND);
  16. function InstExecEx(const RunAsOriginalUser: Boolean;
  17. const DisableFsRedir: Boolean; const Filename, Params, WorkingDir: String;
  18. const Wait: TExecWait; const ShowCmd: Integer;
  19. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  20. function InstShellExecEx(const RunAsOriginalUser: Boolean;
  21. const Verb, Filename, Params, WorkingDir: String;
  22. const Wait: TExecWait; const ShowCmd: Integer;
  23. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  24. implementation
  25. uses
  26. Classes, CmnFunc2, SpawnCommon;
  27. var
  28. SpawnServerPresent: Boolean;
  29. SpawnServerWnd: HWND;
  30. procedure WriteLongintToStream(const M: TMemoryStream; const Value: Longint);
  31. begin
  32. M.WriteBuffer(Value, SizeOf(Value));
  33. end;
  34. procedure WriteStringToStream(const M: TMemoryStream; const Value: String);
  35. var
  36. Len: Integer;
  37. begin
  38. Len := Length(Value);
  39. if Len > $FFFF then
  40. InternalError('WriteStringToStream: Length limit exceeded');
  41. WriteLongintToStream(M, Len);
  42. M.WriteBuffer(Value[1], Len * SizeOf(Value[1]));
  43. end;
  44. procedure AllowSpawnServerToSetForegroundWindow;
  45. { This is called to allow processes started by the spawn server process to
  46. come to the foreground, above the current process's windows. The effect
  47. normally lasts until new input is generated (a keystroke or click, not
  48. simply mouse movement).
  49. Note: If the spawn server process has no visible windows, it seems this
  50. isn't needed (on 2000 & Vista); the process can set the foreground window
  51. as it pleases. If it does have a visible window, though, it definitely is
  52. needed (e.g. in the /DebugSpawnServer case). Let's not rely on any
  53. undocumented behavior and call AllowSetForegroundWindow unconditionally. }
  54. var
  55. PID: DWORD;
  56. AllowSetForegroundWindowFunc: function(dwProcessId: DWORD): BOOL; stdcall;
  57. begin
  58. if GetWindowThreadProcessId(SpawnServerWnd, @PID) <> 0 then begin
  59. AllowSetForegroundWindowFunc := GetProcAddress(GetModuleHandle(user32),
  60. 'AllowSetForegroundWindow');
  61. if Assigned(AllowSetForegroundWindowFunc) then
  62. AllowSetForegroundWindowFunc(PID);
  63. end;
  64. end;
  65. function QuerySpawnServer(const SequenceNumber: Word;
  66. const Operation: Integer): Word;
  67. var
  68. MsgResult: LRESULT;
  69. begin
  70. MsgResult := SendMessage(SpawnServerWnd, WM_SpawnServer_Query, Operation,
  71. SequenceNumber);
  72. if MsgResult and not $FFFF <> SPAWN_MSGRESULT_SUCCESS_BITS then
  73. InternalErrorFmt('QuerySpawnServer: Unexpected response: $%x', [MsgResult]);
  74. Result := Word(MsgResult);
  75. end;
  76. function CallSpawnServer(const CopyDataMsg: DWORD; var M: TMemoryStream;
  77. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  78. var
  79. CopyDataStruct: TCopyDataStruct;
  80. MsgResult: LRESULT;
  81. SequenceNumber: Word;
  82. Status: Word;
  83. LastQueryTime, NowTime: DWORD;
  84. begin
  85. CopyDataStruct.dwData := CopyDataMsg;
  86. CopyDataStruct.cbData := M.Size;
  87. CopyDataStruct.lpData := M.Memory;
  88. AllowSpawnServerToSetForegroundWindow;
  89. MsgResult := SendMessage(SpawnServerWnd, WM_COPYDATA, 0, LPARAM(@CopyDataStruct));
  90. FreeAndNil(M); { it isn't needed anymore, might as well free now }
  91. if MsgResult = SPAWN_MSGRESULT_OUT_OF_MEMORY then
  92. OutOfMemoryError;
  93. if MsgResult and not $FFFF <> SPAWN_MSGRESULT_SUCCESS_BITS then
  94. InternalErrorFmt('CallSpawnServer: Unexpected response: $%x', [MsgResult]);
  95. SequenceNumber := Word(MsgResult);
  96. LastQueryTime := GetTickCount;
  97. repeat
  98. ProcessMessagesProc;
  99. { Now that the queue is empty (we mustn't break without first processing
  100. messages found by a previous MsgWaitForMultipleObjects call), see if
  101. the status changed, but only if at least 10 ms has elapsed since the
  102. last query }
  103. NowTime := GetTickCount;
  104. if Cardinal(NowTime - LastQueryTime) >= Cardinal(10) then begin
  105. LastQueryTime := NowTime;
  106. Status := QuerySpawnServer(SequenceNumber, SPAWN_QUERY_STATUS);
  107. case Status of
  108. SPAWN_STATUS_RUNNING: ;
  109. SPAWN_STATUS_RETURNED_TRUE, SPAWN_STATUS_RETURNED_FALSE: Break;
  110. else
  111. InternalErrorFmt('CallSpawnServer: Unexpected status: %d', [Status]);
  112. end;
  113. end;
  114. { Delay for 10 ms, or until a message arrives }
  115. MsgWaitForMultipleObjects(0, THandle(nil^), False, 10, QS_ALLINPUT);
  116. until False;
  117. ResultCode := QuerySpawnServer(SequenceNumber, SPAWN_QUERY_RESULTCODE_LO) or
  118. (QuerySpawnServer(SequenceNumber, SPAWN_QUERY_RESULTCODE_HI) shl 16);
  119. Result := (Status = SPAWN_STATUS_RETURNED_TRUE);
  120. end;
  121. function InstExecEx(const RunAsOriginalUser: Boolean;
  122. const DisableFsRedir: Boolean; const Filename, Params, WorkingDir: String;
  123. const Wait: TExecWait; const ShowCmd: Integer;
  124. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  125. var
  126. M: TMemoryStream;
  127. begin
  128. if not RunAsOriginalUser or not SpawnServerPresent then begin
  129. Result := InstExec(DisableFsRedir, Filename, Params, WorkingDir,
  130. Wait, ShowCmd, ProcessMessagesProc, ResultCode);
  131. Exit;
  132. end;
  133. M := TMemoryStream.Create;
  134. try
  135. WriteLongintToStream(M, Ord(DisableFsRedir));
  136. WriteStringToStream(M, Filename);
  137. WriteStringToStream(M, Params);
  138. WriteStringToStream(M, WorkingDir);
  139. WriteLongintToStream(M, Ord(Wait));
  140. WriteLongintToStream(M, ShowCmd);
  141. WriteStringToStream(M, GetCurrentDir);
  142. Result := CallSpawnServer(CD_SpawnServer_Exec, M, ProcessMessagesProc,
  143. ResultCode);
  144. finally
  145. M.Free;
  146. end;
  147. end;
  148. function InstShellExecEx(const RunAsOriginalUser: Boolean;
  149. const Verb, Filename, Params, WorkingDir: String;
  150. const Wait: TExecWait; const ShowCmd: Integer;
  151. const ProcessMessagesProc: TProcedure; var ResultCode: Integer): Boolean;
  152. var
  153. M: TMemoryStream;
  154. begin
  155. if not RunAsOriginalUser or not SpawnServerPresent then begin
  156. Result := InstShellExec(Verb, Filename, Params, WorkingDir,
  157. Wait, ShowCmd, ProcessMessagesProc, ResultCode);
  158. Exit;
  159. end;
  160. M := TMemoryStream.Create;
  161. try
  162. WriteStringToStream(M, Verb);
  163. WriteStringToStream(M, Filename);
  164. WriteStringToStream(M, Params);
  165. WriteStringToStream(M, WorkingDir);
  166. WriteLongintToStream(M, Ord(Wait));
  167. WriteLongintToStream(M, ShowCmd);
  168. WriteStringToStream(M, GetCurrentDir);
  169. Result := CallSpawnServer(CD_SpawnServer_ShellExec, M, ProcessMessagesProc,
  170. ResultCode);
  171. finally
  172. M.Free;
  173. end;
  174. end;
  175. procedure InitializeSpawnClient(const AServerWnd: HWND);
  176. begin
  177. SpawnServerWnd := AServerWnd;
  178. SpawnServerPresent := True;
  179. end;
  180. end.