Compression.LZMACompressor.pas 39 KB

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