The annoying problem of the growing GExperts menu

 Delphi, GExperts  Comments Off on The annoying problem of the growing GExperts menu
Dec 252019
 

As I add new functionality to GExperts the menu it displays grows larger and larger. On small monitors (there are still computers with e.g. 1024×600 pixels screen size in use, usually not with the latest Delphi version though) this means that the menu has to be broken into chunks, each with a “more” entry at the end to display the next chunk.
While this has been implemented for years (decades actually) it didn’t work very well. For Delphi 6 the code was completely broken (I might have been the culprit myself) it still had some bugs with all later versions.

No more. Today I fixed those bugs and tested it with screens as small as 640×480 pixels.

Also, when moving the GExperts menu into the Tools menu, there was a problem that the maximum number of entries for a chunk was not calculated correctly which would mean that the “more” entry could end up outside the visible area. This has also been fixed.

Unfortunately this does not solve the underlying problem: The menu keeps growing. There are now a maximum of 45 + 3 Entries in that menu. The way that menu is created on demand, depending on which experts are enabled or not, does not lend itself to a sensible menu structure.

I have added a “Category” property to each expert, which so far is neither filled nor used anywhere. I plan to create only one entry per Category in the GExperts menu with sub menus for each category. Maybe I will even make those categories configurable. But that’s for another day.

 Posted by on 2019-12-25 at 18:34

Updating to Windows 10 broke Delphi 6 and 2007 again

 Delphi, Windows, Windows 10  Comments Off on Updating to Windows 10 broke Delphi 6 and 2007 again
Dec 212019
 

Since Microsoft will end the free support for Windows 7 in January 2020, we are updating all our computers to Windows 10 (I would really have liked to avoid that. Windows 7 is definitely not the best Windows ever but its annoyances are known. Windows 10 started to annoy me with new so called “features” immediately after the installation finished. But hey, that’s what you get when you make a living developing software for this stinking pile of sh*t. sorry excuse for an operating system.)

Anyway: As before, when I updated from Windows 8 to Windows 8.1, the Windows 10 update broke my Delphi 6 and 2007 installations. Fortunately my workarounds / fixes for Windows 8.1 also work for Windows 10. Also fortunately I blogged about them

so I could look them up.

 Posted by on 2019-12-21 at 15:07

Accessing bitmap pixels with less ScanLine[] calls in Delphi

 Delphi  Comments Off on Accessing bitmap pixels with less ScanLine[] calls in Delphi
Dec 122019
 

As you will find in the documentation and on the web the usual way to access the pixels of a Bitmap in Delphi is using the Scanline[] array property. Something like this:

type
  TRgbTriple = packed record
    // do not change the order of the fields, do not add any fields
    Blue: Byte;
    Green: Byte;
    Red: Byte;
  end;

  TRgbTripleArray = packed array[0..MaxInt div SizeOf(TRgbTriple) - 1] of TRgbTriple;
  PRgbTripleArray = ^TRgbTripleArray;

procedure ConvertBmp(_InBmp, _OutBmp: TBitmap);
var
  x, y: Integer;
  Pixel: TRgbTriple;
begin
  Assert(_InBmp.PixelFormat = pf24bit);
  _OutBmp.SetSize(_InBmp.Width, _InBmp.Height);
  _OutBmp.PixelFormat := pf24bit;
  for y := 0 to Height - 1 do begin
    for x := 0 to Width - 1 do begin
      Pixel := PRgbTripleArray(_InBmp.Scanline[y])^[x];
      doSomething(Pixel);
      PRgbTripleArray(_OutBmp.Scanline[y])^[x] := Pixel;
    end;
  end;
end;

This code first checks that the input bitmap is using 24 bits per pixel, then sets the output bitmap to do the same. Then it enumerates through all the lines in the input bitmap and then all the pixels in that line, reads them does “something” with them and finally writes them to the corresponding pixel in the output bitmap.

Let’s assume a small VGA sized bitmap, so we get 480 lines with 640 pixels each. In total that makes 2 * 640 * 480 calls to ScanLine[], each taking a short time (There are multiple function calls in the getter method.).

Call that code for several bitmaps and you will end up with a huge amount of time spent in the calls to ScanLine[]. (Don’t just take my word for it, go ahead and time it!)

The first optimization that can be done is calling ScanLine[] only once for each line of each bitmap:

var
  InScanLine: PRgbTripleArray;
  OutScanLine: PRgbTripleArray;
// ...
  for y := 0 to Height - 1 do begin
    InScanLine := PRgbTripleArray(_InBmp.Scanline[y]);
    OutScanline := PRgbTripleArray(_OutBmp.Scanline[y]);
    for x := 0 to Width - 1 do begin
      Pixel := InScanLine^[x];
      doSomething(Pixel);
      OutScanLine^[x] := Pixel;
    end;
  end;

This reduces the number of calls to ScanLine by a factor of 640, which you will find is quite significant. (Again: Time it!)

But we still can do more. What if we only needed 4 calls in total rather than 2 * 480 ?

All we need to do is calculating the line addresses ourself. To do that we simply need two addresses, the one of the first and the one of the second line:

// if you are using Delphi 2007 or older you need to correct the NativeInt declaration from 8 bytes to 4 bytes:
{$IF SizeOf(Pointer) = 4}
type
  NativeInt = Integer;
{$IFEND}

function AddToPtr(const _Ptr: Pointer; _Offset: NativeInt): Pointer; inline;
begin
  Result := Pointer(NativeInt(_Ptr) + _Offset);
end;

function PtrDiff(const _Ptr1, _Ptr2: Pointer): NativeInt; inline;
begin
  Result := NativeInt(_Ptr1) - NativeInt(_Ptr2);
end;

var
  BytesPerPixel: NativeInt;
  InScanLine0: Pointer;
  InBytesPerLine: NativeInt;
  OutScanLine0: Pointer;
  InBytesPerLine: NativeInt;
  InPixel: PRgbTriple;
  OutPixel: PRgbTriple;
// ...
  BytesPerPixel := SizeOf(Pixel)  
  InScanLine0 := InBmp.ScanLine[0];
  InBytesPerLine := NativeInt(_InBmp.ScanLine[1]) - NativeInt(InScanLine0);
  OutScanLine0 := _OutputBmp.ScanLine[0];
  OutBytesPerLine := NativeInt(_OutBmp.ScanLine[1]) - NativeInt(OutScanLine0);
  OutPixel := OutScanLine0;
  for y := 0 to Height - 1 do begin
    for x := 0 to Width - 1 do begin
      InPixel := AddToPtr(InScanLine0, InBytesPerLine * y + x * BytesPerPixel);
      Pixel := InPixel^;
      doSomething(Pixel);
      OutPixel := AddToPtr(OutScanLine0, OutBytesPerLine * y + x * BytesPerPixel);
      OutPixel^ := Pixel;
    end;
  end;

What we do here is calculate the difference between the first two scan lines and use it to calculate the address of each scan line in the bitmap.

Note: Most of the time this difference will be negative because on Windows Bitmaps usually are stored bottom to top.

There are two inlined helper functions AddToPtr and PtrDiff who do the pointer arithmetic by converting the pointer to NativeInt and back.

Note that the NativeInt declaration in Delphi 2007 and older is wrong. That’s why we redeclare it as 4 bytes in the conditional define above if SizeOf(Pointer) is 4 bytes (32 bits). The code should also work for 64 bits, but I haven’t tried it.

 Posted by on 2019-12-12 at 18:34

Assigning an ImageMagick wand picture to a Delphi TBitmap

 Delphi  Comments Off on Assigning an ImageMagick wand picture to a Delphi TBitmap
Dec 062019
 

I could not find this anywhere so I had to experiment:

I mentioned PascalMagick before and that it’s part of the FreePascal RTL.

The following assigns an image that has been processed with the Magick Wand API of ImageMagick to a Delphi TBitmap.

First, we need to convert the image to bitmap format using MagickSetFormat:

  Status := MagickSetImageFormat(FWand, PAnsiChar('BMP'));
  // check status here

Then we get it as a blob by calling MagickGetImageBlob.

var
  Size: Cardinal;
  blob: Pointer;
begin
  blob := MagickGetImageBlob(FWand, @Size);
  // check if blob is assigned

Note that the memory for the blob is allocated by the dll, we will have to pass it to MagickRelinquishMemory to free it later.

The easiest way I found to assign some memory to a TBitmap is TBitmap.LoadFromStream. The stream in this case could be a TMemoryStream, but unfortunately there is no way to create a TMemoryStream for an existing memory block. So I wrote a descendant of TCustomMemoryStream that provides this functionality.

