Thursday, June 14, 2007

Connecting an event to a Active Script function

In this article I described how to connect Active Script to a VCL Win32 application. This post will describe how to extend that demo to show how to directly connect an event (like OnClick) directly up to an ActiveScript function. You can get the new version of the demo from CodeCentral right here. Let's get started.

First off we'll add a button to the form and change the caption to “Hook Event.” We'll use the TEdit that's already there to get the name of the Active Script function to hook up when the Hook Even button is pressed. Here'e what the form looks like now:



Double-click the Hook Event button and then add this code:
procedure TForm3.Button2Click(Sender: TObject);
var
Info: EXCEPINFO;
State: TOleEnum;
Code: WideString;
Disp: IDispatch;
Id: Cardinal;
Name: WideString;
PropInfo: PPropInfo;
begin
Code := Memo1.Text;
OleCheck(FScript.GetScriptState(State));
if State = SCRIPTSTATE_CONNECTED then
OleCheck(FScript.SetScriptState(SCRIPTSTATE_DISCONNECTED));
OleCheck(FParse.ParseScriptText(PWideChar(Code), nil, nil, nil, 0, 0,
SCRIPTITEM_ISVISIBLE or SCRIPTITEM_ISPERSISTENT, nil, Info));
OleCheck(FScript.SetScriptState(SCRIPTSTATE_CONNECTED));
OleCheck(FScript.GetScriptDispatch(nil, Disp));
Name := Edit1.Text;
OleCheck(Disp.GetIDsOfNames(GUID_NULL, @Name, 1, GetThreadLocale, @Id));
if FClickMethod.Code <> nil then
begin
ReleaseMethodPointer(FClickMethod);
FillChar(FClickMethod, SizeOf(FClickMethod), 0);
end;
PropInfo := GetPropInfo(CheckBox1, 'OnClick', [tkMethod]);
FClickMethod := CreateMethodPointer(Disp, Id, PropInfo.PropType^);
SetMethodProp(CheckBox1, 'OnClick', FClickMethod);
end;

So the difference we have from the last method is that we don't want the script state to be disconnected at the end, so we first get the script state. If its connected, then disconnect it. Just like the last installment, we'll parse the script text, and set the state to connected. If there are any statements outside a function block, they'll execute immediately but we want to leave the script in the connected state. So the next thing is to get an IDispatch for the whole script. That is the GetScriptDispatch call. This is where we'll use the TEdit control on the form. The text contains the name of the Active Script function to look up in the GetIDsOfNames call. If the function is found, this function will return its “ID.” Once we have that, the rest of the code is all about creating a special TMethod* “stub” that is then assigned to the OnClick of the CheckBox1 component. Remember I mentioned the CreateMethodPointer() function in ObjComAuto? This is where that little tidbit of magic is used. Once we have a TMethod we can assign that to the OnClick of CheckBox1 using the SetMethodProp function. We have to do it this way to get around the type incompatibility of the TNotifyEvent and TMethod. That's all there is to wiring up the event. Let's see this puppy in action!

Run the application and paste the following code into Memo1:
function CheckBox1Click(sender) {
if (sender.Checked == 1) {
Application.MainForm.Caption = "CheckBox1 is checked"
} else {
Application.MainForm.Caption = "CheckBox1 is NOT checked"
}
}

Then enter “CheckBox1Click” into Edit1 and press the “Hook Event” button. As long as you got no error messages, you should be able to click CheckBox1 on and off and the caption of the form should change to reflect the checked state of the checkbox!

Again, the source code for this demo is in CodeCentral here: http://cc.codegear.com/item/24667
*TMethod - This is a data structure that reflects the actual in-memory representation of a method pointer. It simply consists of the address of the actual method to call and a reference to the specific instance of the class on which the method is defined.

Wednesday, June 13, 2007

Adding Active Scripting to your Delphi Win32 application

