Compression.LZMACompressor.pas 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218
  1. unit Compression.LZMACompressor;
  2. {
  3. Inno Setup
  4. Copyright (C) 1997-2026 Jordan Russell
  5. Portions by Martijn Laan
  6. For conditions of distribution and use, see LICENSE.TXT.
  7. Interface to the islzma LZMA/LZMA2 compression DLL and EXEs in
  8. Compression.LZMACompressor\islzma, used by ISCmplr.
  9. }
  10. interface
  11. uses
  12. Windows, SysUtils,
  13. Compression.Base;
  14. function LZMAInitCompressFunctions(Module: HMODULE): Boolean;
  15. function LZMAGetLevel(const Value: String; var Level: Integer): Boolean;
  16. const
  17. clLZMAFast = 1;
  18. clLZMANormal = 2;
  19. clLZMAMax = 3;
  20. clLZMAUltra = 4;
  21. clLZMAUltra64 = 5;
  22. type
  23. { Internally-used types }
  24. TLZMASRes = type Integer;
  25. TLZMACompressorCustomWorker = class;
  26. TLZMACompressorProps = class(TCompressorProps)
  27. public
  28. Algorithm: Integer;
  29. BlockSize: Integer;
  30. BTMode: Integer;
  31. DictionarySize: Cardinal;
  32. NumBlockThreads: Integer;
  33. NumFastBytes: Integer;
  34. NumThreads: Integer;
  35. NumThreadGroups: Integer;
  36. WorkerProcessCheckTrust: Boolean;
  37. WorkerProcessOnCheckedTrust: TProc<Boolean>;
  38. WorkerProcessFilename: String;
  39. constructor Create;
  40. end;
  41. { Internally-used records }
  42. TLZMAEncoderProps = record
  43. Algorithm: Integer;
  44. BlockSize: Integer;
  45. BTMode: Integer;
  46. NumHashBytes: Integer;
  47. DictionarySize: Cardinal;
  48. NumBlockThreads: Integer;
  49. NumFastBytes: Integer;
  50. NumThreads: Integer;
  51. NumThreadGroups: Integer;
  52. end;
  53. TLZMACompressorRingBuffer = record
  54. Count: Integer; { updated by reader and writer using InterlockedExchangeAdd only }
  55. WriterOffset: Integer; { accessed only by writer thread }
  56. ReaderOffset: Integer; { accessed only by reader thread }
  57. Buf: array[0..$FFFFF] of Byte;
  58. end;
  59. { Using 32-bit handles to ensure islzma.exe doesn't depend on our bitness. This is safe
  60. because 64-bit versions of Windows use 32-bit handles for interoperability. See
  61. https://learn.microsoft.com/en-us/windows/win32/winprog64/interprocess-communication }
  62. TLZMAHandle32 = type UInt32;
  63. PLZMACompressorSharedEvents = ^TLZMACompressorSharedEvents;
  64. TLZMACompressorSharedEvents = record
  65. TerminateWorkerEvent: TLZMAHandle32;
  66. StartEncodeEvent: TLZMAHandle32;
  67. EndWaitOnInputEvent: TLZMAHandle32;
  68. EndWaitOnOutputEvent: TLZMAHandle32;
  69. WorkerWaitingOnInputEvent: TLZMAHandle32;
  70. WorkerWaitingOnOutputEvent: TLZMAHandle32;
  71. WorkerEncodeFinishedEvent: TLZMAHandle32;
  72. end;
  73. PLZMACompressorSharedData = ^TLZMACompressorSharedData;
  74. TLZMACompressorSharedData = record
  75. ProgressBytes: Int64;
  76. NoMoreInput: BOOL;
  77. EncodeResult: TLZMASRes;
  78. InputBuffer: TLZMACompressorRingBuffer;
  79. OutputBuffer: TLZMACompressorRingBuffer;
  80. end;
  81. PLZMACompressorProcessData = ^TLZMACompressorProcessData;
  82. TLZMACompressorProcessData = record
  83. StructSize: Cardinal;
  84. ParentProcess: TLZMAHandle32;
  85. LZMA2: BOOL;
  86. EncoderProps: TLZMAEncoderProps;
  87. Events: TLZMACompressorSharedEvents;
  88. SharedDataStructSize: Cardinal;
  89. SharedDataMapping: TLZMAHandle32;
  90. end;
  91. TLZMACompressor = class(TCustomCompressor)
  92. private
  93. FUseLZMA2: Boolean;
  94. FEvents: TLZMACompressorSharedEvents;
  95. FShared: PLZMACompressorSharedData;
  96. FWorker: TLZMACompressorCustomWorker;
  97. FEncodeStarted: Boolean;
  98. FEncodeFinished: Boolean;
  99. FLastInputWriteCount: Cardinal;
  100. FLastProgressBytes: Int64;
  101. FProgressTimer: THandle;
  102. FProgressTimerSignaled: Boolean;
  103. procedure FlushOutputBuffer(const OnlyOptimalSize: Boolean);
  104. procedure InitializeProps(const CompressionLevel: Integer;
  105. const ACompressorProps: TCompressorProps);
  106. class function IsObjectSignaled(const AObject: THandle): Boolean; static;
  107. class procedure SatisfyWorkerWait(const AWorkerEvent, AMainEvent: THandle); static;
  108. procedure SatisfyWorkerWaitOnInput;
  109. procedure SatisfyWorkerWaitOnOutput;
  110. procedure StartEncode;
  111. procedure UpdateProgress;
  112. procedure WaitForWorkerEvent;
  113. protected
  114. procedure DoCompress(const Buffer; Count: Cardinal); override;
  115. procedure DoFinish; override;
  116. public
  117. constructor Create(AWriteProc: TCompressorWriteProc;
  118. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  119. ACompressorProps: TCompressorProps); override;
  120. destructor Destroy; override;
  121. end;
  122. TLZMA2Compressor = class(TLZMACompressor)
  123. public
  124. constructor Create(AWriteProc: TCompressorWriteProc;
  125. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  126. ACompressorProps: TCompressorProps); override;
  127. end;
  128. { Internally-used classes }
  129. TLZMACompressorCustomWorker = class
  130. protected
  131. FEvents: PLZMACompressorSharedEvents;
  132. FShared: PLZMACompressorSharedData;
  133. public
  134. constructor Create(const AEvents: PLZMACompressorSharedEvents); virtual;
  135. function GetExitHandle: THandle; virtual; abstract;
  136. procedure SetProps(const LZMA2: Boolean; const EncProps: TLZMAEncoderProps);
  137. virtual; abstract;
  138. procedure UnexpectedTerminationError; virtual; abstract;
  139. end;
  140. implementation
  141. uses
  142. Classes, TrustFunc, Shared.CommonFunc, Compiler.Messages;
  143. const
  144. ISLZMA_EXE_VERSION = 102;
  145. type
  146. TLZMACompressorHandle = type Pointer;
  147. TLZMAWorkerThread = class(TLZMACompressorCustomWorker)
  148. private
  149. FThread: THandle;
  150. FLZMAHandle: TLZMACompressorHandle;
  151. FReadLock, FWriteLock, FProgressLock: Integer;
  152. FSavedFatalException: TObject;
  153. function CheckTerminateWorkerEvent: HRESULT;
  154. function FillBuffer(const AWrite: Boolean; const Data: Pointer;
  155. Size: NativeUInt; var ProcessedSize: NativeUInt): HRESULT;
  156. function ProgressMade(const TotalBytesProcessed: UInt64): HRESULT;
  157. function Read(var Data; Size: NativeUInt; var ProcessedSize: NativeUInt): HRESULT;
  158. function WakeMainAndWaitUntil(const AWakeEvent, AWaitEvent: THandle): HRESULT;
  159. procedure WorkerThreadProc;
  160. function Write(const Data; Size: NativeUInt; var ProcessedSize: NativeUInt): HRESULT;
  161. public
  162. constructor Create(const AEvents: PLZMACompressorSharedEvents); override;
  163. destructor Destroy; override;
  164. function GetExitHandle: THandle; override;
  165. procedure SetProps(const LZMA2: Boolean; const EncProps: TLZMAEncoderProps);
  166. override;
  167. procedure UnexpectedTerminationError; override;
  168. end;
  169. TLZMAWorkerProcess = class(TLZMACompressorCustomWorker)
  170. private
  171. FProcess: THandle;
  172. FSharedMapping: THandle;
  173. FCheckTrust: Boolean;
  174. FOnCheckedTrust: TProc<Boolean>;
  175. FExeFilename: String;
  176. public
  177. constructor Create(const AEvents: PLZMACompressorSharedEvents); override;
  178. destructor Destroy; override;
  179. function GetExitHandle: THandle; override;
  180. procedure SetProps(const LZMA2: Boolean; const EncProps: TLZMAEncoderProps);
  181. override;
  182. procedure UnexpectedTerminationError; override;
  183. property CheckTrust: Boolean read FCheckTrust write FCheckTrust;
  184. property OnCheckedTrust: TProc<Boolean> read FOnCheckedTrust write FOnCheckedTrust;
  185. property ExeFilename: String read FExeFilename write FExeFilename;
  186. end;
  187. PLZMASeqInStream = ^TLZMASeqInStream;
  188. TLZMASeqInStream = record
  189. Read: function(p: PLZMASeqInStream; var buf; var size: NativeUInt): TLZMASRes; stdcall;
  190. Instance: TLZMAWorkerThread;
  191. end;
  192. PLZMASeqOutStream = ^TLZMASeqOutStream;
  193. TLZMASeqOutStream = record
  194. Write: function(p: PLZMASeqOutStream; const buf; size: NativeUInt): NativeUInt; stdcall;
  195. Instance: TLZMAWorkerThread;
  196. end;
  197. PLZMACompressProgress = ^TLZMACompressProgress;
  198. TLZMACompressProgress = record
  199. Progress: function(p: PLZMACompressProgress; inSize, outSize: UInt64): TLZMASRes; stdcall;
  200. Instance: TLZMAWorkerThread;
  201. end;
  202. var
  203. LZMADLLInitialized: Boolean;
  204. LZMA_Init: function(LZMA2: BOOL; var handle: TLZMACompressorHandle): TLZMASRes;
  205. stdcall;
  206. LZMA_SetProps: function(handle: TLZMACompressorHandle;
  207. const encProps: TLZMAEncoderProps; encPropsSize: NativeUInt): TLZMASRes; stdcall;
  208. LZMA_Encode: function(handle: TLZMACompressorHandle;
  209. const inStream: TLZMASeqInStream; const outStream: TLZMASeqOutStream;
  210. const progress: TLZMACompressProgress): TLZMASRes; stdcall;
  211. LZMA_End: function(handle: TLZMACompressorHandle): TLZMASRes; stdcall;
  212. const
  213. { SRes (TLZMASRes) }
  214. SZ_OK = 0;
  215. SZ_ERROR_MEM = 2;
  216. SZ_ERROR_READ = 8;
  217. SZ_ERROR_PROGRESS = 10;
  218. SZ_ERROR_FAIL = 11;
  219. function LZMAInitCompressFunctions(Module: HMODULE): Boolean;
  220. begin
  221. LZMADLLInitialized := False;
  222. LZMA_Init := GetProcAddress(Module, 'LZMA_Init3');
  223. LZMA_SetProps := GetProcAddress(Module, 'LZMA_SetProps3');
  224. LZMA_Encode := GetProcAddress(Module, 'LZMA_Encode3');
  225. LZMA_End := GetProcAddress(Module, 'LZMA_End3');
  226. Result := Assigned(LZMA_Init) and Assigned(LZMA_SetProps) and
  227. Assigned(LZMA_Encode) and Assigned(LZMA_End);
  228. if Result then
  229. LZMADLLInitialized := True
  230. else begin
  231. LZMA_Init := nil;
  232. LZMA_SetProps := nil;
  233. LZMA_Encode := nil;
  234. LZMA_End := nil;
  235. end;
  236. end;
  237. procedure LZMAInternalError(const Msg: String);
  238. begin
  239. raise ECompressInternalError.Create('lzma: ' + Msg);
  240. end;
  241. procedure LZMAInternalErrorFmt(const Msg: String; const Args: array of const);
  242. begin
  243. LZMAInternalError(Format(Msg, Args));
  244. end;
  245. procedure LZMAWin32Error(const FunctionName: String);
  246. var
  247. LastError: DWORD;
  248. begin
  249. LastError := GetLastError;
  250. LZMAInternalErrorFmt('%s failed (%u)', [FunctionName, LastError]);
  251. end;
  252. function LZMAGetLevel(const Value: String; var Level: Integer): Boolean;
  253. begin
  254. Result := True;
  255. if CompareText(Value, 'fast') = 0 then
  256. Level := clLZMAFast
  257. else if CompareText(Value, 'normal') = 0 then
  258. Level := clLZMANormal
  259. else if CompareText(Value, 'max') = 0 then
  260. Level := clLZMAMax
  261. else if CompareText(Value, 'ultra') = 0 then
  262. Level := clLZMAUltra
  263. else if CompareText(Value, 'ultra64') = 0 then
  264. Level := clLZMAUltra64
  265. else
  266. Result := False;
  267. end;
  268. function LZMACreateEvent(const ManualReset: BOOL): TLZMAHandle32;
  269. begin
  270. Result := TLZMAHandle32(CreateEvent(nil, ManualReset, False, nil));
  271. if Result = 0 then
  272. LZMAWin32Error('CreateEvent');
  273. end;
  274. function LZMASeqInStreamReadWrapper(p: PLZMASeqInStream; var buf;
  275. var size: NativeUInt): TLZMASRes; stdcall;
  276. begin
  277. if p.Instance.Read(buf, size, size) = S_OK then
  278. Result := SZ_OK
  279. else
  280. Result := SZ_ERROR_READ;
  281. end;
  282. function LZMASeqOutStreamWriteWrapper(p: PLZMASeqOutStream; const buf;
  283. size: NativeUInt): NativeUInt; stdcall;
  284. begin
  285. if p.Instance.Write(buf, size, Result) <> S_OK then
  286. Result := 0;
  287. end;
  288. function LZMACompressProgressProgressWrapper(p: PLZMACompressProgress;
  289. inSize, outSize: UInt64): TLZMASRes; stdcall;
  290. begin
  291. if p.Instance.ProgressMade(inSize) = S_OK then
  292. Result := SZ_OK
  293. else
  294. Result := SZ_ERROR_PROGRESS;
  295. end;
  296. { TLZMACompressorRingBuffer:
  297. Designed to support concurrent, lock-free access by two threads in a
  298. pipe-like fashion: one thread may read from the buffer (FIFO) at the same
  299. time another thread is writing to it. Two threads, however, may NOT both
  300. read, or both write simultaneously. }
  301. procedure RingBufferReset(var Ring: TLZMACompressorRingBuffer);
  302. begin
  303. Ring.Count := 0;
  304. Ring.WriterOffset := 0;
  305. Ring.ReaderOffset := 0;
  306. end;
  307. function RingBufferInternalWriteOrRead(var Ring: TLZMACompressorRingBuffer;
  308. const AWrite: Boolean; var Offset: Integer; const Data: Pointer;
  309. Size: Integer): Integer;
  310. var
  311. P: ^Byte;
  312. Bytes: Integer;
  313. begin
  314. Result := 0;
  315. P := Data;
  316. while Size > 0 do begin
  317. if AWrite then
  318. Bytes := SizeOf(Ring.Buf) - Ring.Count
  319. else
  320. Bytes := Ring.Count;
  321. if Bytes = 0 then
  322. { Buffer is full (write) or empty (read) }
  323. Break;
  324. if Bytes > Size then
  325. Bytes := Size;
  326. if Bytes > SizeOf(Ring.Buf) - Offset then
  327. Bytes := SizeOf(Ring.Buf) - Offset;
  328. { On a weakly-ordered CPU, the read of Count above must happen before
  329. Buf content is read below (otherwise the content could be stale) }
  330. MemoryBarrier;
  331. if AWrite then begin
  332. Move(P^, Ring.Buf[Offset], Bytes);
  333. InterlockedExchangeAdd(Ring.Count, Bytes); { full barrier }
  334. end
  335. else begin
  336. Move(Ring.Buf[Offset], P^, Bytes);
  337. InterlockedExchangeAdd(Ring.Count, -Bytes); { full barrier }
  338. end;
  339. if Offset + Bytes = SizeOf(Ring.Buf) then
  340. Offset := 0
  341. else
  342. Inc(Offset, Bytes);
  343. Dec(Size, Bytes);
  344. Inc(Result, Bytes);
  345. Inc(P, Bytes);
  346. end;
  347. end;
  348. function RingBufferRead(var Ring: TLZMACompressorRingBuffer; var Buf;
  349. const Size: Integer): Integer;
  350. begin
  351. Result := RingBufferInternalWriteOrRead(Ring, False, Ring.ReaderOffset,
  352. @Buf, Size);
  353. end;
  354. function RingBufferWrite(var Ring: TLZMACompressorRingBuffer; const Buf;
  355. const Size: Integer): Integer;
  356. begin
  357. Result := RingBufferInternalWriteOrRead(Ring, True, Ring.WriterOffset,
  358. @Buf, Size);
  359. end;
  360. function RingBufferReadToCallback(var Ring: TLZMACompressorRingBuffer;
  361. const AWriteProc: TCompressorWriteProc; Size: Integer): Integer;
  362. var
  363. Bytes: Integer;
  364. begin
  365. Result := 0;
  366. while Size > 0 do begin
  367. Bytes := Ring.Count;
  368. if Bytes = 0 then
  369. Break;
  370. if Bytes > Size then
  371. Bytes := Size;
  372. if Bytes > SizeOf(Ring.Buf) - Ring.ReaderOffset then
  373. Bytes := SizeOf(Ring.Buf) - Ring.ReaderOffset;
  374. { On a weakly-ordered CPU, the read of Count above must happen before
  375. Buf content is read below (otherwise the content could be stale) }
  376. MemoryBarrier;
  377. AWriteProc(Ring.Buf[Ring.ReaderOffset], Cardinal(Bytes));
  378. InterlockedExchangeAdd(Ring.Count, -Bytes); { full barrier }
  379. if Ring.ReaderOffset + Bytes = SizeOf(Ring.Buf) then
  380. Ring.ReaderOffset := 0
  381. else
  382. Inc(Ring.ReaderOffset, Bytes);
  383. Dec(Size, Bytes);
  384. Inc(Result, Bytes);
  385. end;
  386. end;
  387. { TLZMACompressorProps }
  388. constructor TLZMACompressorProps.Create;
  389. begin
  390. inherited;
  391. Algorithm := -1;
  392. BTMode := -1;
  393. end;
  394. { TLZMACompressorCustomWorker }
  395. constructor TLZMACompressorCustomWorker.Create(const AEvents: PLZMACompressorSharedEvents);
  396. begin
  397. inherited Create;
  398. FEvents := AEvents;
  399. end;
  400. { TLZMAWorkerThread }
  401. function WorkerThreadFunc(Parameter: Pointer): Integer;
  402. begin
  403. const T = TLZMAWorkerThread(Parameter);
  404. try
  405. T.WorkerThreadProc;
  406. except
  407. const Ex = AcquireExceptionObject;
  408. MemoryBarrier;
  409. T.FSavedFatalException := Ex;
  410. end;
  411. { Be extra sure FSavedFatalException (and everything else) is made visible
  412. prior to thread termination. (Likely redundant, but you never know...) }
  413. MemoryBarrier;
  414. Result := 0;
  415. end;
  416. constructor TLZMAWorkerThread.Create(const AEvents: PLZMACompressorSharedEvents);
  417. begin
  418. inherited;
  419. FShared := VirtualAlloc(nil, SizeOf(FShared^), MEM_COMMIT, PAGE_READWRITE);
  420. if FShared = nil then
  421. OutOfMemoryError;
  422. end;
  423. destructor TLZMAWorkerThread.Destroy;
  424. begin
  425. if FThread <> 0 then begin
  426. SetEvent(FEvents.TerminateWorkerEvent);
  427. WaitForSingleObject(FThread, INFINITE);
  428. CloseHandle(FThread);
  429. FThread := 0;
  430. end;
  431. if Assigned(FLZMAHandle) then
  432. LZMA_End(FLZMAHandle);
  433. if Assigned(FShared) then
  434. VirtualFree(FShared, 0, MEM_RELEASE);
  435. FreeAndNil(FSavedFatalException);
  436. inherited;
  437. end;
  438. function TLZMAWorkerThread.GetExitHandle: THandle;
  439. begin
  440. Result := FThread;
  441. end;
  442. procedure TLZMAWorkerThread.SetProps(const LZMA2: Boolean;
  443. const EncProps: TLZMAEncoderProps);
  444. var
  445. Res: TLZMASRes;
  446. ThreadID: TThreadID;
  447. begin
  448. Res := LZMA_Init(LZMA2, FLZMAHandle);
  449. if Res = SZ_ERROR_MEM then
  450. OutOfMemoryError;
  451. if Res <> SZ_OK then
  452. LZMAInternalErrorFmt('LZMA_Init failed with code %d', [Res]);
  453. if LZMA_SetProps(FLZMAHandle, EncProps, SizeOf(EncProps)) <> SZ_OK then
  454. LZMAInternalError('LZMA_SetProps failed');
  455. FThread := BeginThread(nil, 0, WorkerThreadFunc, Self, 0, ThreadID);
  456. if FThread = 0 then
  457. LZMAWin32Error('BeginThread');
  458. end;
  459. procedure TLZMAWorkerThread.UnexpectedTerminationError;
  460. begin
  461. if Assigned(FSavedFatalException) then begin
  462. var Msg: String;
  463. if FSavedFatalException is Exception then
  464. Msg := (FSavedFatalException as Exception).Message
  465. else
  466. Msg := FSavedFatalException.ClassName;
  467. LZMAInternalErrorFmt('Worker thread terminated unexpectedly with exception: %s',
  468. [Msg]);
  469. end else
  470. LZMAInternalError('Worker thread terminated unexpectedly; no exception');
  471. end;
  472. procedure TLZMAWorkerThread.WorkerThreadProc;
  473. { Worker thread main procedure }
  474. var
  475. InStream: TLZMASeqInStream;
  476. OutStream: TLZMASeqOutStream;
  477. CompressProgress: TLZMACompressProgress;
  478. H: array[0..1] of THandle;
  479. begin
  480. InStream.Read := LZMASeqInStreamReadWrapper;
  481. InStream.Instance := Self;
  482. OutStream.Write := LZMASeqOutStreamWriteWrapper;
  483. OutStream.Instance := Self;
  484. CompressProgress.Progress := LZMACompressProgressProgressWrapper;
  485. CompressProgress.Instance := Self;
  486. H[0] := FEvents.TerminateWorkerEvent;
  487. H[1] := FEvents.StartEncodeEvent;
  488. while WaitForMultipleObjects(2, PWOHandleArray(@H), False, INFINITE) = WAIT_OBJECT_0 + 1 do begin
  489. FShared.EncodeResult := LZMA_Encode(FLZMAHandle, InStream, OutStream,
  490. CompressProgress);
  491. if not SetEvent(FEvents.WorkerEncodeFinishedEvent) then
  492. Break;
  493. end;
  494. end;
  495. function TLZMAWorkerThread.WakeMainAndWaitUntil(const AWakeEvent,
  496. AWaitEvent: THandle): HRESULT;
  497. var
  498. H: array[0..1] of THandle;
  499. begin
  500. if not SetEvent(AWakeEvent) then begin
  501. SetEvent(FEvents.TerminateWorkerEvent);
  502. Result := E_FAIL;
  503. Exit;
  504. end;
  505. H[0] := FEvents.TerminateWorkerEvent;
  506. H[1] := AWaitEvent;
  507. case WaitForMultipleObjects(2, PWOHandleArray(@H), False, INFINITE) of
  508. WAIT_OBJECT_0 + 0: Result := E_ABORT;
  509. WAIT_OBJECT_0 + 1: Result := S_OK;
  510. else
  511. SetEvent(FEvents.TerminateWorkerEvent);
  512. Result := E_FAIL;
  513. end;
  514. end;
  515. function TLZMAWorkerThread.CheckTerminateWorkerEvent: HRESULT;
  516. begin
  517. case WaitForSingleObject(FEvents.TerminateWorkerEvent, 0) of
  518. WAIT_OBJECT_0 + 0: Result := E_ABORT;
  519. WAIT_TIMEOUT: Result := S_OK;
  520. else
  521. SetEvent(FEvents.TerminateWorkerEvent);
  522. Result := E_FAIL;
  523. end;
  524. end;
  525. function TLZMAWorkerThread.FillBuffer(const AWrite: Boolean;
  526. const Data: Pointer; Size: NativeUInt; var ProcessedSize: NativeUInt): HRESULT;
  527. { Called from worker thread (or a thread spawned by the worker thread) }
  528. var
  529. P: ^Byte;
  530. Bytes: Integer;
  531. begin
  532. ProcessedSize := 0;
  533. P := Data;
  534. while Size <> 0 do begin
  535. var LimitedSize: Integer;
  536. if Size > Cardinal(High(Integer)) then
  537. LimitedSize := High(Integer)
  538. else
  539. LimitedSize := Integer(Size);
  540. if AWrite then
  541. Bytes := RingBufferWrite(FShared.OutputBuffer, P^, LimitedSize)
  542. else begin
  543. if FShared.NoMoreInput then begin
  544. { If NoMoreInput=True and *then* we see that the input buffer is
  545. empty (ordering matters!), we know that all input has been
  546. processed and that the input buffer will stay empty }
  547. MemoryBarrier;
  548. if FShared.InputBuffer.Count = 0 then
  549. Break;
  550. end;
  551. Bytes := RingBufferRead(FShared.InputBuffer, P^, LimitedSize);
  552. end;
  553. if Bytes = 0 then begin
  554. if AWrite then begin
  555. { Output buffer full; wait for the main thread to flush it }
  556. Result := WakeMainAndWaitUntil(FEvents.WorkerWaitingOnOutputEvent,
  557. FEvents.EndWaitOnOutputEvent);
  558. if Result <> S_OK then
  559. Exit;
  560. end
  561. else begin
  562. { Input buffer empty; wait for the main thread to fill it }
  563. Result := WakeMainAndWaitUntil(FEvents.WorkerWaitingOnInputEvent,
  564. FEvents.EndWaitOnInputEvent);
  565. if Result <> S_OK then
  566. Exit;
  567. end;
  568. end
  569. else begin
  570. Inc(ProcessedSize, Bytes);
  571. Dec(Size, Bytes);
  572. Inc(P, Bytes);
  573. end;
  574. end;
  575. Result := S_OK;
  576. end;
  577. function TLZMAWorkerThread.Read(var Data; Size: NativeUInt;
  578. var ProcessedSize: NativeUInt): HRESULT;
  579. { Called from worker thread (or a thread spawned by the worker thread) }
  580. begin
  581. { Sanity check: Make sure we're the only thread inside Read }
  582. if InterlockedExchange(FReadLock, 1) <> 0 then begin
  583. Result := E_FAIL;
  584. Exit;
  585. end;
  586. Result := FillBuffer(False, @Data, Size, ProcessedSize);
  587. InterlockedExchange(FReadLock, 0);
  588. end;
  589. function TLZMAWorkerThread.Write(const Data; Size: NativeUInt;
  590. var ProcessedSize: NativeUInt): HRESULT;
  591. { Called from worker thread (or a thread spawned by the worker thread) }
  592. begin
  593. { Sanity check: Make sure we're the only thread inside Write }
  594. if InterlockedExchange(FWriteLock, 1) <> 0 then begin
  595. Result := E_FAIL;
  596. Exit;
  597. end;
  598. Result := FillBuffer(True, @Data, Size, ProcessedSize);
  599. InterlockedExchange(FWriteLock, 0);
  600. end;
  601. function TLZMAWorkerThread.ProgressMade(const TotalBytesProcessed: UInt64): HRESULT;
  602. { Called from worker thread (or a thread spawned by the worker thread) }
  603. begin
  604. { Sanity check: Make sure we're the only thread inside Progress }
  605. if InterlockedExchange(FProgressLock, 1) <> 0 then begin
  606. Result := E_FAIL;
  607. Exit;
  608. end;
  609. { An Interlocked function is used to ensure the 64-bit value is written
  610. atomically (not with two separate 32-bit writes).
  611. TLZMACompressor will ignore negative values. LZMA SDK's 7zTypes.h says
  612. "-1 for size means unknown value", though I don't see any place
  613. where LzmaEnc actually does call Progress with inSize = -1. }
  614. InterlockedExchange64(FShared.ProgressBytes, Int64(TotalBytesProcessed));
  615. Result := CheckTerminateWorkerEvent;
  616. InterlockedExchange(FProgressLock, 0);
  617. end;
  618. { TLZMAWorkerProcess }
  619. constructor TLZMAWorkerProcess.Create(const AEvents: PLZMACompressorSharedEvents);
  620. begin
  621. inherited;
  622. FSharedMapping := CreateFileMapping(INVALID_HANDLE_VALUE, nil,
  623. PAGE_READWRITE, 0, SizeOf(FShared^), nil);
  624. if FSharedMapping = 0 then
  625. LZMAWin32Error('CreateFileMapping');
  626. FShared := MapViewOfFile(FSharedMapping, FILE_MAP_WRITE, 0, 0,
  627. SizeOf(FShared^));
  628. if FShared = nil then
  629. LZMAWin32Error('MapViewOfFile');
  630. end;
  631. destructor TLZMAWorkerProcess.Destroy;
  632. begin
  633. if FProcess <> 0 then begin
  634. SetEvent(FEvents.TerminateWorkerEvent);
  635. WaitForSingleObject(FProcess, INFINITE);
  636. CloseHandle(FProcess);
  637. FProcess := 0;
  638. end;
  639. if Assigned(FShared) then
  640. UnmapViewOfFile(FShared);
  641. if FSharedMapping <> 0 then
  642. CloseHandle(FSharedMapping);
  643. inherited;
  644. end;
  645. function TLZMAWorkerProcess.GetExitHandle: THandle;
  646. begin
  647. Result := FProcess;
  648. end;
  649. procedure TLZMAWorkerProcess.SetProps(const LZMA2: Boolean;
  650. const EncProps: TLZMAEncoderProps);
  651. function GetSystemDir: String;
  652. var
  653. Buf: array[0..MAX_PATH-1] of Char;
  654. begin
  655. GetSystemDirectory(Buf, SizeOf(Buf) div SizeOf(Buf[0]));
  656. Result := Buf;
  657. end;
  658. procedure DupeHandle(const SourceHandle: THandle; const DestProcess: THandle;
  659. var DestHandle: TLZMAHandle32; const DesiredAccess: DWORD); overload;
  660. begin
  661. var NativeDestHandle: THandle;
  662. if not DuplicateHandle(GetCurrentProcess, SourceHandle, DestProcess,
  663. @NativeDestHandle, DesiredAccess, False, 0) then
  664. LZMAWin32Error('DuplicateHandle');
  665. DestHandle := TLZMAHandle32(NativeDestHandle);
  666. end;
  667. procedure DupeEventHandles(const Src: TLZMACompressorSharedEvents;
  668. const Process: THandle; var Dest: TLZMACompressorSharedEvents);
  669. procedure DupeEvent(const SourceHandle: TLZMAHandle32; var DestHandle: TLZMAHandle32);
  670. begin
  671. DupeHandle(SourceHandle, Process, DestHandle, SYNCHRONIZE or
  672. EVENT_MODIFY_STATE);
  673. end;
  674. begin
  675. DupeEvent(Src.TerminateWorkerEvent, Dest.TerminateWorkerEvent);
  676. DupeEvent(Src.StartEncodeEvent, Dest.StartEncodeEvent);
  677. DupeEvent(Src.EndWaitOnInputEvent, Dest.EndWaitOnInputEvent);
  678. DupeEvent(Src.EndWaitOnOutputEvent, Dest.EndWaitOnOutputEvent);
  679. DupeEvent(Src.WorkerWaitingOnInputEvent, Dest.WorkerWaitingOnInputEvent);
  680. DupeEvent(Src.WorkerWaitingOnOutputEvent, Dest.WorkerWaitingOnOutputEvent);
  681. DupeEvent(Src.WorkerEncodeFinishedEvent, Dest.WorkerEncodeFinishedEvent);
  682. end;
  683. const
  684. InheritableSecurity: TSecurityAttributes = (
  685. nLength: SizeOf(InheritableSecurity); lpSecurityDescriptor: nil;
  686. bInheritHandle: True);
  687. var
  688. ProcessDataMapping: THandle;
  689. ProcessData: PLZMACompressorProcessData;
  690. StartupInfo: TStartupInfo;
  691. ProcessInfo: TProcessInformation;
  692. begin
  693. ProcessData := nil;
  694. ProcessDataMapping := CreateFileMapping(INVALID_HANDLE_VALUE,
  695. @InheritableSecurity, PAGE_READWRITE, 0, SizeOf(ProcessData^), nil);
  696. if ProcessDataMapping = 0 then
  697. LZMAWin32Error('CreateFileMapping');
  698. try
  699. ProcessData := MapViewOfFile(ProcessDataMapping, FILE_MAP_WRITE, 0, 0,
  700. SizeOf(ProcessData^));
  701. if ProcessData = nil then
  702. LZMAWin32Error('MapViewOfFile');
  703. ProcessData.StructSize := SizeOf(ProcessData^);
  704. ProcessData.LZMA2 := LZMA2;
  705. ProcessData.EncoderProps := EncProps;
  706. ProcessData.SharedDataStructSize := SizeOf(FShared^);
  707. FillChar(StartupInfo, SizeOf(StartupInfo), 0);
  708. StartupInfo.cb := SizeOf(StartupInfo);
  709. StartupInfo.dwFlags := STARTF_FORCEOFFFEEDBACK;
  710. var F: TFileStream := nil;
  711. if FCheckTrust then begin
  712. try
  713. F := CheckFileTrust(FExeFilename, [cftoKeepOpen]);
  714. except
  715. LZMAInternalError(Format(SCompilerCheckPrecompiledFileTrustError, [GetExceptMessage]));
  716. end;
  717. end;
  718. if Assigned(FOnCheckedTrust) then
  719. FOnCheckedTrust(FCheckTrust);
  720. try
  721. if not CreateProcess(PChar(FExeFilename),
  722. PChar(Format('islzma_exe %d 0x%x', [ISLZMA_EXE_VERSION, ProcessDataMapping])),
  723. nil, nil, True, CREATE_DEFAULT_ERROR_MODE or CREATE_SUSPENDED, nil,
  724. PChar(GetSystemDir), StartupInfo, ProcessInfo) then
  725. LZMAWin32Error('CreateProcess');
  726. finally
  727. F.Free;
  728. end;
  729. try
  730. { We duplicate the handles instead of using inheritable handles so that
  731. if something outside this unit calls CreateProcess() while compression
  732. is in progress, our handles won't be inadvertently passed on to that
  733. other process. }
  734. DupeHandle(GetCurrentProcess, ProcessInfo.hProcess,
  735. ProcessData.ParentProcess, SYNCHRONIZE);
  736. DupeHandle(FSharedMapping, ProcessInfo.hProcess,
  737. ProcessData.SharedDataMapping, FILE_MAP_WRITE);
  738. DupeEventHandles(FEvents^, ProcessInfo.hProcess, ProcessData.Events);
  739. if ResumeThread(ProcessInfo.hThread) = DWORD(-1) then
  740. LZMAWin32Error('ResumeThread');
  741. except
  742. CloseHandle(ProcessInfo.hThread);
  743. TerminateProcess(ProcessInfo.hProcess, 1);
  744. WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
  745. CloseHandle(ProcessInfo.hProcess);
  746. raise;
  747. end;
  748. FProcess := ProcessInfo.hProcess;
  749. CloseHandle(ProcessInfo.hThread);
  750. finally
  751. if Assigned(ProcessData) then
  752. UnmapViewOfFile(ProcessData);
  753. CloseHandle(ProcessDataMapping);
  754. end;
  755. end;
  756. procedure TLZMAWorkerProcess.UnexpectedTerminationError;
  757. var
  758. ProcessExitCode: DWORD;
  759. begin
  760. if GetExitCodeProcess(FProcess, ProcessExitCode) then
  761. LZMAInternalErrorFmt('Worker process terminated unexpectedly (0x%x)',
  762. [ProcessExitCode])
  763. else
  764. LZMAInternalError('Worker process terminated unexpectedly ' +
  765. '(failed to get exit code)');
  766. end;
  767. { TLZMACompressor }
  768. constructor TLZMACompressor.Create(AWriteProc: TCompressorWriteProc;
  769. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  770. ACompressorProps: TCompressorProps);
  771. begin
  772. inherited;
  773. FEvents.TerminateWorkerEvent := LZMACreateEvent(True); { manual reset }
  774. FEvents.StartEncodeEvent := LZMACreateEvent(False); { auto reset }
  775. FEvents.EndWaitOnInputEvent := LZMACreateEvent(False); { auto reset }
  776. FEvents.EndWaitOnOutputEvent := LZMACreateEvent(False); { auto reset }
  777. FEvents.WorkerWaitingOnInputEvent := LZMACreateEvent(True); { manual reset }
  778. FEvents.WorkerWaitingOnOutputEvent := LZMACreateEvent(True); { manual reset }
  779. FEvents.WorkerEncodeFinishedEvent := LZMACreateEvent(True); { manual reset }
  780. FProgressTimer := CreateWaitableTimer(nil, False, nil); { auto reset }
  781. if FProgressTimer = 0 then
  782. LZMAWin32Error('CreateWaitableTimer');
  783. InitializeProps(CompressionLevel, ACompressorProps);
  784. end;
  785. destructor TLZMACompressor.Destroy;
  786. procedure DestroyEvent(const AEvent: THandle);
  787. begin
  788. if AEvent <> 0 then
  789. CloseHandle(AEvent);
  790. end;
  791. begin
  792. FWorker.Free;
  793. DestroyEvent(FProgressTimer);
  794. DestroyEvent(FEvents.WorkerEncodeFinishedEvent);
  795. DestroyEvent(FEvents.WorkerWaitingOnOutputEvent);
  796. DestroyEvent(FEvents.WorkerWaitingOnInputEvent);
  797. DestroyEvent(FEvents.EndWaitOnOutputEvent);
  798. DestroyEvent(FEvents.EndWaitOnInputEvent);
  799. DestroyEvent(FEvents.StartEncodeEvent);
  800. DestroyEvent(FEvents.TerminateWorkerEvent);
  801. inherited;
  802. end;
  803. procedure TLZMACompressor.InitializeProps(const CompressionLevel: Integer;
  804. const ACompressorProps: TCompressorProps);
  805. const
  806. algorithm: array [clLZMAFast..clLZMAUltra64] of Integer = (0, 1, 1, 1, 1);
  807. dicSize: array [clLZMAFast..clLZMAUltra64] of Cardinal = (32 shl 10, 2 shl 20, 8 shl 20, 32 shl 20, 64 shl 20);
  808. numFastBytes: array [clLZMAFast..clLZMAUltra64] of Integer = (32, 32, 64, 64, 64);
  809. btMode: array [clLZMAFast..clLZMAUltra64] of Integer = (0, 1, 1, 1, 1);
  810. numHashBytes: array [Boolean] of Integer = (5, 4);
  811. var
  812. EncProps: TLZMAEncoderProps;
  813. Props: TLZMACompressorProps;
  814. begin
  815. if (CompressionLevel < Low(algorithm)) or (CompressionLevel > High(algorithm)) then
  816. LZMAInternalError('TLZMACompressor.Create got invalid CompressionLevel ' + IntToStr(CompressionLevel));
  817. FillChar(EncProps, SizeOf(EncProps), 0);
  818. EncProps.Algorithm := algorithm[CompressionLevel];
  819. EncProps.BTMode := btMode[CompressionLevel];
  820. EncProps.DictionarySize := dicSize[CompressionLevel];
  821. EncProps.NumBlockThreads := -1;
  822. EncProps.NumFastBytes := numFastBytes[CompressionLevel];
  823. EncProps.NumThreads := -1;
  824. var WorkerProcessCheckTrust := False;
  825. var WorkerProcessOnCheckedTrust: TProc<Boolean> := nil;
  826. var WorkerProcessFilename := '';
  827. if ACompressorProps is TLZMACompressorProps then begin
  828. Props := (ACompressorProps as TLZMACompressorProps);
  829. if Props.Algorithm <> -1 then
  830. EncProps.Algorithm := Props.Algorithm;
  831. EncProps.BlockSize := Props.BlockSize;
  832. if Props.BTMode <> -1 then
  833. EncProps.BTMode := Props.BTMode;
  834. if Props.DictionarySize <> 0 then
  835. EncProps.DictionarySize := Props.DictionarySize;
  836. if Props.NumBlockThreads <> 0 then
  837. EncProps.NumBlockThreads := Props.NumBlockThreads;
  838. if Props.NumFastBytes <> 0 then
  839. EncProps.NumFastBytes := Props.NumFastBytes;
  840. if Props.NumThreads <> 0 then
  841. EncProps.NumThreads := Props.NumThreads;
  842. EncProps.NumThreadGroups := Props.NumThreadGroups;
  843. WorkerProcessCheckTrust := Props.WorkerProcessCheckTrust;
  844. WorkerProcessOnCheckedTrust := Props.WorkerProcessOnCheckedTrust;
  845. WorkerProcessFilename := Props.WorkerProcessFilename;
  846. end;
  847. EncProps.NumHashBytes := numHashBytes[EncProps.BTMode = 1];
  848. if WorkerProcessFilename <> '' then begin
  849. const LZMAWorker = TLZMAWorkerProcess.Create(@FEvents);
  850. FWorker := LZMAWorker;
  851. LZMAWorker.CheckTrust := WorkerProcessCheckTrust;
  852. LZMAWorker.OnCheckedTrust := WorkerProcessOnCheckedTrust;
  853. LZMAWorker.ExeFilename := WorkerProcessFilename;
  854. end
  855. else begin
  856. if not LZMADLLInitialized then
  857. LZMAInternalError('LZMA DLL functions not initialized');
  858. FWorker := TLZMAWorkerThread.Create(@FEvents);
  859. end;
  860. FShared := FWorker.FShared;
  861. FWorker.SetProps(FUseLZMA2, EncProps);
  862. end;
  863. class function TLZMACompressor.IsObjectSignaled(const AObject: THandle): Boolean;
  864. begin
  865. Result := False;
  866. case WaitForSingleObject(AObject, 0) of
  867. WAIT_OBJECT_0: Result := True;
  868. WAIT_TIMEOUT: ;
  869. else
  870. LZMAInternalError('IsObjectSignaled: WaitForSingleObject failed');
  871. end;
  872. end;
  873. class procedure TLZMACompressor.SatisfyWorkerWait(const AWorkerEvent,
  874. AMainEvent: THandle);
  875. begin
  876. if IsObjectSignaled(AWorkerEvent) then begin
  877. if not ResetEvent(AWorkerEvent) then
  878. LZMAWin32Error('SatisfyWorkerWait: ResetEvent');
  879. if not SetEvent(AMainEvent) then
  880. LZMAWin32Error('SatisfyWorkerWait: SetEvent');
  881. end;
  882. end;
  883. procedure TLZMACompressor.SatisfyWorkerWaitOnInput;
  884. begin
  885. SatisfyWorkerWait(FEvents.WorkerWaitingOnInputEvent, FEvents.EndWaitOnInputEvent);
  886. end;
  887. procedure TLZMACompressor.SatisfyWorkerWaitOnOutput;
  888. begin
  889. SatisfyWorkerWait(FEvents.WorkerWaitingOnOutputEvent, FEvents.EndWaitOnOutputEvent);
  890. end;
  891. procedure TLZMACompressor.UpdateProgress;
  892. const
  893. MaxBytesPerProgressProcCall = 1 shl 30; { 1 GB }
  894. var
  895. NewProgressBytes, Bytes: Int64;
  896. LimitedBytes: Cardinal;
  897. begin
  898. { Check if the timer is signaled. Because it's an auto-reset timer, this
  899. also resets it to non-signaled. Note that WaitForWorkerEvent also waits
  900. on the timer and sets FProgressTimerSignaled. }
  901. if IsObjectSignaled(FProgressTimer) then
  902. FProgressTimerSignaled := True;
  903. if FProgressTimerSignaled then begin
  904. FProgressTimerSignaled := False;
  905. if Assigned(ProgressProc) then begin
  906. { An Interlocked function is used to ensure the 64-bit value is read
  907. atomically (not with two separate 32-bit reads). }
  908. NewProgressBytes := InterlockedExchangeAdd64(FShared.ProgressBytes, 0);
  909. { Make sure the new value isn't negative or going backwards. A call
  910. to ProgressProc is always made, even if the byte count is 0. }
  911. if NewProgressBytes > FLastProgressBytes then begin
  912. Bytes := NewProgressBytes - FLastProgressBytes;
  913. FLastProgressBytes := NewProgressBytes;
  914. end else
  915. Bytes := 0;
  916. repeat
  917. if Bytes >= MaxBytesPerProgressProcCall then
  918. LimitedBytes := MaxBytesPerProgressProcCall
  919. else
  920. LimitedBytes := Cardinal(Bytes);
  921. ProgressProc(LimitedBytes);
  922. Dec(Bytes, LimitedBytes);
  923. until Bytes = 0;
  924. end;
  925. end;
  926. end;
  927. procedure TLZMACompressor.FlushOutputBuffer(const OnlyOptimalSize: Boolean);
  928. const
  929. { Calling WriteProc may be an expensive operation, so we prefer to wait
  930. until we've accumulated a reasonable number of bytes before flushing }
  931. OptimalFlushSize = $10000; { can't exceed size of OutputBuffer.Buf }
  932. var
  933. Bytes: Integer;
  934. begin
  935. while True do begin
  936. Bytes := FShared.OutputBuffer.Count;
  937. if Bytes = 0 then
  938. Break;
  939. if Bytes > OptimalFlushSize then
  940. Bytes := OptimalFlushSize;
  941. if OnlyOptimalSize and (Bytes < OptimalFlushSize) then
  942. Break;
  943. RingBufferReadToCallback(FShared.OutputBuffer, WriteProc, Bytes);
  944. { Output buffer (partially?) flushed; unblock worker Write }
  945. SatisfyWorkerWaitOnOutput;
  946. end;
  947. { Must satisfy a waiting worker even if there was nothing to flush. (Needed
  948. to avoid deadlock in the event the main thread empties the output buffer
  949. after the worker's FillBuffer(AWrite=True) gets Bytes=0 but *before* it
  950. sets WorkerWaitingOnOutputEvent and waits on EndWaitOnOutputEvent.) }
  951. SatisfyWorkerWaitOnOutput;
  952. end;
  953. procedure TLZMACompressor.StartEncode;
  954. procedure StartProgressTimer;
  955. const
  956. { This interval was chosen because:
  957. - It's two system timer ticks, rounded up:
  958. (1000 / 64) * 2 = 31.25
  959. - The keyboard repeat rate is 30/s by default:
  960. 1000 / 30 = 33.333
  961. So if an edit control is focused and the ProgressProc is processing
  962. messages, the caret should move at full speed when an arrow key is
  963. held down. }
  964. Interval = 32;
  965. begin
  966. FProgressTimerSignaled := False;
  967. var DueTime := Int64(-10000) * Interval;
  968. if not SetWaitableTimer(FProgressTimer, DueTime, Interval, nil, nil, False) then
  969. LZMAWin32Error('SetWaitableTimer');
  970. end;
  971. begin
  972. if not FEncodeStarted then begin
  973. FShared.NoMoreInput := False;
  974. FShared.ProgressBytes := 0;
  975. FShared.EncodeResult := -1;
  976. RingBufferReset(FShared.InputBuffer);
  977. RingBufferReset(FShared.OutputBuffer);
  978. FLastInputWriteCount := 0;
  979. FLastProgressBytes := 0;
  980. FEncodeFinished := False;
  981. FEncodeStarted := True;
  982. if not ResetEvent(FEvents.WorkerEncodeFinishedEvent) then
  983. LZMAWin32Error('StartEncode: ResetEvent');
  984. StartProgressTimer;
  985. if not SetEvent(FEvents.StartEncodeEvent) then
  986. LZMAWin32Error('StartEncode: SetEvent');
  987. end;
  988. end;
  989. procedure TLZMACompressor.WaitForWorkerEvent;
  990. var
  991. H: array[0..4] of THandle;
  992. begin
  993. { Wait until the worker needs our attention. Separate, manual-reset events
  994. are used for progress/input/output because it allows us to see
  995. specifically what the worker is waiting for, which eases debugging and
  996. helps to avoid unnecessary wakeups.
  997. Note that the order of the handles in the array is significant: when more
  998. than one object is signaled, WaitForMultipleObjects returns the index of
  999. the array's first signaled object. The "worker unexpectedly terminated"
  1000. object must be at the front to ensure it takes precedence over the Worker*
  1001. events. }
  1002. H[0] := FWorker.GetExitHandle;
  1003. H[1] := FEvents.WorkerEncodeFinishedEvent;
  1004. H[2] := FProgressTimer;
  1005. H[3] := FEvents.WorkerWaitingOnInputEvent;
  1006. H[4] := FEvents.WorkerWaitingOnOutputEvent;
  1007. case WaitForMultipleObjects(5, PWOHandleArray(@H), False, INFINITE) of
  1008. WAIT_OBJECT_0 + 0: FWorker.UnexpectedTerminationError;
  1009. WAIT_OBJECT_0 + 1: FEncodeFinished := True;
  1010. WAIT_OBJECT_0 + 2: FProgressTimerSignaled := True;
  1011. WAIT_OBJECT_0 + 3,
  1012. WAIT_OBJECT_0 + 4: ;
  1013. else
  1014. LZMAInternalError('WaitForWorkerEvent: WaitForMultipleObjects failed');
  1015. end;
  1016. end;
  1017. procedure TLZMACompressor.DoCompress(const Buffer; Count: Cardinal);
  1018. var
  1019. P: ^Byte;
  1020. BytesWritten: Integer;
  1021. InputWriteCount: Cardinal;
  1022. begin
  1023. StartEncode;
  1024. if Count > Cardinal(High(Integer)) then { Because of the cast below }
  1025. LZMAInternalError('Compress: Unexpected Count value');
  1026. P := @Buffer;
  1027. while Count > 0 do begin
  1028. if FEncodeFinished then begin
  1029. if FShared.EncodeResult = SZ_ERROR_MEM then
  1030. OutOfMemoryError;
  1031. LZMAInternalErrorFmt('Compress: LZMA_Encode failed with code %d',
  1032. [FShared.EncodeResult]);
  1033. end;
  1034. UpdateProgress;
  1035. { Note that the progress updates that come in every ~100 ms also serve to
  1036. keep the output buffer flushed well before it fills up. }
  1037. FlushOutputBuffer(True);
  1038. BytesWritten := RingBufferWrite(FShared.InputBuffer, P^, Integer(Count)); { Also see check above }
  1039. if BytesWritten = 0 then begin
  1040. { Input buffer full; unblock worker Read }
  1041. SatisfyWorkerWaitOnInput;
  1042. { Wait until the worker wants more input, needs output to be flushed,
  1043. and/or has progress to report. All combinations are possible, so we
  1044. need to handle all three before waiting again. }
  1045. WaitForWorkerEvent;
  1046. end
  1047. else begin
  1048. Dec(Count, BytesWritten);
  1049. Inc(P, BytesWritten);
  1050. { Unblock the worker every 64 KB so it doesn't have to wait until the
  1051. entire input buffer is filled to begin/continue compressing. }
  1052. InputWriteCount := FLastInputWriteCount + Cardinal(BytesWritten);
  1053. if InputWriteCount shr 16 <> FLastInputWriteCount shr 16 then
  1054. SatisfyWorkerWaitOnInput;
  1055. FLastInputWriteCount := InputWriteCount;
  1056. end;
  1057. end;
  1058. end;
  1059. procedure TLZMACompressor.DoFinish;
  1060. begin
  1061. StartEncode;
  1062. { Ensure prior InputBuffer updates are made visible before setting
  1063. NoMoreInput. (This isn't actually needed right now because there's
  1064. already a full barrier inside RingBufferWrite. But that's an
  1065. implementation detail.) }
  1066. MemoryBarrier;
  1067. FShared.NoMoreInput := True;
  1068. while not FEncodeFinished do begin
  1069. SatisfyWorkerWaitOnInput;
  1070. UpdateProgress;
  1071. FlushOutputBuffer(True);
  1072. { Wait until the worker wants more input, needs output to be flushed,
  1073. and/or has progress to report. All combinations are possible, so we
  1074. need to handle all three before waiting again. }
  1075. WaitForWorkerEvent;
  1076. end;
  1077. { Flush any remaining output in optimally-sized blocks, then flush whatever
  1078. is left }
  1079. FlushOutputBuffer(True);
  1080. FlushOutputBuffer(False);
  1081. case FShared.EncodeResult of
  1082. SZ_OK: ;
  1083. SZ_ERROR_MEM: OutOfMemoryError;
  1084. else
  1085. LZMAInternalErrorFmt('Finish: LZMA_Encode failed with code %d',
  1086. [FShared.EncodeResult]);
  1087. end;
  1088. { Encoding was successful; verify that all input was consumed }
  1089. if FShared.InputBuffer.Count <> 0 then
  1090. LZMAInternalErrorFmt('Finish: Input buffer is not empty (%d)',
  1091. [FShared.InputBuffer.Count]);
  1092. FEncodeStarted := False;
  1093. if not CancelWaitableTimer(FProgressTimer) then
  1094. LZMAWin32Error('CancelWaitableTimer');
  1095. end;
  1096. { TLZMA2Compressor }
  1097. constructor TLZMA2Compressor.Create(AWriteProc: TCompressorWriteProc;
  1098. AProgressProc: TCompressorProgressProc; CompressionLevel: Integer;
  1099. ACompressorProps: TCompressorProps);
  1100. begin
  1101. FUseLZMA2 := True;
  1102. inherited;
  1103. end;
  1104. end.