Friday, December 22, 2006

The missing Import Component Wizard in Turbo Delphi (Win32)


Apparently the Import Component and View Type Library wizards are not enabled in Turbo Delphi (Win32) by default.
A workaround has been posted on the newsgroups:

Create an empty file named: ATLBASE.H in this directory: $(BDS)\include\atl\

Monday, December 18, 2006

The missing ActiveX Control Wizard


The ActiveX Control Wizard is missing in Delphi 2006 by default.
You can bring it back by adding the following string value in the registry:

[HKEY_CURRENT_USER\Software\Borland\BDS\4.0\Type Library]
"ActiveXWizard"="True"

I've just found it in a comment to QC25012.

Also see Delphi 2006 Hidden COM Registry Entries.

Regmon reveals another missing registry entry, "InteropCheck"="True", also mentioned here.

Friday, December 08, 2006

Don't mistake SyncEdit for Refactoring ;-)

Don't mistake Delphi's SyncEdit for Refactoring. (In case you wonder what SyncEdit is, it's invoked by pressing Ctrl+Shift+J on a selected text in the editor. Works in Delphi 2005 and higher.)

By doing so I've just introduced a rather obscure bug in my code which took me a while to recognize. ;-)
When using SyncEdit, be aware that you're simply doing a search/replace within the selected block of code.

The help says:
Sync Edit determines indentical identifiers by matching text strings; it does not analyze the identifiers. For example, it does not distinguish between two like-named identifiers of different types in different scopes. Therefore, Sync Edit is intended for small sections of code, such as a single method or a page of text. For changing larger sections of code, consider using refactoring.

There's also a nice article by Bob Swart about SyncEdit, Refactoring and more.

Friday, December 01, 2006

The 2006 Delphi Survey

Here's your chance to let CodeGear know what you would like to see in future versions of Delphi:

Sunday, November 19, 2006

New default timeouts in IE7 wininet

The new wininet.dll which comes with Internet Explorer 7 uses new default timeouts:
INTERNET_OPTION_SEND_TIMEOUT
30000 ms
INTERNET_OPTION_RECEIVE_TIMEOUT
30000 ms
The good news is that you can now override these values by calling the InternetSetOption API (zero value seems to indicate infinite wait, see this example).

In previous versions, InternetSetOption didn't work but at least the default values were quite large:
INTERNET_OPTION_SEND_TIMEOUT
300000 ms
INTERNET_OPTION_RECEIVE_TIMEOUT
3600000 ms

Wednesday, November 15, 2006

CodeGear

CodeGear := TCompany.Create;

Congratulations and best wishes! Kick into high gear! :-)

Go, CodeGear!

Tuesday, November 07, 2006

delphisvn for Delphi 7

Here are some screenshots of delphisvn for Delphi 7 which I'm working on. Some people have asked for it. I rarely use Delphi 7 anymore and I'm doing it just for fun. I expect to release it within a few days.

Update: The release is now available at SourceForge.

Content
Info
Diff

Tuesday, September 26, 2006

delphisvn: updated to Subversion 1.4.0

delphisvn now includes Subversion 1.4.0 client DLL. It can still use the previous (Subversion 1.3.2) client DLL.

Note: Using the Subversion 1.4.0 client library may automatically upgrade your working copy format which will make your working copy directories unusable by pre-1.4.0 clients.
For more information, see Subversion 1.4.0 Release Notes.

I've just upgraded my home computer to use Subversion 1.4.0 and TortoiseSVN 1.4.0.
The speed improvement is amazing! Kudos to the Subversion dev team!

Monday, July 24, 2006

Delphi Wiki

A Delphi Wiki has been started on http://delphi.wikia.com. I think it's a brilliant idea and I hope it takes off well and becomes a great community resource. So please also spread the word to other Delphi developers.

Wednesday, July 12, 2006

Shenoy's PInvoke Live Template

