Plug-in, Switch On, Fall Over: Adventures with the Open Tools API

fibplus-8.0.0.zip FIBPlus 是一套面向 Delphi / C++Builder 的 Firebird、InterBase 数据库访问组件,特点是直接走原生客户端 API,不经 BDE/ODBC,速度快且可控性强。8.0.0 版本主要适配 Delphi 2009 及以后的版本(Delphi 6–2007 需用 7.9.x),核心组件包括 TpFIBDatabase、TpFIBTransaction、TpFIBDataSet 等,支持精细事务管理、本地缓存、BLOB 处理和备份恢复等数据库服务。该库原为 Devrace 的商业组件,现由社区在 GitHub 的 madorin/fibplus 项目开源维护,建议从官方仓库获取源码而非第三方网盘压缩包。安装时解压后用对应版本的工程组(10.4+ 统一为 `FIB.groupproj`)Build 全部包,再 Install `dclFIB` 并在 Library 路径加入 Source 目录即可。新项目中若需官方技术支持,也可考虑 FireDAC、IBDAC 等更现代的替代方案。 立即下载
 

Email: info at xpro.com.au
Phone:
+61 3 9479 3988 (Don) +61 3 9479 3932 (Paul)
Fax: +61 3 9479 1675
Street/Postal:
Suite 3B La Trobe R&D Park,
2 Research Ave, Bundoora Vic 3083, Australia


Download paper, slides and code (2753kB)

Plug-in, Switch On, Fall Over:
Adventures with the Open Tools API

by Paul Spain,  The Excellent Programming Company, Melbourne, Australia.
Contact: paul@xpro.com.au  Internet:
http://www.xpro.com.au/  

Introduction

The Open Tools API, a.k.a. Open Tools, or simply OTA, is the third-party plug-in architecture provided by Borland for its Rapid Application Development (RAD) tools. There is a common API for the Delphi, Kylix and C++Builder tools, and a distinct, yet similarly named, API for the JBuilder product. Open Tools for JBuilder is not discussed here.

Using the OTA, you can write modules that extend and customise the Integrated Development Environment (IDE) of Borland’s RAD Tools. These modules take the form of dynamic link libraries (DLLs) or design-time packages for the Win32 products, and shared objects under Linux.

As DLLs are a standard binary format, you can write Win32 plug-ins in any language that supports function pointers, and associated tools that, obviously, can produce DLLs. However, there are significant advantages in producing your plug-in as a proprietary Borland design-time package - more on this later…

Unless noted otherwise, all subsequent references to the OTA indicate the version shipped with Delphi 5.

I use the term “plug-in” here, whereas Borland has used “expert” and more recently “wizard” to describe these modules. To my mind, “wizard” implies a particular cookbook type of plug-in, which automates a manual process; but the OTA has evolved into something much broader. Also, the term “plug-in” has much wider currency in the developer community.

Code examples

There is an associated zip archive containing all the examples referenced in this paper. After unzipping the archive, refer to the file BorCon2001/OpenTools/README.TXT for instructions on loading and running the examples.

Most of the examples are licensed under the Lesser GPL Open Source licence, available for your perusal at http://www.gnu.org/copyleft/lesser.html.

…Approximating in plain English: You are free to use, modify and distribute this software in open source and proprietary applications,  provided you make this source code, including any modifications you make, freely available under the same Lesser GPL licence.

History

Delphi 1 shipped with a simple Expert API, manifested as four files within the Visual Component Library (VCL) source directory. It facilitated the creation of custom forms and project types, and other Experts that appeared as new items on the IDE Help submenu. There was also an interface for a Version Control System, which simply created a new submenu on the IDE main menu. Experts gained access to IDE facilities through a global ToolServices object.

Delphi 2 saw the birth of the OTA, in its own ToolsAPI source directory. Four new files augmented Delphi 1’s Expert API, with support for form designers, property editors, new menu items and virtual file systems. It also provided access to the contents of the file buffers within the IDE, and provided notifications for IDE events, such as files and projects opening and closing.

Delphi 3 introduced packages, some new methods, and some reorganisation.

Delphi 4 saw a move from an object-based API, to interfaces, the new language feature. The interface names and methods closely followed the classes of the earlier OTA. There was a jargon switch from Experts to Wizards, and the monolithic ToolServices object was split into a collection of smaller Services interfaces. These are accessed by querying the global interface variable, BorlandIDEServices. New features included debugger integration with access to the CallStack, breakpoints, and processes and threads on local and remote hosts. The MessageView was surfaced, and new Notifier interfaces to deal with IDE events, and closure of transient IDE artefacts. The new interfaces were wholly contained in the new file ToolsAPI.pas. Apart from DsgnIntf.pas, which contained the property editor and form designer code, all the files for the class-based OTA were deprecated.

Delphi 5 saw new interfaces introduced for keyboard bindings, search and replace, editor form access, selected text blocks, IDE automation, extended breakpoint support and ToDo lists.

Kylix introduced CLX (cross-platform component library) support.

I have had little time to explore the Delphi 6 OTA, but the first thing I noticed was the removal of the deprecated class-based OTA.

Future

Coding with the OTA will give you a different perspective on your Borland RAD tool. These polished performers can look pretty flaky from a plug-in developers perspective. Open Tools is an under-resourced area of Borland R&D. It appears to be a very part-time project of one (albeit very talented) engineer, Allen Bauer, kept otherwise busy as the Delphi/C++Builder IDE R&D Manager. Developer relations toes the very reasonable line that they will devote resources in accordance with customer requests. This leaves the OTA in a Catch-22 situation: the unimplemented features, long-standing bugs and dearth of current documentation make it difficult to generate the critical mass of third-party developers necessary to properly resource OTA R&D. Furthermore, the OTA is essentially unused within the RAD tools. Compare this with the robust JBuilder Open Tools API, which is the backbone of that very cool product.

