A Tale of 3 APIs: VCL integration with WinAPI, COM & ShellAPI, WinRT

by Sep 9, 2019

In this blog post I want to offer some highlights from the session, but I won’t cover all of the details of the 40+ minutes technical discussion of the webinar.

Windows APIs Timeline

Let me start by covering the timeline of the various APIs added to the operating system over the years, by sharing the introductory slide I created:

Beside the timeline itself, it is important to notice that all of these three layers (the traditional API, COM, WinRT) are written in C/C++ and are natively compiled libraries. This means that a native compiled language like Delphi can bind directly to these APIs, with no marshaling needed. All three mechanisms use memory directly and manually (although with notable differences) and none uses .NET or garbage collection.

Finally, I don’t want to get into the discussion of trends, Microsoft dropping or not UWP, and so on — I prefer keeping this blog post focused on technical elements of Delphi APIs support.

The Traditional Windows API

Calling Windows APIs is quite simple for functions Delphi already provides a translated header, but there are multiple options. Suppose you want to call MessageBox. This is declared in the Winapi.Windows unit as:

function MessageBox(hWnd: HWND; lpText, lpCaption: LPCWSTR; uType: UINT): Integer; stdcall;

And it is defined as an external function from a system DLL:

function MessageBox; external user32 name 'MessageBoxW';

where user32 is defined as:

const user32 = 'user32.dll';

You can call it directly just as you’d call a regular function:

procedure TFormCallAPI.btnStandardClick(Sender: TObject);
begin
  MessageBox (0, 'Some text here', 'Caption', MB_OK);
end;

This creates an entry in your application import table, that will need to match an export table entry in the DLL when the application starts and the library is loaded. In other words, linking happens at initial execution time. As an alternative (which makes more sense for a DLL which might be missing on the end user computer) you can load the library and bind to the function at the time you want to execute it.

First you need to have a specific procedural type:

type TMessageBoxCall = function (hWnd: HWND; lpText, lpCaption: PWideChar; uType: UINT): Integer; stdcall;

At this point, you can use the following code:

There is also an in-between options called delayed loading. In this case (sed for new functions added to an existing library, something Microsoft does for core system libraries over time) you need to use a specific delayed keyword after the declaration. This is something we add for more recent APIs, for example:

function WindowFromPhysicalPoint; external user32 name 'WindowFromPhysicalPoint' delayed;

In your code, you write the standard call, with no change needed, although you might want to make the call only if you are on a recent enough version of Windows:

if CheckWin32Version (6, 0) then 
  hwnd1 := WindowFromPhysicalPoint (aPoint);

The complete code of this demo is available at https://github.com/marcocantu/DelphiSessions/tree/master/Win10Session/CallWinApi

There is another area I covered in the webinar in terms of core APIs, and that is the ability to handle Windows messages by using the matching method modifier, as in:

type
  TJumpListForm = class(TForm)
  public
    procedure DropFiles(var Msg: TWmDropFiles); message wm_DropFiles;

COM and Shell Interfaces

The integration with COM is also quite deep but fairly simple in Delphi. It has long been considered the best COM integration available — and probably still is even if COM is not so popular any more, it is still the foundation of the integration with the Windows shell and desktop. 

If you open the Winapi.ShellObj unit you can find a large number of the declarations of COM interfaces for the Windows shell. Here is an image with a short section — the unit interface section is 14,000 lines! 

In the demo I covered in the webinar I used a component (TJumpList) which wraps some shell API rather than going deep level and doing it manually. The full demo is at https://github.com/marcocantu/DelphiSessions/tree/master/Win10Session/JumpListFilesDemo

COM has it own logic in terms of interfaces, IID (Interface IDs), the use of the registry and more. However COM interfaces maps to interfaces in the Delphi language and integration is smooth. The complexity comes more from proper lifetime management and objects creation, but this goes beyond the scope of this blog port.

Interfacing with WinRT

WinRT is a different but still native library, that has a different type system and memory management model compared to COM, but shared with it the binary interface (and VTable) model. Therefore, WinRT interfaces map naturally to Delphi interfaces.

This is how WinRT interface looks once remapped to a Delphi language declaration:

The system also defines a IToastNotificationFactory used to create an instance, and this is marked with a unique string — which replaces the use of the registry and GUIDs — namely 'Windows.UI.Notifications.ToastNotification'. Delphi also defines a helper “factory, in this case the class TToastNotification that is fairly simple to use, as in the code below:

This is the core of the code required to show a notification in Windows 10, although the TNotificationCenter component wraps it nicely in few lines of code. Both demos (the complete low-level WinRT API version and the easy to use component-based one) are available on GitHub respectively as https://github.com/marcocantu/DelphiSessions/tree/master/Win10Session/WinRTDirect and https://github.com/marcocantu/DelphiSessions/tree/master/Win10Session/Windows%2010%20Notifications.

Conclusion

There is again much more information in the webinar, and also additional demos covering how we are extending the core API support continuously in the VCL. For this blog post, let me end with the final summary slide of the webinar:

If not already there, the webinar replay will soon be available and get listed at https://community.idera.com/developer-tools/b/blog/posts/windows-10-modernize-webinar-series#replays.