Shenoy has written a great example of a live template extension. It uses PInvoke web service from the PInvoke.net website. For Delphi code, it also uses the BabelCode web service to translate the declaration to Delphi (since PInvoke.net only returns C# and perhaps VB.NET code, I think).

So, you're writing a .NET app and need to make a PInvoke call to some unmanaged Win32 API? With this live template extension, no problem: type pinvd (in Delphi), or pinvc (in C#), press Ctrl+J, type in the function name, and press Enter ;-) The declaration will be inserted directly into your code. I think it's very nice!

Saturday, July 01, 2006

Blame

A few people have asked me if I plan to include Subversion blame functionality.
Here's a screenshot which shows what I've done:


Running blame may take a long time, especially with a file with a lot of history, so I wrote it to run in a separate thread.

The hint shows the commit log message of the blame line revision.

Thursday, June 22, 2006

delphisvn

I've started to prepare the Subversion stuff I've blogged about here for release as open source.
I've submitted project registration to Sourceforge. The project, if approved, will be called delphisvn and will be released under Mozilla Public License 1.1.

Update: Yay, I've just uploaded the initial release to SourceForge! :-)

Wednesday, June 21, 2006

More Subversion

In the recent few days, I've been able to spare some time for my Subversion IDE plugin again. I've written a dockable IDE form which can be used to perform some actions and view their results. If you're interested in how to write a dockable IDE form, see Corbin's article and example. Who knows, perhaps this plugin might actually become useful one day ;-)
See for yourself, in the screenshots below.

Subversion dockable IDE form 1

Subversion dockable IDE form 2
Here you can see that the form inherits from Delphi's internal TDockableToolbarForm.
The top two menu items are my own: Open will open the file in the IDE, and Show Diff will open its history view and show the differences between the local copy and the base revision.

Subversion dockable IDE form 3
I'm using Mike Lischke's excellent Virtual Treeview and I've noticed it's already also used by the IDE, e.g. in the Project Manager.
The columns can be moved, resized, shown or hidden. Their state is saved as part of your desktop (*.dst files in $(BDS)\bin directory). The treeviews can be sorted by individual columns by clicking on the header.

Subversion dockable IDE form 4
The Options dialog uses Delphi's internal dialog (TOrderedListEditDlg) to edit the list of Subversion working copy directories.

Subversion dockable IDE form 5
Here's a prompt for a commit log message.

Subversion dockable IDE form 6
Finally, the results of the Commit action.

Wednesday, April 05, 2006

Subversion editor view

Subversion editor view

Today I've played with the idea of a new Subversion editor view in the Delphi 2006 IDE.
To add a new editor view to the editor, you have to implement ICustomEditorView interface and register it with a RegisterEditorView call.
You'll need to add EditorViewSupport unit to your uses clause. This unit is only available through designide.dcp; there is no source code. But you can use Code Insight and Help Insight to see what methods are declared in the interface:

TSvnEditorView = class(TInterfacedObject, ICustomEditorView, ICustomEditorFrameView)
private
{ ICustomEditorView }
function GetCanCloneView: Boolean;
function GetCaption: string;
function GetPriority: Integer;
function GetStyle: TEditorViewStyle;
function GetViewIdentifier: string;
procedure Display(const AContext: IInterface; AViewObject: TObject);
function EditAction(const AContext: IInterface; Action: TEditAction; AViewObject: TObject): Boolean;
function GetEditState(const AContext: IInterface; AViewObject: TObject): TEditState;
function Handles(const AContext: IInterface): Boolean;
procedure Hide(const AContext: IInterface; AViewObject: TObject);
procedure ViewClosed(const AContext: IInterface; AViewObject: TObject);

{ ICustomEditorFrameView }
function GetFrameClass: TCustomFrameClass;
end;

Wednesday, March 22, 2006

XMLDoc regions CC#23955

In response to a comment here on this blog, I've uploaded source code of my XMLDoc regions plugin to Code Central.
I hope you'll find it useful.

Wednesday, March 08, 2006

Help Insight

I like Help Insight in Delphi 2006. I think it's going to prove to be very useful. The cool thing is that it's customizable: the popup window is a browser view of an HTML page generated from the XML compiler output by applying HelpInsight.xsl and HelpInsight.css files located in your $(BDS)\Objrepos directory.
If you want to have a look at what the XML compiler output looks like, you can modify your HelpInsight.xsl (back up the original first!) like this:
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html"/>
<xsl:template match="/">
<html>
<script language="javascript">
function showsrc() {
window.alert(document.body.innerHTML);
}
</script>
<body>
<a href="javascript:showsrc();">source</a>
<xsl:copy-of select="."/>
</body>
</html>
</xsl:template>
</xsl:stylesheet>
Then move your mouse over some identifiers in your source code to bring up the Help Insight browser window, and click on the "source" link to see the original XML produced by the compiler.
The format is basically the following:
<MEMBER DisplayName=|DisplayName| name=|name|>
<SOURCE declaredOn=|declaredOn|
declaredIn=|declaredIn|
declaredInShort=|declaredInShort|></SOURCE>
[|your XML comments|]
<SUMMARY><PARA>|summary|</PARA></SUMMARY>
[|params|]
</MEMBER>
I'm not going to write up a complete description of the elements, here are just a few quick examples. Given the following source code, you get the XML shown below when you invoke Help Insight for Const1, TForm1, Field, Func, Prop, Event and Form1, respectively:
const
Const1 = 42; // the answer to life, the universe and everything