I know, I know. This has been done before and there are components out there that add this kind of capability to a Delphi application. What I wanted to do, however, was to show that nearly all the functionality to do this is already in the product you have right now (at least in Delphi 2007). Yes, this is based heavily on COM. Yes, COM is a huge, complicated technology. What is interesting is how well Delphi and the Object Pascal language on which it is based is suited to making your life so much easier when dealing with COM. The complete source to this demo project is available on CodeCentral right here.

Native Object Pascal language support for reference counted interfaces is only the start. There is a whole host of other things that Delphi provides in both the RTL and through a very little known technique of enabling extended meta-data on all public methods and properties of a class. Steve Trefethen has been hinting at a lot of this kind of support in his articles about creating a native compiled Win32 web “mash-up” with Google Maps. In nothing more than a couple of hours, I had a simple demo up and running that was running Java Script from within a Delphi application which would then access and manipulate various controls on a form. Let's get started.

First, we're going to examine the units from the Delphi RTL that are pertinent to making this happen. Looking in sourcewin32rtlcommon, you'll find an interesting unit, ObjAuto.pas. Take a moment to peruse this units. Looking in ObjAuto.pas there is some... how should I say this... very “interesting” code. I won't go into the gory details here since that would be a whole series of articles. Basically what this code does is allow a method of an object to be dynamically invoked through an IDispatch Invoke method call. This code also forms a lot of the basis for the implementation of Win32 SOAP servers and clients. The IDispatch angle is very important since that is how any Active Script code accesses stuff “on the outside.” There is another unit, ObjComAuto.dcu, for which we don't supply source (I'll work on rectifying that oversight), which contains the TObjectDispatch class and a couple of helper global functions.

Between ObjAuto.pas and ObjComAuto.pas nearly 80% of what is needed to make this all work is already there. All we need to do now is to provide a little bit of glue code to wire some of this stuff up. First up is to implement the IActiveScriptSite and IActiveScriptSiteWindow interfaces. You can find the declaration of these interfaces in AscrLib.pas down in sourcewin32websnap. You can read the documentation about them here.

First is IActiveScriptSite:
IActiveScriptSite = interface(IUnknown)
['{DB01A1E3-A42B-11CF-8F20-00805F2CD064}']
function GetLCID(out plcid: LongWord): HResult; stdcall;
function GetItemInfo(pstrName: PWideChar;
dwReturnMask: LongWord;
out ppiunkItem: IUnknown;
out ppti: IUnknown): HResult; stdcall;
function GetDocVersionString(out pbstrVersion: WideString): HResult; stdcall;
function OnScriptTerminate(var pvarResult: OleVariant;
var pexcepinfo: EXCEPINFO): HResult; stdcall;
function OnStateChange(ssScriptState: tagSCRIPTSTATE): HResult; stdcall;
function OnScriptError(const pscripterror: IActiveScriptError): HResult; stdcall;
function OnEnterScript: HResult; stdcall;
function OnLeaveScript: HResult; stdcall;
end;

Then there is IActiveScriptSiteWindow:
IActiveScriptSiteWindow = interface(IUnknown)
['{D10F6761-83E9-11CF-8F20-00805F2CD064}']
function GetWindow(out phwnd: wireHWND): HResult; stdcall;
function EnableModeless(fEnable: Integer): HResult; stdcall;
end;

These are the only two interfaces you need to implement for the running Active Script to communicate back to the hosting application. Let's get started on the actual demostration app. Create a new VCL Win32 application and drop a TMemo control and a TButton. Then drop a few other controls down on the form because these will be the ones manipulated by the Active Script (although you can manipulate all the controls on the form). Here's what mine looked like:



Now switch to the source view and add AscrLib to the uses clause and add IActiveScriptSite and IActiveScriptSiteWindow to the form declaration as interfaces to implement.
TForm3 = class(TForm, IActiveScriptSite, IActiveScriptSiteWindow)

Then “implement” them:
TForm3 = class(TForm, IActiveScriptSite, IActiveScriptSiteWindow)
Memo1: TMemo;
Button1: TButton;
RadioButton1: TRadioButton;
RadioButton2: TRadioButton;
ListBox1: TListBox;
ComboBox1: TComboBox;
Edit1: TEdit;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
private
{ Private declarations }
{ IActiveScriptSite }
function GetLCID(out plcid: LongWord): HResult; stdcall;
function GetItemInfo(pstrName: PWideChar;
dwReturnMask: LongWord;
out ppiunkItem: IUnknown;
out ppti: IUnknown): HResult; stdcall;
function GetDocVersionString(out pbstrVersion: WideString): HResult; stdcall;
function OnScriptTerminate(var pvarResult: OleVariant;
var pexcepinfo: EXCEPINFO): HResult; stdcall;
function OnStateChange(ssScriptState: tagSCRIPTSTATE): HResult; stdcall;
function OnScriptError(const pscripterror: IActiveScriptError): HResult; stdcall;
function OnEnterScript: HResult; stdcall;
function OnLeaveScript: HResult; stdcall;
{ IActiveScriptSiteWindow }
function GetWindow(out phwnd: HWND): HResult; stdcall;
function EnableModeless(fEnable: Integer): HResult; stdcall;
public
{ Public declarations }
end;

Most of these functions we don't care at this point, so just put Result := S_OK; in the body of them. The methods GetItemInfo, GetWindow, and OnScriptError are the ones we're going to look at right now. Here's GetItemInfo:
function TForm3.GetItemInfo(pstrName: PWideChar; dwReturnMask: LongWord; out ppiunkItem,
ppti: IInterface): HResult;
begin
Result := S_FALSE;
if SameText('Application', pstrName) then
begin
if dwReturnMask and SCRIPTINFO_IUNKNOWN <> 0 then
begin
ppiunkItem := TAutoObjectDispatch.Create(TApplicationWrapper.Connect(Application)) as IInterface;
Result := S_OK;
Exit;
end;
if dwReturnMask and SCRIPTINFO_ITYPEINFO <> 0 then
begin
Result := TYPE_E_ELEMENTNOTFOUND;
Exit;
end;
end;
end;

This is really all that is needed in order for the Active Script to get access to the application's context. We'll discuss TAutoObjectDispatch and TApplicationWrapper in a moment. So what happens in the script when it refers to “Application“ that is an unknown identifier so the script engine calls out to the script site and asks for this object. In this case this will be the VCL application object, so we have to create a wrapper object and around that a dispatch object that provides the necessary IDispatch based late-binding for the script runtime. From here the real magic starts.

Now lets tell the Active Scripting engine to run a script. To do this we first need to create a CoClass that implements the IActiveScript interface. Through some judicious spelunking with Google, I was able to find the JavaScript and VBScript CLSID GUIDs.
const
CLSID_VBScript: TGUID = '{b54f3741-5b07-11cf-a4b0-00aa004a55e8}';
CLSID_JScript: TGUID = '{f414c260-6ac0-11cf-b6d1-00aa00bbbb58}';

Just put these at the top of the unit. Add two private fields to the form class of type IActiveScript and IActiveScriptParse. Add ComObj to the uses clause. Double click the form to create the OnCreate event and inject this code into it:
procedure TForm3.FormCreate(Sender: TObject);
begin
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
OleCheck(CoCreateInstance(CLSID_JScript, nil, CLSCTX_INPROC_SERVER, IID_IActiveScript, FScript));
OleCheck(FScript.SetScriptSite(Self as IActiveScriptSite));
OleCheck(FScript.AddNamedItem('Application', SCRIPTITEM_ISVISIBLE or SCRIPTITEM_ISSOURCE));
if Supports(FScript, IActiveScriptParse, FParse) then
OleCheck(FParse.InitNew);
end;

Note the AddNamedItem call. That adds the “Application” object to the namespace of the script. So now we're ready to execute some script. Double click the Button1 (the one next to the Memo1). Add the following code to the method body:
procedure TForm3.Button1Click(Sender: TObject);
var
Info: EXCEPINFO;
Code: WideString;
begin
Code := Memo1.Text;
OleCheck(FParse.ParseScriptText(PWideChar(Code), nil, nil, nil, 0, 0,
SCRIPTITEM_ISVISIBLE or SCRIPTITEM_ISPERSISTENT, nil, Info));
OleCheck(FScript.SetScriptState(SCRIPTSTATE_CONNECTED));
OleCheck(FScript.SetScriptState(SCRIPTSTATE_DISCONNECTED));
end;

Now run the application and enter the following into the Memo:
function main() {
var Component
var MainForm = Application.MainForm
for (var i = 0; i < MainForm.ComponentCount; i++) {
Component = MainForm.GetComponent(i)
MainForm.ListBox1.Items.Add(Component.Name)
}
}

main()

Press Button1. The Listbox should now fill up with all the names of the components on the form. So what the blazes happened!?? Remember the TAutoObjectDispatch class and the TApplicationWrapper from the GetItemInfo function? TAutoObjectDispatch is a descendant of the TObjectDispatch class in the ObjComAuto.dcu unit. Here's the basic declaration of TObjectDispatch:
TObjectDispatch = class(TInterfacedObject, IDispatch)
protected
function GetObjectDispatch(Obj: TObject): TObjectDispatch; virtual;
function GetMethodInfo(const AName: ShortString; var AInstance: TObject): PMethodInfoHeader; virtual;
function GetPropInfo(const AName: string; var AInstance: TObject; var CompIndex: Integer): PPropInfo; virtual;
property Instance: TObject read FInstance;
public
{ IDispatch }
function GetIDsOfNames(const IID: TGUID; Names: Pointer;
NameCount: Integer; LocaleID: Integer; DispIDs: Pointer): HRESULT;
virtual; stdcall;
function GetTypeInfo(Index: Integer; LocaleID: Integer;
out TypeInfo): HRESULT; stdcall;
function GetTypeInfoCount(out Count: Integer): HRESULT; stdcall;
function Invoke(DispID: Integer; const IID: TGUID; LocaleID: Integer;
Flags: Word; var Params; VarResult: Pointer; ExcepInfo: Pointer;
ArgErr: Pointer): HRESULT; virtual; stdcall;
public
constructor Create(Instance: TObject; Owned: Boolean = True);
destructor Destroy; override;
end;

We're going to create a descendant, TAutoObjectDispatch, that overrides GetObjectDispatch, GetMethodInfo, and GetPropInfo. In GetObjectDispatch, we'll look up a registered “wrapper” class for the given object, create that wrapper, and then place that back into another instance of TAutoObjectDispatch. I've already done a lot of this in an Automation.pas unit that is included with this project. There are also a bunch of “wrapper” classes for various VCL objects. Remember where I mentioned extended meta-data on public methods? Well the wrappers are where this is handled. The base wrapper class is declared within the {$METHODINFO ON} directive which will tell the compiler to generate extra method RTTI on all public methods of this class and all descendants of that class. It is this extra information that the dynamic invocation code uses to call the functions.

When the scripting engine calls GetItemInfo asking for an interface to the “Application” item, an IDispatch is returned. Then it calls GetIDsOfNames asking for the “MainForm” property. It then calls Invoke to get another IDispatch representing the MainForm, and the process starts all over again. By using the object wrappers you can control with fine detail everything that you want the script to have access to. Keep in mind that all published properties are always available, however you can still control this access by overriding GetPropInfo on TObjectDispatch.

In the next installment, I'll show how you can wire an event directly up to a script function. In the mean-time, take a close look at CreateMethodPointer in ObjComAuto.int contained within the demo for a clue as to how this will be done.

Again, the CodeCentral demo can be found here: http://cc.codegear.com/item/24664

Friday, June 1, 2007

A look at "array of const" for fun and profit

Back when Delphi was being developed we began to lament the lack of a function that would be able to format a string with format specifiers and some unknown number of parameters. C and C++ folks have long enjoyed the “...” variable number of parameters declaration used so effectively on things like printf, sprintf and others. Without getting too deep into the reasons and mechanics behind why this was not actually possible given the way function parameters were passed in Pascal, but suffice it to say it had to do with the order in which parameters were pushed onto the stack and who was responsible for cleaning said stack. Actually, if you really want the gory details, I'm sure Hallvard would be able to wax poetically on the whole underpinnings and machine-level workings :-).

So here we are wanting to have a nice string format function that allows you to specify any number of parameters both as constants and variables for maximum flexibility. Sure we could have just introduced a direct clone of printf and even followed the same syntax, but that just didn't seem to “fit” the whole idea of maximum type safety. See the problem with the printf function is that if you want to format a string with the text representation of the value of a byte followed by an integer, there was no information passed in that clearly indicated that 'x' param is a byte. It also forces you to specify the same parameter multiple times if you wanted to use it in more than one place. It was essentially a variable length array with elements of varying sizes without any information as to the overall length and size of each element! Gee... I wonder why the world is filled with so many buffer overrun errors? Anyway, I digress :-).

So the requirements were simple. We needed a language construct that would allow us to more-or-less declare a function to take a variable number of parameters. Since a function's parameter list is essentially a compiler generated array pushed onto the stack (or passed in CPU registers), why not just allow an array to be declared in place and passed to the function? We also wanted this array to be self-describing and type-safe. So when you declare a parameter as an “array of const” the compiler actually makes that into an open array parameter as an “array of TVarRec.” The declaration for TVarRec is as follows:
PVarRec = ^TVarRec;
TVarRec = record { do not pack this record; it is compiler-generated }
case Byte of
vtInteger: (VInteger: Integer; VType: Byte);
vtBoolean: (VBoolean: Boolean);
vtChar: (VChar: Char);
vtExtended: (VExtended: PExtended);
vtString: (VString: PShortString);
vtPointer: (VPointer: Pointer);
vtPChar: (VPChar: PChar);
vtObject: (VObject: TObject);
vtClass: (VClass: TClass);
vtWideChar: (VWideChar: WideChar);
vtPWideChar: (VPWideChar: PWideChar);
vtAnsiString: (VAnsiString: Pointer);
vtCurrency: (VCurrency: PCurrency);
vtVariant: (VVariant: PVariant);
vtInterface: (VInterface: Pointer);
vtWideString: (VWideString: Pointer);
vtInt64: (VInt64: PInt64);
end;

It's just a variant record. If you also look closely, all the data fields are the same size. They all max out at pointer size (which has some interesting implications for 64bit, but that is a subject for another day). So if the data being passed in is > 4 bytes it is done as a pointer to this data. An array with elements of this type will be a constant element size array. Also, since “array of const” becomes an “array of TVarRec” it is an open array so the length of the array is also passed in. This satisfies one objection to the C-style “...” construct. The other objection is solved by the fact that the compiler will encode into the VType field a value representing the type of that element. Another side benefit of this is that you can now refer to the position of an element in addition to its value. This allows you to do interesting things like explicitly refer to the element you want to format in the string using the format specifier '%x:y' where x is the ordinal position of the element with 0 being the first element.
So how do you use this special array construct? If you declared your function or method with a parameter of type “array of const” in the body of the method you just treat that parameter as an “array of TVarRec” and use all the standard array indexing and range checking functions, like Low and High. Here's a simple function that just writes to the console the type and value of each element:

procedure PrintArrayOfConst(const Args: array of const);
var
I: Integer;
begin
for I := Low(Args) to High(Args) do
begin
Write('Arg[', I, ']:');
case Args[I].VType of
vtInteger: Writeln('Integer = ', Args[I].VInteger);
vtBoolean: Writeln('Boolean = ', BoolToStr(Args[I].VBoolean, True));
vtChar: Writeln('Char = ''', Args[I].VChar, '''');
vtExtended: Writeln('Extended = ', FloatToStr(Args[I].VExtended^));
vtString: Writeln('ShortString = ''', Args[I].VString^, '''');
vtPChar: Writeln('PChar = ''', Args[I].VPChar, '''');
vtAnsiString: Writeln('AnsiString = ''', string(Args[I].VAnsiString), '''');
vtWideChar: Writeln('WideChar = ''', Args[I].VWideChar, '''');
vtPWideChar: Writeln('PWideChar = ''', Args[I].VPWideChar, '''');
vtWideString: Writeln('WideString = ''', WideString(Args[I].VWideChar), '''');
vtInt64: Writeln('Int64 = ', Args[I].VInt64^);
vtCurrency: Writeln('Currency = ', CurrToStr(Args[I].VCurrency^));
else
Writeln('Unsupported');
end;
end;
end;

Of course it doesn't support all things, but it should give you an idea what you can do with this little known but widely used (you've used Format() right?) language feature. Here's a sample of how to call this function:
    PrintArrayOfConst([1, 'c', 'this is a string', 12.5, True, PChar('This is a pchar'), Int64(123456)]);

I had to do some typecasting in order to get the compiler to recognize some of the literals as specific type because it will automatically pick the most natural type to use. Of course if you pass in variables for the array elements, the type of that variable is preserved. So go have fun, and definately do try this at home.

Tuesday, May 22, 2007

Roadmaps! Git yer roadmaps, here!

In case you missed it, Jim Douglas, CodeGear CEO has finally gone on record about when we'll be publishing our roadmaps. You have no idea about the collective sigh of relief from the whole team when we heard the news. Please be sure to stay tuned for when we finally get our roadmaps up and published in early June (a roadmap for the roadmap?? ;-). We'll finally (again) be able to give some clarity on CodeGear's direction for our products. For the Delphi folks out there this includes VCL/Unicode, .NET 2-3.5, Win64 and other stuff.

Friday, May 18, 2007

Softening stance...

After thouroughly reading this article about How To Keep Hostile Jerks From Taking Over Your Online Community from the other day and having some time to really think about it, I'm beginning to rethink a few things.  First of all I want CodeGear's online community presence to be as comfortable and inviting both for our loyal customers and “fanboys” ;-) in addition to making sure all the tire-kickers out there are not immediatly put off by a negative environment.  Some would argue that “that's just the way it is and to grow a thicker skin.”  But the old adage that you can catch more flys with honey than vinegar is as true in the online world as it is anywhere else.

I remember many years ago reading this book, All I Really Need To Know I Learned In Kindergarten.  I think that many folks tend to forget a lot of these simple truths because they are somehow “enlightened” or “educated.”  Posturing and “oneupmanship” dominates a lot of online communities.  I specifically did not use the word “competitive” because what many folks view as competition is in fact nothing of the sort.  It is actually elevating oneself at the expense of others.  Recognition and prominance cannot be done by yourself.  I remember an old story that my father used to tell to illustrate the point that it is perfectly ok to ask for help and that every now and then you need this help to solve problems and get yourself out of a bind.

The basic story talks about a little monkey that rejects everyone's offers for help and wisdom.  He is repeatedly told to never go into a certain section of the jungle because of the pools of quicksand.  The problem is that it is also the best location to get bananas.  So in defiance to his peers and parents, he ventures into that area and promptly gets stuck.  The other jungle animals rush to his aid and he just tells them to go away because he can get himself out.  The problem is that his solution is grab his own whiskers and pull himself up.  Even very young children immediately understand the folly of such a tactic.  Yet little monkey keeps eshewing all offers for help and is, in return, abusive and rude to all those who offer help.  Ultimately the monkey succumbs and the last thing seen of the monkey is two fists full of whiskers above the pool of quicksand.

What is the ultimate solution for creating an online environment that is provocative, inviting, fun, and most of all informative and helpful for developers of all levels?  I don't know if I have all the answers, but ignoring those “hostile jerks” and allowing them to run amok isn't the answer either.  Clearly this is not a simple solution because it is not a simple problem, right?  In keeping with the whole notion of constructive, reasoned and productive discussion, what do you think?  Do we throw up our hands in despair?  Should a hard-line no-tolerance policy be put into place?  I suspect it is something in between, no?

Wednesday, May 16, 2007

Hey that car looks just like mine!

How To Keep Hostile Jerks From Taking Over Your Online Community.  Interesting.  I guess it's like buying a new car.  Before you bought it, you hardly ever saw another one, but as soon as you drive off the lot, it seems like on every street corner there is another one just like yours.  Along the same vein, I posted yesterday about the interconnected developer community and mentioned things like wild and wooly newsgroups and flaimbait, trolls and such, so I was bound to find some other information about such things.  You can bet I'll be forwarding this article around internally...

Why can't I be a fanboy??

Why aren't the so-called “fanboys” taken seriously?  Why is it always assumed that a “fanboy” is a blindly faithful, shill or troll?  With all the hype and press around Ruby and Rails, wouldn't you classify these folks as “fanboys?”  No? So if you're an early adopter of anything new, you're on the bleading edge, but if the technology is more mainstream and mature but is still clearly valuable and has an excellent track record, you're a “fanboy.”  One of the comments many folks make in arguments against the “fanboy-ism” is that they don't have an open mind.  To me that is simply a “PC” way of saying “you have a closed mind because you don't agree with me.”  I'm sorry, but how do you know that?

I was in a very mixed social setting this past weekend with a lot of folks of varying disciplines.  There were the financial folks, the sales/marketing folks, there were the sofware engineers (most of which were not from CodeGear).  I find that the engineers are usually the most closed minded bunch of folks, yet continue to think they are the most open-minded!  My wife was with me and happened to overhear a conversation between two non-CodeGear engineers.  She came and whispered in my ear saying, “That group over there is really trash-talking Delphi!  Don't they know that there are some folks from CodeGear here?” 

Now I don't know these folks and never actually had a chance to engage with them so I'm going to make some wild speculations here.  I'm probably wrong, but I'm going to use this to illustrate a point.  I've talked with folks that have “trash talked” Delphi and other CodeGear products.  There have also been times where I've just let them rant and rave, and when they finally ask what I do... the look on their face is priceless!  If they happen to actually remain in the conversation and I ask them what is it they're basing this opinion on, invariably it is either that they heard something about it, or it is based on a mental comparison of what their current tool of choice is doing for them and Delphi 1-3.  The former is just inexcusable, and the latter is like comparing and old Ferrari 308 GTB against the new Saleen S7.  Both are fine examples of automotive engineering for their respective eras, but the 308 just isn't quite up to modern day technical standards.  The comparison just isn't valid.

So, I guess the point I'm making here is that I think it is great to be a fanboy.  Passionate, dedicated, excited; these are qualities people view as positive attributes.  But isn't that what a fanboy is?  Maybe what causes folks to bristle at the notion of a fanboy is that they're not as passionate, dedicated, and excited about their tool of choice?  This is something I see in spades among the Ruby on Rails crowd.  They're passionate, excited, dedicated to this emerging technology.  Why should we be threatened by this?  We should be looking at what it is about their choice of tool and library that is getting them excited.  So maybe there is something behind that excitement?  Should I be looking at that technology?

So, are you a fanboy?  C'mon, stand up and be counted!  You don't even have to be a Delphi fanboy.  What would you consider yourself as being a fanboy of?

UPDATE: “fangirls” are welcome to participate... While I was using “fanboy” in the genderless, generic sense I figured I'd clarify.

C++Builder announced, why should Delphi folks care?

During the development of C++Builder 2007 after the release of Delphi 2007, a lot has been happening.  We've been getting a lot of excellent feedback on how our drive to increase quality and stability has been going.  If you haven't tried Delphi 2007, I think it is worth your time to check it out.

One thing that is really exciting is the potential behind the use of the new installer technology from InstallAware.  Delphi 2007 was the first release that was using this new installer so there was bound to be a few rough edges and that need to be polished.  We've been able to incorporate a lot of your feedback in a newly updated version of the installer for C++Builder 2007.  But that doesn't mean that only the C++Builder folks are going to benefit.  Part of the overal design of the installer is that it is the same installer for all the Windows IDE products.  So when C++Builder is released, a new install of Delphi 2007 will be available.  Here's just a couple of things we've added as a result of direct feedback.

Many folks that didn't purchase a media-kit and used the installer to download complained that they wanted to be able to save off the original download files so they could archive it.  We've fixed that.  If the installer sees that it will be downloading anything, it will give the user the opportunity to select a local folder into which the downloaded data blocks will be placed.  So if you ever needed to reinstall, simply re-run the setup from that folder and since, presumeably, all the data blocks are already present, there would be no need to redownload.

Due to the way the install is designed for flexibility, it does tend to keep a cache of all the data blocks under Documents and SettingsAll UsersApplication Data, which can be fairly large.  Another option that has been added is to allow the user to tell the installer to delete this cached information once the product is installed.  This does have a down side, but I think it is a fair compromise.  The main down side is that should you ever need to do a repair, patch, or upgrade, the installer will need to unpack that cache data again.  You will always be able to uninstall without that cache data... but then again... why would you be uninstalling? :-).

Another thing we've done to greatly increase the overall delivery efficiency is that we build one, and only, one install image.  Rather than building upwards of 16 (many, many more if you include trials, and Turbos) different install images, we build only one.  This also reduces our costs both in terms of time for install testing and also in actual Cost of Goods Sold (COGS).  Why should you, the customer, care?  Because this means we now have more money available for other things, like more developers, QA engineers and documentation writers.  This also goes back to former CEO now advisor, Ben Smith's, driving mantra of simplifying the business.

I hold no illusions that we're done.  We will still continue to improve the experience as we move on ahead.  One of my favorite sayings is, “How do you eat a 3 ton elephant?”  It's really simple,  “One bite at a time.”

 

“If you're going through hell, keep going.”  Winston Churchill

Tuesday, May 15, 2007

Who is not connected these days?

This probably seems like a stupid question to ask.  It's like a teacher asking all those not present to please raise their hands.  Aside from that mythical programmer living in a cardboard hut in East Botswana (appologies to anyone who actually live there...) with some strange bastardised bicycle generator device powering their all night binge programming session, I'd say that most folks these days are connected at least some of the time.  By connected, I connected to the Internet in some fashion.  Of course if you're reading this, you're obviously one of the connected ones.  Because of this one of the many things we're always thinking about is what do connected developers mean for how they work and interact on a day-to-day basis.  This goes for globally interconnected virtual teams as well as physically localized teams.

What kinds of online resources do you use on a day-to-day basis?  Blogs, Newsgroups, forums, chat, IM?  For all these kinds of resources, do you prefer RSS/Atom feeds, direct web-browser access, or some combination of these things?  Do you like  online forums with a reasonable level of moderation or the mad, mad world of open, anonymous, unmoderated newsgroups?  What about something in-between?

My personal feeling is that the Borland/CodeGear newsgroups in their current form are not as helpful as they could be.  It'd be nice to be able to quickly identify those folks who are the most helpful, insightful, and generally add value to that experience.  This is actually one thing I tend to like about web-based forums that have a peer rating system.  This allows someone to easily filter out the flame-bait and only look at the stuff that meets certain criterion.  NNTP in its current form just hasn't caught up to this kind of online experience.  Of course I'm not advocating that we eliminate NNTP, but rather provide a richer experience while still allowing those old-skool folks to stick with their news-reader of choice.  I'm sure there will be many of you shouting “Heresy!  You'll pry my news reader from my cold, dead- fingers!”  But maybe it's time to mix in a richer, more interactive experience?  Not just in online discussions but in all aspects of the developer's everyday online experience...

Links and such

Just some links to some news:

New C++ IDE from CodeGear™ Brings Rapid Application Development to Windows Vista

CodeGear™ Unveils First IDE for Agile Ruby on Rails Web 2.0 Development

CodeGear shines up Ruby on Rails tool

CodeGear goes Ruby on Rails route

Allen Bauer on CodeGear, Dynamic Languages and Delphi