New cross-platform plugin template for Delphi developers
-
NICE!
Would you like that template linked from the next edition of the User Manual? Right now, it links to the original template, but if you want me to, I could add your updated 32bit/64bit template along side it.
-
@rdipardo said in New cross-platform plugin template for Delphi developers:
from his DBGP plugin
So is there any hope of resurrecting DBGp Plugin for 64-bit now?
Please?
Cheers.
-
Would you like that template linked from the next edition of the User Manual?
That was the intention, yes. But since the Scintilla 5 migration has suddenly broken ground, I should probably take the time to revise the include files first.
-
@michael-vincent,
I haven’t forgotten your request.
At this point I can confidently say this much: DBGP is a masterpiece, full stop. But it’s also a work of its time and place. The (Delphi-only) JVCL libraries it depends on for those fancy tabbed dialogs were written for Delphi 6. (See my remarks about porting NppCalc.) Throw a 64-bit pointer into the mix and the whole thing blows up, often at strange times, like application shutdown. Damjan himself found no better solution for this issue than the following self-confessed hack:{ This is hacking for trouble... We need to unset the Application handler so that the forms don't get berserk and start throwing OS error 1004. This happens because the main NPP HWND is already lost when the DLL_PROCESS_DETACH gets called, and the form tries to allocate a new handler for sending the "close" windows message... } procedure TNppPlugin.BeforeDestruction; begin Application.Handle := 0; Application.Terminate; inherited; end;
Even if this could be ported to 64-bit, I don’t know how it could conscionably be released for general consumption until it was thoroughly tested with a (modern) PHP server. All of which precedes the Scintilla compatibility issues . . .
-
@rdipardo said in New cross-platform plugin template for Delphi developers:
At this point I can confidently say this much: DBGP is a masterpiece, full stop. But it’s also a work of its time and place.
Elegant statement. :-)
Cheers.
-
So is there any hope of resurrecting DBGp Plugin for 64-bit now?
It’s far from stable, but at least it can step into a hello-world script without crashing:
The binaries are online for testing: https://bitbucket.org/rdipardo/dbgp/downloads
I can’t say when or if they’ll be ready for the central plugin repository:To ease migration, I sought out the same vintage version of the JEDI component libraries that Damjan used, but there was no getting a modern IDE to compile such old versions of the Borland sockets library or VirtualTreeView, so I linked in newer ones, at the risk of some undefined behaviour. I’ve already noticed a problem with tooltips leaving artifacts on the screen.
A new JVCL installation means nuking the previous one, or else installing a second IDE first. That can wait until the more critical bugs have been worked out.
-
Updates
1. Template compatibility with future N++ versions
As announced, Scintilla’s
*FULL
messages and structures are slated to completely replace the non-*FULL
equivalents in Notepad++'s code base, somewhat ahead of Scintilla’s own timeline, in which version 6.0 would deprecate the non-*FULL
messages, with 7.0 finally dropping them.As explained in the template README, plugins developed from the
api-v5.2.3
tagged revision or later will provide the new definitions.2. Deprecation of all current DBGp versions
Since DBGp calls into
SCI_GETTEXTRANGE
, the removal of Scintilla’s non-*FULL
APIs will cripple the core functionality of all released versions, both 32- and 64-bit, in a future N++ release.The next version of DBGp will be forward- and backward-compatible: https://bitbucket.org/rdipardo/dbgp/commits/6e09a9d5586827f9d43f5f1403cbcd627837326b
-
I’m thinking about to bring an old NPP plugin i programmed times ago with Delphi7 to 64bit, using Lazarus 3.6 (and up) for Notepad++ recent version 8.7.2. resp. 8.7.4.
As i got aware of the DelphiPluginTemplate from the rdipardo repository, i tried it, and got this to work within some minutes. Wow!
However i did run into some beginner hurdles; hopefully it’s allowed to ask this here:- Size and position of the docking window:
// HelloWorldDockingforms.pas: procedure THelloWorldDockingForm.FormCreate(Sender: TObject); begin ....... //self.NppDefaultDockingMask := DWS_DF_CONT_RIGHT; self.NppDefaultDockingMask := DWS_DF_CONT_LEFT; self.Width := 440;
Both have no effect - docking window stays right, and the size stays unchanged. Why?
- About dark mode:
I was interested to see how a treeview and a listview would look like regarding the themeing. The treeview stays unthemed (light), the listview themed (dark)).
Why?
Background: if it should not be possible to theme the treeview in dark, alternatively i’d think about
to switch this theming off (possible at a central place?) and to use the themeing mechanism i’m using in other Lazarus programs (this is from the “metadarkstyle” repository. -
@klaus101 said in New cross-platform plugin template for Delphi developers:
[The] docking window stays right
If you previously loaded the plugin with the
DWS_DF_CONT_RIGHT
bitmask or something else, the position has been saved by the Docking Manager, which will reload the form with the same parameters, regardless of any changes you make in the code. There’s a comment in the template mentioning this. You need to do the following after rebuilding the plugin:- Locate the XML node
/NotepadPlus/GUIConfigs/GUIConfig[@name="DockingManager"]/PluginDlg[@pluginName="HelloWorld.dll"
in$(PORTABLE_NPP_DIR)\config.xml
(portable Notepad++) or%AppData%\Notepad++\config.xml
(fully installed Notepad++), e.g.,
<!-- ... --> <GUIConfig name="DockingManager" leftWidth="200" rightWidth="582" topHeight="200" bottomHeight="200"> <PluginDlg pluginName="HelloWorld.dll" id="2" curr="1" prev="-1" isVisible="yes" /> <ActiveTabs cont="0" activeTab="-1" /> <ActiveTabs cont="1" activeTab="0" /> <ActiveTabs cont="2" activeTab="-1" /> <ActiveTabs cont="3" activeTab="-1" /> </GUIConfig> <!-- ... -->
- Delete (at least) the
PluginDlg
child element, or the wholeGUIConfig
parent element
the size stays unchanged.
It looks like Notepad++ docks forms with a hard-coded initial width (or height, if docked top or bottom). The same Docking Manager mentioned above will save any changed dimensions after the user stretches the form. I guess the form’s design-time dimensions only make a difference when it’s undocked (or “floating”).
I was interested to see how a treeview and a listview would look like regarding the themeing.
Your mileage will vary depending on which component library you’re using. My experience has been that LCL components are more adaptable (e.g., buttons and scrollbars go dark when subclassed with
NPPM_DARKMODESUBCLASSANDTHEME
, provided thatDWS_USEOWNDARKMODE
is not used; Delphi VCL buttons and scrollbars will not change theme without a custom draw procedure, which the template does not provide; see:For involved use cases, you will probably have to send the
NPPM_DARKMODESUBCLASSANDTHEME
message with a handle to each sub-component to achieve a consistent appearance; see here for an example. In other words, I would not expect the design-time components in metadarkstyle to work out of the box; your forms are going to be drawn by the same Win32 APIs that Notepad++ uses, not the Lazarus runtime. - Locate the XML node
-
@rdipardo, great thanks for this very detailed initial help!
- Position and size:
For the size i could change it by modifying the width parameter(s) in "<GUIConfig name=“DockingManager”.
For the position i could change it by a) removing the “PluginDlg” line (-> dock. window will then appear at bottom) and b) then modifying the newly recreated value: from curr=“3” to curr=“0”. Just as:
<GUIConfig name="DockingManager" leftWidth="390" rightWidth="340" topHeight="200" bottomHeight="200"> <PluginDlg pluginName="HelloWorld.dll" id="2" curr="0" prev="-1" isVisible="yes" />
Solved …
- TreeView’s dark mode
First i try with the punctual SendMessage of NPPM_DARKMODESUBCLASSANDTHEME with the treeview’s handle as of the example.
procedure THelloWorldDockingForm.FormCreate(Sender: TObject); var DmFlag: Cardinal; DarkModeColors: TDarkModeColors; begin // -- Not work. -> Do changes in config.xml instead: self.NppDefaultDockingMask := DWS_DF_CONT_LEFT; // <PluginDlg pluginName="HelloWorld.dll" ... curr="0" self.Width := 390; // GUIConfig name="DockingManager" leftWidth="390" //inherited SubclassAndTheme(DmfMask); // Should have been already done if Npp.IsDarkModeEnabled then begin DmFlag := dmfInit; // dmfHandleChange // DmFlag: dmfInit or dmfHandleChange SendMessage(Npp.NppData.NppHandle, NPPM_DARKMODESUBCLASSANDTHEME, DmFlag, Self.TreeView1.Handle); DarkModeColors := Default(TDarkModeColors); // Only for test (i don't believe it influences here, Colors are already set) Npp.GetDarkModeColors(@DarkModeColors); TreeView1.Color := TColor(DarkModeColors.PureBackground); TreeView1.Font.Color := TColor(DarkModeColors.Text); ....
- Right, TreeView’s scrollbars are not changed yet.
- TreeView’s unselected item’s font color is a Todo too.
Basically i don’t know if i understand your last sentence right (" … not the Lazarus runtime").
The treeview targeted at the end is a LCL custom treeview, with custom drawing for specific things, but regarding the dark theme mainly using the metadarkstyle mechanism (Windows theme API operations based on UXTheme.dll). But only at runtime (the package is intentionally Not installed).
The component (beneath other controls) does all dark stuff itself with the help of metadarkstyle.
So, my alternative approach would be: switch, for this docking window, all NPP dark theme handling off and let me do that with metadarkstyle and custom drawing.
Is this possible? - Position and size:
-
A little thing besides: if i remove (set in comment), in the demo some menu items from the plugin’s menu,
so that e.g. the menu only contains ‘Load docking form’, separator, About box, then i get strange results
at next restart(s). E.g. N++ starts with the plugin’s About box. Even after having removed the session.xml.
Somewhere a limit, or an index to a last used id, or a defaulting action might be stored. But where? -
Check your config.xml.
The ID of your plugin must match the zero-based ID of the function array that you specify in getFuncsArray. -
@Ekopalypse said in New cross-platform plugin template for Delphi developers:
The ID of your plugin must match the zero-based ID of the function array that you specify in getFuncsArray.
Hm - but in the demo DLLExports’ getFuncsArray is provided, but never is called afais?
Where might one have set this ID unintentionally?Try the Demo (compiled with Laz. 3.6 64bit), remove from THelloWorldPlugin.Create some of the AddFuncItems except Load docking form, separator and About and restart one or two times …
-
getFuncsArray is called by Npp itself to find out which methods your plugin provides and the ID defined there must match the one when the dialog is registered.
Unfortunately I have no idea about Delphi. -
As a quick&dirty check i wrote a “halt” inside the function, but the process didn’t stop it’s execution; so i assumed it’s not called from outside.
-
that sounds strange if
halt
is supposed to terminate the running process, but I can assure you that without calling getFuncsArray through Npp you will not see your plugin being loaded. -
@Ekopalypse Coming back to npp plugin stuff again first time after, oh, 11 years later (now with Lazarus), i’m now still unsure about debugging it. A second quick&dirty check instead therefore for now, had been to skip the contents of function getFuncsArray completely - for to see if anything changes at all. No changes in behaviour …
-
@klaus101
Unfortunately, I can’t help you with Delphi, but I will try to explain what is going on under the hood.
Npp calls getFuncsArray in your plugin to find out which functions and therefore which menu items your plugin provides.
As the name suggests, this is an array of FuncItem structures and in each FuncItem structure a field called _cmdID is filled with an ID and a field _pFunc with the function pointer of the respective plugin function.
Later Npp checks via the config.xml and the DockingManager section whether your plugin is listed there and if so, the ID from the config.xml is compared with the ID from the FuncsArray and if something is found, the corresponding function is called via the function pointer.
If you have now removed all the menu items (FuncItems) except for the about entry and an entry with ID 0 for your plugin is found in the config.xml, the about function pointer is then called. -
@Ekopalypse said in New cross-platform plugin template for Delphi developers:
that sounds strange if
halt
is supposed to terminate the running process […]Like @klaus101 observed, it does not kill the
notepad++.exe
process because the Free Pascal version of the template does not own the main application handle (the way the Delphi version — unfortunately — does, for reasons that go back to Delphi’s history as the precursor to .NET, as .NET plugins likewise share the notepad++ process ID).@klaus101 said in New cross-platform plugin template for Delphi developers:
i’m now still unsure about debugging it.
That’s a problem with DLLs in general and with plugin DLLs in particular. For example, the
getFuncsArray
function is called bynotepad++.exe
through a function pointer, i.e., a memory address pointing to where the compiled Pascal code is sitting in the DLL. I don’t know if the Lazarus IDE’s debugger can even trace that; you would have to first of all attach it to the runningnotepad++.exe
process, and the function call happens so early you will miss it.In general, the stuff in the template’s DllExports unit should be considered read-only, except for the
DLL_ATTACH
hook where you construct your plugin object.As for tooling, I suggest using GNU Debug if you want to monitor every step of the DLL lifecycle. My personal setup uses VS Code with the C++ Tools extension. All I do is create a launch profile based on the
cppdbg
template, e.g.,{ "version": "0.2.0", "configurations": [ { "name": "Notepad++", "request": "launch", "args": [], "stopAtEntry": false, "cwd": "${workspaceFolder}", "type": "cppdbg", "MIMode": "gdb", "miDebuggerPath": "${env:LAZARUS_DIR}\\mingw\\x86_64-win64\\bin\\gdb.exe", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true }, { "description": "Set Disassembly Flavor to Intel", "text": "-gdb-set disassembly-flavor intel", "ignoreFailures": true } ], "logging": { "moduleLoad": true }, "program": "${full_path_to}\\notepad++.exe" } ] }
Save this as
.vscode/launch.json
and open the root source folder in VS Code. Replace"${full_path_to}\\notepad++.exe"
to the one where the plugin is installed. Every Lazarus toolchain comes with a compatible version of gdb, so replace${env:LAZARUS_DIR}\\mingw\\x86_64-win64\\bin\\gdb.exe
with the actual path on your PC.After that, you should be able to set breakpoints (by clicking on the line number margin) and start the editor with the
F5
key. -
@rdipardo and @Ekopalypse,
many thanks for your valuable explanations!In Lazarus, for debugging DLLs, one can set a ‘host application’ to execute with; so one is able to set breakpoints in the pascal code etc.
All plugin templates i tried before, crashed … at addresses outside the scope of the pascal code though.
Your template rdipardo was the first one i found that showed up as a really fine working example :-) :-). Unfortunately, after setting the host application, the “run” functionality is not enabled. For all crashing templates, it had been enabled. I just kicked a question in the Laz. newsgroup what’s required to enable it.Could you at least, for the moment, confirm (or maybe not confirm, so that’s my fault) if you could reproduce? By commenting a few lines, and retry?
I try to describe more precise what happens:
-
First, a session with the unchanged / full menu. - config.xml says:
<PluginDlg pluginName=“HelloWorld.dll” id=“2” curr=“0” prev=“-1” isVisible=“yes” /> -
Then, shrinked menu creation to the three items “Load docking form”, Separator, “About”
-
Removed the old entry for pluginName=“HelloWorld.dll” from the config.xml for to have a clean restart
-
Start NPP with the new compiled DLL and click menu item “Load docking form”, and close NPP again
-
NPP rewrites the new line:
<PluginDlg pluginName=“HelloWorld.dll” id=“2” curr=“0” prev=“-1” isVisible=“yes” />
So far all looks fine! -
Now restart NPP again … and, what will appear at first, is the demo About box.
-
Afterwards, config.xml entry showed up unchanged:
<PluginDlg pluginName=“HelloWorld.dll” id=“2” curr=“0” prev=“-1” isVisible=“yes” />
I understand that, if some first pointers a invalid, but the last one (for About) keeps to be valid, this last one will be used.
But who stored the unbalanced info where?–> Btw: i fear that this specific issue - and the question about the dark theme - will run extremely out of scope of this specific topic, which wants to inform about the new template. Better to continue in/as a new topic if needed?
-