type
TForm1 = class(TForm)
public
Field: Integer;
FEvent: TNotifyEvent;
function Func(X, Y: Integer; const S: string): Boolean;
property Prop: Integer read Field;
property Event: TNotifyEvent read FEvent;
end;

var
Form1: TForm1;

Const1:
<MEMBER DisplayName="Const1 Constant" name="C:Const1">
<SOURCE declaredOn="10,3"
declaredIn="C:\Temp\Unit1.pas"
declaredInShort="Unit1.pas"></SOURCE>
<SUMMARY><PARA>Const1 = 42 - System.Integer</PARA></SUMMARY>
<MEMBER>

TForm1:
<MEMBER DisplayName="TForm1 Type" name="T:Unit1.TForm1">
<SOURCE declaredOn="13,3"
declaredIn="C:\Temp\Unit1.pas"
declaredInShort="Unit1.pas"></SOURCE>
<SUMMARY><PARA>Declared in Unit1</PARA></SUMMARY>
</MEMBER>

Field:
<MEMBER DisplayName="Field Field" name="F:Unit1.TForm1.Field">
<SOURCE declaredOn="15,5"
declaredIn="C:\Temp\Unit1.pas"
declaredInShort="Unit1.pas"></SOURCE>
<SUMMARY><PARA>Field - System.Integer</PARA></SUMMARY>
</MEMBER>

Func:
<MEMBER DisplayName="TForm1.Func(Integer,Integer,string) Method"
name="M:Unit1.TForm1.Func(System.Integer,System.Integer,System.string)">
<SOURCE declaredOn="17,14"
declaredIn="C:\Temp\Unit1.pas"
declaredInShort="Unit1.pas"></SOURCE>
<SUMMARY><PARA>Declared in <SEE DisplayName="Unit1.TForm1"
cref="Unit1|Unit1.TForm1"></SEE></PARA></SUMMARY>
<PARAM name="X">System.Integer
<PARAM name="Y">System.Integer
<PARAM name="S">System.string
<RETURNS><PARA>System.Boolean</PARA></RETURNS>
</MEMBER>

Prop:
<MEMBER DisplayName="Prop Property" name="P:Unit1.TForm1.Prop">
<SOURCE declaredOn="18,14"
declaredIn="C:\Temp\Unit1.pas"
declaredInShort="Unit1.pas"></SOURCE>
<SUMMARY><PARA>Prop - System.Integer</PARA></SUMMARY>
</MEMBER>

Event:
<MEMBER DisplayName="Event Event" name="E:Unit1.TForm1.Event">
<SOURCE declaredOn="19,14"
declaredIn="C:\Temp\Unit1.pas"
declaredInShort="Unit1.pas"></SOURCE>
<SUMMARY><PARA>Declared in <SEE DisplayName="Unit1.TForm1"
cref="Unit1|Unit1.TForm"></SEE></PARA></SUMMARY>
<PARAM name="Sender">System.TObject
</MEMBER>

Form1:
<MEMBER DisplayName="Form1 Field" name="F:Unit1.Form1">
<SOURCE declaredOn="23,3"
declaredIn="C:\Temp\Unit1.pas"
declaredInShort="Unit1.pas"></SOURCE>
<SUMMARY><PARA>Form1 - <SEE DisplayName="Unit1.TForm1"
cref="Unit1|Unit1.TForm"></SEE></PARA></SUMMARY>
</MEMBER>
As you can see, the <SOURCE> node contains information about the source code file name, line number and column of the declaration. Also note the unclosed <PARAM> tags ;-)

The above XML is what you get when the source code doesn't contain any XML comments. If it does, they are simply inserted between Help Insight's <SOURCE> and <SUMMARY> nodes. Whatever your comments are, they all go there. The weird part is that if your comments contain any <PARAM> or <RETURNS> tags, or even any tags starting with these strings, such as <PARAMDEF> or <RETURNSDESC>, you will suppress the Help Insight's own <PARAM> and <RETURNS> sections (they will not be included anymore). If you want to have, for example, a list of parameters from the compiler mixed with your own descriptions of them, you'll have to use different tag names (I use <xparam> and <xreturns>) and write some XSL to produce the combined output.

As one can see in the original HelpInsight.xsl, the Help Insight browser supports some special URLs, e.g. a link with href="helpinsight:typelink:Unit1|Unit1.TForm1" would open Help Insight window for my TForm1 type. Another I've found is href="helpinsight:filelink:C:\Temp\Unit1.pas?10,3" which would make your IDE editor go to Unit1.pas at line 10, character position 3.

