Compression.LZMACompressor.pas 40 KB

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