It’s hardly an original thought, but why does the off-topic group at the Embarcadero forums still exist? At my time of writing (I’m doing so in advance of actually posting BTW), it’s dominated by some American guy whining about supposed censorship from political opponents — typical ideologue, he assumes everyone else is ‘really’ as politically obsessed as himself, and so finds political motivation everywhere regardless of how non-political the words of others appear to be. In response to what turned out to be only a temporary banning of him, a supporter writes:
Nobody will be left to post here if it goes on, the whole newsgroup will stop to exist. Nobody posts a lot here anymore, not like a few years ago anyway.
To which I can only reply: good riddance. The group does only harm to Embarcadero (a technology company, not a low-rent rival for The Huffington Post or Pajamas Media) for hosting it.
PS: while the off-topic group has its dubious prominence (it’s proudly listed near the top of the forums homepage), interesting technical things like Eli Boling’s blog are buried away. Admittedly, this largely saves him from the ‘you’re all a bunch of idiots who don’t know what they’re doing’ crowd, but still…
A reasonable way to understand the semantics of dynamic arrays in Delphi is to recall the sort of code you might have used as a substitute before they where introduced in Delphi 4. Assuming only a single dimension to keep things simple, stage one would be to declare dummy static array type, together with a corresponding pointer type:
type PRectArray = ^TRectArray; TRectArray = array[0..$FFFFF] of TRect;
Allocation and reallocation may then be done using the appropriately-named ReallocMem routine. More exactly, you can use GetMem and FreeMem as well, though since ReallocMem can do both initial allocation and final deallocation, there’s no need — just remember to initialise the variable to nil if declared in a local routine, and always call ReallocMem at the end to free the array:
read more…
I haven’t looked at his blog in a while, but I see Mr Hater had an unusally constructive post last month. As with other examples of the genre, it does have its parochial edge (see in particular point 13 — Embarcadero are going wrong since, having failed to create a direct replacement, they haven’t restarted maintaining the BDE?!). Nonetheless, it’s an interesting list with some good points. If reading such things are what you like to do, then have a look…
In a nice little series on ‘Delphi in a Unicode world’ written and published around the time of Delphi 2009’s release, Nick Hodges writes on the topic of using strings as binary buffers as thus:
A common idiom is to use a string as a data buffer. It’s common because it’s been easy — manipulating strings is generally pretty straight forward. However, existing code that does this will almost certainly need to be adjusted given the fact that string now is a UnicodeString.
There are a couple of ways to deal with code that uses a string as a data buffer. The first is to simply declare the variable being used as a data buffer as an AnsiString instead of string [...] The second and preferred way dealing with this situation [, however,] is to convert your buffer from a string type to an array of bytes, or TBytes. TBytes is designed specifically for this purpose, and works as you likely were using the string type previously.
Now, I’m totally at one with those who think misusing the string type for binary buffers was a silly thing to do. Nevertheless, to say TBytes was ‘designed specifically for this purpose’ is equally as silly in my view, since in being a simple typedef for a dynamic array of bytes that was only added in D2007 (dynamic arrays themselves being added way back in D4), it patently wasn’t.
More to the point, despite having an implementation that redeployed that of the original AnsiString type for more general purposes, dynamic arrays at large — and thus, TBytes specifically — suffer from various key shortcomings in comparison:
- No copy-on-write semantics. The fact that dynamic arrays and strings share key RTL functions (Copy, Length and SetLength) frequently leads me to forget this, as well as the fact that dynamic arrays aren’t in fact pure reference types in use.
- The equals (=) and not equals (<>) operators compare references rather than data. (Note how the string type is simply more flexible here, since you can just cast to Pointer if you do want to compare string references.)
- You can’t use the addition (+) operator. For sure, using this in a light loop is highly inefficient — but if it’s so terrible in principle, why allow it for strings? [Edit: before you get the wrong idea, see my response to Luigi Sandon -- 'LDS' -- in the comments.]
- You cannot assign an array constant to a dynamic array. Cf. how there isn’t a practical distinction between string constants and string variables — they’re all just ’strings’, and even under the hood, a string constant is just a string with a dummy reference count.
- No copy-on-write semantics means you lose much of the const-ness of constant paramaters and read-only properties — basically, the consumers of an object can change the elements of a read-only dynamic array property where they can’t change the characters of a read-only string property. Admittedly, the loss of the const-ness of constant parameters is much alleviated by the open array syntax (though let’s not dilute this by encouraging the use of paramaters declared as TBytes rather than ‘const array of Byte’, eh?).* Nonetheless, it is still an unfortunate side effect of dynamic arrays not being implemented as quasi-value types, à la AnsiString and UnicodeString.
In my view, it is these features that make manipulating strings ‘pretty straight forward’, and moreover, not prone to bugs through not fully understanding the type’s internal semantics. The fact that dynamic arrays do not have them, then, makes the idea of TBytes being some sort of genuine substitute for the misused old AnsiString quite false. That said, one particular issue with dynamic arrays especially gets my beef, but I’ll leave elucidating that to another time…
* Thus:
procedure Test(const Arg1: TBytes; const Arg2: array of Byte); begin Arg1[0] := 99; //compiles! Arg2[0] := 99; //doesn't compile end;
I’ve just put up another revision of my Delphi Exif parsing code. This revision has two main themes:
- Sanity checks have been added to the parsing code, meaning every single TIFF offset is now checked. Connected to this, and by popular demand (or so it seems), the balance between accepting malformed metadata and raising an exception has now swung a bit towards the former.
- Better maker note support: specifically, the tag structures of Canon, Panasonic and Sony MakerNotes are now understood. The interpretation of maker note tag values is still left to the user however.
Other, more minor changes include:
- Fixed typo in GPS direction tag setter which meant the value could never be changed.
- Added memory leak fix to CCR.XMPUtils.pas suggested by David Hoyle.
- Added delay loading semantics to the XMPPacket property of TCustomExifData, the idea being that attempts to read Exif tags should not ever lead to an EInvalidXMPPacket exception being raised. Equivalent behaviour has been built into the new maker note parser code too.
- More helper methods of the TryGetXXXValue and ReadXXX kind.
- Surfaced two interop IFD tags as properties on TCustomExifData.
- Maker note data are now moved back to their original position on save if the OffsetSchema tag had been set. (Actually, this should have been the case for the previous release but for a bug, typing Inc where I meant Dec.)
- Demos rejigged a bit — PanasonicMakerNoteView.exe removed (its functionality has been added to an improved ExifList.exe), and two new console ones added (CreateXMPSidecar.exe and PanaMakerPatch.exe). You can download compiled versions of the demos from here.
One final note — idly Googling, I’ve found that there’s at least one person around who believes it might be realistic to backport my code to Delphi 7. Two words of advice: don’t bother. You’ll just have one problem after another.
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.