Delphi Gary's Delphi Page

Contents


Components I have developed.

Tools for Delphi Programmers.

Patches to the VCL code.

Borland Delphi is a fine product. Someone at Borland really had their thinking cap on when they designed the Object Pascal language and the Integrated Development Environment. I can find nothing wrong with these parts of the product. The Visual Component Library (VCL) is adequate but does contain a number of design flaws, limitations, and bugs. I estimate that at least 75% of the over 300 hours of time I devoted to developing Columbine Bookmark Merge was time spent debugging the VCL. The purpose of this page is to convey some of the improvements I've developed and collected for the VCL. Hopefully, this will save other programmers much time. This page applies to Delphi 1.02 (16-bit) only. Some of the material below may apply to the 32-bit version as well, but I don't normally program with Delphi 2.

You can contact Gary Cramblitt via e-mail at garyc@clark.net I'd like to hear from you.


Components I have developed

License to freely use these components is hereby granted.

  • WwwDde.PAS - A component for using DDE to control the NetScape or Microsoft Internet Explorer web browsers. See the source code for important notes about which versions of the web browsers it work's with. This unit also fixes a defect in the Borland TDdeClientConv.RequestData method. newThis component was improved 3/2/97 to prevent hanging in the CaptureBrowserDoc function.

  • NETBIOS.PAS - A unit for communicating over networks using the NetBIOS protocol. Here's some sample code that uses this unit to obtain the MAC (hardware) ethernet address of a network interface card:

    function GetNICMACAddress: string;
    { Uses the NetBIOS Adapter_Status function to retrieve
      the network interface card's permanent station
      (MAC) address. }
    var
      NCB:            NetworkControlBlock;
      AdapterStatus:  Adapter_Status_Buffer;
      J:              integer;
    begin
      Result := '';
      SetNCBDefaults(NCB);
      NCB.Num := 1;  { physical card }
      NCB.Cmd := Cmd_Adapter_Status;
      NCB.Buffer := Addr(AdapterStatus);
      NCB.Len := SizeOf(AdapterStatus);
      SetAdapterName(NCB.CallName,'*              ',0);
      CallNetBIOS(NCB);
      if NCB.RetCode = RetCode_Success then
        Result := Result +
          HexByte(AdapterStatus.Ethernet_Address[1]) +
          HexByte(AdapterStatus.Ethernet_Address[2]) +
          '.' +
          HexByte(AdapterStatus.Ethernet_Address[3]) +
          HexByte(AdapterStatus.Ethernet_Address[4]) +
          '.' +
          HexByte(AdapterStatus.Ethernet_Address[5]) +
          HexByte(AdapterStatus.Ethernet_Address[6]);
      if Result = '' then Result := 'NOTAVAIL';
    end;

  • BIOS.PAS - A unit for accessing memory in the BIOS of your machine. For example, you can extract the BIOS DATE using this code. This code ONLY works for 16-bit operating systems; not Win95 or WinNT. Contains highly machine-specific constants, but the technique should work on any machine.

  • new ResWatch.PAS - A component that automatically watches Windows resources (GDI, User, and global memory). When resources drop to preset thresholds, warns user to save their work. Only works under 16-bit Windows. Just drop this component on your main form, set the threshold properties and you're done. If someone can provide sample code for measuring Windows Dos memory, we'd be grateful.

  • new Coming Soon -- F32Ctrl.PAS -- a set of components and utility routines that permit Delphi 16-bit programs to support long file and directory names when running under Windows 95 or Windows NT. Includes the Explorer-like file browsing component featured in Columbine Bookmark Merge.

Invaluable Tools for Delphi Programmers

  • WinMiser Pro - This amazing program will detect and automatically fix memory and resource leaks in any Windows 3.1 or 3.11 program. It should be part of every programmer's workbench. All of the resource leak patches listed below were discovered with it.

Bug Fixes - Patches to the Delphi 1.02 VCL

