Yes, it’s been done before – indeed, I’ve used Brian Moelke’s simple Hunspell wrapper from a few years back myself – but a few posts on the Embarcadero forums in the past couple of months have prompted me to write up my own.
Basically, if you haven’t heard of it, Hunspell is the open source spell checking engine used in OpenOffice, and very good it is too, at least for English – I’ve found it much better than Ispell, for example, in terms of both speed and the quality of its suggestions.
The Hunspell source itself can be downloaded from SourceForge here – you’ll need a C++ compiler to build a DLL from it (VC++ Express is fine for this purpose, and so might C++Builder – I don’t know). Calling a resulting DLL is then fairly straightforward, though one slightly tricky thing – and where my own code has its main reason for being – is in using dictionaries with foreign code pages, such as a Greek dictionary on an English system. The difficulty here is that while Hunspell itself supports UTF-8 encoded dictionaries, most actually-existing ones have an ANSI encoding – and the strings you pass to the Hunspell engine must have the encoding of the dictionary being used, the engine itself doing no conversions. In light of that, my wrapper transparently does any needed conversions for you, with the key methods having Ansi and Unicode overloads when compiling in Delphi 2006 or 2007. Moreover, I’ve also tried to write the source in a D2009+ friendly manner too.
Naturally, it may turn out that no one but myself will find it useful though, but anyhow, it’s available here if you’re interested. The ZIP includes a demo app (as one might expect), together with a prebuilt Hunspell DLL compiled with the current-at-my-time-of-typing version of the Hunspell source, namely v1.2.8.
It’s taken a while, but I’ve just completed another revision of my MPL’ed Exif reader/writer code, Exif being the standard format for JPEG metadata (see here). The biggest new feature in terms of effort spent is much better XMP support – the default behaviour is now to update the equivalent XMP property whenever an Exif tag value is changed, though only when the former already exists. If you want, you can get the behaviour of Vista’s (and quite possibly Windows 7’s) Windows Explorer instead, which is to always create an XMP value whenever an Exif one is set, with a single property change – set XMPWritePolicy to xwAlwaysUpdate.
In terms of actual usefulness though, possibly a bigger change is the fact that by default, MakerNote tag data are now always written out to their original location. Unlike implementing proper XMP support, which was a right drag, this turned out to be pretty straightforward. Other than that, I’ve also fixed some bugs and fiddled around with some of the lower level code a bit – in particular, where I had previously assumed the ExifImageWidth and ExifImageHeight tags would always have longword values, I now support word-sized ones too. Moreover, the ‘correct’ positions of the JFIF, Exif and XMP segments are now enforced by TExifData.SaveToJPEG, any comment segments (for example) being moved below.
That said, looking at the CodeCentral stats, it seems quite a few people have downloaded earlier revisions of the code, which makes me think – it could do with a better name! Unfortunately, the most obvious one (dExif) has already been taken. So, any ideas..?
Update 1 (19/10/09): in the hours since I first posted this, I’ve slightly amended the original ZIP to avoid some D2009 issues — the current version of CCR.Exif.pas is thus 0.9.8a.
Update 2 (29/10/09): I’ve also now slightly amended CCR.Exif.XMPUtils.pas. Like CCR.Exif.pas, it now stands at version 0.9.8a.
I’ve updated the Delphi 2010 IDispatch proxy code I posted a couple of weeks ago, fixing a few bugs, improving how Delphi exceptions are handled in my IDispatch.Invoke implementation, and adding some set type support. For the latter, set values are now surfaced as objects with Empty and Contains methods, with the creation of new set values in script being made possible via a new descendent of TCustomDispProxy specially for set types.
That said, I’ve also added a more realistic demo in the form of a scriptable version of the old text editor standby. Being ‘more realistic’ meant two things in particular: drastically cutting down the scope for scripts creating Delphi objects (the issue here being Delphi’s lack of garbage collection in combination with VBScript’s lack of a try/finally equivalent), and creating some script-specific classes to abstract from the application’s internals – basically, you don’t really want to be exposing an application’s internals directly.
One thing I should mention is that because I was finishing it right at the end of my trial period, there’s the odd bug in the new demo’s UI that I didn’t have the time to clean up – in particular, I forgot to add disabled images for the formatting actions, which causes their icons go a bit funny when the app loses the focus. Nonetheless, the proxy code itself should be pretty solid. If you want to see it in action, download the revised code from here.
Consider the following declaration, which happens to be from the AutoCorrect component code I posted a couple of months or so ago:
const RightDoubleQuote = '”';
As you can probably guess, RightDoubleQuote then gets assigned to the Key parameter of a control’s OnKeyPress event handler, as appropriate. Should work fine, right? Well, it indeed does in D2007 or earlier, but not in the D2010 trial I’ve been playing with (I assume it’s broken in D2009 too). Basically, RightDoubleQuote in D2010, like in D7 or D2007, is compiled as an AnsiChar; and since the Ansi and unicode ordinal values are different, and no conversion from Ansi to unicode is made when assigning the constant to a Char value (now a WideChar value in D2009+), the latter gets junk. Unhelpfully enough, the compiler doesn’t issue a warning, and the $HIGHCHARUNICODE directive makes no difference either.
Nevertheless, the workaround — compatible with older Delphi versions — is easy: just explicitly make the constant a Char value with a cast (casting to WideChar would work in D2009+ too, but this would change the type when compiled in legacy Delphi versions):
const
RightDoubleQuote = Char('”');
I’ve uploaded an updated version of my AutoCorrect components as a result.
In the comments to my previous post, Barry Kelly has popped up to explain the reasons behind the limitations of the new RTTI interface I listed. One thing he did correct me on was the issue of event handlers, since while I had been thinking they are put into TValue records as anonymous methods, this is in fact not the case — in reality, they are put in as TMethod records, as you would expect.
This discovery made, I wondered whether it is possible to invoke event handlers using RTTI — and, in short, they indeed can, since while they cannot be invoked in a single call directly (TRttiMethodType not having an Invoke method), it isn’t hard to write a simple-ish wrapper routine to do the deed for you. One caveat, though, is that because you call Invoke upon a TRttiMethod instance, calling a handler whose type you don’t know up front requires its implementing method to be exposed by RTTI, which in practical terms means it must be with public or published scope. For event types you do know up front, however, you can avoid TRttiMethod completely and just call the handler directly, negating the need for its implementing method to be public or published.
Well, putting this and a bit of knowledge about the TMethod type all together, I came up with the following implementation:
uses
Classes, TypInfo, Rtti, Controls;
resourcestring
SMissingEvent = '%s does not have an event called %s';
SPropertyNotAnEvent = '%s.%s is not an event';
SEventHandlerHasInsufficientRTTI = 'Event handler does not ' +
'have the required RTTI to be dynamically invoked';
function CallEventHandler(Instance: TObject; Event: TRttiProperty;
const Args: array of TValue): TValue; overload;
var
HandlerValue: TValue;
HandlerObj: TObject;
MethodRecPtr: ^TMethod;
RttiContext: TRttiContext;
RttiMethod: TRttiMethod;
begin
if Event.PropertyType.TypeKind <> tkMethod then
raise EInvocationError.CreateFmt(SPropertyNotAnEvent, [Instance.ClassName, Event.Name]);
Result := nil;
HandlerValue := Event.GetValue(Instance);
if HandlerValue.IsEmpty then Exit;
MethodRecPtr := HandlerValue.GetReferenceToRawData;
{ check for event types we know }
if HandlerValue.TypeInfo = TypeInfo(TNotifyEvent) then
begin
TNotifyEvent(MethodRecPtr^)(Args[0].AsObject);
Exit;
end;
if HandlerValue.TypeInfo = TypeInfo(TMouseEvent) then
begin
TMouseEvent(MethodRecPtr^)(Args[0].AsObject, TMouseButton(Args[1].AsOrdinal),
Args[2].AsType<TShiftState>, Args[3].AsInteger, Args[4].AsInteger);
Exit;
end;
if HandlerValue.TypeInfo = TypeInfo(TMouseMoveEvent) then
begin
TMouseMoveEvent(MethodRecPtr^)(Args[0].AsObject,
Args[1].AsType<TShiftState>, Args[2].AsInteger, Args[3].AsInteger);
Exit;
end;
{ still here? well, let's go for the generic approach }
HandlerObj := MethodRecPtr.Data;
for RttiMethod in RttiContext.GetType(HandlerObj.ClassType).GetMethods do
if RttiMethod.CodeAddress = MethodRecPtr.Code then
begin
Result := RttiMethod.Invoke(HandlerObj, Args);
Exit;
end;
raise EInsufficientRtti.Create(SEventHandlerHasInsufficientRTTI);
end;
function CallEventHandler(Instance: TObject; const EventName: string;
const Args: array of TValue): TValue; overload;
var
RttiContext: TRttiContext;
Prop: TRttiProperty;
begin
Prop := RttiContext.GetType(Instance.ClassType).GetProperty(EventName);
if Prop = nil then
raise EInvocationError.CreateFmt(SMissingEvent, [Instance.ClassName, EventName]);
Result := CallEventHandler(Instance, Prop, Args);
end;
If you have any other standard event types you wish to handle directly, you can add them after TNotifyEvent is taken care of, following the pattern given — note that for any argument type that doesn’t have a corresponding AsXXX method on TValue, you should use the angle bracket syntax, like I do for TShiftState.
In use, you can then do the following:
CallEventHandler(MyButton, 'OnClick', [MyButton]);
This calls the OnClick event handler for MyButton, passing MyButton as the Sender parameter.
Now, OnClick, being of the TNotifyEvent type, was called directly by CallEventHandler. To test the generic fallback approach, add the following as the handler to the form’s OnGesture event:
procedure TForm1.FormGesture(Sender: TObject;
const EventInfo: TGestureEventInfo; var Handled: Boolean);
begin
ShowMessageFmt('Distance = %d', [EventInfo.Distance]);
end;
To do the actual testing, handle the OnDblClick event of the form as thus:
procedure TForm1.FormDblClick(Sender: TObject); var EventInfo: TGestureEventInfo; Handled: Boolean; begin EventInfo.Distance := 999; Handled := False; CallEventHandler(Self, 'OnGesture', [Self, TValue.From(EventInfo), Handled]); end;
Note how because EventInfo is a record, you need to use the TValue.From syntax — a minor incovenience for sure, but no more than that.
Try this out by running the app and double-clicking the form, and you should find that it works. What, though, of the var parameter? For, if you change the Handled parameter in the handler, you’ll find that the Handled variable in the caller is not changed. This makes sense if you recognise that TValue records contain copies of, and not pointers to, their source data. If the ‘var-ness’ of a parameter is important to the caller, though, then you will need to construct an array of TValue records manually:
procedure TForm1.FormDblClick(Sender: TObject);
var
EventInfo: TGestureEventInfo;
Handled: Boolean;
Args: TArray<TValue>;
begin
EventInfo.Distance := 999;
Handled := False;
Args := TArray<TValue>.Create(Self, TValue.From(EventInfo), Handled);
CallEventHandler(Self, 'OnGesture', Args);
Handled := Args[2].AsBoolean;
ShowMessage('After being OnGesture has been called, Handled is now ' +
BoolToStr(Handled, True));
end;
Or, using a static rather than a dynamic array:
procedure TForm1.FormDblClick(Sender: TObject);
var
EventInfo: TGestureEventInfo;
Handled: Boolean;
Args: array[0..2] of TValue;
begin
EventInfo.Distance := 999;
Handled := False;
Args[0] := Self;
Args[1] := TValue.From(EventInfo);
Args[2] := Handled;
CallEventHandler(Self, 'OnGesture', Args);
Handled := Args[2].AsBoolean;
ShowMessage('After being OnGesture has been called, Handled is now ' +
BoolToStr(Handled, True));
end;
To test, we can alter the OnGesture handler to be as thus:
procedure TfrmMain.FormGesture(Sender: TObject;
const EventInfo: TGestureEventInfo; var Handled: Boolean);
begin
ShowMessageFmt('Distance = %d; on input, Handled is %s',
[EventInfo.Distance, BoolToStr(Handled, True)]);
Handled := not Handled;
end;
Try it out, and you should find it all works as expected.
I’ve finally put up the IDispatch wrapper code I mentioned in my previous post — see here for some information and the CodeCentral link. Compared to the compiled demo I posted earlier, I’ve implemented a few more things, e.g. read-only dynamic array support and the automatic exposure of most enumerators, the latter enabling For Each in VBScript where you would use for/in in Delphi.
Nonetheless,whether it’s practically useful or not I don’t really know — writing it just scratched a very old itch really, having played around with the script control many years ago. Moreover, as a demo of the new RTTI, my code is not exactly the best, since most of it concerns implementing IDispatch rather than using Rtti.pas — though of course, that’s as much a tribute to the latter as it is a criticism of my code qua demo of it.
Having said that, writing the proxy classes did make explicit certain limitations of the new RTTI — basically, while its coverage is very good, it isn’t perfect:
- Where TRttiMethod.Invoke accepts a TValue, object or class as its first parameter, TRttiProperty.GetValue only accepts a pointer. This probably just refects underlying limitations, but nonetheless, the interface should match Invoke IMO.
- Indexed properties are not surfaced at all.
- You can’t tell whether such-and-so property is a default property.
- Class vars are not surfaced.
- Method pointers (= events) seem tricky
(even impossible)to work with when put into a TValue . (Note they aren’t put in as TMethod records, but anonymous method interfaces, or at least, seem to be.Ignore that — I must have been testing incorrectly. They are in fact stored as TMethod records, as you would expect. See my post here for how to invoke an event handler using RTTI.) - Sets aren’t exposed as nicely as arrays are.
- An interface type requires the explicit addition of $M+ (or be derived from IInvokable) for its methods to be surfaced. Even then however, interface properties are ignored. (I’m guessing this is yet another couple of quirks due to the Delphi interface type’s origins as a COM support feature.)
Overall though, the new RTTI is still a very impressive feature and one I think people should use in confidence.
[Update: Barry Kelly, the author of the new RTTI, explains the limitations just listed in the comments.]
I’ve been playing around with the new RTTI stuff in the D2010 trial, and I have to say, I’m impressed. But for what it surfaces for method pointers (= event types) and sets, it seems both very complete and very intuitive to use. Not only have I managed to use it without having reference to either source code or an API reference (though let’s not encourage Embarcadero on the latter, eh?), but it has been generally very solid — do something wrong, and an appropriate exception is cleanly raised with a message that tells you straighaway what the issue is. The one (er) exception to this is TValue not doing quite enough verification when attempting a cast to a Variant, though as the latter is an explicit operation, it’s easy enough to work around if and when it bites.
That said, reading the small amount of documentation, I happened across mention of an Invoke method, which set me thinking — is it finally possible to cleanly (and generically!) wrap Delphi objects into IDispatch ones for use in Active Scripting? Well, apart from the events issue and the difficulties of surfacing set types in a script-friendly manner, my answer is: for sure! Check out this compiled EXE if you’re interested (screenshot here).
Basically, what I’ve done is to write a TCustomDispProxy class with TObjectDispProxy and TClassDispProxy descendants; in the demo, these are then used to directly surface the Application, Mouse and Screen objects, together with the form instance (object properties and fields are handled automatically) and some metaclasses (TButton, TLabel, etc.).The idea is that no specific object or class type requires a specific wrapper — all just use TObjectDispProxy and TClassDispProxy respectively, with wrappers for sub-objects created on the fly. Now unlike Allen Bauer, I’m not a masochist in these things, and so have used the MS Script Control rather than implemented the Active Scripting interfaces directly; because of this, be warned that if the control isn’t installed (which is unlikely these days, though could be the case), the EXE won’t run.
Anyhow, if anyone’s interested in the actual code, add a comment saying so and I’ll endeavour to do it up for public consumption. Alternatively, if you’re not interested, then don’t leave a comment and I won’t bother…
[Update: I've put up the code now, adding enumerator support and a few other bits compared to the original demo -- see here.]
I downloaded the D2010 trial last night to see how the latest and greatest version of Delphi is looking. Like Tim Anderson, my initial impressions are mixed — not terrible by any means, but not as good as they maybe should have been.
To start with the good though, downloading and installing was nice and efficient for me. I’ve never installed Visual Studio to compare, but in relation to MS Office (which I have installed quite a few times and over several versions), the Delphi installation process comes across smelling of roses. Of course, one might say that anything could look good next to Office’s over-engineered lump of a setup process, but even still, the Delphi installation is still fine IMO.
Getting down to the actual implementation of interface-to-object casts in D2010, Allen Bauer reports that the solution was actually quite simple. Basically, given interface casting already used IInterface.QueryInterface, and the default implementation of IInterface.QueryInterface called TObject.GetInterface, ‘all’ that was needed was for TObject.GetInterface to check for a special GUID, outputting Self if found. This made me wonder — can we use the same technique in earlier versions of Delphi? Well, one thing we can’t do is override the semantics of the ‘as’ and ‘is’ operators, or indeed hard casts of the form TMyObject(IntfRef). Nonetheless, a long-established alternative to casting for interfaces is the Supports RTL function, added in D6 IIRC. What we can do, then, is to add an overload to Supports that takes an interface reference as its first parameter and a class type as its second one. While this isn’t as neat as the D2010 solution, it can be as generic.
Taking one step at a time though, start up the IDE and create a new console project. In the first instance, we’ll try to implement the functionality just for one class and at the level of QueryInterface — a proof of concept if you will:
read more…
Interfaces are a bit of a funny feature in native code Delphi — a fairly basic construct, and indeed, one around since Delphi 3, yet implemented in a manner that makes their use needlessly fiddly outside of their original purpose, which was to be a support feature for COM development.* Ever cursed how interface types appear to support inheritance, but don’t really (e.g., a class needs to explicitly implement every interface down the inheritance tree rather than just the bottom one)? Or how you have to be careful about not leaving danging interface references about, since a virtual method call (namely, to _Release) *will* be made behind your back (and what do you mean, ‘but _Release isn’t a virtual method on the implementing object…’)? Or how you have to concentrate when using runtime-only TXMLDocument instances in ways irrelevant for runtime-only components generally? Blame the Delphi 3 engineers baking COM conventions into the language!
With that in mind, I thought it very much a Good Thing when Malcolm Groves reported how yet another COM-related quirk — namely, the inability to cast from an interface to an object reference — will be removed in Delphi 2010, a thought strengthened when I read Allen Bauer’s subsequent post on the not particularly gory details. The one quibble I have, though, is how the new feature is being presented, viz., as the ability to cast from an interface to the implementing object. For, if we leave aside COM-specific issues and think of what an interface is in principle, surely the answer is, nothing more and nothing less than a collection of methods. What, though, is an object? In short, a collection of methods + other stuff. At heart, an object — any object! — is thus a strict superset of an interface. In querying for a particular class from an interface reference, then, what you’re doing is querying for just another ‘interface’ type. Think of it like that, and the fact that an ‘EInvalidCast’ exception will be raised upon trying to cast from an external interface not implemented in Delphi, far from being a limitation or quirk of the new feature, makes sense — for, any Delphi class will be an ‘interface’ that ‘extends’ the TObject ‘interface’, and by definition, no externally-authored object will do that.
Anyhow, on reading Allen Bauer’s post, I wondered how far (and how easily) the technique D2010 uses can be employed in earlier versions. Ten minutes playing around gave an answer. More on that next time though…
* Knowing nothing about the internal discussions at the time, I should probably hedge and say motives may have been mixed — at least, Java emerged in the same timeframe with interface types as a first class language feature not designed to support for any particular external API. The fact that the base Delphi interface type was called IUnknown rather than IInterface up until Delphi 6 emphasises the Delphi engineers’ COM-centric mindset though.