type
  TdzMagickBlob = class(TCustomMemoryStream)
  public
    constructor Create(_Memory: Pointer; _Size: Cardinal);
    destructor Destroy; override;
  end;

constructor TdzMagickBlob.Create(_Memory: Pointer; _Size: Cardinal);
begin
  inherited Create;
  SetPointer(_Memory, _Size);
end;

destructor TdzMagickBlob.Destroy;
begin
  if Size <> 0 then
    MagickRelinquishMemory(Memory);
  inherited;
end;

Remember what I said above about passing the blob back to the dll to free the memory? That’s what the destructor does.

Now we only need to load the bitmap from the stream:

  bmp := TBitmap.Create;

  blobStream := TdzMagickBlob.Create(blob, size);
  try
    blobStream.Position := 0;
    bmp.LoadFromStream(blobStream);
  finally
    FreeAndNil(blobStream);
  end;

There we go: The image has been assigned to the TBitmap object.

I would have liked to omit the stream and directly assign the memory to TBitmap.Scanline[], but I found no easy way to do that.

This is part of the object oriented layer I wrote for encapsulating (some of) the MagickWand functionality. I will put this code into my dzlib later.

 Posted by on 2019-12-06 at 09:54

Working on the GExperts Code Formatter again

 Delphi, GExperts  Comments Off on Working on the GExperts Code Formatter again
Nov 302019
 