My current modifications to HelpInsight.xsl are such that my Help Insight shows me its own parameter list for methods, combined with my own descriptions of them. Summary and remarks are also included. I can also use URLs within my comments; these will produce HTML links which open a new IE window with the URL.
I've also modified HelpInsight.css a little bit to change colors, fonts etc.

Friday, March 03, 2006

XML Documentation in Delphi 2006

Delphi 2006 compiler supports an option to generate XML representation of your source code which can be processed (using XSLT) to produce, for example, HTML or HTML Help output.

To associate a piece of your own documentation with a class or method, you can simply put your comments directly before its declaration. The compiler will copy them into a separate <devnotes> node in the XML output.

Another place where these comments are used is the new, customizable Help Insight which I find very cool.

The only problem I have with writing documentation directly in source code is that the source code becomes cluttered and therefore less readable. (And it seems I'm not alone.)

I think one reasonable workaround for this problem could be this: Enclose all your documentation comments in regions with a special description, say, 'xmldoc', like this:

type
{$region 'xmldoc'}
/// <summary>
/// This is the application's main form.
/// </summary>
{$endregion}
TForm1 = class(TForm)

The important part is to use the 'xmldoc' description only for regions which contain your XML documentation so they can be processed separately from other regions in your code. Then you can write an IDE plugin to elide or unelide all such regions. Since I prefer using keyboard, I've implemented it as a keyboard binding:

procedure TXMLDocRegionKeyBinding.BindKeyboard(const BindingServices: IOTAKeyBindingServices);
begin
BindingServices.AddKeyBinding([ShortCut(Ord('D'), [ssShift, ssCtrl])], Execute, nil);
end;

procedure TXMLDocRegionKeyBinding.Execute(const Context: IOTAKeyContext; KeyCode: TShortcut;
var BindingResult: TKeyBindingResult);
var
AllElided: Boolean;
Row, Col: Integer;
Lines: TStringList;
I: Integer;
ElideActions: IOTAElideActions;
begin
BindingResult := krHandled;

if Supports(Context.EditBuffer.TopView, IOTAElideActions, ElideActions) then
begin
Row := Context.EditBuffer.EditPosition.Row;
Col := Context.EditBuffer.EditPosition.Column;
try
Lines := TStringList.Create;
try
Lines.Text := ReadEditorSource(Context.EditBuffer);
// if all 'xmldoc' regions are elided then we want to unelide all;
// otherwise we want to elide (those which are not)
AllElided := True;
for I := 0 to Lines.Count - 1 do
if AnsiSameText('{$region ''xmldoc''}', TrimLeft(Lines[I])) then
begin
Context.EditBuffer.EditPosition.Move(I + 1, 1);
if not IsRegionElided(Context.EditBuffer.EditPosition, Lines[I]) then
begin
AllElided := False;
Break;
end;
end;
for I := 0 to Lines.Count - 1 do
if AnsiSameText('{$region ''xmldoc''}', TrimLeft(Lines[I])) then
begin
Context.EditBuffer.EditPosition.Move(I + 1, 1);
if AllElided then
ElideActions.UnElideNearestBlock
else if not IsRegionElided(Context.EditBuffer.EditPosition, Lines[I]) then
begin
Context.EditBuffer.EditPosition.Move(I + 1, 1);
ElideActions.ElideNearestBlock;
end;
end;
Context.EditBuffer.TopView.Paint;
finally
Lines.Free;
end;
finally
Context.EditBuffer.EditPosition.Move(Row, Col);
end;
end;
end;

function TXMLDocRegionKeyBinding.IsRegionElided(const EditPosition: IOTAEditPosition; const Line: string): Boolean;
begin
EditPosition.MoveEOL;
Result := EditPosition.Column < Length(Line);
end;

This allows me to quickly show or hide all my XML documentation in the current source code unit with a single keystroke (Ctrl+Shift+D) while keeping the current elision state of other regions.

Update: Note that after the elide/unelide code, I've added a call to IOTAEditView.Paint. This forces the edit view to update itself on the screen. One case when this is necessary is when you're eliding while viewing the bottom part of your unit. After eliding, the portion of the window after the final "end." is not updated/cleared. The Paint call solves this problem.

Tuesday, February 28, 2006

Subversion in my Delphi IDE

Subversion in my Delphi IDE
Subversion in my Delphi IDE

For over a year, I've been using Subversion version control software to manage my source code, with TortoiseSVN as client on Windows. I find TortoiseSVN extremely helpful in my everyday work. It's implemented as a Windows shell extension (so it's integrated with Windows Explorer) which makes it very comfortable to use.

For the last couple of days, I've been trying to write my own Subversion client and integrate it with my Delphi IDE. The picture above shows my humble first results.

There's Subversion client API but I had to use Microsoft Visual Studio to create a DLL to expose it to Delphi because Windows builds of Subversion link all the relevant code statically.

Then I had to write import units (translation of C headers into Delphi) to be able to use the DLL in Delphi. Subversion uses Apache Portable Runtime library, so I had to translate those headers, too (fortunately, APR on Windows does include DLLs). BTW, I find my own Delphi keyboard macro manager quite useful for translating C into Delphi. :-)

Today I finally used Delphi's new FileHistoryAPI to write a Subversion history provider which is shown in the screenshot. This part seems to be very straightforward.

This pet project of mine is in a very early alpha stage now. My header translations are incomplete and some parts are probably incorrect. Also, I don't know Subversion and APR internals very well yet so it's quite possible that what I'm doing can be done in a much more efficient way.

I plan to work on this project in my free time, extend it bit by bit, and when I have something stable and usable enough I will upload it to Borland's (or the new "DevCo"'s ;-)) CodeCentral.

