The C#Builder Open Tools API
The Open Tools API (OTA) provides developers with the ability to
extend and enhance the functionality of the C#Builder IDE. Because
the C#Builder OTA closely resembles the interfaces provided with
the Delphi and C++Builder products, developers who are familiar
with those tools should feel at home quickly when creating addins
for C#Builder. This article will introduce seasoned developers to
the major changes and additions to the new .NET-based OTA as well
as providing an overview of the interfaces and development process
for new OTA developers. The
sample code
for this article is available for download in
CodeCentral.
The OTA is contained in the Borland.Studio.ToolsAPI namespace inside
the assembly Borland.Studio.ToolsAPI.dll. C#Builder includes basic
documentation on the contents of the assembly, but you can also use .NET Reflector or the
Borland Reflection.exe tool to inspect the assembly directly.
For all of the OTA addin assemblies you create, you must add a
reference to the Borland.Studio.ToolsAPI.dll assembly located in the
BDS\1.0\Bin directory.
"Hello World"
Every introductory lesson needs a basic "Hello World"
example, so here comes one for C#Builder. Though these examples are
written in C#, any .NET language can be used to create OTA addins. The
class below implements IOTAMenuWizard, which is a basic type of addin
that adds a menu item to the Help menu and can execute some code when
the menu item is selected. Create a new .NET assembly by selecting
File, New, Other, C# Projects, Class Library. Add a reference to the
Borland.Studio.ToolsAPI.dll assembly by choosing Projects, Add
Reference and clicking the Browse button. Also add a reference to the
System.Windows.Forms.dll assembly so the MessageBox class is available.
Then edit the class code to look like this:
using System;
using Borland.Studio.ToolsAPI;
using System.Windows.Forms;
namespace ToolsAPI.Introduction
{
public class HelloWorldWizard : IOTAMenuWizard
{
public static void IDERegister()
{
IOTAWizardService wizServ = (IOTAWizardService)
BorlandIDE.GetService(typeof(IOTAWizardService));
wizServ.AddWizard(new HelloWorldWizard());
}
public string IDString { get { return "EB.HelloWizard"; } }
public void Execute() { MessageBox.Show("Hello World!"); }
public string Name { get { return "Hello World Wizard"; } }
public void Destroyed() { /* nothing */ }
public string MenuText { get { return "Hello World Wizard"; } }
public bool Checked { get { return false; } }
public bool Enabled { get { return true; } }
}
}
The example illustrates both of the major kinds of interfaces you
will find in the OTA. The
IDE implements many interfaces (including all of those with "Service" or
"Manager" in their name) for you that you will never have to implement
yourself. IOTAWizardService is one such interface that allows adding
and removing wizards (the OTA term for a generic addin) inside the IDE.
The other major interface type is one that you must implement so the IDE
can interface with your code. For example, to add a menu item to the IDE
using IOTAMenuWizard, the IDE needs to know things like the menu item's
text, whether the menu item is enabled, and a unique ID for your wizard.
After you compile your assembly, to get it loaded into the
IDE, you can add a new string entry to the registry here:
HKCU\Software\Borland\BDS\1.0\Known IDE
Assemblies
The entry name should be the full path and file name of the compiled
assembly dll. The entry data can be any string, but some value is
required for the assembly to be loaded into the IDE:
C:\OTAIntro\OTAIntro.dll = OTAIntro

After adding the registry entry above, the IDE will load your
assembly at startup. The IDE will try to locate a public static void
function with the name IDERegister. If such a method is found, it is
executed, and this is where you should initialize and register your
addin with the IDE. After the IDE loads, your new menu item will
appear in the Help menu.

