Showing posts with label Delphi. Show all posts
Showing posts with label Delphi. Show all posts

Friday, 6 June 2025

Job Objects

- or How to bundle and control your (child) processes better.


This is partly a follow up to my old post on processes - found here, and an introduction to Job Objects.

A Job Object is a container where you can attach processes to, so that they are controlled within the same context.

To see what processes consist of Job Objects, we could install Process Explorer from Sysinternals from here

After installation Jobs by default are not shown, so you must select "Options|Configure Colors...", and enable the Jobs to be shown.

We can see that more and more applications tend to contain more and more processes and bundle these up - browsers and applications like Teams springs to mind.

The list of benefits of a Job Object is long:

- Allow a group of processes to be managed as a one.
- Starting and terminating related external processes.
- They can be named. 
- If main process dies, Windows will terminate the child processes.
- Can be nested.
- Accounting info – I/O, CPU usage
- More and more used – since more application consists of many processes
- They can impose limits on its processes.
- CPU: Maximum processes active, time, Affinity, rate control
- Memory: minimum and maximum working set, commit maximum
- Network: Maximum bandwidth 
- I/O: Maximum rate, read and write bytes 
- UI: User and GDI handles, clipboard access, exiting windows, desktop switching/creation

Below is an example of a Job with limitations:


Example

To illustrate a Job Object I want my "main" application to create a Job Object, create a Process and attach that to the Job Object - I want to pass a callback to get the output from the STDERR steam and if the "child" terminates I also want to be notified.

In the FormCreate I do create an instance of TChildThread - more on that later - with the parameters of the command of the process to run and the callback procdure:

var
  ChildExeName: string;
begin
  // Start Child process and attach to this process via Job Object
  ChildExeName := TPath.Combine(ExtractFilePath(ParamStr(0)), 'ChildProcessC.exe');
  if FileExists(ChildExeName) then
  begin
    ChildExeName := '"'+ChildExeName+'"';
    System.UniqueString(ChildExeName);
    ChildThread := TChildThread.Create(ChildExeName, LogChildError);
    ChildThread.OnTerminate := DidChildTerminateUnexpected;
  end
  else
    memLog.Lines.Add('Error: Not able to find any child process exe to start.');

The OnTerminate event is assigned to the procedure that notifies me of an unexpected termination.

And in the FormDestroy I terminated the child process and close its ProcessInfo handles. Remembering to set the onTerminate to nil prior - since this is the expected termination:

  if Assigned(ChildThread) and (ChildThread.ProcessInfo.hProcess>0) then
  begin
    ChildThread.OnTerminate := nil;
    TerminateProcess(ChildThread.ProcessInfo.hProcess, 0);
    ChildThread.Free;
  end;

Back to the TChildThread:

  TChildThread = class(TThread)
  const
    bufSize = 2400;
  private
    FReadBuf: array [0..bufSize] of AnsiChar;
    FCmd: string;
    FCallbackProc: TOnCaptureProc;
    FProcessInfo: TProcessInformation;
    function GethProcess: TProcessInformation;
    procedure SendLogMsg;
  public
    constructor Create(const cmd: string; CallBackProc: TOnCaptureProc);
    destructor Destroy; override;
    procedure Execute; override;
    property ProcessInfo: TProcessInformation read GethProcess;
  end;

and the callback is of type:

TOnCaptureProc = reference to procedure(const Value:string);

The most interesting thing here is the Execute procedure:

procedure TChildThread.Execute;
const
  secAttr: TSecurityAttributes = (
    nLength: SizeOf(TSecurityAttributes);
    bInheritHandle: True);
var
  rPipe: THandle; // Could PeekNamedPipe this for normal console info
  wPipe: THandle;
  erPipe: THandle; // STDERR pipe
  ewPipe: THandle;
  startupInfo: TStartupInfo;
  dRun, dAvail, dRead: DWORD;
  jobObject: NativeUInt;
  jobLimitInfo: TJobObjectExtendedLimitInformation;