Thursday, February 23, 2006

Monday, February 06, 2006

CC2209 fix

I've just fixed a memory leak in my entry #2209, titled "Access client's IP address from a DataSnap remote data module" on Borland's CodeCentral.

The problem was on the COM server side, in the included DSnapCom unit. The client's IP address (passed in by httpsrvr.dll or scktsrvr.exe as an additional BSTR parameter) was not released properly, which caused a few bytes of memory leak for each method call.

Looking back, this bug seems pretty obvious, and it seems strange that I didn't discover it at the time when I wrote the code. I was under the impression that I had tested everything properly. Duh!

Oh well. It's back to testing then. (I've discovered this while trying to solve another problem which might or might not be related to it.)

Sunday, January 22, 2006

Erik's OpenTools FAQ

I've just noticed that Erik's OpenTools API FAQ and Resources web page links to my CodeCentral entry as an example on getting the target executable's file name from Delphi. Cool! :-)
Note that you only need this for Delphi version 7 or earlier; newer versions already expose TargetName property.

Saturday, January 14, 2006

Free Threaded Marshaler

When writing a DataSnap appserver, you should probably try to make it thread-safe and register it to use the multi-threaded apartment (MTA) model (ThreadingModel = tmFree) because it gives you total control over synchronization and optimization of your code. But writing thread-safe code also requires more work. In some cases, it may be useful to register your appserver to use the single-threaded apartment (STA) model (ThreadingModel = tmApartment) and rely on COM to serialize calls from different threads into one thread "apartment" - the same thread which was used to create the instance.

Recently, after reviewing an external library I'm using in my appserver, and coming to suspect that it might not be thread-safe, I've chosen to use the STA model as a temporary workaround until I fix the problem.

Soon enough, I hit a problem - in the STA model, interface pointers cannot be freely passed from thread to thread - they need to be marshaled across thread boundaries, otherwise the call will fail with RPC_E_WRONG_THREAD error.
To marshal interface pointers across threads, you can use CoMarshalInterThreadInterfaceInStream . Another, very easy and convenient way is to aggregate the Free Threaded Marshaler.

Delphi makes aggregation a piece of cake! ;-) Here's a simple class which you can derive your appserver from (instead of deriving from TRemoteDataModule directly):

unit DataBkrEx;

interface

uses
Classes, ActiveX,
DataBkr;

type
TRemoteDataModuleEx = class(TRemoteDataModule, IMarshal)
private
FMarshal: IUnknown;
function GetMarshal: IMarshal;
property Marshal: IMarshal read GetMarshal implements IMarshal;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
end;

implementation

uses ComObj;

{ TRemoteDataModuleEx private }

function TRemoteDataModuleEx.GetMarshal: IMarshal;
begin
if not Assigned(FMarshal) then
OleCheck(CoCreateFreeThreadedMarshaler(Self as IUnknown, FMarshal));
Result := FMarshal as IMarshal;
end;

{ TRemoteDataModuleEx public }

constructor TRemoteDataModuleEx.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FMarshal := nil;
end;

destructor TRemoteDataModuleEx.Destroy;
begin
FMarshal := nil;
inherited Destroy;
end;

end.
Note that I'm not checking for a controller object (in case the appserver itself is aggregated) but that's normally not used in DataSnap appservers. The correct implementation would also require a replacement for TComponentFactory in the VclCom unit.