The code formatter can now handle inline variable declarations. That was actually just a simple change (fixes bugs #157 and #158 (and the duplicate #165))

Now I’m trying to get it to format function declarations like this:

function RegisterClipboardFormatW(lpszFormat: PWideChar): UINT; stdcall;
  external user32 Name 'RegisterClipboardFormatW';

or something as simple as this;

procedure bla;
  overload;
  forward;

Currently it doesn’t indent the additional lines. It works fine for method declarations though.

This turns out to be much more complicated than I thought. In my attempts to fix this, I have multiple times broken other test cases. Good thing there’s unit tests and source code management.

By pure chance I found a nasty side effect in the code.

 Posted by on 2019-11-30 at 20:23

GExperts 1.3.15 installers were/are detected as malware

 Delphi, GExperts  Comments Off on GExperts 1.3.15 installers were/are detected as malware
Nov 282019
 

The installers for GExperts 1.3.15 were/are detected as malware by several virus scanners on Virus Total. I have since submitted them all as false positives to Kaspersky and some to Microsoft and after their latest signature updates their scanners are now fine. I tried the same with other virus scanner vendors, but their procedure was so complicated that I decided that it is not worth the effort. So if you are using BitDefender or McAfee, you might be out of luck. Try to submit your installer to them yourself if you like, just so you see how much time that takes. Now multiply that by 19 installers and > 10 virus scanners … I guess you will understand then why I don’t bother.

 Posted by on 2019-11-28 at 17:45

Adding Menu Items to the IDE Editor’s Context Menu

 Delphi  Comments Off on Adding Menu Items to the IDE Editor’s Context Menu
Nov 262019
 

David Hoyle just blogged about Adding Menu Items to the IDE Editor’s Context Menu. As always his article is quite insightful, in particular the last part, because many people forget this:

That’s it, all done, or is it…

Well for a DLL yes as you would never get a situation where the Editor Popup Menu is called when you DLL is not in memory any more however this is not true for a BPL based plug-in so we need to reverse our hook.

… because a BPL based plugin can be loaded and unloaded at any time.

For which he then gives the following code:

Procedure TDDTWizard.UnhookEditorPopupMenu;
Var
  EditorPopupMenu : TPopupActionBar;
Begin
  EditorPopupMenu := FindEditorPopup;
  If Assigned(EditorPopupMenu) And Assigned(EditorPopupMenu.OnPopup) Then
    EditorPopupMenu.OnPopup := TNotifyEvent(FEditorPopupMethod);
End; 

And here we have a problem: How do you know that you can safely remove your hook? Some other plugin might have changed it and you will never know. If you simply change the OnPopup event back to the value you saved when you installed it, you might disable another plugin which was installed/initialized after yours. Or even worse, what if the plugin whose event you saved also has been unloaded? What do you do then? Simply reinstalling its original event handler will potenially crash the IDE the next time the event is called.

That’s exactly the reason why I wrote this article (years ago):
Safe event hooking for Delphi IDE plugins

I use this in my own plugins (Delphi IDE Explorer and parts of GExperts) and it works fine if both plugins adhere to this “standard”. Unfortunately I know of no other plugins who do. If you write Delphi IDE plugins, you might want to consider also tyring to play nice with other plugins.

(Btw David, your blog still says “Copyright David Hoyle 2018” in the footer.)

 Posted by on 2019-11-26 at 13:58

GExperts 1.3.15 experimental twm 2019-11-23 released

 Delphi, GExperts  Comments Off on GExperts 1.3.15 experimental twm 2019-11-23 released
Nov 232019
 

I just released GExperts 1.3.15 for all supported Delphi versions.

There have again been various bug fixes and the following notable changes:

  • There is now a start menu entry for a new stand alone version of the GExperts Code Formatter. Note that it not only has a GUI for selecting a single file to format and show the settings dialog, but can also be called with a list of files to be formatted. So, you could for example drag a group of files onto the executable to format them all. I plan to expand that command line interface in the future to allow formatting all files in a project or in a directory and its subdirectories.
  • Talking about the code formatter: Many more bugs have been fixed. A while ago I was actually thinking I got them all, but unfortunately that turned out to be a delusion. 😉
  • The GoTo Line Number dialog enhancer has been removed and replaced with a Go To expert with the same functionality but its own dialog, which has a setting to replace the Search -> Go to Line Number menu entry. If you have any suggestions on further improving that expert, please file a feature request.
  • Bugfix for the PE Information expert: It should now display all exported class names.
  • Many changes to the Uses Clause Manager expert (most of them contributed by Peter Panettone)
    • Bugfixes for the Identifier tab. It wrongly found some identifiers that just weren’t exported from that unit
    • It now highlights any units it will add.
    • It can now parse map files generated by the Win64 compiler, not just those from the Win32 compiler
    • It now shows an error message if it cannot use the map file and resorts to the dpr file.
    • The filter on the Identfier tab now has two modes:
      • Match anywhere
      • Match at start
    • There is now a status bar that shows the full file name of the currently selected unit. A popup menu allows to
      • Copy that file name to the clipboard
      • Copy that file to the clipboard (like pressing Ctrl+C on the file name in the Windows File Explorer)
      • Open the file’s location in the Windows File Explorer
    • The library path for Delphi XE and later was wrong, so the VCL/RTL tab was always empty.
  • There was a bug with the Clipboard History expert: It still hooked the clipboard even if it was disabled. In addition it could kill the IDE when it added new entries to the (possibly invisible) list view. Under some strange conditions setting the text of a memo apparently can fail with an EInvalidOperation exception. This exception was not caught and since it happened deep inside a chain of event handlers, it caused an Access Violation somewhere which then silently killed the IDE. That one was quite difficult to track down.
  • Improvement to the Backup Project expert: A new dialog shows any files it cannot find and it only shows each file once, not every time it does not find it. This was particularly annoying with include files.
  • And last, but not least: The GExperts version for Delphi 10.3 is now compiled with the latest and greatest Delphi 10.3 update 3 release. Let’s hope this fixed some bugs and hasn’t introduced any new ones. (Unfortunately RSP-25645 “Creating sub components with IOTAFormEditor.CreateComponent raises access violation” still hasn’t been fixed, so replacing e.g. TTable with TSqlTable still does not work.)

The new version is available for download on the GExperts download page.

 Posted by on 2019-11-23 at 19:09

dzBdsLauncher 1.0.2 released

 Delphi, dzBdsLauncher  Comments Off on dzBdsLauncher 1.0.2 released
Nov 232019
 

dzBdsLauncher tries to solve the problem of accidentally opening a Delphi project with the wrong Delphi version. The latest version 1.0.2 now also detects .DPROJ file from Delphi 10.3.3. See the dzBdsLauncher page for details.

 Posted by on 2019-11-23 at 16:01

No new GExperts release yet for Delphi 10.3.3

 Delphi, GExperts  Comments Off on No new GExperts release yet for Delphi 10.3.3
Nov 212019
 

Edit: There is now a new release.

Before you ask: No, there is no new GExperts release yet for Delphi 10.3.3. I haven’t even downloaded it yet. I might get to it this weekend.

In the meantime you can get the sources and compile your own DLL with the new Delphi version. I don’t expect any problems (but I have been wrong before).

 Posted by on 2019-11-21 at 18:13