begin
  inherited;
  if CreatePipe(rPipe, wPipe, @secAttr, 0) and 
     CreatePipe(erPipe, ewPipe, @secAttr, 0) then
  try
    FillChar(startupInfo, SizeOf(TStartupInfo), #0);
    startupInfo.cb := SizeOf(TStartupInfo);
    startupInfo.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW;
    startupInfo.wShowWindow := SW_HIDE;
    startupInfo.hStdInput := rPipe;
    startupInfo.hStdOutput := wPipe;
    startupInfo.hStdError := ewPipe;

After having create the pipes to capture the STDERR (and STDIN and STDOUT), and set these in the StartupInfo structure, we create the Job Object and set some "Limits" for the Job Object.

    jobObject := CreateJobObject(nil, PChar(Format('Global\%d', [GetCurrentProcessID])));
    if jobObject <> 0 then
    begin
      jobLimitInfo.BasicLimitInformation.LimitFlags := JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
      SetInformationJobObject(JobObject, JobObjectExtendedLimitInformation, @jobLimitInfo, SizeOf(TJobObjectExtendedLimitInformation));
    end;

Then we create the process and assign it to our Job Object

    if CreateProcess(nil, PChar(FCmd), @secAttr, @secAttr, True,   // !!!!
      CREATE_BREAKAWAY_FROM_JOB, nil, nil, startupInfo, FProcessInfo) then
    try
      if FProcessInfo.hProcess <> INVALID_HANDLE_VALUE then
        AssignProcessToJobObject(jobObject, FProcessInfo.hProcess);

And then we read from STDERR, and send any data back via the callback. 

      repeat
     dRun := WaitForSingleObject(FProcessInfo.hProcess, 100);
     PeekNamedPipe(erPipe, nil, 0, nil, @dAvail, nil);
     if (dAvail > 0) then
     repeat
       dRead := 0;
       ReadFile(erPipe, FReadBuf[0], bufSize, dRead, nil);
       FReadBuf[dRead] := #0;
       OemToCharA(FReadBuf, FReadBuf);
       Synchronize(SendLogMsg);
     until (dRead < bufSize);
   until (dRun <> WAIT_TIMEOUT);

And after termination, clean up and close the various handles.

    finally
      CloseHandle(FProcessInfo.hProcess);
      CloseHandle(FProcessInfo.hThread);
    end;
  finally
    CloseHandle(rPipe);
    CloseHandle(wPipe);
    CloseHandle(erPipe);
    CloseHandle(ewPipe);
  end;
end;

One important thing is to remember to set the Inheritable Handles to True - otherwise the handles to the "pipe" streams might not be what you expect - you want them typically to be the same as your "main" process.

Also note, that prior to Delphi 12, a needed Windows API const was missing, so you had to declare that yourself within your code:

const
  CREATE_BREAKAWAY_FROM_JOB       = $01000000;
  {$EXTERNALSYM CREATE_BREAKAWAY_FROM_JOB}

Documentation on Job Objects and of all the limit flag and other parameters found here: https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects

Update: There will be a session at CodeRage 2025 - will put the link here when a replay is avaiable.

/Enjoy




Monday, 24 February 2025

IAM Cloak, without the Dagger

 - or using a Keycloak setup as your Authentication solution in your native Delphi application.



With no reference to melodramatic intrigues, espionage, secret agents or a Marvel crime-fighting team involving experimental drugs, this post will just try to enlighten how fairly easy it is to get started with Keycloak as an authentication backend for your Delphi application.

This post is a continuation of some earlier post about IAM related stuff and my Auth component samples for PingOne, EntraID and now Keycloak on GitHub.

The posts can be found here:

GitHub gist/repos:
The EntraIDAuth was used in my Delphi Summit 2024 session: I can, therefore IAM.

Keycloak

Keycloak is a mature Open Source IAM solution, and I am by not means an expert - but I have been dabbling with some of the other provides - and this is as good as any.

The easiest way to get started is to install a docker container as described in the official documentation here.

There are other options and a lot more to it, but that short guide has you started, and we use that sample as the basis for the KeycloakAuth component, found in the last repo link above. Note that at the time of writing this post, it does only parse an access token for the "uniqueid", and the state parameter is by default added.

The state value is a GUID (or should I say UUID) - so to get "proper" UUID, I end up doing this:

if FUseState then
begin
  FStateValue := TGUID.NewGuid;
  URL := URL + '&state=' + GUIDToString(FStateValue).Trim(['{','}']).ToLower;
end;

Using the state parameter in the authorization code flow, helps to check that the client value sent matches the value in the response - to mitigate CSRF/XSRF (Cross-site request forgery) attacks. After that initial check one should then check the nonce given within the id_token.

But getting ahead of myself - to use and play around with my KeycloakAuth component - get it from GitHub as mentioned above - and install the component in the IDE (Delphi 12 package in repo).

As any of my other Auth components, this is based of TWebBrowser - and I hear you ask why not TEdgeBrowser? Well I had done some things similar years back before Edge was a thing, but since I do strongly suggest that the "SelectedEngine" property is set to "EdgeOnly" - TWebBrowser does internally use a TEdgeBrowser. It does mean you do need to install the Edge WebView2 SDK, and deploy the correct dll. Read all about it here.

Create a new VCL application and drop a KeycloakAuth component, align to client and set the following properties:

    RealmName = 'myrealm'
    AuthPath = 'http://localhost:8080/realms/'
    ClientId = 'myclient'
    RedirectUri = 'http://localhost:1234'
    AuthEndpoint = '/protocol/openid-connect/auth'
    TokenEndpoint = '/protocol/openid-connect/token'
    Scope = 'openid'
    UseState = False
    ResponseType = 'code'
    UserIdClaim = sub
    OnAuthenticated = KeycloakAuthenticated
    OnDenied = KeycloakDenied

Add an OnAuthenticated and OnDenied - like:

procedure TForm6.KeycloakAuthenticated(Sender: TObject);
begin
  ShowMessage('Welcome '+KeycloakAuth1.GreetName+'!'+
    sLineBreak+sLineBreak+'You have been authenticated as userId:'+
    KeycloakAuth1.UserId);
end;

procedure TForm6.KeycloakDenied(Sender: TObject);
begin
  ShowMessage('You have not been authenticated!');
end;

In the forms FormShow event call the components Authorize procedure, to start the flow.

I will add more capabilities to the component - such as nonce and code challenge, and cover all OpenID Connect response_type combinations.

To get a good grasp and overview of the OpenID Connect flows - take a look at this Medium post.

Happy 30th anniversary - Delphi! and see you at Delphi Summit 2025.

/Enjoy

Saturday, 13 January 2024

Splitters Helpers

 - or how to find ways to add own helpers and keeping existing helpers.



Wanted to do a pun on Finders Keepers, but failed - so this post is a sample on how to add your own record helper - in this case for the string type.

Delphi/Object Pascal does not allow for multiple helpers for the same type to be available at the same time.

So in my little example I wanted a string function that would split a string by a char, but up till a given max length.

A scenario for that use could be if you need to feed a system that has limited fixed size fields, spanning over more fields - CompanyName1, CampanyName2 only being 30 chars each. And for readability and UI, you also need to consider not to split mid-word.

To overcome the issue with  the one helper active per type, defined your own matching type:

MyString = type string;

Define the new record helper for that type with its function:

MyStringHelper = record helper for MyString
  function SplitMaxProper(const ch: char; const len: Integer): TArray<string>;
end;

Since we also want to use the normal string helpers within the helper function, we need to do some casting when referring to the helpers type itself:

function MyStringHelper.SplitMaxProper(const ch: char; const len: Integer): TArray<string>;
var
  sl: TStringList;
begin
  var sidx := 0;
  var done := False;
  sl := TStringList.Create;
  try
    while (not done) do
    begin
      var delta := string(Self).LastIndexOf(ch, sidx+len, len);
      if (delta = -1) or (string(Self).Length-sidx <= len) then
      begin
        sl.Add(Trim(string(Self).Substring(sidx)));
        done := True;
      end
      else
        sl.Add(Trim(string(Self).Substring(sidx, delta-sidx)));
      sidx := delta;
    end;
    Result := sl.ToStringArray;
  finally
    sl.Free;
  end;
end;

And when using the new string helper we do need to cast to get to our new function:

procedure TForm6.btnSplitClick(Sender: TObject);
begin
  meSplitText.Clear;
  var len := StrToInt(edSplitLength.Text);
  var str := MyString(edTextToSplit.Text);
  meSplitText.Lines.AddStrings(str.SplitMaxProper(#32, len));
end;

Disclaimer: The code is done so that it does fix better in the narrow width of my blog layout, and the function might also need some optimization :)

A note on the LastIndexOf string helper functions, the current official documentation seems to me a bit unclear:

StartIndex specifies the offset in this 0-based string where the LastIndexOf method begins the search, and Count specifies the end offset where the search ends.

Do remember that the search in this case, of cause goes from right to left - so StartIndex would normally be the length of the string.

I started with a Stephen King book title pun, so I should also comment on the quote used in the image shown - a quote from the brilliant author Tom Holt and the book The Portable Door - which is highly recommended, even if you have watched the movie adaptation, which is also good - but different - since adapting Tom Holt's books is no trivial task.

/Enjoy


Tuesday, 2 January 2024

Just Ping someone!

- or a component for Ping Identity's authentication within your Delphi application.


It has often been the rule that native applications either authenticate the user by current OS user or by an application-centric user and role model - but that might for multiple reasons not be good enough anymore.

The old AD or LDAP lookups are being replaced by cloud IAM platforms, to control and secure the authentication of the identity of the user.

After the user is authenticated (and is authorised "access" your application), the application-centric roles flow can continue as-is.

One of these IAM cloud provides is PingIdentity, and they have a fairly extensive Postman collection for their PingOne Platform API - found here. Their developer documentation is also very useful - found here.

I have previously used various ways against various providers, but it seemed that when testing against PingOne with MFA enabled (Multi-Factor Authentication - which might include the annoying phone thing - that everyone uses) - I was hit by either a CORS issue or something else.

When using an OAuth2/OpenID Connect authentication, it does require that you setup and use a redirect uri, to tell the provider who is locally listening/waiting for the "response".

The idea with this type of authentication, is that the flow is handled securely within a web browser session, and the client does at no point in time know the login credentials - only when the user is authenticated do we need an "id token". So no "local" storage/handling of "passwords".

The listener part and possible timeouts has always annoyed me, so it seemed based on the flow, that I could handle it differently by just intercepting the local redirect call with the auth code - when the user is authenticated on the PingOne side - to get the id token needed.

Implementation

The TPingOneAuth component is a descendant of standard TWebBrowser overriding the IDocHostUIHandler interface, and adding a some properties.

The GetHostInfo override is to ensure that all redirects in the browser component is triggering the OnBeforeNavigate2 event - which then on the redirect auth code call, will get the wanted id token.

function TPingOneAuth.GetHostInfo(var pInfo: TDocHostUIInfo): HRESULT;
begin
  pInfo.cbSize := SizeOf(pInfo);
  pInfo.dwFlags := 0;
  pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_NO3DBORDER;
  pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_THEME;
  pInfo.dwFlags := pInfo.dwFlags or DOCHOSTUIFLAG_ENABLE_REDIRECT_NOTIFICATION;
  Result := S_OK;
end;

The id token is then parsed using Pablo Rossi's brilliant JOSE and JWT library, that might as well have been done using a call to one of the PingOne's Token Introspection endpoints.

Since we do need an user id - the OpenID Connect scope must include profile also.

To parse the custom claim a custom TJWTClaims class is added, containing the profile claims we want to read.

TPingOneClaims = class(TJWTClaims)
// Adding some given by the OpenID Connect scope: profile
private
  function GetPreferredUsername: string;
  procedure SetPreferredUsername(const Value: string);
  function GetGivenName: string;
  procedure SetGivenName(const Value: string);
  function GetFamilyName: string;
  procedure SetFamilyName(const Value: string);
public
  property PreferredUsername: string read GetPreferredUsername write SetPreferredUsername;
  property GivenName: string read GetGivenName write SetGivenName;
  property FamilyName: string read GetFamilyName write SetFamilyName;
end;


Usage

Install the TPingOneAuth component in the Delphi IDE, and set the library path - business as usual.

There is a small sample application in the GitHub repo, but steps are at follows:

Drop or Create the TPingOneAuth on a form, setting following properties:

  • AuthEndpoint: /as/authorize
  • AuthPath: https://auth.pingone.eu/
  • ClientId:
  • ClientSecret:
  • EnvironmentId:
  • RedirectUri:
  • ResponseType: code
  • Scope: openid profile
  • TokenEndpoint: /as/token
All these values are found in the PingOne console, under the application you setup as a "authorization" reference to your native Delphi application.


Add code to the OnAuthenticated (and OnDenied) event(s) - were on a successful authentication the public properties UserId and GreetName can be useful.

To start the authentication just call the Authorize method - and with MFA enabled, you will see the prompt bellow, once you initially have given your credentials.


After finding your phone, and having swiped to authenticate in the PingOne mobile companion app, the OnAuthenticated event is called.


The code for the component and the sample is found here.

A thing to note:

When having MFA enabled - and you need to pair a device the first time around - the url Ping provides does contain some "strict mode" Javascript that IE will prompt you about twice, the solution to this is to set the SelectedEngine property to EdgeIfAvailable or EdgeOnly and deploy the EdgeView2 Runtime (WebView2Loader.dll) which can be found in GetIt as the EdgeView2 SDK.

/Enjoy



Saturday, 22 May 2021

VAT's in it for me? - or I call my layer.

 - or a shout-out to the various services of apilayer.com



Sorry about the intended pun in the title - but it will all make sense.

Have you ever wanted to know the various VAT rates in the EU - no? - well neither have I, because it might put me in a bad mood, but it fits the intended pun for the post, and I also wanted to show how it is done from from Delphi.

The mother company of Embarcadero, Idera acquired another company early this year - this time apilayer.com, which provides numerous "simple" cloud-based API services - among these are:

  • IP geolocation and reverse lookup
  • Language detection
  • Mail address validation
  • Phone number validation
  • Flight tracking
  • Currency conversion and rates (including crypto currency)
  • Weather data and forecast
  • News, Headline and Stock apis
  • Conversion PDF and scraping
But go to https://apilayer.com/, and read more about their various layers and tiers for these.

We will in this short demo look at the vatlayer - which does EU VAT stuff.

So I started by signing up for the free tier on https://vatlayer.com/ - which gives you an API access key and access to a dashboard. As always do not share the access key - just saying.

Now you can fire up Delphi, create a new application and either use the REST Debugger or just manually throw in the REST component or create runtime - what you prefer.

I just threw in a edit control and a couple of memos and buttons, as seem above. And then I added an parameter on the RESTClient with the access_key - since I wanted to clear the request parameters on the RESTRequest, but keep the access_key. And set the BaseURL property to http://apilayer.net/api.

I just took two of the methods/resources from https://vatlayer.com/documentation - so the VAT lookup button looks like this:

var
  json: TJSONValue;
begin
  Memo1.Clear;
  RESTRequest1.Method := rmGET;
  RESTRequest1.Resource := 'validate';
  RESTRequest1.Params.Clear;
  RESTRequest1.AddParameter('vat_number', LabeledEdit2.Text, pkGETorPOST);
  RESTRequest1.Execute;
  if RESTResponse1.StatusCode=200 then
  begin
    json := RESTResponse1.JSONValue;
    Memo1.Lines.Add(json.GetValue<string>('company_name'));
    Memo1.Lines.Add(json.GetValue<string>('company_address'));
  end;
end;

..and the VAT rates for HU (since they have a higher VAT rate than DK :D) button looks like this:

var
  json: TJSONValue;
begin
  Memo2.Clear;
  RESTRequest1.Method := rmGET;
  RESTRequest1.Resource := 'rate';
  RESTRequest1.Params.Clear;
  RESTRequest1.AddParameter('country_code', 'HU', pkGETorPOST);
  RESTRequest1.Execute;
  if RESTResponse1.StatusCode=200 then
  begin
    json := RESTResponse1.JSONValue;
    Memo2.Lines.Add(json.Format());
  end;
end;

There nice twists to these - by IP address or get list of rate types - and if combining these services by apilayer.com - I could make a speeding white-van "whistle-blower" mobile app - since all company owned registered vehicles in DK - need to have their VAT numbers on their fleet.

So I could lookup the company and their address, get their phone number by a different api layer - and then call and tell on them - MUHAAHAAAHAA (evil laughter)

..or maybe I should just create something useful and fun.

And the best part about writing this post, I now learned that Denmark has only the second highest VAT rate in EU :D

/Enjoy




Saturday, 28 November 2020

A Set back?

- or how could I forget some set operators.


Recently a colleague of mine asked me to review some old code, and a line similar to the one below pop into my view as strange:

if (doorStates * [dsClosed, dsUnlocked] <> []) then

It had been so many years since I had used that specific syntax, that I completely forgot about some of the set operators in Pascal and Delphi - so I thought I would write up a small post on Sets in Pascal/Delphi.

In regards to the silly doorStates example above - here is a little riddle: When is a door not a door?

Friday, 31 July 2020

I am no Charles TDickensionary<>

- or a few TDictionary<> tricks.


One of my favourite classes in Delphi is TDictionary<> - might be because it resembles a database table which can have composite keys, and I have always been a ClientDataset/VirtualTable/FDMemTable fan-boy :)

I do not get to use the memTables that much i recent years, and the RTL generic collections also got better - while not on par with third party collections like Spring4D - which I also not get to use much.

I recently had a task where I had to transform some code that was collecting data from an external source, that provided data in a way that multiple iterations had to be done, before each row/item was complete - since data was coming in subsets per field/property. TDictionary<> to the rescue.


Saturday, 8 June 2019

Cure for POX found in Avalon

- or how reverse engineering makes a game engine portable.



One thing that can stop software from being portable and surviving the decay of time, is when closed and proprietary file formats block any chance of progress - the game Siege of Avalon has the POX.

Wednesday, 5 June 2019

Take a "leak"

- or introducing Deleaker - now available for Delphi/C++Builder/RAD Studio



Since we in RAD Studio need to be able to master the fine art of manual memory management - things might leak if you do not pay attention and follow the rules. The most basic rule being: What you create - you must "destroy" (read free).

Saturday, 23 March 2019

Siege of Avalon lifted

- or an example of migrating old source code to newer grounds.

Compiled with latest Delphi (10.3.1 Rio) - Nice inventory graphics.

This post is just about the few steps I did on migrating the Delphi 4 source of Siege of Avalon to Delphi 10.3.1, and the intentions and road-map I have proposed to the players and modders of this nice game.

Wednesday, 18 July 2018

Delphi and C++Builder Coming Everywhere

- or it finally happened - a Delphi and C++Builder Community Edition!


The great thing about these editions is that they are free to use without being amputated like the Starter Edition was, feature-wise they are identical to the Professional Edition - with mobile now included.

I have personally been looking forward to this move, and that should help a lot of hobbyists get out of their Delphi 7 stasis and discover how much Delphi has evolved over the years.

The restrictions that has been applied has a lot to do with common sense - if you earn money and profit from the use of the CE license, you can afford and must convert to a paid license that includes some extra license benefits.

Wednesday, 14 February 2018

JSON Find / Rewind

- or using the TJSONIterator without wearing a Cardigan.


Working with JSON files has been made easier over the years with Delphi - which btw turns 23 today - by either third-party libraries or especially within the RTL.

The System.JSON and the REST.JSON units have added the one-lines ObjectToJSON and JSONToObject, which can add a tiny bit of fat to the JSON generated to enable to get it back into your TObjectLists - but makes your code cleaner to read.

Another way is the adding of the TJSONWriter and TJSONReader which by using the TTextWriter and TTextReader mimics the .Net equivalents.

The TJSONWriter includes easy formatting of the output and in the System.JSON.Builders unit there are a couple of extra goodies, two of these being the TJSONObjectBuilder and the TJSONIterator.

I will dig a bit into TJSONIterator, since only its Recurse, Next and Return methods is mentioned on its DocWiki page here.

Tuesday, 12 December 2017

The Latest (J)IDE

- or how to move to the dark side a day in advance



Star Wars - The Last Jedi has premier tomorrow the 13th December (at least here in Denmark), but some of us can already move to the dark side today - since the new update 2 of RAD Studio 10.2 Tokyo has just been released.

Saturday, 18 November 2017

Using TensorFlow™ with Delphi

- or how to use a TStack<T> to simulate a RPN calculator.



This post is a very simple example on how to use "Google's" TensorFlow - which is an open source Machine Learning library. And this is also a tribute to the old HP reverse polish notation calculators I never had 😞.

Update: Hartmut David was so kind to now put his code on GitHub (https://github.com/hartmutdavid/TensorFlow4Delphi) - thanks!. And I did a pull request for the missing OpSub. The repository also includes the newest .dll so you might skip the renaming hassle described below.

Friday, 15 September 2017

Match of the Day

- or a simple example on using OpenCV Template Matching (or where is Billy Bunter these days?).


This post is not about sports or old Genesis songs - but a bit about comics and mostly about Delphi and OpenCV - the Open Source Computer Vision Library.

Saturday, 12 August 2017

Why I keep preferring Delphi

- or why are we still discussion this - get updated.


There has been a trend in the various discussions that popup on the internet or in real life - that Delphi and the ObjectPascal language is mentioned in past tense. Which indicates to me that those who do that, have either not paid attention or are just simply ignorant on facts. Which also seems to be a trend in politics - that relates to the facts - not Delphi :D

The most recent version of Delphi/C++ Builder/RAD Studio - version 10.2.1, was just released a few days back - which I would not qualify for talking about Delphi in past tense in general.

But a thing that is very common in the long discussions I bothered to read - is that almost everyone says something in the lines of:
- "Oh I also remember doing my first program in Delphi - great times"
- "I really enjoyed programming (with Delphi) - I miss those days"
- "In those days programming was fun."
- "The best tool to create Windows desktop applications - period. Miss it."

..and the list goes on - oozing of nostalgia, longing, the fun and the enjoyment and productivity everyone had "back in the days" - well these days are not gone for those of us that kept around using the language and tools we preferred.

That is also the reason why Delphi is my preferred tool: Fun, readability, fun, productivity, multi-platform, performance, fun and enables me to get things done faster.

Wednesday, 15 March 2017

The Old Vic

- or hopefully adding a nostalgic sample to a nice project.




This is actually short tribute to the work of someone else - I did just add a bit of "older" stuff.

A few days back Dennis D. Spreen posted a really nice post: MOS6502-delphi – a MOS 6502 CPU emulator for Delphi - if you haven't seen it already, check it out.

UPDATE: I did actually get the key matrix correct the day after - and submitted code to Dennis' GitHub where it is now included: https://github.com/Dennis1000/mos6502-delphi

Saturday, 11 March 2017

The Game is On - in one line of code

- or how to play around with the Steam Web API and a honorable mentioning of a nice new game on Steam build using Delphi.


The title of this post could indicate that this is about the BBC series "Sherlock" - which I would highly recommend - but this is not what this is about.


Almost 4 years back I did a small client for myself that did consume various APIs to get a unified client and overview of the various games I had acquired over the years on various platforms like - Battle.net, Desura (now OnePlay it seems), Uplay, GOG, OriginEA, PSN and Steam - combined with data from TheGameDB and others.

Saturday, 21 January 2017

Self-updating Application with SHA1 check and FireDAC

- or basic self-improvement with room for improvement.


There are many ways to keep the users windows applications updated, like pushing them out via enterprise distribution setups, click-once, Squirrel (not the scripting language or the animal). But what if I just want the basics - and as simple as possible?

Disclaimer: This is not a post about security - even if SHA1 is mentioned - this is solely intended as an example on how an update process could be done in a controlled environment - like in-house distribution.

Sunday, 15 January 2017

Integrating with your favorite CRM/ERP web based client

- or poor mans integration?


Sometimes you need to integrate to other systems in the organisation. And the ways of doing it always matters on which interfaces/skills/tools are at hand - but also whether you need a tight dependency and if you want or can customized anything on that client.

In the below example I will just illustrate how a standard Microsoft Dynamics 2016 CRM "Lookup Up Record" dialog can be used to link some other data/application that has interest in the CRM data - so that doing BI across systems might be more fun.