Warning: Borland never distributed the source code for 1.02. The latest code they distributed was for 1.01. Applying patches to this source code can introduce bugs that were fixed in 1.02. Caveat Emptor. One way to get around this is to obtain the source code for Delphi 2 and incorporate any fixes from it as well as those listed below. But you should be very careful and selective when doing this.

  • OUTLINE.PAS - This is one of the more buggy VCL components. This file contains the following fixes:
    • Numerous fixes extracted from the Delphi 2 source code.
    • OwnerDraw fixes per Craig Ward with modifications by Gary Cramblitt. Here's Craig's info:

      I've heard that there is a bug in TOutline Component's OnDrawItem Event for OwnerDraw TOulines. so I made an effort to fix it ! and here it is all for you ! NOW you can create ownerdraw outlines as many as you like ! (I hope) i also made few improvements: new properties :

      • TopRow  - just like grid toprow property - controls the top displayed row.
      • VisibleRowCount - again just like grid , counts the visible items on screen.
      • TopItem : longint - controls the top shown item ! diferent from toprow by the fact that it takes care of invisble items by the fact that setting it to an unvisible item will cause the outline to show on top the first visible parent.

  • GRIDS.PAS - Patches as documented by Stefan Hoffmeister, fixes problem in drawing the outline when the outline is resized. Here's Stefan's fix:

    implementation

    uses WinProcs, Consts;

    {$IFDEF FixHorzScrollbarBug}
    const
      Unit_GlobalBool_PatchFocus : boolean = false;
    {$ENDIF}

    procedure TCustomGrid.ModifyScrollBar(ScrollBar, ScrollCode, Pos: Word);

      { LOCAL PROCEDURE CUT OUT }

    begin
      if CanFocus and TabStop and not (csDesigning in ComponentState)
         {$IFDEF FixHorzScrollbarBug} and not Unit_GlobalBool_PatchFocus {$ENDIF} then
        SetFocus;

      {$IFDEF FixHorzScrollbarBug}
        Unit_GlobalBool_PatchFocus := false;
      {$ENDIF}

      if (ScrollBar = SB_HORZ) and (ColCount = 1) then
      begin
        ModifyPixelScrollBar(ScrollCode, Pos);
        Exit;
      end;

      .........
    end;

    procedure TCustomGrid.UpdateScrollPos;
    var
      DrawInfo: TGridDrawInfo;
      MaxTopLeft: TGridCoord;

      procedure SetScroll(Code: Word; Value: Integer);
      var
        NewOffset,
        OldOffset : integer;
        R: TGridRect;
      begin
        if GetScrollPos(Handle, Code) <> Value then
        begin
          SetScrollPos(Handle, Code, Value, True);

          {$IFDEF FixHorzScrollbarBug}
            if IsWindowVisible(Handle) then
            begin
              if Value < 0 then
                Value := 0;

              Unit_GlobalBool_PatchFocus := true;
              {

                this global variable is needed to prevent "SetFocus" from being
                called in the course of "ModifyScrollBar", see above

                It is initalized const := false and only set here to true.

                As soon as the "Unit_GlobalBool_PatchFocus" has been queried and "SetFocus"
                has been passed it is immediately set back to := false

                It would have been MUCH nicer if, instead of using "Unit_GlobalBool_PatchFocus",
                one had used a private class variable, e.g. FPatchFocus (which would actually
                have worked as well), but then the object would have changed in the INTERFACE
                part and thus all dependant units would have to be recompiled.

                Thus in favour of a plug-and-play replacement solution the rather unelegant way
                was chosen.

                If you don't like it - just move the global variable to the private section,
                it automatically gets initialized with the correct value (=false)

              }

              ModifyScrollBar(Code, SB_THUMBPOSITION, Value);
            end;
          {$ENDIF}

  • DBGRIDS.PAS - There is a font leak in Delphi 1.02, unit DBGRIDS.PAS. Here's the fix if you have the VCL code. The bug is in the TCustomDBGrid object and affects all components that inherit from it, including TDBLookupCombo, TDBLookupList, and TDBGrid.

    destructor TCustomDBGrid.Destroy;
    begin
      FDataLink.Free;
      FDataLink := nil;
      FIndicators.Free;

      { 12/22/96: Fix GDI (font) leak. grc }
      FTitleFont.Free;  { Add this line for the fix. }

      inherited Destroy;
      ReleaseBitmap;
    end;

  • DBLOOKUP.PAS - There is a GDI resource leak in Delphi 1.02 DBLOOKUP.PAS. The leak only occurs if the style is csDropDown. If you have the VCL source code, here is the fix.

    destructor TDBLookupCombo.Destroy;
    begin
      FFieldLink.OnDataChange := nil;
      FFieldLink.Free;
      FFieldLink := nil;

      { Following line plugs a GDI resource leak. Gary Cramblitt. }
      FCanvas.Free;

      inherited Destroy;
    end;

  • MENUS.PAS - There is a menu resource leak in MENUS.PAS. Borland's code looks like this:

    destructor TMenuItem.Destroy;
    begin
      if FParent <> nil then
      begin
        FParent.Remove(Self);
        FParent := nil;
      end;
      if FHandle <> 0 then
      begin
        MergeWith(nil);
        DestroyMenu(FHandle);
        ClearHandles;
      end;
      while Count > 0 do Items[0].Free;
      FItems.Free;
      DisposeStr(FCaption);
      if FCommand <> 0 then CommandPool[FCommand] := False;
      inherited Destroy;
    end;

    procedure TMenuItem.ClearHandles;

      procedure Clear(Item: TMenuItem);
      var
        I: Integer;
      begin
        with Item do
        begin
          FHandle := 0;
          for I := 0 to GetCount - 1 do Clear(FItems[I]);
        end;
      end;

    begin
      Clear(Self);
    end;

    The fix for the Menu resource leak is to remove the ClearHandles method call from Destroy. What I don't understand now is why did Borland even write that code? The

        while Count > 0 do Items[0].Free;


    loop that follows the ClearHandles call will destroy all the Menu objects that ClearHandles is clearing. So why do that?  Even stranger, if I move the ClearHandles to below the while loop, the leak is not fixed, i.e., like this

      if FHandle <> 0 then
      begin
        MergeWith(nil);
        DestroyMenu(FHandle);
      end;
      while Count > 0 do Items[0].Free;
      ClearHandles;

    What is ClearHandles doing?  Does it have something to do with menu merging?

  • GRAPHICS.PAS There is a bug in Delphi 1.0 that causes a memory leak when using the TImage component with bitmaps that have a size which is a multiple of 64KB (e.g 768 X 512 with 256 colors). Delphi 2.0 does not have the same bug. The solution was provided by Ray Lischner of Tempest Software, the Author of Secrets of Delphi 2 Edit Graphics.pas. Look for all the uses of MemAlloc. Change the FreeMem calls that free the MemAlloc-ed memory to use MemFree:

    procedure MemFree(Ptr: Pointer; Size: LongInt);
    begin
      if Size > $FFFF then
        GlobalFreePtr(Ptr)
      else
        FreeMem(Ptr, Word(Size))
    end;

  • DDEMAN.PAS - Fix to TDdeServerItem.CopyToClipboard per Brad Choate of Choate Consulting,.

    procedure TDdeServerItem.CopyToClipboard;
    var
      Data: THandle;
      LinkData: string;
      DataPtr: Pointer;
    begin
      if FServerConv <> nil then
        LinkData := ddeMgr.AppName + #0 + FServerConv.Name + #0 + Name
      else if (Owner =nil) then
        Exit
      else if Owner is TForm then
        LinkData := ddeMgr.AppName + #0 + TForm(Owner).Caption + #0 + Name;

      try
        Clipboard.Open; { 12/28/96. Fix per Brad Choate. grc }
        Clipboard.AsText := Text;
        Data := GlobalAlloc(GMEM_MOVEABLE, Length(LinkData) + 1);
        try
          DataPtr := GlobalLock(Data);
          try
            { 12/28/96 Following line change was suggested by Brad Choate,
            but will not compile. ?? grc
            Move(PChar(LinkData)^, DataPtr^, Length(LinkData) + 1);
            }
            StrPCopy(DataPtr, LinkData);

            Clipboard.SetAsHandle(DdeMgr.LinkClipFmt, Data);
          finally
            GlobalUnlock(Data);
          end;
        except
          GlobalFree(Data);
          raise;
        end;
      finally
        Clipboard.Close;
      end;
    end;


Please visit our Columbine Bookmark Merge page to learn about a free web browser accessory, written in Delphi 1.02, which you can download.


(c) Copyright 1997 by Gary Cramblitt and Columbine Enterprises.
Last update 3/2/97.