[SOLVED] Minor Q: HowTo use 'DebugLn' in a DLL? lazarus / 12-Debugger ---------------------------------------------------------------------- d7_2_laz · 2025-01-20 19:39 I'd oftenly used "OutputDebugString" for to trace some stuff, but sometimes there are situations where i couldn't use it (as it requires the "Windows" uses, and that's oftenly not appliable). So i switched more and more to use "DebugLn", and for a stand-alone exe i do need to: 1. Main form: include "LazLogger" in the use clause 2. Project Options > Compiler Options > 'Config and Target':  'Win32 gui application (-WG)' => no 3. Run Parameters > Command line parameters :  --debug- log= The latter isn't possible when developing a plugin DLL. As - related to 3. - the "Host Application" exe of course will not understand this command line parameter resp. interpretes it in an unpredictable way. Is there an equivalent for this Run parameter for a DLL? PS: background is: in a plugin DLL, i had a problem with my docking form's toolbar that sometimes lost the trigger to be drawn and appeared empty. Occured when the host application opened a docked subdialog below my docking form, or, same, when i closed this docked subdialog. Toolbar buttons were not drawn anymore. I found a workaround for this. But i'm still interested to see which layer maybe not passed a paint message to the toolbar (the OnPaintButtons callback won't be called in this case). ---------------------------------------------------------------------- Martin_fr · 2025-01-20 20:05 Well you need to use the unit LazLagger in the dll => that will initialize the logger instance. Actually => assuming initialization sections are run. Not sure that happens in a dll. So if initialization sections are not run, then you must at some point trigger the setup yourself Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   uses LazLoger; function CreateDebugLogger: TRefCountedObject;begin  Result := TLazLoggerFile.Create;//  TLazLoggerFile(Result).Assign(GetExistingDebugLogger);  // optional / if you call only once there should be none to copy any setting fromend; begin  LazDebugLoggerCreator := @CreateDebugLogger;  Then the first call to  Debugln will call you creator, and the logger will be setup. I don't know if the logger will be able to see the argc/argv in the dll. And even if, it may not be possible to give the relevant switches. In that case your creator method can setup hardcoded settings. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i YourLazLoggerFile.LogName := 'log.txt'; Feel free to add it to the relevant wiki pages ---------------------------------------------------------------------- PascalDragon · 2025-01-20 21:06 Quote from: d7_2_laz on January 20, 2025, 08:39:18 pmI'd oftenly used "OutputDebugString" for to trace some stuff, but sometimes there are situations where i couldn't use it (as it requires the "Windows" uses, and that's oftenly not appliable). You can always import that function yourself by copying the declaration. The kernel32.dll is always loaded anyway. ---------------------------------------------------------------------- d7_2_laz · 2025-01-20 21:38 Hi Martin, that sounds very good; i can try to elaborate tomorrow and come back with the result. Some small remarks here: - yes, the plugin's controlling unit has an initialization section as well as a finalization.   Otherwise this shouldn't be a problem either by the method yoo describe. One could do it (LazDebugLoggerCreator := .. etc.) in FormCreate (or plugin's Create) too and that should be sufficient.   As far as Lazarus internal units are concerned and it's about to trace back here in higher levels of the hierarchy why some callback might be _Not triggered_ (*). - "if the logger will be able to see the argc/argv in the dll" -> in my case (plugin for NotePad++) the host application will eat up this argument (it tries to open it as file and claims, this cannot be opened). (*) In this situation (Not triggered) the means of stacktrace, breakpoint & Co. are of limit help. Normally i don't have such situation and so i'm still a noob in the matters of DebugLn. That won't be sufficient for a wiki article. But what I could contribute - if i should succeed - is a kind of summary 'from a noob perspective' as attached document, for any free usage. Hi PascalDragon:, "import that function": that could be, but i'm oftenly within diverse .inc files (and then their includers, and up) and feel, to manipulate here wouldn't be a preferred strategy and it would be better to use the native DebugLn. (Import in the main unit / main form / plugin's pas file wouldn't be a problem. But here's the OutputDebugString already available anyway). ---------------------------------------------------------------------- d7_2_laz · 2025-01-21 14:09 Would the following procedure from a first test basically be correct? 'The first call to Debugln will call your creator' .. but it appears to me it's never entered. I guess it still relies somehow on the command line param (resp. an env. variable). The "LogName" property: might it contain a path particle? Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i interfaceuses ....,    LazLogger.    LazLoggerBase,   // needed for GetExistingDebugLogger    LazClasses;      // needed for TRefCountedObject function CreateDebugLogger: TRefCountedObject;begin  Result := TLazLoggerFile.Create;  //TLazLoggerFile(Result).Assign(GetExistingDebugLogger);  // optional / if you call only once there should be none to copy any setting from  //TLazLoggerFile(Result).LogName := '___aLogfile.txt';  TLazLoggerFile(Result).LogName := 'D:\test\___aLogfile.txt';  // With path, possible? Otherwise, what might be the default path?  outputdebugstring(pchar(' --- check: am i called? '));   // No output in the event log  // A breakpoint on any line in this function won't be entered.end; procedure TXYZDockingForm.FormCreate(Sender: TObject);var .....,  LazDebugLoggerCreator: TRefCountedObject;begin   LazDebugLoggerCreator := @CreateDebugLogger;   DebugLn(' ==== testTXYZDockingForm.FormCreate');  // ... the first call to Debugln will call your creator ---------------------------------------------------------------------- Thaddy · 2025-01-21 14:39 My solution is this: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i {$ifdef mswindows}uses windows;{$endif}procedure DebugStr(const s:string);begin{$ifdef mswindows}  OutputDebugString(s);{$else}  writeln(ErrOutPut, s);// is in system{$endif}end;Crude and only windows and nixes. But it works.... ---------------------------------------------------------------------- d7_2_laz · 2025-01-21 15:11 Thank you for replying Thaddy  ) The essential part in question is: where to place this 'uses Windows'. Imagine you are in a treeview.inc and don't want to change it's includer (here: comctrls.pas)  (in some cases such may end up desastrous if this unit appears in the wrong order). So placing the little helper interimswise directly within the .inc file, this might result in: see image Means, one would be redirected to change the uses of comctrls.pas, or did i miss something? Edit: more generally spoken: we cannot place some other 'uses' within such .inc files, even not directly on the top. (At least i think so) ---------------------------------------------------------------------- Martin_fr · 2025-01-21 15:31 Quote from: d7_2_laz on January 21, 2025, 03:09:04 pmThe "LogName" property: might it contain a path particle? Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   outputdebugstring(pchar(' --- check: am i called? '));   // No output in the event log Path in Logname: I think, yes that is ok. But I would have to test myself outputdebugstring: Where is that coming from? That has nothing to do with debugln and LazLogger. I know you mentioned it before, but I thought you said it was an alternative that you did not pursue (at least not as part of the LazLogger issue). LazLogger use debugln, so to test that log, call Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i debugln('check, am I called'); outputdebugstring goes to Windows API, and afaik will only show up via a debugger, if one is attached. ---------------------------------------------------------------------- d7_2_laz · 2025-01-21 16:00 Martin,  misunderstanding: this "OutputDebugString" at this place mentioned has nothing to do with the DebugLn - it was  simply meant as an additional verification only, that the create-procedure is not called (as a breakpoint herein won't be reached). For me, the need for the DebugLn arises where a "uses Windows" is either not available or not applicable, i.e. iin .inc files. So i thought: stop using this instrument here and use the correct instrument. As the create-procedure obviously won't be called by the code. as next step a direct approach: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TXYZDockingForm.FormCreate(Sender: TObject);var .....,  LazDebugLoggerCreator: TRefCountedObject;  begin   //LazDebugLoggerCreator := @CreateDebugLogger; // Then the first call to  Debugln will call your creator   LazDebugLoggerCreator := TLazLoggerFile.Create;   TLazLoggerFile(LazDebugLoggerCreator).LogName := 'D:\test\___aLogfile.txt';   DebugLn(' ==== test TXYZDockingForm.FormCreate'); Still no log file could be seen (and i'm not really surprised -> why the DebugLn should know about this - here still purely local - logger instance. But it won't make a difference if i define this as a Form's instance variable instead). There's still some connector missing. ---------------------------------------------------------------------- Martin_fr · 2025-01-21 16:34 QuoteCode: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   //LazDebugLoggerCreator := @CreateDebugLogger; // Then the first call to  Debugln will call your creator   LazDebugLoggerCreator := TLazLoggerFile.Create; Putting an object reference into the function pointer => that should crash. So if it doesn't then for some reason your "debugln" does not create a logger => maybe some Logger already exists... Check what "DebugLogger" contains / if it is not nil, then set the LogName to that. Well, if you use LazLoggerDummy then that wont work, because it is a dummy logger, but otherwise it should be the correct class (if it is non nil). If that doesn't help then make sure you use either   LazLogger.debugln   LazLoggerBase.debugln There is a version in LCL, it should forward, but not sure. Or of course again, if LazLoggerDummy is in the way (but really it shouldn't / you would know if you put it there.... ) Otherwise, I am not sure / Sorry can't do a deep dive into that now... ---------------------------------------------------------------------- d7_2_laz · 2025-01-21 21:25 - Which units used: as mentioned earlier today:  LazLogger, LazLoggerBase (for: GetExistingDebugLogger), LazClasses  (for: TRefCountedObject).   (i see, it would be better to keep the pieces together at one place, so below). - "Putting an object reference into the function pointer => that should crash"   But isn't it the same if one does :   "Result := TLazLoggerFile.Create"  and Result is type TRefCountedObject   or one does :  var xyz: TRefCountedObject;  and then   xyz := TLazLoggerFile.Create ?   The question is only: what can we do with such instance of a reference counter, can we operate on it (for me it sounds confusing, and i can only guess).   At least: it doesn't crash (and the object hadn't been nil). - I had also tried with "DebugLogger", but had not success. But now: i tried to show all steps and interims results more detailed. And, doing so, i got a new situation. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i interfaceuses ....,    LazLogger.    LazLoggerBase,   // needed for GetExistingDebugLogger    LazClasses;      // needed for TRefCountedObject  procedure TXYZDockingForm.FormCreate(Sender: TObject);var .....,  LazDebugLoggerCreator: TRefCountedObject;begin// -- Test 1   if DebugLogger = nil then         OutputDebugString(pchar('DebugLogger is nil'))      // Note: OutputDebugString here only as temporary display instrument:   else   begin         OutputDebugString(pchar('DebugLogger LogName = ' + TLazLoggerFile(DebugLogger).LogName )); // "DebugLogger.LogName" would be not compileable         TLazLoggerFile(DebugLogger).LogName := 'D:\__Temp\yy\___aLogfile.txt';         OutputDebugString(pchar('DebugLogger new assigned LogName = ' + TLazLoggerFile(DebugLogger).LogName )); // "DebugLogger.LogName" would be not compileable   end;   DebugLn(' -----  Welcome logfile !'); // -- Or Test 2   LazDebugLoggerCreator := TLazLoggerFile.Create;   if LazDebugLoggerCreator = nil then      OutputDebugString(pchar('LazDebugLoggerCreator is nil'))   else   if TLazLoggerFile(LazDebugLoggerCreator) = nil then      OutputDebugString(pchar('TLazLoggerFile(LazDebugLoggerCreator) is nil'))   else   begin         // This was only temporarely an additional test:          TLazLoggerFile(LazDebugLoggerCreator).Assign(GetExistingDebugLogger);  // optional / if you call only once there should be none to copy any setting from          OutputDebugString(pchar(' Assigned GetExistingDebugLogger;   log name =  ' + TLazLoggerFile(LazDebugLoggerCreator).LogName));       TLazLoggerFile(LazDebugLoggerCreator).LogName := 'D:\__Temp\yy\___aLogfile.txt';      OutputDebugString(pchar(' Assigned new log name:  ' + TLazLoggerFile(LazDebugLoggerCreator).LogName));   end;   DebugLn(' -----  Welcome logfile !'); Attached as images the individual output results  (i did either Test1 or Test2, not both together). One can see: both appear to behave very unsuspicious (no crash, no nil; assignment appeared to be good). Test2 (that was the previous approach) did Not result in a logfile. Test1 - using the existing "DebugLogger" - finally generated the desired log file, and it said:  -----  Welcome logfile ! That means, success so far: useable in the DLL's main unit / form at least seems to be: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i    if DebugLogger <> nil then      TLazLoggerFile(DebugLogger).LogName := ; This can be applied to other forms within the dll app too (uses LauLogger here and doing the same assignment as above. DebugLn then will append to the same log file). So far, so very good! We can defiine the logfile name (in a DLL ->) there, were a command line parameter is not applicable. The second and last chaptor will be: how to make this operational for not self-written units, eg. toolbutton.inc, toolbar.inc etc. (First i'll try to transfer it to the core pas file of the plugin / initialization section, and then see, how far this matters. (Actually a DebugLn in eg. treeview.inc is compilable, but results in nothing. Probably LazLoggerDummy is at work. Tried to compile with custom option -dDEBUG, but that, actually, caused other issues; couldn't compile; range check errors. Need to investigate.) ---------------------------------------------------------------------- d7_2_laz · 2025-01-21 22:16 Oh wow, it works° Quote --- Hello logfile for FormCreate  ------DebugLn in treeview.inc.  TCustomTreeView.UpdateHotTrack  ------DebugLn in treeview.inc.  TCustomTreeView.UpdateHotTrack Need to see why it works in the small demo and not in the original app, but those are details (i hope). Basically it's operational as desired, perfect! What did i need to to other than to assign the log file path/name for the DebugLogger as described (in FormCreate of the main form)? Nothing! Thank you Martin for to have pointed to that! If i succeed to transfer it to the original more complex app, i'll come back here with a small 'noob howto summary'). ---------------------------------------------------------------------- Martin_fr · 2025-01-21 22:41 Quote "Putting an object reference into the function pointer => that should crash"   But isn't it the same if one does :   "Result := TLazLoggerFile.Create"  and Result is type TRefCountedObject   or one does :  var xyz: TRefCountedObject;  and then   xyz := TLazLoggerFile.Create ?   The question is only: what can we do with such instance of a reference counter, can we operate on it (for me it sounds confusing, and i can only guess). Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i type  TLazDebugLoggerCreator = function: TRefCountedObject;var  LazDebugLoggerCreator: TLazDebugLoggerCreator = nil;  LazDebugLoggerCreator is a function reference. I don't even understand how you get the assignment to compile. I get: Code: Text  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i project1.lpr(8,43) Error: Incompatible types: got "TLazLoggerFile" expected "" You can't even do Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i LazDebugLoggerCreator := @TLazLoggerFile.Create;because the constructor has a different calling convention. (Well it compiles in mode delphi / but no idea / not tested) With LazLogger there are very few cases where you may want to refcount it yourself (and its not refcounted automatically, this needs to be called by code explicitly). If you do stuff that replaces the logger, then you could get a copy before, and could protect it from being destroyed. I have some code that uses multiply loggers => basically a testcase, where the tested code use LazLogger, and the test does capture all output. But then the test has a 2nd logger for its own logging. Quote using the existing "DebugLogger" - finally generated the desired log file, and it said:If you have an existing logger (that is, apparently initialization was called? Or don't know...) then the LazDebugLoggerCreator is not invoked => its only done once, when there is no logger yet. QuoteThis can be applied to other forms within the dll app too (uses LauLogger here and doing the same assignment as above. DebugLn then will append to the same log file). They all share one instance of the logger. And if you set the same logfile everywhere then once it is set, the other assignments don't change anything. LCL units use LazLogger too => so they use the same instance. Once any of your code has set the logfile name, then they should start logging. You just need to see what the earliest hook is where you can do that. In normal apps that is initialization sections. In dll, I don't know. I didn't expect them to be run. But if the logger has already an instance then maybe the do? ---------------------------------------------------------------------- d7_2_laz · 2025-01-22 13:18 Martin, yes, by your quote of the type definition for TLazDebugLoggerCreator i understand better. what you mean. (I was wondering from the beginning anyhow how should have worked, to gain properties for a file object from a ref counter. even when doing a cast to TLazLoggerFile). Why such even compiles? I'd guess too it's by mode delphi (yes. this had been set via compiler options > parsing, from an earlier delphi conversion). I don't plan to do anything else with the ref counter. Nor to have multiple logs. QuoteIf you have an existing logger (that is, apparently initialization was called? ...) I had not needed yet to step further to the initialization (nor in the main form, neither in the plugin's core code) what might be somehow related to the DebugLogger object. It was simply there and acessible, whoever it had created. I just checked: it's even available in a release build mode). QuoteAnd if you set the same logfile everywhere then once it is set, the other assignments don't change anything. Yes, that was the plan .. eg. main form and another form do set LogName to the same location and each's output will be appended there. QuoteLCL units use LazLogger too => so they use the same instance.Fine for me, and btw equals the behaviour of OutputDebugString / event log, where output from diverse sources might appear. Btw, in my test log file i had also entries from other sources. Eg. from a  debugln('HandleListViewOwnerDataHint') in win32wscustomlistview.inc, being set in the LCL code. QuoteYou just need to see what the earliest hook is where you can do that. In normal apps that is initialization sections. In dll, I don't know. I didn't expect them to be run. In the plugin DLL those initialization sections are there and used as well (for other purposes, not Debug yet). My first thought earlier had been i'd need to hook it into initialization, but, until there might be further needs, doing it in FormCreate is sufficient. ---------------------------------------------------------------------- Martin_fr · 2025-01-22 15:50 Quote(I was wondering from the beginning anyhow how should have worked, to gain properties for a file object from a ref counter. even when doing a cast to TLazLoggerFile). It's not a "ref counter". It is an object (instance of a class) => That class inherits from the class TRefCountedObject. => That the result is not more specific is probably some workaround for the dummy logger... Otherwise it should probably return a TLazLogger object. QuoteIn the plugin DLL those initialization sections are there and used as well (for other purposes, not Debug yet). My first thought earlier had been i'd need to hook it into initialization, but, until there might be further needs, doing it in FormCreate is sufficient. I wasn't sure if those "unit initialization" sections would be run in a dll => if / since they do, the logger will be created correctly. So, then yes, no need to deal with LazLoggerCreator. Just use the instance available through DebugLogger. Also, you may be able to give the name for the logfile via environment. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   FEnvironmentForLogFileName   := '*_debuglog'; The * is replaced by the exe name => without exe extension. That is, if the code in the dll can manage to get the exe name, or whatever it gets for that. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i         ChangeFileExt(ExtractFileName(ParamStrUTF8(0)),''),   [rfReplaceAll,rfIgnoreCase]); ---------------------------------------------------------------------- d7_2_laz · 2025-01-22 20:02 About 'initialization in a DLL' see the attached image from a quick test as illustration. About 'name for the logfile via environment': yes, i did read about this possibliity, but it hadn't been my top favorite to pursue (probably a matter of taste, although i can imagine there are good reasons tu use the environment variable for certain use-cases). To retrieve from inside the dll code the path and name (without extension) of the 'host applicaton' shouldn't be a problem. Martin, thank you so much for having involved in this matters and exchange so intensively! I hope that other uses might benefit from it too (but i'd think so). From my side i'll try to summarize my findings as of today, for to bring the pieces together at one place. My personal summary regarding the "HowTo use DebugLn in a stand-alone exe and a DLL" A.  I want to use "DebugLn" in my stand-alone executable with results in a log file. What i'd need to do? 1a. In the main unit: include "LazLogger" in the use claus Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i      uses  ........ ,      {$IFDEF DEBUG}        LazLogger;      {$ENDIF}1b. In any other dependent/included unit, in which DebugLn should be used, include "LazLoggerBase". See     https://wiki.freepascal.org/LazLogger 2.  Project Options > Compiler Options > 'Config and Target':  'Win32 gui application (-WG)'  =>  no     (Otherwise a log file might be created, but you'll see no output from DebugLn in it) 3.  Run Parameters > Command line parameters :  --debug- log= Note: output written with DebugLn will be added to the log file by default. It survives sessions. If one wants to have a 'fresh' logfile for each session, there are already existing properties available, or simply remove the log file before a session. B.  I want to use "DebugLn" in my plugin DLL and want to have a log file.     This relates to other forms/units herein too, as well as to embedded/included code, like .inc files. What i'd need to do? 1a. In the main unit, or other forms. or "used" units ("use myUtils"), include "LazLogger" in their use clause if you want to use DebugLn herein: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i uses  ........ ,     {$IFDEF DEBUG}        LazLogger     {$ENDIF} 1b. In any other code being within the scope of the inheritance tree of your controls, in which DebugLn should be used, there's nothing else to do, see 3b. 2.  An uncheck of 'Win32 gui application' (in Compiler Options > 'Config and Target') is _Not_ needed here, afais. 3.  But what could be the path and name of the debug log file? With "Run Parameters > Command line parameters" one won't achieve.     The 'Host application' will interprete the parameter, and it will it interprete in a way one cannot predict.     Afaik, one cannot pass command line parameters to a DLL, or, otherwise, how to define such in the project settings?      Fortunately a predefined "DebugLogger" object does already exist that allows to add/append contents from diverse sources. 3a. Within your DLL app, in each independent form or an "use"d unit wherein DebugLn should be used, set at an appropriate place: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i     procedure TMyFormXYZ.FormCreate   // Example     .....           if DebugLogger <> nil then         TLazLoggerFile(DebugLogger).LogName := ;3b. For each unit attached via class inheritance, in which DebugLn should be used. there's nothing special to do.     It will be seen via the inheritance tree (at least that would be my interpretation).     Example: you have a listview in the form, and can now place a DebugLn in a customlistview.inc without further measures. 3c. That does even work when using build mode Release as well, as far as i can actually see! (without having used "IfDef Debug" of course) 4.  Please note that there is also the possibility to set log file name via an environment variable (i didn't test this yet). ---------------------------------------------------------------------- d7_2_laz · 2025-01-22 20:40 ... and having available this means now, i was able to find the causing line for the issue i mentioned at the beginning within 5 minutes - that the toolbuttons of my dockingform stopped drawing, when i openend another docking subform by toolbutton click. It's due to graphiccontrol.inc, TGraphicControl.WMPaint: Canvas.Lock / Canvas.UnLock. If i comment those just for test, the issue disappears. That does absolutely not mean i'd know how to best fix it, but at least i now could locate the reason ...