However, information available via the Borland Community web site (http://community.borland.com), surrounding the release of Kylix and Delphi 6 indicates that there is a progressive conversion to interfaces within these products. We can hope that these interfaces will closely align with the OTA, or eventually be surfaced as a replacement. I have also heard at BorCon Asia Pacific 2001 that Open Tools will receive more attention in the Delphi 7 release, so we shall have to wait and see.

DLLs or Packages?

In common with most plug-in architectures, OTA plug-ins are dynamically linked binary modules. As previously mentioned, these are either common DLLs or Borland design-time packages, as introduced in Delphi 3.

The advantages of DLLs:

Use non-Borland languages and tools to produce a plug-in DLL.

Legacy support for Delphi 2 through to Delphi 5 from a single code base using the deprecated class-based OTA.

Port plug-ins written in C++Builder to Delphi.

Separate language namespace. Packages share namespace with the VCL and all other installed packages. Religiously using a unique identifier prefix and/or extracting common code to a run-time package can help to solve this problem.

The advantages of design-time packages:

They can be loaded and unloaded repeatedly from the IDE, which makes the edit-compile-run cycle considerably shorter. Conversely, DLL plug-ins are fixed in memory for the lifetime of the RAD host application.

Access to the same instances of the VCL and run-time libraries as your RAD host – you can directly manipulate global objects and data, such as the Application object. Such direct manipulation is not officially encouraged… of course!

Access to NTA (Native Tools API) interfaces for directly manipulating IDE VCL objects  …the nice way!

Access to data members exposed by other packages, whereas common DLLs only export functions/procedures.

Which tool for development?

Delphi, C++Builder and Kylix support the OTA, and all products use packages. However, as I understand it, packages created in Delphi will run without modification in C++Builder, whereas packages created in C++Builder won’t work in Delphi. Kylix is another kettle of fish. The process involves creating your plug-in in Delphi, and recompiling in Kylix, but you should read Ray Lischner’s article on the Borland Community web site. See the Resources section at the end of this paper.

Bottom line… if you want to easily write plug-ins that will work in Delphi, C++Builder and Kylix, develop them in Delphi.

All the examples in this paper are design-time packages for Delphi 5 and 6. Usually, wherever I refer to Delphi in the text, you can substitute your RAD tool of choice.

Structural outline

The following diagram represents the relationship between design-time package plug-ins and the RAD tool IDE. Design-time packages share the libraries linked to the IDE, but can only access the non-visual artefacts of the IDE via the Open Tools API.

Plugging-in without the OTA

As mentioned earlier, a feature of design-time packages is sharing your RAD host’s VCL and run-time libraries. This feature allows us to plunder all the global objects and variables in the libraries, such as Application, Screen, and Database, without writing a jot of OTA code. This is something of a double-edged sword, as there is no guarantee of the continuity of the IDE implementation between versions, or even point releases within a version.

The entry-point for all packages is a global procedure, Register, (case-sensitive) [1] . You could also use an initialisation section, but the IDE is guaranteed to be ready for you by the time it calls your Register procedure. Any cleanup required for the package that isn’t handled in destructors should occur in a finalization section.

Example: NoOTA.dpk

This package changes some of the global objects and variables in the VCL and RTL that are shared with the host application, Delphi. In the Register procedure in main.pas, we change the colour of Delphi’s main form, the menu bar font, and the Windows build number as reported in the Delphi About… box. These changes take effect as the package is loaded by Delphi. The changes are reverted by the finalization section, which executes when the package is unloaded.

…

interface

// Standard Delphi package entry point
procedure Register;

implementation

uses
  Graphics,     // TColor
  SysUtils,     // Win32BuildNumber
  Forms;        // Application

…

procedure Register;
  begin

  // Preserve original values
  …

  //
  // Let's give Delphi a facial and upgrade Windows!
  //

  // Manipulate global VCL global Application object shared with Delphi
  Application.MainForm.Color := clLime;

  // Manipulate global VCL global Screen object shared with Delphi
  Screen.MenuFont.Name := 'Showcard Gothic';
  Screen.MenuFont.Height := 24;

  // Manipulate global RTL global variable shared with Delphi
  SysUtils.Win32BuildNumber :=  3000;
  end;

initialization
finalization

// This code will execute when the package is unloaded
// Restore old values and free resources
…
end.

Playing safe …and smart

An interface is a contract between the implementer and the user. The contents of an interface should be immutable once it has been published. But… the OTA is no exception to the list of exceptions! Regardless, using the OTA interfaces is your best bet for minimising the amount of work required to port your plug-in across versions and tools.

Having said that, it’s time to explore the landscape that is the Open Tools API!

OTA Architecture

The OTA is a collection of source files, stored in the ToolsAPI subdirectory of the Source directory of Delphi or C++Builder. For Delphi 4&5, the only relevant files are ToolsAPI.pas and DsgnIntf.pas. All the other files are the previous class-based OTA, now deprecated, and finally expunged in Delphi 6.

ToolsAPI.pas is your first port of call. It is a bulging swag of interfaces, a helper base class, and a few global variables and functions. Of primary concern are the interfaces. Some are implemented by your RAD tool and accessed via the Services interfaces. Others, including Wizards and Notifiers, are implemented by you, the plug-in author.

Let’s start with a simple example OTA plug-in…

Example: HelloWorld.dpk  …like there’s a choice here!

This plug-in is as simple as it gets, but demonstrates the essential elements for integrating with the Borland IDE. It installs a new menu item on Delphi’s Help submenu. Choosing that menu item will add a ‘Hello World!’ message to Delphi’s MessageView window.

I will use the term “wizard” to describe the plug-in class herein, as it ties closely to the OTA naming scheme. These code fragments are taken from Simple.pas.

interface

…

The entry point for any and all Borland packages, Register…

procedure Register;

implementation

All the Open Tools interfaces are declared in one file, $(DELPHI)/Source/Toolsapi/ToolsAPI.pas. The path to this file is not included in the factory-default search or library paths. You’ll need to add it to the Search Path in Project Options for each plug-in you write, or you can add it to the Library Path in your Environment Options, and forget about it thereafter. If you plan to distribute your source to other developers, add it to the Search Path in Project Options and distribute your packages’s CFG and DOF files.

uses ToolsAPI;

Note that our wizard, TSimpleWizard, is declared within the implementation section of the unit. It is never used outside the scope of this unit, so it can be declared here to maximise encapsulation. It also reduces the package size, as there are fewer exported symbols. This is not a necessary requirement for a wizard, just good style, IMHO!

TSimpleWizard inherits from TNotifierObject, a helper class defined in ToolsAPI.pas, which inherits an IUnknown implementation and provides a stubbed (empty methods) implementation of IOTANotifier. This is a very helpful helper class, as most wizards only need to meaningfully implement a subset of IOTANotifier. Our wizard explicitly implements IOTAWizard and IOTAMenuWizard, as we want the wizard to appear as a menu item (on the Help submenu)

Type

  TSimpleWizard = class(TNotifierObject, IOTAWizard, IOTAMenuWizard)

    Private

As a rule of thumb preventive measure, I implement interface methods as private or protected methods within the implementing class. Visibility doesn’t matter for interface implementation methods, so we are only blocking direct public usage of the object methods. Allowing access to the implementing object within the same scope as the interface will often lead to invalid reference bugs.

    // IOTAWizard implementation

    function GetIDString: string;
    function GetName: string;
    function GetState: TWizardState;
    procedure Execute;

    // IOTAMenuWizard implementation

    function GetMenuText: string;
    end;

As previously noted, Register is the preferred entry point for the IDE. Here we are calling the global procedure, ToolsAPI.RegisterPackageWizard, passing an IOTAWizard interface to the IDE. The compiler retrieves the interface from the object (TSimpleWizard instance). As there is no local reference to the interface, the IDE controls the lifetime of the wizard.

Where possible, avoid holding a reference to an interface also held by the IDE, as you could interfere with the operation of the IDE by stopping destruction of the underlying objects. There are solutions for these situations, but they are sometimes difficult to detect, and why introduce unnecessary complexity?

procedure Register;
  begin
  ToolsAPI.RegisterPackageWizard(TSimpleWizard.Create);
  end;

GetIDString must return a unique identifier. By convention, the identifier has two parts: organization & a wizard name.

function TSimpleWizard.GetIDString: string;
  begin
  Result := 'BorCon 2001.Simple Wizard';
  end;

GetName returns a user-friendly name for the wizard. Delphi displays the name in error messages, and for repository wizards, in the Object Repository.

function TSimpleWizard.GetName: string;
  begin
  Result := 'Simple Wizard';
  end;

Execute only fires for menu, form, and project wizards. It is invoked by selection of the menu item or Object Repository entry respectively. Here, we write a message in Delphi’s MessageView. First, we reference BorlandIDEServices, which is the universal entry point for the OTA. This is an interface of type IBorlandIDEServices, an empty descendant of IUnknown, so we query it for the interface we are interested in, IOTAMessageServices, and invoke the interface method, AddTitleMessage. Supports [2] is a convenience function, which combines the querying with assignment of the resultant interface (the third (out) parameter, MessageView), and returns a boolean.

procedure TSimpleWizard.Execute;
  var
  MessageView: IOTAMessageServices;

  begin

  if SysUtils.Supports(BorlandIDEServices, IOTAMessageServices, MessageView) then
    MessageView.AddTitleMessage('Hello World!');

  end;

GetState only fires for menu wizards. It describes the state of the menu item, enabled/disabled and/or checked.

function TSimpleWizard.GetState: TWizardState;
  begin
  Result := [wsEnabled];
  end;

GetMenuText is the sole method of the IOTAMenuWizard interface, and returns the label for the associated menu item.

function TSimpleWizard.GetMenuText: string;
  begin
 
Result := 'Greetings from BorCon 2001!';
  end;

Add any cleanup code for wizard unloading to the finalization section, or, better yet, handle it in the wizard destructor. There is no Unregister equivalent for the Register procedure. Remember that the IDE frees the wizard object we created in Register!

initialization

finalization

end.

Notifiers

Many IDE artefacts, CodeEditor modules and breakpoints for example, are transient in nature. It is therefore hazardous to hold references to these things, as the reference may be invalid when you attempt to use it. The OTA has provided a solution to this problem through a hierarchy of notifiers, an implementation of the Observer pattern. These are interfaces, which you, the plug-in author, must implement. You register a notifier with the IDE, which will subsequently call the methods of your interface.

Each notifier is associated with another OTA interface. For example IOTAModuleNotifier is associated with IOTAModule40. When an IOTAModule is about to be destroyed, all its observers (who have registered notifiers) will be notified.

This notification mechanism has been leveraged throughout the notifier hierarchy to inform plug-ins of other IDE events, such as editor windows receiving focus, or files being renamed. The base interface of the notifier hierarchy is IOTANotifier.

IOTANotifier = interface(IUnknown)
  ['{F17A7BCF-E07D-11D1-AB0B-00C04FB16FB3}']
 
procedure AfterSave;
  procedure BeforeSave;
  procedure Destroyed;
  procedure Modified;
  end;

The method called by the IDE for most implementations of this interface is Destroyed. This occurs when the notifying or observed IDE artefact is being destroyed, oddly enough! Any interfaces you are holding for that artefact must be released in this method. This practice is fundamental to the health of your plug-in and its host application.

The other methods of IOTANotifier are only called for some notifiers. This lack of optimisation is apparent in several OTA interfaces. I suspect it is largely a legacy issue - to remain backwardly compatible with older plug-ins as the OTA evolves and grows.

Note that from Delphi 5 onwards, there is a helper class, TNotifierObject, which inherits from TInterfacedObject, and provides a stubbed implementation of IOTANotifier. This is a good base class for plug-ins which need to implement IOTANotifier. Just remember that it may be necessary to reimplement IOTANotifier.Destroyed if you are holding IDE interfaces. You will need to declare IOTANotifier in your plug-in’s interface implementation list in that case.

Wizards

Wizard plug-ins date back to Delphi 1, when they were known as Experts. There are three flavours of wizard: menu (a.k.a standard), form and project.

As of Delphi 4, to write a wizard, you must write a class which implements several wizard interfaces, and register your wizard in your package’s Register implementation by calling ToolsAPI.RegisterPackageWizard.

Stubbed implementations will suffice for some wizard interface methods. Refer to the comments for the relevant interfaces in ToolsAPI.pas.

Menu Wizards

Menu Wizards appear as new menu items. Unfortunately, they are always added to the Help menu, so they are not generally useful for much besides demo-ware and testing. In your wizard class, you implement IOTANotifier, IOTAWizard and IOTAMenuWizard.

Example: EPCErrorTextWizard.dpk - error codes as text

This wizard creates a Help menu item “ErrorCode As Text…”. Choosing this item displays a modal dialogue that will convert numeric (decimal or hex) Win32 and BDE error codes to their textual equivalents - much easier to comprehend for mortals!

To install a new menu item elsewhere, manipulate the host application’s main menu, surfaced on the INTAServices interface. The next example uses this technique.

Form and Project Wizards

Form Wizards and Project Wizards both produce new items in the Object Repository. Both wizard types must implement IOTANotifier, IOTAWizard and IOTARepositoryWizard. Furthermore, Form Wizards must implement IOTAFormWizard, and Project Wizards IOTAProjectWizard.

By default, Form Wizards will appear as a new item on the Forms page of the repository (File|New…|Forms). Selecting this item will create a new form in the IDE, in accordance with your implementation.

Likewise, the default location for a Project Wizard is on File|New…|Projects. This will create a new IDE project in accordance with your wizard.

Example: EPCSourceTemplatesWizard.dpk - generate new forms and units from your template(s).

This is a Form Wizard that also creates a new menu item on the File menu. You can use, modify add or delete new templates for units and forms, editing their source in the IDE. The provided templates generate licence and copyright information, class and interface skeletons, and specific mark-up for CVS version control.

This wizard uses IOTAModuleServices.CreateModule to create the new units and forms. CreateModule expects an IOTAModuleCreator argument.

procedure TSTBaseWizard.WizardFormCreateClick(Sender: TObject);
  begin
  (BorlandIDEServices as IOTAModuleServices).CreateModule(self);
  end;

TSTBaseWizard, passed in as “self” above, implements IOTAModuleCreator and its ancestor interface, IOTACreator. The content of the files is returned via the following method implementations for IOTAModuleCreator. Note that the file content returned is expected as an IOTAFile implementation. Here, I have delegated that responsibility to other classes, TSTFormFile and TSTUnitFile for form and unit content respectively.

function TSTBaseWizard.NewFormFile(
  const FormIdent, AncestorIdent: string): IOTAFile;
  begin
  Result := TSTFormFile.Create(WizardForm);
  end;

function TSTBaseWizard.NewImplSource(
  const ModuleIdent, FormIdent, AncestorIdent: string): IOTAFile;
  begin
  Result := TSTUnitFile.Create(WizardForm);
  end;

The template files are edited within the IDE editor by calling on IOTAActionServices.OpenFile.

procedure TSTBaseWizard.WizardEditTemplateClick(Sender: TObject);
  begin
  (BorlandIDEServices as IOTAActionServices).OpenFile(WizardForm.EditTemplate);
  end;

Services

As previously seen, the entry point for OTA services is the global interface variable, BorlandIDEServices. This variable is of type IBorlandIDEServices, an empty descendant of IUnknown. To do anything useful, you must query BorlandIDEServices for one of the following interfaces:

 

Interface
Facilities

INTAServices

Access the host application’s main menu, action list, image list and toolbars.

IOTAActionServices

Open, close, save and reload files.
Open projects.

IOTADebuggerServices

Enumerate, create and attach to processes on local and/or remote hosts.
Create and access address and source breakpoints.
Create module load breakpoints.
Add and remove notifiers.

IOTAEditorServices

Get and set Editor Options.
Retrieve iterator to access edit buffers.
Get active editor buffer and view.

IOTAKeyBindingServices [3]

Add custom IDE behaviour.
Bind new or existing IDE behaviour to keyboard shortcuts.

IOTAKeyboardServices

Add and remove keyboard bindings.
Record, play and delete editor macros.
Push and pop keyboards (collections of keybindings).
Iterate, lookup and execute keybindings.

IOTAMessageServices

Add messages to the Message View of type:
Custom: customised behaviour and/or drawing.
Tool: standardised display and context behaviour.
Title: bold text with no associated context behaviour.

IOTAModuleServices

Close and save all modules.
Create and manipulate modules.
Add, retrieve and delete virtual file systems.

IOTAPackageServices

Retrieve names of loaded packages and names of components contained therein.

IOTAServices

Get Environment options.
Get base Registry key and product ID.
Get Application handle (HWND).
Add and remove notifier for general IDE events.

IOTAToDoServices

Retrieve ToDo items by index.
Add and remove collections (Managers) of ToDo items.
Add and remove notifiers.

IOTAWizardServices

Add and remove IOTAWizards and descendants.

Debugging plug-ins

Debugging is the dark side of plug-in development - it puts the “F” into  “Fall Over”. However, with some care and thought, you can save yourself a lot of grief.

As previously mentioned, your package shares its instance of the VCL and RTL with your RAD tool’s IDE, and also with any other installed design-time packages. This is the key to much of the power of the OTA, but one slip-up by any of these players is the difference between soufflé and omelette, between a Jumbo Jet and a flying brick, between… you get the picture!

So, obviously, you want to isolate yourself from the effects of other players. Initially, uninstall as many third-party components and third-party design-time packages as is practicable.

The host application for your package is also your debugging tool, ie you’ll be debugging Delphi with Delphi. Running an untested package into your development/debugging environment is something you do only once …hopefully. After you’ve mopped up the blood and brains from the Delphi explosion, you’ll realise there has to be another way. The solution involves running two concurrent copies of your RAD tool.

If you have a well-behaved package and Delphi installation, you can proceed as for any package or DLL:

Ensure that your package is not checked in the design packages list in the Project Options dialog. (Project|Options..|Packages|Design packages). Save your project to persist this setting.

As mentioned, you debug your package or DLL in the context of its host application. To specify the host application, go to the Parameters dialog off the Run menu (Run|Parameters…|Local|Host Application). For Delphi, specify delphi32.exe, found in the Bin subdirectory of your Delphi installation.

I have usually found it necessary to restart Delphi to properly persist the above settings.

Compile your package with debug information on and optimisation off. Add debug DCUs, stack frames, range checking, overflow checking etc. to taste. All these options are on the Compile tab of the Project Options dialog (Select Options.. off the Project menu). My preferences are shown in the above screenshot.

Having two copies of Delphi running can get a bit confusing, so I check “Minimize on run” in the Environment Options (Tools|Environment Options..|Preferences|Compiling and running|Minimize on run). Seeing the debugger copy shrink down and pop up keeps me sane for a bit longer.

The Register global procedure in your package is the official entry point for your plug-in, so I tend to put a breakpoint on the first line of Register.

Now run the host application (Run|Parameters…|Load, F9 key, toolbar button, whatever…). When the second copy of the IDE appears, add your package to the list of design packages, if not already present, and enable it (Project|Options..|Packages|Design packages) or (Component|Install Packages..).

All going well, the first (debugger) copy of the IDE should now pop up, with execution halted on your breakpoint in Register. You can now step through code, set breakpoints, set watches, or whatever your heart desires. Note that enabling “debug DCUs” in your Compiler settings links a debug version of the VCL. Much of the code you will be executing is within the Delphi binary and associated packages, which you won’t be able to step through.

If you have an ill-behaved package, using the integrated debugger can be difficult or impossible.

In that situation, it is wise to run two copies of the IDE, one for coding, the other for testing. You will need to resort to old-fashioned debugging techniques such as log messages to determine execution flow and parameter values.

If you have a really badly behaved package, which is enabled on the design-time package list (Component|Install Packages…), it is possible to crash the IDE during startup.

The solution is to remove or disable the offending package in the design-time packages list via some Registry hacking, using the Windows utility, regedit.exe. Using Delphi 6 as our example, the design-time packages loaded at start-up are listed under the key HKEY_CURRENT_USER/Software/Borland/Delphi/6.0/Known Packages, as seen below:

Deleting an entry from this key will remove the associated package from the list. To only disable the package, add a similar entry for the package to HKEY_CURRENT_USER/Software/Borland/Delphi/6.0/Disabled Packages, visible towards the middle of the above figure.

Example: EPCCodeJumpWizard.dpk – IDE editor enhancement module.

We will now look at a more extensive example that adds new behaviour to the IDE editor. This plug-in makes use of the keyboard facilities introduced in Delphi 5. The keyboard architecture can be conceptualised as a collection of key-mapping modules, and a list of enhancement modules, each module containing a collection of behaviours and associated keystroke bindings.

This is presented in the IDE in Tools|Editor Properties…|Key Mappings

Key-mapping modules must provide a handler for all keystrokes, a full keyboard implementation. The current key-mapping module can be changed via the dialog mentioned above.

Enhancement modules, by definition, provide a partial keyboard implementation. Multiple enhancement modules can be installed and ordered, overlying the current key-mapping module.

Keystrokes entered into the IDE editor filter down the list of enhancement modules, till a matching keystroke binding is encountered. The key-mapping module, at the bottom, supplies the default behaviour.

This example implements an enhancement module. It provides block-end matching for all Object Pascal block statement constructs up to and including Delphi 6 (excluding repeat/until)  - toggling the cursor position to either end of the block, or highlighting the enclosing block. It also provides cyclic jumps between all uses clauses and the current position, and cyclic jumps to the interface, implementation, initialisation, finalization sections and the current position.

Keybinding
Description

Alt+Shift+Home

Acts as a toggle. The first press will move the cursor from current position to the start of the minimally enclosing block statement. The second press restores the previous cursor position.

Alt+Shift+End

Acts as a toggle. The first press will move the cursor from current position to the end of the minimally enclosing block statement. The second press restores the previous cursor position.

Alt+Shift+B

Acts as a toggle. The first press will select all the minimally enclosing block statement for the current cursor position. The second press unselects the block and restores the previous cursor position.

Alt+Shift+Left

Moves the cursor back to the previous position after one of the following jumps:

Alt+Shift+I

Repetitive presses cycles through: INTERFACE, IMPLEMENTATION, INITIALIZATION, FINALIZATION sections, current position

Alt+Shift+U

Repetitive presses cycles through: INTERFACE USES clause, IMPLEMENTATION USES clause, current position

The OpenTools-related code all resides in the XPCodeJumpWizard.pas unit.

This plug-in works by scanning the currently focussed IDE editor buffer, when it either changes or is modified. One token event handler for the scanner, TCodeJumpWizard.BlocksParser, parses for block syntax and creates a mapping of all language block constructs and their endpoints in the current buffer. Another, TCodeJumpWizard.JumpsParser, parses for uses clause and section heading syntax, also creating a mapping to buffer positions. The keybinding handlers perform look-ups against these mappings.

We need to determine when the current IDE editor buffer changes, or is closed. For this we implement IOTAModuleNotifier, and register it with the current module. There is also an IOTAEditorNotifier interface, but these are never called by the IDE. We determine the current module, and all other context-related properties, through the IOTAKeyContext variable passed in to all keybinding handlers. The signature for those handlers looks like:

type TKeyBindingProc = procedure (const Context: IOTAKeyContext; KeyCode:
  TShortcut; var BindingResult: TKeyBindingResult) of object;

An enhancement or key-mapping module must implement the IOTAKeyboardBinding interface.

  TCodeJumpWizard = class(TNotifierObject, IOTAKeyboardBinding,
    IOTANotifier, IOTAModuleNotifier)

…

Return btPartial or btComplete to indicate an enhancement module or key-mapping module respectively.

    function GetBindingType: TBindingType;

Return user-friendly and unique name (typically Organisation.BindingName) respectively.

    function GetDisplayName: string;
    function GetName: string;

Bind locally defined handler functions to keystroke combinations. Look over the examples in Demos/ToolsAPI/Editor Keybinding in your product installation or CD. All keystrokes are not the same! Also, avoid any keystrokes with only an Alt modifier, eg Alt+X. These may cause access violations when your plug-in is loaded or unloaded. Other combinations, eg Alt+Shift+X are safe. Unused key combinations are a scarce commodity, so you may end up overriding some other IDE behaviour, or in turn, get clobbered by another enhancement module. Do a bit of research before you choose.

    procedure BindKeyboard(const BindingServices: IOTAKeyBindingServices);

IOTAKeyBindingServices, passed as an argument above, is the one Services interface not accessible through querying the BorlandIDEServices global interface variable.

The plug-in also handles syntactically incomplete units, adding context-jumpable messages to the IDE’s MessageView, similar to compiler messages. See TCodeJumpWizard.CheckBlocks.

There is another class declared in XPCodeJumpWizard.pas.This is TCJOwnerWizard which acts as a container for a TCodeJumpWizard instance. The reasons are explained in the code and relate to reference counting and unloading of the plug-in mid-session. Using a container interface is often a good solution for reference count problems.

Example: EPCBufferList.dpk – dockable IDE Buffers ListView.

This example is a work in progress. It implements a dockable list-view of all the open buffers in the IDE. Stemming from a dockable IDE example published on the Borland Community web site (see Opening Doors – Article 3 in Resources section below), the desired outcome was a better UI than the tabbed notebook of the IDE editor. That interface is fine for a handful of open files, but useless for managing lots of open files.

The list view is sortable in both directions via file name and location. Select or multi-select entries to focus or close buffers. It uses the INTAServices to install the menu item, and IOTAServices to receive notifications of files opening or closing and synchronise the display. Unfortunately, this doesn’t provide individual file notifications when the encompassing project or project group is closed, so an IOTAModuleNotifier approach will be required for a complete solution.

I would advise you to read the article mentioned above, as there is a lot of dummy code to get the undocumented IDE package to work with the plug-in in the form designer. The active code for this plug-in is contained in the file XPBufferListToolbarForm.pas.

Resources

Borland Community site (community.borland.com)

"Opening doors (Getting inside the IDE)"
by Allen Bauer, Staff Engineer/Delphi&C++Builder R&D Manager
Article 1: http://community.borland.com/article/0,1410,20360,00.html
Article 2: http://community.borland.com/article/0,1410,20419,00.html
Article 3: http://community.borland.com/article/0,1410,21114,00.html

Interview with Allen Bauer
Talking about OTA enhancements for Delphi 5. This download appears corrupted nowadays.
http://community.borland.com/article/0,1410,21046,00.html

Open Tools API "Live Chat" transcript (Nov 18, 1999)
http://community.borland.com/article/1,1410,20099,00.html

"Using the Open Tools API in Kylix"
by Ray Lischner, Tempest Software  (Author of "Hidden Paths of Delphi 3", and others)
http://community.borland.com/article/0,1410,27205,00.html

Open Tools API

by Ray Lischner, Tempest Software.
Covers Delphi 3 & 4, with good bug coverage: http://www.tempest-sw.com/opentools

GExperts Open Tools API FAQ

Lots of howtos and bug documentation: http://www.gexperts.org/opentools

Newsgroups

The OpenTools API newsgroup. An excellent resource for answers to specific questions. Ray Lischner (Hidden Paths) and Erik Berry (GExperts) are regular posters here…
news://newsgroups.borland.com:119/borland.public.delphi.opentoolsapi

Formerly DejaNews, acquired by Google. A searchable archive of the borland.public.delphi.opentoolsapi newsgroup
http://groups.google.com/groups?hl=en&lr=lang_en&safe=off&group=borland.public.delphi.opentoolsapi

Books

“Hidden Paths of Delphi 3” by Ray Lischner, Informant Press 1997
ISBN 0-9657366-0-1

Software

GExperts

An open source collection of Delphi wizards:http://www.gexperts.org

Ray Lischner's Expert building components
Unsupported freeware with source
Delphi 3: http://www.tempest-sw.com/freeware/Delphi/etk10.exe
Delphi 4: http://www.tempest-sw.com/freeware/Delphi/etk40.exe
Other shareware and freeware
Delphi Super Pages (Australian mirror): http://mirror.aarnet.edu.au/pub/delphi
Torry’s Delphi Pages: http://www.torry.net


[1]   There is much urban myth about the reason for the case-sensitivity of Register in the otherwise case-insensitive Delphi, but Allen Bauer sets the record straight in his third “Opening Doors” article on the Borland Community website:
”The actual reason is that with Win32, all exports from a DLL are case-sensitive. When Delphi loads a design-time package it looks in a special compiler generated resource in the package that describes its contents. This resource lists the unit names within the package. The IDE then scans through this list creating a string containing the unit name and the Register procedure then does a GetProcAddress call on that string. If it locates that entry-point it is called. Sure, we could try all possible combinations, but that would be 256 combinations checked for each unit in that package!”

[2]   Supports can also tolerate a nil first argument, whereas QueryInterface, the function it wraps, will cause an exception when applied to an unassigned interface.

[3] IOTAKeyBindingServices is not accessible directly through the BorlandIDEServices global variable. Instead, it is passed as an argument to the IOTAKeyboardBinding.BindKeyboard method. Keyboard bindings are manipulated via the IOTAKeyboardServices interface, which is accessible via BorlandIDEServices.


Copyright © 2001-2003, The Excellent Programming Company
Last updated: 02/18/2003 21:15:46
基于K8S + apisix/istio 实现云原生模块化微服务.自带代码生成器,能够生成对表的增删改查的逻辑代码 基于K8S + apisix/istio 实现云原生模块化微服务.自带代码生成器,能够生成对表的增删改查的逻辑代码.是标准Maven项目,只依赖springboot,没有hibernate,struts,ibatis,数据库调优可以使用druid输出慢sql,比分析xml中的语句更直观,springrain所有的sql语句都使用Finder封装管理,只要查看Finder的引用即可. 立即下载

相关推荐

工艺部2026年终总结及2027工作规划PPT.pptx

制造业生产、工艺、设备、质量全套 PPT 资料,包含月度经营复盘、季度数据分析、部门年终总结规划、产线与检验规范、TPM 设备保全、质量意识、质量总监高阶培训等课件。内容兼顾经营指标偏差分析、述职汇报、现场制度宣贯、岗位技能教学。 适合操作工、检验员、设备维保员、工艺 / 品质 / 设备工程师、生产主管、各部门部长、质量总监、厂长、内部培训师、精益专员使用,覆盖一线执行、骨干分析、部门负责人述职、高管进阶全层级,可直接用于工厂汇报、内部培训、述职复盘。

Eclipse设置、调优、使用

eclipse调优  一般在不对eclipse进行相关设置的时候,使用eclipse总是会觉得启动好慢,用起来好卡,其实只要对eclipse的相关参数进行一些配置,就会有很大的改善。  加快启动速度  1.在eclipse启动的时候,它总是会搜索让其运行的jre,往往就是这个搜索过程让eclipse启动变慢了。(没设置时,等2-3s出现进度条,设置后直接出现进度条)  只要在eclip

reille的笔记 8379

锂电池基于电热耦合模型与NSGA-II算法的充电曲线优化:温升与损耗权衡及多目标帕累托边界求解 MATLAB 锂电池充电曲线优化|NSGA-II求解与温升损耗权衡

内容概要:本文介绍了一个基于MATLAB的锂电池充电曲线优化项目,旨在通过NSGA-II多目标进化算法,平衡充电过程中的温升与综合损耗。项目构建了电热耦合模型,将充电过程划分为多个荷电状态阶段,每个阶段的充电倍率作为决策变量,目标是最小化峰值温度和综合损耗,同时满足充电时间、温度、电压和电流变化率等约束条件。通过快速非支配排序和拥挤距离等机制,算法生成帕累托最优解集,用户可根据需求选择安全优先、损耗优先或综合折中的充电策略。项目还包括了数据生成、模型验证、结果可视化和GUI界面,形成了完整的从模型构建到策略输出的技术闭环。 适合人群:具备一定MATLAB编程基础,从事电池管理系统、新能源汽车、储能系统或电力电子等领域研发工作的工程师和技术人员。 使用场景及目标:①为新能源汽车、无人机、储能电站等提供快速且安全的充电策略;②研究多目标优化算法在工程实际问题中的应用;③构建电池充电过程的数字孪生模型,支撑电池全生命周期的精细化管理。 阅读建议:此资源以实际工程项目为导向,不仅展示了算法实现,更强调了工程约束、模型校准和结果验证的重要性。学习者应在理解电化学与热力学基础知识的前提下,结合提供的完整代码进行调试和运行,并重点关注约束处理、目标函数设计和帕累托前沿分析等核心环节,以深入掌握多目标优化在复杂工程系统中的应用精髓。

Delphi 10.1 调试出现死机的解决方法

现象: Win7 64位,使用Delphi 10.1 Berlin调试的时候,经常出现死机,一天几次,桌面上所有程序都无法正常操作,所有窗口只能最小化、恢复,其他操作都不能操作,关闭也不行,关闭会出现结束任务,但是结束不了。在任务管理器中强制结束Delphi、被调试的程序,都无效,开始菜单中重新启动计算机无效,只能通过关闭电源重新启动(主机上的Reset按键没有接线,防止孩子误按) 解决方法:

正己的专栏 4056

wordbuddy破甲经验分享

wordbuddy破甲经验分享

双闭环PWM整流器VOC控制 +SVPWM调制仿真(Simulink仿真实现)

内容概要:本文详细介绍了一种基于Simulink的双闭环PWM整流器采用虚拟同步发电机控制(VOC)策略与空间矢量脉宽调制(SVPWM)技术的仿真模型。该模型在dq同步旋转坐标系下构建电压定向控制系统,实现了电压外环与电流内环的双闭环控制结构,重点阐述了VOC控制原理、SVPWM调制算法的实现方法及其在Simulink环境中的模块化搭建过程。通过该仿真平台,能够有效实现单位功率因数运行、直流母线电压稳定控制以及优异的动态响应性能,适用于电力电子变换器、新能源并网系统及电机驱动等领域的控制策略验证与性能评估。; 适合人群:电气工程、自动化、电力电子与电力传动等相关专业的高年级本科生、研究生,以及从事电力电子系统仿真、新能源并网控制研究的科研人员和技术工程师。; 使用场景及目标:①深入理解三相PWM整流器在dq坐标系下的数学建模与控制机理;②掌握VOC控制策略的核心思想及其相较于传统矢量控制的优势;③学习SVPWM调制技术的实现流程与Simulink建模技巧;④通过仿真实验验证双闭环控制系统的静态精度与动态稳定性,提升对先进电力电子控制系统的分析与设计能力。; 阅读建议:建议结合电力电子技术、现代控制理论和电机控制相关基础知识进行学习,按照仿真模型的结构逐步分析各功能模块的信号流向与参数配置,重点关注锁相环(PLL)、坐标变换、PI控制器设计及SVPWM模块的实现细节,并可通过调整控制参数观察系统响应变化,以深化对控制策略动态特性的理解。

解决Myeclipse启动慢

MyEclipse功能非常的强大,现在基本是使用Eclipse开发JavaEE的必备插件了。但是巨大的MyEclipse也导致Eclipse启动的速度加慢,另外开发的过程中有时候容易造成界面“假死”的状况。 今天介绍几个优化MyEclipse的方法 1. 加大JVM的非堆内存,具体的做法和原因参考《由MyEclipse内存不足谈谈JVM内存》 2. 去除不需要加载的模块 ...

weixin_34387284的博客 151

储能双向Buck-Boost+模型预测电流控制+电压下垂控制仿真【附参考文献】(Simulink仿真实现)

内容概要:本文系统介绍了基于Simulink平台构建的储能双向Buck-Boost变换器仿真模型,融合模型预测电流控制与电压下垂控制策略,面向锂电池储能系统的高效充放电管理。该模型深入实现了双闭环控制架构的设计与动态性能分析,涵盖系统建模、关键控制算法实现及稳定性验证,并配套提供参考文献以支撑理论深度与技术复现性。资源包还整合了多种典型电力电子拓扑的仿真案例,如各类DC-DC变换器、谐振变换器、PWM整流器等,充分体现了其在新能源发电、微电网能量调度及电力系统稳定控制中的广泛应用价值和技术延展性。; 适合人群:适用于电气工程、自动化、电力电子及相关专业的高校研究生、科研人员以及从事能源系统仿真与控制设计的工程技术人员;要求具备一定的Simulink/MATLAB操作能力及电力电子与自动控制基础知识;; 使用场景及目标:①深入理解双向Buck-Boost电路在储能系统中的工作机理与能量双向流动控制方法;②掌握模型预测控制(MPC)与下垂控制在实际电力变换系统中的建模流程与参数整定技巧;③为科研课题、学位论文或工程项目提供高可靠性、可复用的仿真原型与技术参考方案; 阅读建议:建议结合文中列出的参考文献与网盘提供的完整仿真资源,按照目录结构循序渐进地学习各模块案例,重点剖析控制逻辑搭建、调节器设计与系统动态响应特性,通过动手仿真与参数调试深化对控制策略性能与鲁棒性的理解。

Delphi Code Editor 之 编辑器选项

可从Code Editor的右键菜单中选择“Properties”菜单项来查看编辑器选项。也可以从主菜单【Tools | Editor Options…】打开编辑器选项对话框。如下图: 下面分别来讲述各个页面。 1、General页面 这两个页面用于控制编辑器的工作方式。其中包括很多选项。 页面下面的“Editor SpeedSetting”组合框,可从里面选择多种设置,如下图: ...

weixin_33919941的博客 321

产业园区在推动科技成果落地时,如何高效匹配企业与技术供给方.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

全桥LLC谐振(变频+移相混合控制)变换器闭环仿真模型(Simulink仿真实现)

内容概要:本文详细介绍了基于Simulink平台构建的全桥LLC谐振变换器闭环仿真模型,采用变频控制与移相控制相结合的混合调制策略,实现高效电能转换与高精度输出稳压。模型完整涵盖了系统建模、谐振腔参数设计、软开关技术实现、双闭环控制系统(如电压外环与频率/相位内环)设计及仿真验证全过程,重点突出零电压开通(ZVS)与零电流关断(ZCS)的实现机制,以显著降低开关损耗,提升变换器效率与动态响应性能。配套资源包含完整的Simulink仿真文件、详细的参数设计报告及核心参考文献,为深入理解LLC谐振变换器的工作机理、控制策略优化及其在开关电源、新能源发电等领域的工程应用提供了坚实的技术支撑。; 适合人群:具备电力电子技术、自动控制理论基础,从事高频开关电源、新能源变换器、车载充电机或相关方向研究的研究生、科研人员及电气工程师。; 使用场景及目标:①系统学习全桥LLC谐振变换器的精确建模与仿真方法;②掌握变频+移相混合控制策略的设计原理与实现技巧;③开展高效率、高功率密度电源变换器的科研攻关与工程项目开发;④深入理解软开关技术在实际电路中的应用与优势; 阅读建议:建议结合所提供的仿真模型与设计报告进行动手实践,重点分析控制器参数整定过程、谐振元件(Lr, Cr, Lm)的参数匹配关系,并仔细观察仿真波形,以深刻把握系统的稳态特性、瞬态响应能力及控制稳定性,从而全面提升对复杂谐振变换器系统的设计与分析水平。

[乐意黎原创] WebPack 打包时抛Uncaught Error: Cannot find module '.\dist\bundle.js'

如题:WebPack 打包时抛Uncaught Error: Cannot find module '.\dist\bundle.js' A. Webpack 命令时抛错 B.运行时Chrome 控制台抛错: bundle.js:2 Uncaught Error: Cannot find module '.\dist\bundle.js'     at webpackMissingMo...

打杂人-aerchi 1万+

LLC谐振变换器恒压恒流双竞争闭环控制simulink仿真(附说明文档)

内容概要:本文提供了一套针对LLC谐振变换器恒压恒流双竞争闭环控制的完整Simulink仿真资源,涵盖详细的仿真模型、设计说明文档及配套PPT讲解材料。该资源聚焦于电力电子领域中LLC谐振DC-DC变换器的核心控制策略,重点实现了在不同负载条件下恒压(CV)与恒流(CC)模式之间的无缝切换与双闭环竞争控制机制,确保系统具备高稳定性与动态响应性能。仿真平台基于Matlab/Simulink构建,适用于半桥或全桥LLC拓扑结构,内容包括谐振参数设计、变频控制(PFM)、软启动策略、反馈环路建模及闭环稳定性分析等关键技术环节,具有较强的工程实践价值和科研复现意义。; 适合人群:面向电力电子、电气工程及其自动化等相关专业的研究生、科研人员以及从事开关电源、新能源变换器研发的工程师,尤其适合具备基本电路理论和Simulink仿真操作经验的技术人员;; 使用场景及目标:①深入理解LLC谐振变换器的工作原理及其在恒压恒流输出控制中的实现方法;②支撑科研项目中对模式切换逻辑、软开关实现、系统抗扰能力与稳定性分析等关键问题的研究;③作为毕业设计、学术论文复现或工业级电源产品原型开发的重要技术参考;; 其他说明:资源配套提供了详尽的参数设计指导与PPT解析,有助于快速掌握建模思路与控制器整定方法,建议使用者结合自身应用场景调整谐振腔参数与PI控制器增益,以实现最优性能,同时可通过公众号“荔枝科研社”获取持续的技术支持与扩展资料。

上一篇: 自动规范控件前缀命名的专家
下一篇: ShadowStar CodeFast 0.7 整合了源代码格式化工具
shadowstar
博客等级 码龄26年 32粉丝 33原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值