Also, because SetDialogParams() is taking a TTaskDialogParams by value, a copy is made, which will have its own TStringList object, so you must implement the Assign() operator as well to copy the source record's TStringList data to the copied record after it is Initialized:
class operator TTaskDialogParams.Assign(var Dest: TTaskDialogParams; const [ref] Src: TTaskDialogParams);
begin
Dest.CustomButtons.Assign(Src.CustomButtons);
end;
class operator TTaskDialogParams.Finalize(var Dest: TTaskDialogParams);
begin
Dest.CustomButtons.Free;
end;
class operator TTaskDialogParams.Initialize(out Dest: TTaskDialogParams);
begin
Dest.CustomButtons := TStringList.Create;
end;
Otherwise, if you don't do this, the default assignment behavior will copy the object pointer instead, causing the copied record to lose its own TStringList object (leaking it) and end up pointing to the original TStringList object, so when the copied record is Finalized then the original TStringList object gets freed, corrupting the original record that was copied from.
That being said, there is no good reason to pass the record by value in the code shown. SetDialogParams() should take the record by const reference instead, then no copy is made.
Read the documentation, especially the section on "Passing Managed Records as Parameters" :
https://docwiki.embarcadero.com/RADStudio/en/Custom_Managed_Records