IDE Services
The IDERegister function above shows a common pattern you
should become comfortable with:
IOTAWizardService wizServ = (IOTAWizardService)
BorlandIDE.GetService(typeof(IOTAWizardService));
Many of the IDE-provided interfaces can be obtained by calling
GetService on the BorlandIDE class. If the requested interface is
present, it will be will be returned. Note that usage of the as
operator to typecast BorlandIDE to a specific interface (which was
common in previous IDEs) is no longer supported, so you must use
GetService. Some of the interfaces that can be obtained using
BorlandIDE.GetService are:
| Interface |
New for .NET? |
Interface Usage |
| IOTAAboutBoxService |
Yes |
Add product information to the IDE about box (detailed below) |
| IOTAActionService |
No |
Open, close, save, and reload files and projects |
| IOTAAddInService |
No |
Load new addin assemblies into the IDE |
| IOTAAddReferenceDialog |
Yes |
Shows the Add Reference dialog for the current project |
| IOTAAssemblySearchPathService |
Yes |
List, add, and remove items from the assembly search paths |
| IOTAAssemblyUnloadedService |
Yes |
Determine when assemblies are unloaded from the IDE |
| IOTABitmapService |
Yes |
Load a bitmap from a Windows resource inside an executable file |
| IOTAComponentInstallService |
Yes |
List, add, and remove components from the toolbox |
| IOTADotNetObjectInspectorService |
Yes |
Allows "selecting" arbitrary .NET objects and showing their properties
in the IDE object inspector. |
| IOTAGalleryCategoryManager |
Yes |
List, add, and remove gallery categories. Gallery categories are
the tree nodes in the File, New, Other (Repository) dialog. |
| IOTAIdleNotifier |
Yes |
Receive notification when the IDE is idle |
| IOTAMainMenuService |
Yes |
Iterate through, add, remove, and execute main menu items |
| IOTAMessageService |
No |
Add or remove messages and message groups to the message view |
| IOTAModuleServices |
No |
Get references to the current project group, project, file
group (module). Open, close, and create new modules. |
| IOTAPersistenceManager |
Yes |
Provides in-memory access to the XML document that stores the
default and user-specific IDE settings (ApplicationSettings.xml). |
| IOTAService |
No |
General information such as the location of the settings in
the registry and on disk, the IDE installation directory, and the
environment options interface. Also provides notifications for files
being opened/closed, installation/removal of packages, and
compilation events. |
| IOTASplashScreenService |
Yes |
Add product information to the IDE splash screen |
| IOTAWizardService |
No |
Add or remove wizards from the IDE. Wizards can be generic
addins, menu wizards, repository items, etc. |
From Notifiers to .NET Events
Previous Borland IDEs used notifier interfaces to provide
callbacks when certain events occurred. The new OTA uses .NET events
for this purpose. This means that instead of registering a notifier
that has to be able to receive all 7 events on the old
IOTAIDENotifier interface, you can now register to receive a single
event, such as the FileNotification callback. For example, this code
snippet shows how to receive FileNotification callbacks when files
are opened, closed, etc.:
public static void IDERegister()
{
IOTAService ideService = (IOTAService)
BorlandIDE.GetService(typeof(IOTAService));
ideService.FileNotification +=
new FileNotificationHandler(FileEvent);
}
private static void FileEvent(object sender, FileNotificationEventArgs args)
{
LogMessage(String.Format("{0} notification for: {1}",
args.NotifyCode, args.FileName));
}
Adding a Main Menu Item
Unlike IOTAMenuWizard, which always places the new menu item
in the Help menu, the IOTAMainMenuService interface allows addins to
place new menu items anywhere in the main menu and allows customizing
the menu item bitmap, shortcut, etc. The sample code below shows how to
do this:
protected static void AddMainMenuItem(
{
((Bitmap)form.MenuItemBitmap.Image).MakeTransparent();
IOTAMainMenuService menuServ = OTAUtils.MainMenuService;
menuItem = menuServ.AddMenuItem(OTAUtils.IDEViewDebugItemName,
OTAMenuItemLocation.otamlBefore, MenuItemName, "OTA Intro Form",
(Bitmap)form.MenuItemBitmap.Image).GetHbitmap());
menuItem.Executed += new EventHandler(MenuItemExecuted);
int shortcut = Convert.ToInt32(Keys.Z) | OTAUtils.Shift |
OTAUtils.Control | OTAUtils.Alt;
menuItem.Shortcut = shortcut;
OTAUtils.StartUpdatingMenuShortcut(menuItem, shortcut);
}
Because the OTA interfaces expect a Windows HBITMAP value to set
the menu item bitmap, we must cast the Image to a Bitmap and pass in
the result of GetHbitmap(). The call to Bitmap.MakeTransparent takes
the corner pixel of the bitmap and treats it as the transparent color
for the image. In the sample code, the menu item image is loaded from
a PictureBox component on the form, but you could also use
IOTABitmapService to load it from a resource. The first parameter to
AddMenuItem is a reference menu item name. The new menu item is
placed relative to the reference item. The names of all of the
existing IDE menu items can be obtained using
IOTAMainMenuService.GetFirstMenuItem and the various methods to iterate
over the menu items, such as IOTAMenuItem.ChildMenuItem.
Each IOTAMenuItem can have a shortcut, but the value to pass
in to set the shortcut isn't obvious. At first glance, it might seem
logical that the type would be a .NET Shortcut enumeration, but that
type does not support the majority of the shortcuts that the IDE menu
items support. Instead, the shortcut is treated as a word where the
low byte is a Windows virtual key code value and the high byte
encodes the state of the three shift keys in the high 3 bits. You can
use the or operator on a System.Windows.Forms.Keys key code combined
with any of the Shift, Control, and Alt modifiers defined in OTAUtils
to generate an appropriate shortcut value. Since the .NET OTA does
not yet expose the keybinding functionality from previous IDEs, your
menu item shortcuts might occasionally disappear when packages are
loaded or unloaded and the IDE reinitializes the main menu. To work
around this, you can use the two utility methods provided in
OTAUtils.cs called StartUpdatingMenuShortcut and
StopUpdatingMenuShortcut. This workaround automatically resets the
menu item's shortcut when packages load or unload. The result of
the menu item code is shown here:

If one of your menu event handlers throws an uncaught exception,
the only indication the user will see is the somewhat unhelpful message
"Exception has been thrown by the target of an invocation". This is
because native code deep inside the IDE is making calls into your
.NET object to signal the menu item being selected and the .NET
event invocation framework is catching .NET exceptions and re-throwing
them with the above message. For this reason, it might be prudent to
wrap your menu event handlers in a try/catch block, show the real
exception message to the user, and then handle the exception as
appropriate.
Project Groups, Projects, Modules, Editors, and Views
New OTA developers are sometimes confused by the terminology
used in the OTA to describe files in the IDE. At the top-level is a
project group (.bdsgroup) that is a collection of project files,
such as .bdsproj files. Within a project is any number of modules.
Modules are groups of 1 or more files that are treated as a unit.
For example, an ASP.NET module might contain both an .aspx html
template and a .cs file for the code behind implementation. Within
a module, there can be many IOTAEditors, which generally correspond
to a single physical file on disk. These may be IOTASourceEditors
for source text files or some other type of editor. Source editors
can in turn have one or more IOTAEditViews into their contents.
Though the ability to create multiple edit views for the same source
editor is not present in C#Builder 1.0, the interfaces to iterate
over edit views are still present in the OTA, since the feature will
likely reappear in a future C#Builder release. The sample code for
this article contains an example of how to use IOTAModuleServices
to obtain the current project group, project, module, editor, and
editor source. Install the OTAIntro.dll assembly as described above
and then choose the View, OTA Intro Form menu item. Clicking the
Refresh button will gather the current module data as shown here:

Components
C#Builder replaces the component palette with a multi-purpose Tool
Palette. The OTA provides a way to determine the categories and
components present in the Tool Palette to add to form designers.
The Components tab of the sample code form shows how to list the
component categories, list the components in the category, and
dynamically create components from your OTA addins. Here you can
see the component list and a button having just been created:

Because the C#Builder IDE uses the .NET designer built into
the .NET 1.1 SDK, all of the designer functionality documented for
IDesignerHost and the related interfaces is available to you through
the OTA. You can get an IDesignerHost interface using
IOTADotNetModule.DesignerHost. IDesignerHost allows creating
transactions of atomic/undoable modifications and implements
many of the interfaces in System.ComponentModel.Design, including
IComponentChangeService, ITypeResolutionService, ISelectionService,
INameCreationService, IUIService, IMenuCommandService, etc. The
sample code shows how to use IDesignerHost to get the list of
selected components and how to create a component using
IToolboxUser.ToolPicked.
public static void CreateToolboxItemOnDesigner(ToolboxItem item,
IDesignerHost designer)
{
ISelectionService selection = (ISelectionService)
designer.GetService(typeof(ISelectionService));
IComponent selComp = (IComponent)selection.PrimarySelection;
IDesigner des = null;
if (selComp is IToolboxUser)
des = designer.GetDesigner(selComp);
else
des = designer.GetDesigner(designer.RootComponent);
if (des is IToolboxUser)
{
((IToolboxUser)des).ToolPicked(item);
}
}
About Box Customization
The new IDE was designed to be pluggable both for third-party
addins and new compilers, such as the upcoming Delphi 8. There are
increased customization opportunities available for addin developers
such as placing their product details in the splash screen and about
box. The sample code below shows how to add a logo and related
informational text to the IDE's about box. Similar to the procedure
for menu items, note that the Bitmap object in the image is made
transparent and passed in as a Windows HBITMAP value. Note that you
should save the value returned from AddPluginInfo and use it later
to remove your plugin information using RemovePluginInfo
when your addin unloads.
private void AddAbout_Click(object sender, System.EventArgs e)
{
if (AboutPluginIndex > -1)
throw new ApplicationException("You can't add the plugin twice");
IOTAAboutBoxService aboutServ = OTAUtils.AboutBoxService;
((Bitmap)AboutImage.Image).MakeTransparent();
AboutPluginIndex = aboutServ.AddPluginInfo(AboutTitle.Text,
AboutDescription.Text, ((Bitmap)AboutImage.Image).GetHbitmap(),
AboutUnregistered.Checked, AboutLicenseStatus.Text,
AboutSkuName.Text);
}
After running the above code, the about box looks something like
this:

Note that similar customization of the splash screen is possible
using IOTASplashScreenService.
The sample code also shows how to execute arbitrary IDE menu
items using the provided OTAUtils.ExecuteMainMenuItemByName method.
When you click the "Show About Box" button on the About Box tab,
the IDE menu item named "HelpAboutItem" is located and executed.
A similar method will work for any menu item in the IDE once you
find its name using the relevant IOTAMainMenuService and
IOTAMenuItem interfaces.
The Message View
The sample form also has a Message View tab. The code
invoked there shows how to clear the message view, add messages and
message groups, and add parent/child nested message types that open
files when clicked, etc. The functionality is illustrated below:

General OTA Utilities
The sample code also includes many static utility functions
in the OTAUtils class in OTAUtils.cs that make OTA development easier.
You can, for example, safely and easily retrieve many of the IDE
services, get the current project interface, execute arbitrary IDE
menu items, etc. Feel free to use this unit in your own projects and
add new methods and send them to me for inclusion in the utility
package.
More New Features
Other than the new interfaces described above, seasoned OTA
developers will also find that the following interfaces are new to the
.NET OTA:
- IOTACodeDomProvider - IOTAModule.GetService can return
this interface when it is supported. It allows access to the CodeDom
for a file open in the IDE. The CodeDom is an instance of the
System.CodeDom.CodeObject class and provides a structured view of the
file including the declared types, methods, method implementations,
etc. This is an enormously useful addition to the IDE and should make
many OTA developers happy.
- IOTAElideActions - IOTAEditView implements this interface
to allow
folding and unfolding code sections in the editor. For this specific
interface, you can use the as operator to cast IOTAEditView to
IOTAElideActions.
- IOTADotNetModule - Obtained using IOTAModule.GetService.
Allows access to .NET specific module properties and helper functions
such as showing a specific part of the code in the module, showing the
.NET designer, setting the code generator options that map from the
CodeDom to the actual source, and getting information about and
modifying a designer using the IDesignerHost interface. The
component creation example described above uses this interface.
- IOTADotNetProject - Obtained using IOTAProject.GetService.
Allows access to .NET-specific properties of a project such as the
external references using IOTAReferences and adding controls to the
.licx (component license) file using IOTAProjectLicenseProvider.
- IOTAFileReader and IOTAFileWriter - These replace
IOTAEditReader and IOTAEditWriter in the old OTA and allow directly
reading and writing the bytes in a source file. You can obtain a file
reader or writer using an IOTASourceEditor's CreateReader or
CreateWriter methods, or via the IOTAVirtualFile interface. These
two interfaces operate almost identically to the older ones they
replace.
- IOTAReferences - Allows you to list, add, and remove
assembly, COM server, and other references for a project. This
interface is obtained from IOTADotNetProject.References.
Another bonus (at least until C#Builder 2!) of the .NET OTA
is that the complex interface hierarchies that had developed due to
successive IDE editions adding more features to interfaces were
flattened into a single level. For example, there is no longer an
IOTAServices interface inheriting from IOTAServices60 which inherits
from IOTAServices50. There is now a single IOTAService interface
implementing all of the old IOTAServices methods and any additions
for .NET.
Debugging Addins
Debugging OTA addins is not as easy as it could be, because
C#Builder 1.0 does not directly support debugging into a .NET
assembly when the assembly was loaded by a native Windows executable
like the C#Builder IDE (trying to do so results in the exception
"Unable to scan program's header"). Workarounds include debugging by
attaching to an already running C#Builder instance using the Run,
Attach to Process menu item (see the Readme.txt for details),
debugging using a log file, or messages sent using IOTAMessageService,
etc. To debug the initialization code for your addin, you can place a
MessageBox.Show call as the first line in your IDERegister procedure,
and attach to the new IDE process while the IDE is waiting for the
MessageBox dialog to be confirmed.
OTA Features That Are Not Currently Implemented
There are also several things supported by the older unmanaged
OTA or that would be helpful in .NET but are not currently supported by
the .NET OTA including:
- There is no support for creating dockable IDE windows using the
internal DockForm.TDockableForm class. Normal modal or non-modal WinForms
can be created and shown as expected.
- All of the INTA-type services such as INTACustomDrawMessage,
INTAFormEditor, INTAServices, INTAComponent, etc. are not present, since
you can not access native VCL objects using pure .NET code.
- IOTAFormEditor and IOTAComponent are not present, but the .NET
framework defined IDesignerHost property of an IOTADotNetModule provides
much of the same functionality to list, add, and delete designer
components (see above for more details).
- IOTAKeyboardServices is not supported, but you can do some basic
keystroke handling by adding a menu item with a shortcut.
- The debugger interfaces (IOTADebuggerServices, IOTAProcess,
IOTAThread, IOTABreakpoint, etc.) are not implemented
- Some other rarely used interfaces are not present such as
IOTACodeInsightServices, IOTAFileSystem, IOTASpeedSetting,
IOTAToDoServices, etc.
- Some of the interfaces do not properly return/handle multi-byte
UNICODE characters in the code editor.
The sample code for this article is available in CodeCentral as submission
ID #20287.
Erik Berry is an independent software developer and
consultant working from Saint Louis, MO and with
Oasis Digital Solutions.
He is the project leader for the GExperts
IDE toolkit for Delphi and C++Builder and helped design and implement
the Open Tools API in C#Builder 1.0. He also maintains an Open Tools API FAQ.