SpawnServer.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. unit SpawnServer;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2010 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Spawn server
  8. $jrsoftware: issrc/Projects/SpawnServer.pas,v 1.13 2010/04/17 19:30:25 jr Exp $
  9. }
  10. interface
  11. {$I VERSION.INC}
  12. uses
  13. Windows, SysUtils, Messages;
  14. type
  15. TSpawnServer = class
  16. private
  17. FWnd: HWND;
  18. FSequenceNumber: Word;
  19. FCallStatus: Word;
  20. FResultCode: Integer;
  21. FNotifyRestartRequested: Boolean;
  22. FNotifyNewLanguage: Integer;
  23. function HandleExec(const IsShellExec: Boolean; const ADataPtr: Pointer;
  24. const ADataSize: Cardinal): LRESULT;
  25. procedure WndProc(var Message: TMessage);
  26. public
  27. constructor Create;
  28. destructor Destroy; override;
  29. property NotifyNewLanguage: Integer read FNotifyNewLanguage;
  30. property NotifyRestartRequested: Boolean read FNotifyRestartRequested;
  31. property Wnd: HWND read FWnd;
  32. end;
  33. procedure EnterSpawnServerDebugMode;
  34. function NeedToRespawnSelfElevated(const ARequireAdministrator,
  35. AEmulateHighestAvailable: Boolean): Boolean;
  36. procedure RespawnSelfElevated(const AExeFilename, AParams: String;
  37. var AExitCode: DWORD);
  38. implementation
  39. { For debugging only; remove 'x' to enable the define: }
  40. {x$DEFINE SPAWNSERVER_RESPAWN_ALWAYS}
  41. uses
  42. Classes, Forms, ShellApi, Int64Em, PathFunc, CmnFunc2, InstFunc, SpawnCommon;
  43. type
  44. TPtrAndSize = record
  45. Ptr: ^Byte;
  46. Size: Cardinal;
  47. end;
  48. procedure ProcessMessagesProc;
  49. begin
  50. Application.ProcessMessages;
  51. end;
  52. function ExtractBytes(var Data: TPtrAndSize; const Bytes: Cardinal;
  53. var Value: Pointer): Boolean;
  54. begin
  55. if Data.Size < Bytes then
  56. Result := False
  57. else begin
  58. Value := Data.Ptr;
  59. Dec(Data.Size, Bytes);
  60. Inc(Data.Ptr, Bytes);
  61. Result := True;
  62. end;
  63. end;
  64. function ExtractLongint(var Data: TPtrAndSize; var Value: Longint): Boolean;
  65. var
  66. P: Pointer;
  67. begin
  68. Result := ExtractBytes(Data, SizeOf(Longint), P);
  69. if Result then
  70. Value := Longint(P^);
  71. end;
  72. function ExtractString(var Data: TPtrAndSize; var Value: String): Boolean;
  73. var
  74. Len: Longint;
  75. P: Pointer;
  76. begin
  77. Result := ExtractLongint(Data, Len);
  78. if Result then begin
  79. if (Len < 0) or (Len > $FFFF) then
  80. Result := False
  81. else begin
  82. Result := ExtractBytes(Data, Len * SizeOf(Value[1]), P);
  83. if Result then
  84. SetString(Value, PChar(P), Len);
  85. end;
  86. end;
  87. end;
  88. type
  89. TOSVersionInfoExW = record
  90. dwOSVersionInfoSize: DWORD;
  91. dwMajorVersion: DWORD;
  92. dwMinorVersion: DWORD;
  93. dwBuildNumber: DWORD;
  94. dwPlatformId: DWORD;
  95. szCSDVersion: array[0..127] of WideChar;
  96. wServicePackMajor: Word;
  97. wServicePackMinor: Word;
  98. wSuiteMask: Word;
  99. wProductType: Byte;
  100. wReserved: Byte;
  101. end;
  102. const
  103. VER_MINORVERSION = $0000001;
  104. VER_MAJORVERSION = $0000002;
  105. VER_SERVICEPACKMINOR = $0000010;
  106. VER_SERVICEPACKMAJOR = $0000020;
  107. VER_GREATER_EQUAL = 3;
  108. var
  109. VerSetConditionMaskFunc, VerifyVersionInfoWFunc: Pointer;
  110. { These are implemented in asm because Delphi 2 doesn't support functions that
  111. take 64-bit parameters or return a 64-bit result (in EDX:EAX) }
  112. procedure CallVerSetConditionMask(var dwlConditionMask: Integer64;
  113. dwTypeBitMask: DWORD; dwConditionMask: DWORD);
  114. asm
  115. push esi
  116. mov esi, eax // ESI = @dwlConditionMask
  117. push ecx // dwConditionMask
  118. push edx // dwTypeBitMask
  119. push dword ptr [esi+4] // dwlConditionMask.Hi
  120. push dword ptr [esi] // dwlConditionMask.Lo
  121. call VerSetConditionMaskFunc
  122. mov dword ptr [esi], eax // write dwlConditionMask.Lo
  123. mov dword ptr [esi+4], edx // write dwlConditionMask.Hi
  124. pop esi
  125. end;
  126. function CallVerifyVersionInfoW(const lpVersionInfo: TOSVersionInfoExW;
  127. dwTypeMask: DWORD; const dwlConditionMask: Integer64): BOOL;
  128. asm
  129. push dword ptr [ecx+4] // dwlConditionMask.Hi
  130. push dword ptr [ecx] // dwlConditionMask.Lo
  131. push edx // dwTypeMask
  132. push eax // lpVersionInfo
  133. call VerifyVersionInfoWFunc
  134. end;
  135. function IsReallyVista: Boolean;
  136. { Returns True if the OS is *really* Vista or later. VerifyVersionInfo is used
  137. because it appears to always check the true OS version number, whereas
  138. GetVersion(Ex) can return a fake version number (e.g. 5.x) if the program is
  139. set to run in compatibility mode, or if it is started by a program running
  140. in compatibility mode. }
  141. var
  142. ConditionMask: Integer64;
  143. VerInfo: TOSVersionInfoExW;
  144. begin
  145. Result := False;
  146. { These functions are present on Windows 2000 and later.
  147. NT 4.0 SP6 has VerifyVersionInfoW, but not VerSetConditionMask.
  148. Windows 9x/Me and early versions of NT 4.0 have neither. }
  149. if Assigned(VerSetConditionMaskFunc) and Assigned(VerifyVersionInfoWFunc) then begin
  150. ConditionMask.Lo := 0;
  151. ConditionMask.Hi := 0;
  152. { Docs say: "If you are testing the major version, you must also test the
  153. minor version and the service pack major and minor versions." }
  154. CallVerSetConditionMask(ConditionMask, VER_MAJORVERSION, VER_GREATER_EQUAL);
  155. CallVerSetConditionMask(ConditionMask, VER_MINORVERSION, VER_GREATER_EQUAL);
  156. CallVerSetConditionMask(ConditionMask, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL);
  157. CallVerSetConditionMask(ConditionMask, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL);
  158. FillChar(VerInfo, SizeOf(VerInfo), 0);
  159. VerInfo.dwOSVersionInfoSize := SizeOf(VerInfo);
  160. VerInfo.dwMajorVersion := 6;
  161. Result := CallVerifyVersionInfoW(VerInfo, VER_MAJORVERSION or
  162. VER_MINORVERSION or VER_SERVICEPACKMAJOR or VER_SERVICEPACKMINOR,
  163. ConditionMask);
  164. end;
  165. end;
  166. const
  167. TokenElevationTypeDefault = 1; { User does not have a split token (they're
  168. not an admin, or UAC is turned off) }
  169. TokenElevationTypeFull = 2; { Has split token, process running elevated }
  170. TokenElevationTypeLimited = 3; { Has split token, process not running
  171. elevated }
  172. function GetTokenElevationType: DWORD;
  173. { Returns token elevation type (TokenElevationType* constant). In case of
  174. failure (e.g. not running Vista), 0 is returned. }
  175. const
  176. TokenElevationType = 18;
  177. var
  178. Token: THandle;
  179. ElevationType: DWORD;
  180. ReturnLength: DWORD;
  181. begin
  182. Result := 0;
  183. if OpenProcessToken(GetCurrentProcess, TOKEN_QUERY,
  184. {$IFNDEF Delphi3orHigher} @ {$ENDIF} Token) then begin
  185. ElevationType := 0;
  186. if GetTokenInformation(Token,
  187. {$IFDEF Delphi3orHigher} TTokenInformationClass {$ENDIF} (TokenElevationType),
  188. @ElevationType, SizeOf(ElevationType), ReturnLength) then
  189. Result := ElevationType;
  190. CloseHandle(Token);
  191. end;
  192. end;
  193. function NeedToRespawnSelfElevated(const ARequireAdministrator,
  194. AEmulateHighestAvailable: Boolean): Boolean;
  195. {$IFNDEF SPAWNSERVER_RESPAWN_ALWAYS}
  196. var
  197. ElevationType: DWORD;
  198. begin
  199. Result := False;
  200. if IsReallyVista and not IsAdminLoggedOn then begin
  201. if ARequireAdministrator then
  202. Result := True
  203. else if AEmulateHighestAvailable then begin
  204. { Emulate the "highestAvailable" requestedExecutionLevel: respawn if
  205. the user has a split token and the process isn't running elevated.
  206. (An inverted test for TokenElevationTypeLimited is used, so that if
  207. GetTokenElevationType unexpectedly fails or returns some value we
  208. don't recognize, we default to respawning.) }
  209. ElevationType := GetTokenElevationType;
  210. if (ElevationType <> TokenElevationTypeDefault) and
  211. (ElevationType <> TokenElevationTypeFull) then
  212. Result := True;
  213. end;
  214. end;
  215. end;
  216. {$ELSE}
  217. begin
  218. { For debugging/testing only: }
  219. Result := (Lo(GetVersion) >= 5);
  220. end;
  221. {$ENDIF}
  222. function GetFinalFileName(const Filename: String): String;
  223. { Calls GetFinalPathNameByHandle (new API in Vista) to expand any SUBST'ed
  224. drives, network drives, and symbolic links in Filename.
  225. This is needed for elevation to succeed on Windows Vista/7 when Setup is
  226. started from a SUBST'ed drive letter. }
  227. function ConvertToNormalPath(P: PChar): String;
  228. begin
  229. Result := P;
  230. if StrLComp(P, '\\?\', 4) = 0 then begin
  231. Inc(P, 4);
  232. if (PathStrNextChar(P) = P + 1) and (P[1] = ':') and PathCharIsSlash(P[2]) then
  233. Result := P
  234. else if StrLIComp(P, 'UNC\', 4) = 0 then begin
  235. Inc(P, 4);
  236. Result := '\\' + P;
  237. end;
  238. end;
  239. end;
  240. const
  241. FILE_SHARE_DELETE = $00000004;
  242. var
  243. GetFinalPathNameByHandleFunc: function(hFile: THandle;
  244. lpszFilePath: {$IFDEF UNICODE} PWideChar {$ELSE} PAnsiChar {$ENDIF};
  245. cchFilePath: DWORD; dwFlags: DWORD): DWORD; stdcall;
  246. Attr, FlagsAndAttributes: DWORD;
  247. H: THandle;
  248. Res: Integer;
  249. Buf: array[0..4095] of Char;
  250. begin
  251. GetFinalPathNameByHandleFunc := GetProcAddress(GetModuleHandle(kernel32),
  252. {$IFDEF UNICODE}
  253. 'GetFinalPathNameByHandleW'
  254. {$ELSE}
  255. 'GetFinalPathNameByHandleA'
  256. {$ENDIF} );
  257. if Assigned(GetFinalPathNameByHandleFunc) then begin
  258. Attr := GetFileAttributes(PChar(Filename));
  259. if Attr <> $FFFFFFFF then begin
  260. { Backup semantics must be requested in order to open a directory }
  261. if Attr and FILE_ATTRIBUTE_DIRECTORY <> 0 then
  262. FlagsAndAttributes := FILE_FLAG_BACKUP_SEMANTICS
  263. else
  264. FlagsAndAttributes := 0;
  265. { Use zero access mask and liberal sharing mode to ensure success }
  266. H := CreateFile(PChar(Filename), 0, FILE_SHARE_READ or FILE_SHARE_WRITE or
  267. FILE_SHARE_DELETE, nil, OPEN_EXISTING, FlagsAndAttributes, 0);
  268. if H <> INVALID_HANDLE_VALUE then begin
  269. Res := GetFinalPathNameByHandleFunc(H, Buf, SizeOf(Buf) div SizeOf(Buf[0]), 0);
  270. CloseHandle(H);
  271. if (Res > 0) and (Res < (SizeOf(Buf) div SizeOf(Buf[0])) - 16) then begin
  272. { ShellExecuteEx fails with error 3 on \\?\UNC\ paths, so try to
  273. convert the returned path from \\?\ form }
  274. Result := ConvertToNormalPath(Buf);
  275. Exit;
  276. end;
  277. end;
  278. end;
  279. end;
  280. Result := Filename;
  281. end;
  282. function GetFinalCurrentDir: String;
  283. var
  284. Res: Integer;
  285. Buf: array[0..MAX_PATH-1] of Char;
  286. begin
  287. DWORD(Res) := GetCurrentDirectory(SizeOf(Buf) div SizeOf(Buf[0]), Buf);
  288. if (Res > 0) and (Res < SizeOf(Buf) div SizeOf(Buf[0])) then
  289. Result := GetFinalFileName(Buf)
  290. else begin
  291. RaiseFunctionFailedError('GetCurrentDirectory');
  292. Result := '';
  293. end;
  294. end;
  295. procedure RespawnSelfElevated(const AExeFilename, AParams: String;
  296. var AExitCode: DWORD);
  297. { Spawns a new process using the "runas" verb.
  298. Notes:
  299. 1. Despite the function's name, the spawned process may not actually be
  300. elevated / running as administrator on Vista. If UAC is disabled, "runas"
  301. behaves like "open". Also, if a non-admin user is a member of a special
  302. system group like Backup Operators, they can select their own user account
  303. at a UAC dialog. Therefore, it is critical that the caller include some
  304. kind of protection against respawning more than once.
  305. 2. If AExeFilename is on a network drive, Vista's ShellExecuteEx function is
  306. smart enough to substitute it with a UNC path. XP does not do this, which
  307. causes the function to fail with ERROR_PATH_NOT_FOUND because the new
  308. user doesn't retain the original user's drive mappings. }
  309. const
  310. SEE_MASK_NOZONECHECKS = $00800000;
  311. var
  312. ExpandedExeFilename, WorkingDir: String;
  313. Info: TShellExecuteInfo;
  314. WaitResult: DWORD;
  315. begin
  316. ExpandedExeFilename := GetFinalFileName(AExeFilename);
  317. WorkingDir := GetFinalCurrentDir;
  318. FillChar(Info, SizeOf(Info), 0);
  319. Info.cbSize := SizeOf(Info);
  320. Info.fMask := SEE_MASK_FLAG_NO_UI or SEE_MASK_FLAG_DDEWAIT or
  321. SEE_MASK_NOCLOSEPROCESS or SEE_MASK_NOZONECHECKS;
  322. Info.lpVerb := 'runas';
  323. Info.lpFile := PChar(ExpandedExeFilename);
  324. Info.lpParameters := PChar(AParams);
  325. Info.lpDirectory := PChar(WorkingDir);
  326. Info.nShow := SW_SHOWNORMAL;
  327. if not ShellExecuteEx(@Info) then begin
  328. { Don't display error message if user clicked Cancel at UAC dialog }
  329. if GetLastError = ERROR_CANCELLED then
  330. Abort;
  331. Win32ErrorMsg('ShellExecuteEx');
  332. end;
  333. if Info.hProcess = 0 then
  334. InternalError('ShellExecuteEx returned hProcess=0');
  335. { Wait for the process to terminate, processing messages in the meantime }
  336. try
  337. repeat
  338. ProcessMessagesProc;
  339. WaitResult := MsgWaitForMultipleObjects(1, Info.hProcess, False,
  340. INFINITE, QS_ALLINPUT);
  341. until WaitResult <> WAIT_OBJECT_0+1;
  342. if WaitResult = WAIT_FAILED then
  343. Win32ErrorMsg('MsgWaitForMultipleObjects');
  344. { Now that the process has exited, process any remaining messages.
  345. (If our window is handling notify messages (ANotifyWndPresent=False)
  346. then there may be an asynchronously-sent "restart request" message
  347. still queued if MWFMO saw the process terminate before checking for
  348. new messages.) }
  349. ProcessMessagesProc;
  350. if not GetExitCodeProcess(Info.hProcess, AExitCode) then
  351. Win32ErrorMsg('GetExitCodeProcess');
  352. finally
  353. CloseHandle(Info.hProcess);
  354. end;
  355. end;
  356. procedure EnterSpawnServerDebugMode;
  357. { For debugging purposes only: Creates a spawn server window, but does not
  358. start a new process. Displays the server window handle in the taskbar.
  359. Terminates when F11 is pressed. }
  360. var
  361. Server: TSpawnServer;
  362. begin
  363. Server := TSpawnServer.Create;
  364. try
  365. Application.Title := Format('Wnd=$%x', [Server.FWnd]);
  366. while True do begin
  367. ProcessMessagesProc;
  368. if (GetFocus = Application.Handle) and (GetKeyState(VK_F11) < 0) then
  369. Break;
  370. WaitMessage;
  371. end;
  372. finally
  373. Server.Free;
  374. end;
  375. Halt(1);
  376. end;
  377. { TSpawnServer }
  378. constructor TSpawnServer.Create;
  379. begin
  380. inherited;
  381. FNotifyNewLanguage := -1;
  382. FWnd := AllocateHWnd(WndProc);
  383. if FWnd = 0 then
  384. RaiseFunctionFailedError('AllocateHWnd');
  385. end;
  386. destructor TSpawnServer.Destroy;
  387. begin
  388. if FWnd <> 0 then
  389. DeallocateHWnd(FWnd);
  390. inherited;
  391. end;
  392. function TSpawnServer.HandleExec(const IsShellExec: Boolean;
  393. const ADataPtr: Pointer; const ADataSize: Cardinal): LRESULT;
  394. var
  395. Data: TPtrAndSize;
  396. EDisableFsRedir: Longint;
  397. EVerb, EFilename, EParams, EWorkingDir: String;
  398. EWait, EShowCmd: Longint;
  399. ClientCurrentDir, SaveCurrentDir: String;
  400. ExecResult: Boolean;
  401. begin
  402. { Recursive calls aren't supported }
  403. if FCallStatus = SPAWN_STATUS_RUNNING then begin
  404. Result := SPAWN_MSGRESULT_ALREADY_IN_CALL;
  405. Exit;
  406. end;
  407. Result := SPAWN_MSGRESULT_INVALID_DATA;
  408. Data.Ptr := ADataPtr;
  409. Data.Size := ADataSize;
  410. if IsShellExec then begin
  411. if not ExtractString(Data, EVerb) then Exit;
  412. end
  413. else begin
  414. if not ExtractLongint(Data, EDisableFsRedir) then Exit;
  415. end;
  416. if not ExtractString(Data, EFilename) then Exit;
  417. if not ExtractString(Data, EParams) then Exit;
  418. if not ExtractString(Data, EWorkingDir) then Exit;
  419. if not ExtractLongint(Data, EWait) then Exit;
  420. if not ExtractLongint(Data, EShowCmd) then Exit;
  421. if not ExtractString(Data, ClientCurrentDir) then Exit;
  422. if Data.Size <> 0 then Exit;
  423. Inc(FSequenceNumber);
  424. FResultCode := -1;
  425. FCallStatus := SPAWN_STATUS_RUNNING;
  426. try
  427. SaveCurrentDir := GetCurrentDir;
  428. try
  429. SetCurrentDir(ClientCurrentDir);
  430. Result := SPAWN_MSGRESULT_SUCCESS_BITS or FSequenceNumber;
  431. { Send back the result code now to unblock the client }
  432. ReplyMessage(Result);
  433. if IsShellExec then begin
  434. ExecResult := InstShellExec(EVerb, EFilename, EParams, EWorkingDir,
  435. TExecWait(EWait), EShowCmd, ProcessMessagesProc, FResultCode);
  436. end
  437. else begin
  438. ExecResult := InstExec(EDisableFsRedir <> 0, EFilename, EParams, EWorkingDir,
  439. TExecWait(EWait), EShowCmd, ProcessMessagesProc, FResultCode);
  440. end;
  441. if ExecResult then
  442. FCallStatus := SPAWN_STATUS_RETURNED_TRUE
  443. else
  444. FCallStatus := SPAWN_STATUS_RETURNED_FALSE;
  445. finally
  446. SetCurrentDir(SaveCurrentDir);
  447. end;
  448. finally
  449. { If the status is still SPAWN_STATUS_RUNNING here, then an unexpected
  450. exception must've occurred }
  451. if FCallStatus = SPAWN_STATUS_RUNNING then
  452. FCallStatus := SPAWN_STATUS_EXCEPTION;
  453. end;
  454. end;
  455. procedure TSpawnServer.WndProc(var Message: TMessage);
  456. var
  457. Res: LRESULT;
  458. begin
  459. case Message.Msg of
  460. WM_COPYDATA:
  461. begin
  462. try
  463. case TWMCopyData(Message).CopyDataStruct.dwData of
  464. CD_SpawnServer_Exec,
  465. CD_SpawnServer_ShellExec:
  466. begin
  467. Message.Result := HandleExec(
  468. TWMCopyData(Message).CopyDataStruct.dwData = CD_SpawnServer_ShellExec,
  469. TWMCopyData(Message).CopyDataStruct.lpData,
  470. TWMCopyData(Message).CopyDataStruct.cbData);
  471. end;
  472. end;
  473. except
  474. if ExceptObject is EOutOfMemory then
  475. Message.Result := SPAWN_MSGRESULT_OUT_OF_MEMORY
  476. else
  477. { Shouldn't get here; we don't explicitly raise any exceptions }
  478. Message.Result := SPAWN_MSGRESULT_UNEXPECTED_EXCEPTION;
  479. end;
  480. end;
  481. WM_SpawnServer_Query:
  482. begin
  483. Res := SPAWN_MSGRESULT_INVALID_SEQUENCE_NUMBER;
  484. if Message.LParam = FSequenceNumber then begin
  485. Res := SPAWN_MSGRESULT_INVALID_QUERY_OPERATION;
  486. case Message.WParam of
  487. SPAWN_QUERY_STATUS:
  488. Res := SPAWN_MSGRESULT_SUCCESS_BITS or FCallStatus;
  489. SPAWN_QUERY_RESULTCODE_LO:
  490. Res := SPAWN_MSGRESULT_SUCCESS_BITS or LongRec(FResultCode).Lo;
  491. SPAWN_QUERY_RESULTCODE_HI:
  492. Res := SPAWN_MSGRESULT_SUCCESS_BITS or LongRec(FResultCode).Hi;
  493. end;
  494. end;
  495. Message.Result := Res;
  496. end;
  497. WM_USER + 150: begin
  498. { Got a SetupNotifyWnd message. (See similar handling in SetupLdr.dpr) }
  499. if Message.WParam = 10000 then
  500. FNotifyRestartRequested := True
  501. else if Message.WParam = 10001 then
  502. FNotifyNewLanguage := Message.LParam;
  503. end;
  504. else
  505. Message.Result := DefWindowProc(FWnd, Message.Msg, Message.WParam,
  506. Message.LParam);
  507. end;
  508. end;
  509. var
  510. Kernel32Handle: HMODULE;
  511. initialization
  512. Kernel32Handle := GetModuleHandle(kernel32);
  513. VerSetConditionMaskFunc := GetProcAddress(Kernel32Handle, 'VerSetConditionMask');
  514. VerifyVersionInfoWFunc := GetProcAddress(Kernel32Handle, 'VerifyVersionInfoW');
  515. end.