home *** CD-ROM | disk | FTP | other *** search
/ Power-Programmierung / CD1.mdf / pascal / library / dos / overlay / swaplog.pas < prev   
Encoding:
Pascal/Delphi Source File  |  1994-03-04  |  31.0 KB  |  855 lines

  1. unit swaplog;
  2. { original unit SWAPLOG, written by Tom Field - 76247,3024 as of 30 Aug 91 }
  3. { current unit SWAPLOG, written by Mark Reichert - 72763,2417 as of 13 Dec 93 }
  4. { if you have any questions, PLEASE send me a e-mail letter or leave a
  5.   message directed to me in the Borland Pascal forum }
  6.  
  7. { This unit intercepts overlay load operations and prints a log of each
  8.   overlay load.  It is useful in studying the overlay loading in a
  9.   program when trying to eliminate thrashing.
  10.  
  11.   The unit must find a _current_ .MAP file (produced by TPC /GS) in the
  12.   executable directory. If during swapping, a segment is requested that
  13.   was not in the map file, the segment address is returned, preceded by
  14.   a question mark.
  15.  
  16.   The unit is not as self initializing as the one written by Tom Field.
  17.   You should put it in your mainline's uses list after the "overlay"
  18.   unit is used.  Actually, the saving of the BP OverReadFunc and its
  19.   replacement with the one here must be done after the OvrInit and if
  20.   necessary, the OvrInitEMS, wherever they are called.  This is
  21.   necessary because the filling of the OverReadFunc address location
  22.   with the address of the native function is done in OvrInit and
  23.   redone in OvrInitEMS.  Now, the call of the InitSwap function MUST
  24.   be done after any OvrSetBuf because OvrSetBuf needs the heap to be
  25.   EMPTY when it tries to setup the conventional memory overlay buffer.
  26.  
  27.   (* <<<<< SIMPLE EXAMPLE >>>>> *)
  28.   (* The following is the simplest example of the use of the unit, it uses
  29.   default information and does no error checking, no EMS, no increase in
  30.   overlay buffer size: *)
  31.   Uses Swaplog, Overlay;
  32.  
  33.   (* try to initialize the overlay manager and units where the executable
  34.      is named TOVERLAY.EXE and the overlay file TOVERLAY.OVR *)
  35.   OvrInit('TOVERLAY.OVR');
  36.  
  37.   (* Use InitSwap function to set up the TCollection Object and store
  38.      all the information from the MAP file into it..  Here it is doing
  39.      the initialization with 40 units to start and a 10 unit increase
  40.      whenever the new limit is reached (i.e. 40, 50, 60).  If InitSwap
  41.      was successful, we will save the BP OverReadFunc and substitute
  42.      our own.  The saved procedure will be called by ours to do the
  43.      actual overlay work. *)
  44.   If InitSwap(40, 10) Then
  45.     begin
  46.       SwapLog.SaveOvrRead := OverLay.OvrReadBuf;
  47.       OVERLAY.OvrReadBuf  := SwapLog.SwapOverRead;
  48.     End;
  49.  
  50.   (* <<<<< Main body or loop of program goes here >>>>> *)
  51.  
  52.   (* Write out the overlayed segments sorted by LoadCount.  This can
  53.      be left off if no sorted listing is needed. *)
  54.   WriteSortedSegmentsToLog(OvrSegLoadCount);
  55.  
  56.  
  57.   (* <<<<< ANOTHER EXAMPLE >>>>> *)
  58.   The following is a more complete example of how the setup was done when
  59.   the unit was tested in the TVDEMO program in \BP\EXAMPLES\DOS\TVDEMO.
  60.   I wanted to test a the unit in a full program.  I made an overlayed
  61.   version of this program first, rather than using the program written to
  62.   demo the use of overlays and resources, TVRDEMO, because I didn't want
  63.   the complication of resources.  By the way, use of this unit has taught
  64.   me something about TV programs at least and probably event driven programs
  65.   in general.  The lesson is that, EMS memory or not, the overlay buffer
  66.   needs to be large enough to hold the three or four largest and/or
  67.   frequently called units or the enormous amount of thrashing will really
  68.   slow down the program.
  69.   The code below can be replace the equivalent code in a copy of TVDEMO.
  70.  
  71.   Uses Swaplog, Overlay;
  72.  
  73. (* This procedure allows the switch to be done and redone more easily *)
  74. Procedure SaveAReadBuf;
  75. Begin
  76.   (* If GoodInitSwap is true, then the initialization and filling of
  77.      the TCollection storage object was successful and we can save the
  78.      BP OverReadFunc and substitute our own.  The saved procedure will
  79.      be called by ours to do the actual overlay work. *)
  80.   If SwapLog.GoodInitSwap Then
  81.     begin
  82.       SwapLog.SaveOvrRead := OverLay.OvrReadBuf;
  83.       OVERLAY.OvrReadBuf  := SwapLog.SwapOverRead;
  84.     end;
  85. End;
  86.  
  87. (* If an EMPTY string is fed to this procedure, and is returned still
  88.    empty, then OvrResult needs to be reexamined *)
  89. Procedure SetErrorStr(Var ErrorStr : String);
  90. Begin
  91.    Case OvrResult Of
  92.      ovrError       : ErrorStr := 'General Overlay Manager error.';
  93.      ovrNotFound    : ErrorStr := 'No OVR file not found in EXE dir.';
  94.      ovrNoMemory    : ErrorStr := 'Not enough memory for overlay buffer.';
  95.      ovrIOError     : ErrorStr := 'General Overlay file I/O Error.';
  96.      ovrNoEMSDriver : ErrorStr := 'No EMS Driver (EMM386, QEMM, etc) installed.';
  97.      ovrNoEMSMemory : ErrorStr := 'Insufficient EMS memory available';
  98.      Else             ErrorStr := '';
  99.    End;
  100. End;
  101.  
  102. var
  103.   (* original program variables *)
  104.   Demo: TTVDemo;
  105.   EXEName: PathStr;
  106.   Dir: DirStr;
  107.   Name: NameStr;
  108.   Ext: ExtStr;
  109.  
  110.   UsingEMS : Boolean;
  111.   TempStr  : String;
  112.  
  113. begin
  114.   (* try to find the correct path and name for the overlay file *)
  115.   (* the TVDEMO here should be a copy of the one in the example
  116.      code unless you want to make the change permanent *)
  117.   if Lo(DosVersion) >= 3 then EXEName := ParamStr(0)
  118.   else EXEName := FSearch('TVDEMO.EXE', GetEnv('PATH'));
  119.   FSplit(EXEName, Dir, Name, Ext);
  120.   if Dir[Length(Dir)] = '\' then Dec(Dir[0]);
  121.   EXENAME := FSearch('TVDEMO.OVR', Dir);
  122.  
  123.   (* try to initialize the overlay manager and units *)
  124.   OvrInit(EXEName);
  125.   if OvrResult <> ovrOk then
  126.   begin
  127.     SetErrorStr(TempStr);
  128.     If TempStr <> '' Then
  129.       PrintStr(TempStr+#13#10);
  130.     Halt(1);
  131.   end
  132.   Else
  133.     Begin
  134.       (* Since OvrSetBuf only affects the conventional memory overlay
  135.          buffer, it can be done before OverInitEMS *)
  136.       OvrSetBuf(48 * 1024);
  137.  
  138.       (* open the overlay log file *)
  139.       OpenOverLogFile('OVERLOG.FIL');
  140.  
  141.       (* Set when you want the procedure FlushLog to act -
  142.            NoFlush - has no effect, write to file done when buffer fills
  143.            FlushToDos - flushes OverLog file variable buffer to DOS buffers
  144.            FlushToDisk - flushes OverLog file variable buffer to disk file *)
  145.       SetTypeOfFlush(FlushToDisk);
  146.  
  147.      (* Use InitSwap function to set up the TCollection Object and store
  148.         all the information from the MAP file into it..  Here it is doing
  149.         the initialization with 40 units to start and a 10 unit increase
  150.         whenever the new limit is reached (i.e. 40, 50, 60).  We then
  151.         store whether InitSwap was successful for use in SaveAReadBuf. *)
  152.       GoodInitSwap := InitSwap(40, 10);
  153.  
  154.       SwapLogWrite('Did OvrInit and OvrSetBuf');
  155.       Str(OvrGetBuf:0, TempStr);
  156.       SwapLogWrite('BuffSize = ' + TempStr );
  157.  
  158.       (* Save the BP OverReadFunc and substitute our own *)
  159.       SaveAReadBuf;
  160.     End;
  161.   UsingEMS := False;
  162.   SwapLogWrite('Doing OvrInitEMS');
  163.   (* try to overlay units to EMS memory and redirect manager there
  164.      when units need to be swapped into and out of the overlay buffer *)
  165.   OvrInitEMS;
  166.   If OvrResult = OvrOk Then
  167.     UsingEMS := True
  168.   Else
  169.     Begin
  170.      (* if there is an error, just report it.  Conventional overlay
  171.         management will still go on, so don't Halt the program *)
  172.       SetErrorStr(TempStr);
  173.       If TempStr <> '' Then
  174.         SwapLogWrite(TempStr);
  175.     End;
  176.  
  177.   If UsingEMS Then
  178.     Begin
  179.       SaveAReadBuf;
  180.       SwapLogWrite('Using Expanded')
  181.     End
  182.   Else
  183.     SwapLogWrite('Using Conventional');
  184.  
  185.   Demo.Init;
  186.   Demo.Run;
  187.   Demo.Done;
  188.  
  189.   (* Write out the overlayed segments sorted by LoadCount *)
  190.   WriteSortedSegmentsToLog(OvrSegLoadCount);
  191. }
  192.  
  193. interface
  194. Uses
  195.    Dos,
  196.    Overlay;
  197.  
  198. Type
  199.   { For TSegmentItem Record }
  200.   string8      = string[8];
  201.  
  202.   { Flags for controlling how the text log file will be written }
  203.   FlushType    = (NoFlush, FlushToDos, FlushToDisk);
  204.  
  205.   { Flags for controlling what sort is done in WriteSortedSegmentsToLog }
  206.   SortType      = (OvrSegNo, OvrSegName, OvrSegLoadCount);
  207.  
  208.   { Record that will be the item controlled by TSegmentCollection Object }
  209.   { made global in hopes that will aid typecasts for debugging purposes }
  210.   PSegmentItem = ^TSegmentItem;
  211.   TSegmentItem = record
  212.     SegNo : Word;
  213.     SegName   : String8;
  214.     LoadCount : LongInt;
  215.   end;
  216.  
  217. Var
  218.   { store the BP OvrReadFunc here }
  219.   SaveOvrRead  : OVERLAY.OvrReadFunc;
  220.  
  221.   { tells the calling program that a successful initialization of the
  222.     TCollection object that will store the unit names has occured }
  223.   GoodInitSwap : Boolean;
  224.  
  225.   { Function to be called after a OvrSetBuf is done because OvrSetBuf needs the
  226.     heap to be empty before it runs }
  227.   Function InitSwap(ALimit, ADelta: Integer) : boolean;
  228.  
  229.   { function to replace BP's OvrReadFunc }
  230.   Function SwapOverRead( OvrSeg : Word): integer; far;
  231.  
  232.   { Procedure to allow user to write messages to the log file }
  233.   Procedure SwapLogWrite(InStr : String);
  234.  
  235. { Procedure to allow user to set when the log disk file is actually written to }
  236.   Procedure SetTypeOfFlush(InFlushType : FlushType);
  237.  
  238. { Seperating Log File Opening out of InitSwap allows a SwapLogWrite before OvrSetBuf }
  239.   Procedure OpenOverLogFile(InName : PathStr);
  240.  
  241. { Procedure to allow Writing Sorted List of Segments and Counts at any point of
  242.   program;  Order is reset to SegNo at end of this procedure so that later lookups
  243.   will work. }
  244. Procedure WriteSortedSegmentsToLog(SortChoice : SortType);
  245.  
  246. implementation
  247.  
  248. uses
  249.      Objects,     { To inherit from TSortedCollection Object }
  250.      IOChek;      { has functions with internal I/O Checking, also in Library }
  251.                   { This unit is in Dos Programming in the BP CompuServe Library }
  252. type
  253.   string4       = string[4];
  254.   string19      = String[19];
  255.  
  256.   TSortFunc = function(P1, P2: PSegmentItem): Integer;
  257.  
  258.   PSegmentCollection = ^TSegmentCollection;
  259.   TSegmentCollection = object(TSortedCollection)
  260.     Procedure SetLimit(ALimit: Integer); virtual;
  261.     Function Compare(Key1, Key2: Pointer): Integer; virtual;
  262.     Procedure FreeItem(Item : Pointer); virtual;
  263.     Procedure ReOrder;
  264.   end;
  265.  
  266. Function SortBySegNo(P1, P2: PSegmentItem): Integer; far; assembler;
  267. asm
  268.   les di, P1      { load first pointer }
  269.   mov ax, es:[di] { Put word value at ES:DI (SegNo) into AX }
  270.   les di, P2      { load second pointer }
  271.   sub ax, es:[di] { compare SegNo values }
  272.   jz @end         { 0 is the return value for P1^.SegNo = P2^.SegNo }
  273.   rcr al, 1       { rotate CF=>sign bit for AL, CF=1 =>Neg AL, CF=0 =>Pos AL }
  274.   or al, 1        { make sure that AL <> 0 }
  275.   cbw             { Convert Byte to Word => make signed AX = signed AL }
  276. @end:
  277. End;
  278.  
  279. { Most of the code here was borrowed from the StrCollection Compare
  280.   in the Objects unit }
  281. Function SortBySegName(P1, P2: PSegmentItem): Integer; far; assembler;
  282. asm
  283.    PUSH    DS
  284.    CLD                   { string operations in forward mode }
  285.    LDS     SI,P1
  286.    ADD     SI,OFFSET TSEGMENTITEM.SEGNAME  { point DS:SI to P1^.SegName }
  287.    LES     DI,P2
  288.    ADD     DI,OFFSET TSEGMENTITEM.SEGNAME  { point ES:DI to P2^.SegName }
  289.    LODSB               { put P1^.SegName length byte in AL and inc SI past it }
  290.    MOV     AH,ES:[DI]
  291.    INC     DI          { put P2^.SegName length byte in AH and inc DI past it }
  292.    MOV     CL,AL       { this and the next 3 lines do the following }
  293.    CMP     CL,AH
  294.    JBE     @@1         { CL = Min(Length(P1^.SegName), Length(P2^.SegName) }
  295.    MOV     CL,AH
  296. @@1:    XOR     CH,CH  { make CX = CL }
  297.    REP     CMPSB       { compare until unequal chars found or end of shorter }
  298.    JE      @@2          { if one is substring of other, compare lengths }
  299.    MOV     AL,DS:[SI-1] { otherwise REP inc'd past unequal chars so put }
  300.    MOV     AH,ES:[DI-1] { them in AL and AH, so that subtraction will make }
  301. @@2:    SUB     AL,AH   { AX < 0 if P1^.SegName < P2^.SegName }
  302.    SBB     AH,AH        { and AX > 0 if P1^.SegName > P2^.SegName }
  303.    POP     DS
  304. end;
  305.  
  306. Function SortByLoadCount(P1, P2: PSegmentItem): Integer; far; assembler;
  307. asm
  308.   push ds
  309.   lds si, P1      { load first pointer }
  310.   add si, offset TSEGMENTITEM.LOADCOUNT { point DS:SI to P1^.LOADCOUNT }
  311.   les di, P2      { load second pointer }
  312.   add di, offset TSEGMENTITEM.LOADCOUNT { point ES:DI to P2^.LOADCOUNT }
  313.   mov ax, [si+2]    { Put high word value at DS:SI into AX }
  314.   sub ax, es:[di+2] { compare high word values of P1^ and P2^ LoadCount }
  315.   jnz @end          { If high words not equal, AX properly <0 or >0 }
  316.                     { 0 < Hi word < MaxInt, so no RCR needed as it is below }
  317.   mov ax, [si]      { Put low word value at DS:SI into AX }
  318.   sub ax, es:[di]   { compare low word values of P1^ and P2^ LoadCount }
  319.   jz @end         { 0 is the return value for P1^.LoadCount = P2^.LoadCount }
  320.   rcr al, 1       { rotate CF=>sign bit for AL, CF=1 =>Neg AL, CF=0 =>Pos AL }
  321.   or al, 1        { make sure that AL <> 0 }
  322.   cbw             { Convert Byte to Word => make signed AX = signed AL }
  323. @end:
  324.   pop ds
  325. End;
  326.  
  327. var
  328.   { When the object is relatively small and will stay within the unit, no need
  329.     to add another layer of redirection by using the Pointer to the object }
  330.   SegmentDB: TSegmentCollection;
  331.  
  332.   { holds the sort requested by the WriteSortedSegmentsToLog Procedure }
  333.   SortUsed : SortType;
  334.  
  335. const Sorts : array[SortType] of TSortFunc =
  336.                            (SortBySegNo, SortBySegName, SortByLoadCount);
  337.       SortsStr : array[SortType] of String19 =
  338.                            ('Segment Number', 'Segment Name', 'Segment Load Count');
  339.  
  340. procedure TSegmentCollection.SetLimit(ALimit: Integer);
  341. begin
  342.   inherited SetLimit(ALimit);
  343.   { NIL all pointers after the active ones - with a zero-indexed array,
  344.     the COUNTth item is the one after the last active element }
  345.   { good for debugging and using Assigned to avoid using invalid pointers }
  346.   { If Starting and Count = 0, then the whole array is initialized }
  347.    If Limit > Count Then
  348.      FillChar(Items^[Count], (Limit - Count) * SizeOf(Pointer), 0);
  349. end;
  350.  
  351. { Build of Collection and Lookups are done by Segment Number }
  352. function TSegmentCollection.Compare(Key1, Key2: Pointer): Integer;
  353. begin
  354.   Compare := SortBySegNo(Key1, Key2);
  355. end;
  356.  
  357. { Due to the FillChar in Descendant SetLimit, the Assigned should prevent
  358.   the Disposing of any Invalid pointers }
  359. procedure TSegmentCollection.FreeItem(Item : Pointer);
  360. begin
  361.   If Assigned(Item) Then
  362.     Dispose(PSegmentItem(Item));
  363. end;
  364.  
  365. { In the example program off of which I patterned this sort, Compare was used
  366.   directly, but that overburdened it so that the Lookups would have taken much
  367.   longer, maybe slowing the program down }
  368.  
  369. Function SortCompare(Key1, Key2: Pointer): Integer;
  370. var Result   : Integer;
  371.     SortIndx : SortType;
  372. Begin
  373.   { at the top of the array Key2 would be nil }
  374.   if Key2 = nil then
  375.     begin
  376.       SortCompare := 0;
  377.       Exit;
  378.     end;
  379.   { Do the Selected Sort }
  380.   Result := Sorts[SortUsed](Key1, Key2);
  381.  
  382.   { if the sort is by LoadCount then it should be descending to
  383.     ease the sighting of the most frequently used units,
  384.     so reverse the Result variable to make a descending sort }
  385.   if SortUsed = OvrSegLoadCount Then
  386.     If Result <> 0 then
  387.       Result := Result * -1
  388.     Else
  389.       { units CANNOT have the same name or segment mapping number so the
  390.         Result will NOT be 0;  LoadCounts can be the same so get
  391.         alphabetical name order in that case }
  392.       Result := Sorts[OvrSegName](Key1, Key2);
  393.  
  394.   SortCompare := Result;
  395. End;
  396.  
  397. procedure TSegmentCollection.ReOrder;
  398.  
  399.   { This does a Quicksort, which divides the items into those lesser and
  400.     greater to "x", and then uses recursion to do the same with to each
  401.     subsequently smaller divided area until reaching indivisible single items}
  402.   procedure Sort(l, r: Integer);
  403.   var
  404.     i, j: Integer;
  405.     x, p: Pointer;
  406.   begin
  407.     repeat
  408.       i := l; j := r;
  409.       x := KeyOf(Items^[(l + r) div 2]);
  410.       repeat
  411.         while SortCompare(KeyOf(Items^[i]), x) < 0 do Inc(i);
  412.         while SortCompare(x, KeyOf(Items^[j])) < 0 do Dec(j);
  413.         if i <= j then
  414.         begin
  415.             if i < j then
  416.               begin
  417.                 p := Items^[i];
  418.                 Items^[i] := Items^[j];
  419.                 Items^[j] := p;
  420.               end;
  421.             Inc(i); Dec(j);
  422.         end;
  423.       until i > j;
  424.       if l < j then Sort(l, j);
  425.       l := i;
  426.     until l >= r;
  427.   end;
  428.  
  429. begin
  430.   if Count > 1 then Sort(0, Count - 1);
  431. end;
  432.  
  433. Procedure WriteSortedSegmentsToLog(SortChoice : SortType);
  434. Var I : Integer;
  435.     P : PSegmentItem;
  436.     LCStr : String8;
  437. Begin
  438.   { ReOrder uses this Unit Variable SortUsed }
  439.   SortUsed := SortChoice;
  440.   { The normal order is by SegNo }
  441.   If SortUsed <> OvrSegNo Then
  442.     SegmentDB.Reorder;
  443.   SwapLogWrite('');
  444.   SwapLogWrite('Overlay Segments And LoadCounts Sorted With Primary Key = ' +
  445.                         SortsStr[SortUsed]);
  446.   { the Items Array accessed by At is zero based, from 0 to Count - 1 }
  447.   For I := 0 to Pred(SegmentDB.Count) do
  448.     Begin
  449.       { Get the Ith PSegmentItem Pointer }
  450.       P := SegmentDB.At(I);
  451.       { We only want to list the units that are overlayed;
  452.         The initialization of the Collection does a Lookup immediately after
  453.         inserting a PSegmentItem in to make sure it was a valid Insert,
  454.         which makes LoadCount = 1 before the actual work begins }
  455.       With P^ do
  456.         Begin
  457.           If LoadCount > 1 Then
  458.             Begin
  459.               Str(LoadCount:0, LCStr);
  460.               SwapLogWrite(SegName + ' : ' + LCStr);
  461.             End;
  462.         End;
  463.     End;
  464.   If SortUsed <> OvrSegNo Then
  465.     Begin
  466.       { Reorder by SegNo so that further overlay logging can be done }
  467.       SortUsed := OvrSegNo;
  468.       SegmentDB.Reorder;
  469.     End;
  470. End;
  471.  
  472. function NameSegment(Const SegRec : TSegmentItem) : Boolean;
  473. var
  474.   P: PSegmentItem;
  475. begin
  476.   NameSegment := False;
  477.   New(P);
  478.   If Assigned(P) Then
  479.     Begin
  480.       NameSegment := True;
  481.       P^ := SegRec;
  482.       SegmentDB.Insert(P);
  483.     End;
  484. end;
  485.  
  486. Type
  487.   FlushLogFunc = Function(Var TextFile : Text) : Integer;
  488.  
  489. Var
  490.   OpenedLogFile    : Boolean;
  491.   OverLogName      : PathStr;
  492.   OverLog          : text; { text file, not printer }
  493.   OldExitProc      : Pointer;
  494.   OverLogFlushFunc : FlushLogFunc;
  495.   EXEname          : NameStr;
  496.   EXEDir           : DirStr;
  497.  
  498. Function FlushLog : Integer;
  499. Begin
  500.   FlushLog := 0;
  501.   { If no forced flushes are to be done, OverLogFlushFunc = Nil }
  502.   If Assigned(OverLogFlushFunc) Then
  503.     FlushLog := OverLogFlushFunc(OverLog);
  504. End;
  505.  
  506.  
  507. { This 58 byte function for getting string with current system date, is
  508.   only incrementally faster than an equivalent Pascal Function but it
  509.   is much smaller }
  510. Function Date : Strg12;  assembler;
  511. asm
  512.    cld
  513.    les di, @Result    { get address of output string }
  514.    mov ah, 2Ah
  515.    int 21h            { get system time thru DOS function }
  516.  
  517.    mov ax, cx         { get YEAR result in CX }
  518.  
  519.    mov bx, (100 shl 8) + '/'     { set BH = 100, BL = '/' }
  520.    div bh             { divide AX by 100, get quotient and remainder }
  521.  
  522.    mov bh, al         { save quotient (century) in BL }
  523.    mov al, 0          { set AL to no seperator, remainder already in AH }
  524.    push ax
  525.    push bx            { BX already set }
  526.    mov bh, dl         { get DAY result in DL }
  527.    push bx
  528.    mov dl, 10         { put length byte = 10 in DL, MONTH already in DH }
  529.    push dx
  530.  
  531.    mov si, 3030h      { set up SI for ADDs }
  532.    mov bl, 10         { set up BL for DIVs and MODs }
  533.    mov cx, 4          { four trips thru loop }
  534. @TopOfLoop:
  535.    pop ax             { pop something to work on off the stack }
  536.    xor dx, dx         { setup to make AX = AL, DX = AH }
  537.    xchg ah, dl        { makes DX = AH = days, months, years, or century }
  538.    cmp al, 0          { there will be no seperator between yrs and century }
  539.    jz @nosep
  540.    stosb              { store length byte or seperator }
  541. @nosep :
  542.    xchg ax, dx        { get days, months, years, or century }
  543.    div bl             { divide AX by 10, get quotient and remainder }
  544.    add ax, si         { add 3030h to quotient, remainder into char equivalent }
  545.    stosw              { store quotient and remainder in output }
  546.    loop @TopOfLoop
  547. end;
  548.  
  549. { This 49 byte function for getting string with current system time, is
  550.   only incrementally faster than an equivalent Pascal Function but it
  551.   is much smaller }
  552. Function Time : Strg12;  assembler;
  553. asm
  554.    cld
  555.    mov ah, 2Ch
  556.    int 21h            { get system time thru DOS function }
  557.    les di, @Result    { get address of output string }
  558.  
  559.    mov al, '.'        { set AL to '.' seperator }
  560.    mov ah, dl         { get HUNDREDTHS of SECOND result in DL }
  561.    push ax
  562.    mov dl, ':'        { set DL to ':' seperator, SECOND result in DH }
  563.    push dx
  564.    mov dh, cl         { get MINUTE result in CL }
  565.    push dx
  566.    mov cl, 11         { put fixed length byte of 11 in CL, HOUR is in CH }
  567.    push cx
  568.    mov si, 3030h      { set up SI for ADD }
  569.    mov bl, 10         { set up BL to make DIV do a decimal partitioning }
  570.    mov cx, 4          { four trips thru loop }
  571. @TopOfLoop:
  572.    pop ax             { pop something to work on off the stack }
  573.    xor dx, dx         { setup to make AX = AL, DX = AH }
  574.    xchg ah, dl        { makes DX = AH = 100ths, secs, mins or hours }
  575.    stosb              { store length byte or seperator }
  576.    xchg ax, dx        { get hundredths, seconds, minutes or hours }
  577.    div bl             { divide AX by 10, get quotient and remainder }
  578.    add ax, si         { add 3030h to quotient, remainder into char equivalent }
  579.    stosw              { store quotient and remainder in output }
  580.    loop @TopOfLoop
  581. end;
  582.  
  583. function ByteToHex(BB : byte) : string ; assembler ;
  584. asm
  585.   les di, @Result     { get address of output string }
  586.   mov al, 2
  587.   cld
  588.   stosb               { this string will always be 2 chars long }
  589.   mov al, BB          { get number }
  590.   mov dl, al          { save it in DL for later use }
  591.   shr al, 1
  592.   shr al, 1
  593.   shr al, 1
  594.   shr al, 1           { divide AL by 16 to get value of high char }
  595.   add al, 55          { translate to ord of equivalent char }
  596.   cmp al, 64
  597.   ja @1               { if AL was 10 to 15, skip additional step }
  598.   sub al, 7           { if AL was  0 to  9, must sub 7 to get '0' to '9' }
  599.  @1:
  600.   stosb               { store in first char spot }
  601.   mov al, dl          { restore AL to original value }
  602.   and al, 15          { wipe out high char }
  603.   add al, 55          { translate to ord of equivalent char }
  604.   cmp al, 64
  605.   ja @2               { if AL was 10 to 15, skip additional step }
  606.   sub al, 7           { if AL was  0 to  9, must sub 7 to get '0' to '9' }
  607.  @2:
  608.   stosb               { store in second char spot }
  609. end ; { ByteToHex }
  610.  
  611. Procedure OverExitProc; far;
  612. Begin
  613.   ExitProc := OldExitProc;
  614.   { Since after initialization, the Log File can be written to at any overlay
  615.     swap, we must keep the file open, and force it to be closed only on exit }
  616.   If OpenedLogFile Then
  617.     Begin
  618.       writeln(OverLog, 'Closed ' + OverLogName);
  619.       IO_CloseText(OverLog);
  620.     End;
  621. End;
  622.  
  623. { Returns the name of the segment at SegRec.SegNo in SegRec.SegName, or false }
  624. Function LookUp(Var SegRec : TSegmentItem) : boolean;
  625. var PSegItem : PSegmentItem;
  626.     I : Integer;
  627. begin
  628.   Lookup := False;
  629.   { Search in Items Array for Item with SegRec.SegNo, Return I, the index }
  630.   if SegmentDB.Search(@SegRec, I) then
  631.     Begin
  632.       { Get the Pointer to the Ith item in Items }
  633.       PSegItem := SegmentDB.At(I);
  634.       { Increment LoadCount to track how many times this unit is loaded }
  635.       Inc(PSegItem^.LoadCount);
  636.       { Return the info in SegRec to be printed }
  637.       SegRec := PSegItem^;
  638.       Lookup := True;
  639.     End
  640.   else
  641.   begin
  642.     { If the Search was unsuccessful, return the Segment Number as the name }
  643.     With SegRec do
  644.       Begin
  645.         SegName := '?' + ByteToHex(Hi(SegNo)) + ByteToHex(Lo(SegNo));
  646.         LoadCount := 0;
  647.       End;
  648.   end;
  649. end; { LookUp }
  650.  
  651. Procedure SwapLogWrite(InStr : String);
  652. Begin
  653.   { If the Write was Successful, attempt a Flush from the Overlog Buffer }
  654.   If IO_WritelnTextStr(OverLog, InStr) = 0 Then
  655.     FlushLog;
  656. End;
  657.  
  658. Function InitSwap(ALimit, ADelta: Integer) : boolean;
  659. { reads the program's map into a StringDict }
  660. var
  661.   hex_addr      : string4;    { eg 4C97     }
  662.   SegRec        : TSegmentItem;   { eg 0, OPSTRING, 0 }
  663.   InSeg, SegLine,
  664.   Stop, NotEmpty : Boolean;
  665.   ErrCode       : Integer;
  666.   mem           : longint;
  667.   map_file      : text;      { progname.map }
  668.   fname         : Dos.PathStr;  { filename }
  669.   fext          : Dos.ExtStr;
  670.   map_file_line : string;
  671. begin
  672.   InitSwap := False;
  673.   { This procedure will report the heap memory taken by the Collection }
  674.   mem := memavail;
  675.   { We need an open Log File to have place to which to write messages.
  676.     Since the programmer may just not have called the procedure, we will
  677.     try to force a default open. If still unsuccessful (due to some I/O
  678.     error, we must stop.}
  679.   If Not OpenedLogFile Then
  680.     Begin
  681.       OpenOverLogFile('');
  682.       If Not OpenedLogFile Then
  683.         Begin
  684.           Writeln('Could not open log file ' + OverLogName + '.');
  685.           Writeln('No logging will be done.');
  686.           Exit;
  687.         End;
  688.     End;
  689.   { report when this log was done }
  690.   SwapLogWrite('Opened ' + OverLogName + ' on ' + Date + ' at ' + Time);
  691.  
  692.   { do the actual init of the TCollection object which will store the segment
  693.     numbers of the units and the associated names. If unsuccessful, this will
  694.     leaves us with no way of accomplishing our task. }
  695.   If Not segmentDB.Init(ALimit, ADelta) then
  696.     Begin
  697.       SwapLogWrite('Unable to initialize object to do segment mapping.');
  698.       Exit;
  699.     End;
  700.  
  701.   { EXEDir and EXEName are set in the LogFile Open; If we can't open the
  702.     map, we have no way of associating Segment numbers to unit names }
  703.   fname := EXEDir + EXEName + '.MAP';
  704.   ErrCode := IO_OpenText(fname, map_file, resetfile);
  705.   if ErrCode <> 0 then
  706.     Begin
  707.       SwapLogWrite('Unable to open map file: ' + fname);
  708.       Exit;
  709.     End;
  710.  
  711.   SwapLogWrite('Loading: ' + fname);
  712.   InSeg := False;
  713.   Stop := False;
  714.   SegLine := False;
  715.   NotEmpty := False;
  716.   while (not eof(map_file)) and (ErrCode = 0) and (Not Stop) do
  717.     begin
  718.       ErrCode := IO_ReadlnTextStr(map_file, map_file_line);
  719.       If ErrCode = 0 then
  720.         Begin
  721.           { Is the line a Valid Segment Map area line? }
  722.           SegLine := (length(map_file_line) >= 40) and (map_file_line[7] = 'H');
  723.           { Is code, or just types and constants, from the unit used? }
  724.           NotEmpty := copy(map_file_line,16,5) <> '00000';
  725.           { Until we hit a SegLine, we are not in the SegArea }
  726.           If Not InSeg Then
  727.             Begin
  728.               If SegLine Then
  729.                 InSeg := True;
  730.             End;
  731.  
  732.           If InSeg Then
  733.             If SegLine Then
  734.               Begin
  735.                 if NotEmpty Then
  736.                   begin
  737.                     { get the Hex Address String of the Unit }
  738.                     hex_addr := copy(map_file_line, 2,  4); { eg '4C97'     }
  739.                     With SegRec do
  740.                       Begin
  741.                         { Hex numbers need to be flagged by use of the '$' }
  742.                         Val('$' + Hex_Addr, SegNo, ErrCode);
  743.                         { get the unit name }
  744.                         SegName := copy(map_file_line, 23, 8); { eg 'OPSTRING' }
  745.                         { Setting up a string for latter use }
  746.                         fname := 'Lookup tested Okay for ' + SegName + ': LC = ';
  747.                         LoadCount := 0;
  748.                         SwapLogWrite('Adding ' + hex_addr +  ' ' + SegName);
  749.                       End;
  750.  
  751.                     { put the information in SegRec into the Collection }
  752.                     If Not NameSegment(SegRec) then
  753.                       Begin
  754.                         SwapLogWrite('Failed in Add when adding ' + SegRec.SegName);
  755.                         IO_CloseText(map_file);
  756.                         Exit;
  757.                       End
  758.                     Else
  759.                       { If NameSegment successful, do a lookup to make sure it
  760.                         was completely successful }
  761.                       If LookUp(SegRec) then
  762.                         begin
  763.                           Str(SegRec.LoadCount:0, EXEname);
  764.                           SwapLogWrite(fname + EXEName);
  765.                         End
  766.                       Else
  767.                         SwapLogWrite('Lookup did not test Okay for ' + SegRec.SegName);
  768.                   end;
  769.               End
  770.             Else
  771.               { allowing blank lines to get in but anything else will stop the read }
  772.               If map_file_line <> '' Then
  773.                 Stop := True;
  774.         End;
  775.   End;
  776.  
  777.   { This will show how much heap is being used by the Collection }
  778.   Str(mem - memavail:0, EXEname);
  779.   SwapLogWrite('Memory used by load= ' + EXEName);
  780.  
  781.   If ErrCode = 0 Then
  782.     ErrCode := IO_CloseText(map_file);
  783.   If ErrCode = 0 Then
  784.     InitSwap := True;
  785. end; { LoadList }
  786.  
  787. { The address of this replaces that of the native BP function, so that
  788.   the lookup and write to the log can take place before SaveOvrRead calls
  789.   the native function to do that actual overlay swap }
  790. Function SwapOverRead( OvrSeg : Word): integer;
  791. var
  792.   tempseg  : word;
  793.   hex_seg  : string4;
  794.   CountStr : String8;
  795.   SegRec   : TSegmentItem;
  796. begin
  797. (* In a program, the PrefixSeg variable contains the selector
  798.    (segment address) of the Program Segment Prefix (PSP)
  799.    created by DOS and Windows when the application was
  800.    executed. *)
  801.   SegRec.SegNo := OvrSeg - PrefixSeg - $10;
  802.   { If Lookup successful, write the unit SegName and the LoadCount }
  803.   if LookUp(SegRec) then
  804.     begin
  805.       With SegRec do
  806.         Begin
  807.           Str(LoadCount:0, CountStr);
  808.           SwapLogWrite(SegName + ' : ' + CountStr);
  809.         end;
  810.     End
  811.   Else
  812.     { If Lookup unsuccessful, write SegName which now contains the
  813.       Address as a HexStr }
  814.     SwapLogWrite(SegRec.SegName);
  815.   { Call SaveOvrRead to do the overlay swap }
  816.   SwapOverRead := SaveOvrRead(OvrSeg);
  817. end; { MyOverRead }
  818.  
  819. Procedure SetTypeOfFlush(InFlushType : FlushType);
  820. Begin
  821.   { If InFlushType = NoFlush, OverLogFlushFunc = Nil }
  822.   OverLogFlushFunc := Nil;
  823.   Case InFlushType Of
  824.     FlushToDos  : OverLogFlushFunc := IO_FlushToDos;
  825.     FlushToDisk : OverLogFlushFunc := IO_FlushToDisk;
  826.   End;
  827. End;
  828.  
  829. Procedure OpenOverLogFile(InName : PathStr);
  830. Var FEXT : EXTStr;
  831.     FDir : DirStr;
  832. Begin
  833.   { Parse to get the log file directory and name }
  834.   fsplit(InName, FDir, EXEName, FEXT);
  835.   { If no name given, default to OVERLOG.FIL }
  836.   If EXEName = '' Then
  837.     InName := 'OVERLOG.FIL';
  838.   { Parse to get the executable directory and log name }
  839.   fsplit(ParamStr(0), EXEDir, EXEName, FEXT);
  840.   { If no log directory given, default to executable directory }
  841.   If FDir = '' Then
  842.     FDir := EXEDir;
  843.   { Set the unit variable to allow writing the file name to the file }
  844.   OverLogName := FDir + InName;
  845.   { open the file and set the boolean flag accordingly }
  846.   OpenedLogFile := IO_OpenText(OverLogName, OverLog, RewriteFile) = 0;
  847. End;
  848.  
  849. begin
  850.   OldExitProc := ExitProc;
  851.   ExitProc := @OverExitProc;
  852.   GoodInitSwap := False;
  853.   OverLogFlushFunc := IO_FlushToDisk;
  854. end.
  855.