Virtual method interception

Delphi XE has a new type in Rtti.pas called TVirtualMethodInterceptor. It was originally designed for use in DataSnap authentication scenarios (though I don't think it's currently being used there), but of course, making it only work for that would have been quite a limitation. What does it do? Essentially, it creates a derived metaclass dynamically at runtime that overrides every virtual method in the ancestor, by creating a new virtual method table and populating it with stubs that intercepts calls and arguments. When the metaclass reference for any instance of the "ancestor" is replaced with this new metaclass, the user can then intercept virtual function calls, change arguments on the fly, change the return value, intercept and suppress exceptions or raise new exceptions, or entirely replace calling the underlying method. In concept, it's somewhat similar to dynamic proxies from .NET and Java. It's like being able to derive from a class at runtime, override methods (but not add new instance fields), and then change the runtime type of an instance to this new derived class. Why would you want to do this? Two obvious purposes spring to mind: testing and remoting. Mock objects have been in vogue in the testing space in other languages for some time. By intercepting method calls, one may more easily verify that a particular subsystem is calling all the right methods, with the correct arguments, in the expected order; similarly, the subsystem can proceed with the return values from these method calls, without necessarily having to hit the database, the network, etc. for what should be a unit test. Remoting on the basis of method calls is somewhat less useful, especially when an unreliable and latency-prone network gets into the stack, but that's not the only usage point. The virtual method interceptor logic was originally implemented to be used as part of DataSnap authentication, so that a call that comes in from the network can still be checked as its code flow spreads throughout the graph of objects. Anyhow, here's a simple example to get started: uses SysUtils, Rtti; {$apptype console} type TFoo = class // Frob doubles x and returns the new x + 10 function Frob(var x: Integer): Integer; virtual; end; function TFoo.Frob(var x: Integer): Integer; begin x := x * 2; Result := x + 10; end; procedure WorkWithFoo(Foo: TFoo); var a, b: Integer; begin a := 10; Writeln(' before: a = ', a); try b := Foo.Frob(a); Writeln(' Result = ', b); Writeln(' after: a = ', a); except on e: Exception do Writeln(' Exception: ', e.ClassName); end; end; procedure P; var foo: TFoo; vmi: TVirtualMethodInterceptor; begin vmi := nil; foo := TFoo.Create; try Writeln('Before hackery:'); WorkWithFoo(foo); vmi := TVirtualMethodInterceptor.Create(foo.ClassType); vmi.OnBefore := procedure(Instance: TObject; Method: TRttiMethod; const Args: TArray<TValue>; out DoInvoke: Boolean; out Result: TValue) var i: Integer; begin Write('[before] Calling ', Method.Name, ' with args: '); for i := 0 to Length(Args) - 1 do Write(Args[i].ToString, ' '); Writeln; end; // Change foo's metaclass pointer to our new dynamically derived // and intercepted descendant vmi.Proxify(foo); Writeln('After interception:'); WorkWithFoo(foo); finally foo.Free; vmi.Free; end; end; begin P; end. Here's what it outputs: Before hackery: before: a = 10 Result = 30 after: a = 20 After interception: before: a = 10 [before] Calling Frob with args: 10 Result = 30 after: a = 20 [before] Calling BeforeDestruction with args: [before] Calling FreeInstance with args: You'll notice that it intercepts all the virtual methods, including those called during destruction, not just the one I declared. (The destructor itself is not included.) We can get more ambitious with what it does. I can change the implementation entirely, and skip calling the underlying (i.e. inherited) method body: procedure P; var foo: TFoo; vmi: TVirtualMethodInterceptor; ctx: TRttiContext; m: TRttiMethod; begin vmi := nil; foo := TFoo.Create; try Writeln('Before hackery:'); WorkWithFoo(foo); vmi := TVirtualMethodInterceptor.Create(foo.ClassType); m := ctx.GetType(TFoo).GetMethod('Frob'); vmi.OnBefore := procedure(Instance: TObject; Method: TRttiMethod; const Args: TArray; out DoInvoke: Boolean; out Result: TValue) begin if Method = m then begin DoInvoke := False; Result := 42; Args[0] := -Args[0].AsInteger; end; end; Here, I inhibit the invocation and hard-code the result to 42, while negating the first argument. The proof is in the output: Before hackery: before: a = 10 Result = 30 after: a = 20 After interception: before: a = 10 Result = 42 after: a = -10 I could have inhibited the call by raising an exception instead: vmi.OnBefore := procedure(Instance: TObject; Method: TRttiMethod; const Args: TArray; out DoInvoke: Boolean; out Result: TValue) begin if Method = m then raise Exception.Create('Aborting'); end; And output: Before hackery: before: a = 10 Result = 30 after: a = 20 After interception: before: a = 10 Exception: Exception It's not limited to interception before the logically inherited call, but also interception after the call, again with the opportunity to fiddle with arguments and return value: m := ctx.GetType(TFoo).GetMethod('Frob'); vmi.OnAfter := procedure(Instance: TObject; Method: TRttiMethod; const Args: TArray; var Result: TValue) begin if Method = m then Result := Result.AsInteger + 1000000; end; And output: Before hackery: before: a = 10 Result = 30 after: a = 20 After interception: before: a = 10 Result = 1000030 after: a = 20 And if the inherited implementation raises an exception, that can even be squashed: function TFoo.Frob(var x: Integer): Integer; begin raise Exception.Create('Abort'); end; // ... m := ctx.GetType(TFoo).GetMethod('Frob'); vmi.OnException := procedure(Instance: TObject; Method: TRttiMethod; const Args: TArray; out RaiseException: Boolean; TheException: Exception; out Result: TValue) begin if Method = m then begin RaiseException := False; Args[0] := Args[0].AsInteger * 2; Result := Args[0].AsInteger + 10; end; end; Output: Before hackery: before: a = 10 Exception: Exception After interception: before: a = 10 Result = 30 after: a = 20 One thing the TVirtualMethodInterceptor class doesn't have, however, is a way to unhook (unproxify) the object. If the object is never unhooked, it's important that the object doesn't outlive the interceptor, because the interceptor needs to allocate executable memory in order to create the little stubs with which it redirects method calls to the events. Fortunately, it's pretty trivial to do: PPointer(foo)^ := vmi.OriginalClass; Another point: the class inheritance chain is changed by the hooking process. This can be shown easily: //... Writeln('After interception:'); WorkWithFoo(foo); Writeln('Inheritance chain while intercepted:'); cls := foo.ClassType; while cls nil do begin Writeln(Format(' %s (%p)', [cls.ClassName, Pointer(cls)])); cls := cls.ClassParent; end; PPointer(foo)^ := vmi.OriginalClass; Writeln('After unhooking:'); WorkWithFoo(foo); Writeln('Inheritance chain after unhooking:'); cls := foo.ClassType; while cls nil do begin Writeln(Format(' %s (%p)', [cls.ClassName, Pointer(cls)])); cls := cls.ClassParent; end; // ... And output: Before hackery: before: a = 10 Exception: Exception After interception: before: a = 10 Result = 30 after: a = 20 Inheritance chain while intercepted: TFoo (01F34DA8) TFoo (0048BD84) TObject (004014F0) After unhooking: before: a = 10 Exception: Exception Inheritance chain after unhooking: TFoo (0048BD84) TObject (004014F0) The feature is primarily an infrastructure piece for advanced libraries, but hopefully you can see that it's not too difficult to get into.
Read More

What Nokia must do to stay relevant in mobile

Nokia is losing market share fast, which is being discussed in many places. Nokia's CEO Anssi Vanjoki also expressed his view on this topic. The number of mistakes, that Nokia currently does, is huge, let's take a few: * Focus on OS technology instead of customer-centric parameters. As Anssi puts it: "The current phase of MeeGo development is looking awesome." * Believing that the window of
Read More

Apple iPhone 4 signal strength indicator highlights a common problem

It is old knowledge that if progress bars go faster at the end, the user is happy. In other words, if the progress bar is modified so that it doesn't show the perfect progress percentage, you get a better customer satisfaction. The same principle applies to other indicators, like battery indicators and mobile phone signal strength. Many phones don't seem to lose battery energy until the very
Read More

Android: Momentum and Apps

I'm liking the current momentum behind Android. I'm sure Apple will come out with their new phone soon, and the pendulum will swing a bit, but it's definitely a two horse race, and Apple isn't out of sight, not by a long shot. I got my Nexus One from http://www.google.com/phone a few months ago, to replace my aging K800i (which still takes better photos than the Nexus One, or the iPhone, or indeed most phones). Since I got my phone directly from Google, it was easy to update to the latest Froyo, which got rid of some of my bigger annoyances on the device. Most tedious in previous versions was the need to individually select and authorize every application when an update showed up. Most days I see between two and five updated applications, and updating any given application takes around 30 seconds, so it was turning into a chore. Froyo is also much smoother in its transitions; it's the way Eclair should have been. The Android app store (Marketplace) has gotten some criticism, but I don't know how truly meaningful those criticism are, as I've gotten applications primarily based on recommendations, or searching the store with intent, rather than browsing the store. I recently had occasion to count the number of apps I have installed, and if you judge by Android's app management screen, it's now up to 70 (I have 93 icons in my app icon scroller view). I thought I'd list a few of the best ones, as a way of aggregating my own perspective from the different recommended lists I've seen. Some of these applications are not free, but as a developer I don't baulk at paying the small amounts charged, especially since returning for a refund is built into the marketplace if you don't like the app. ES File Explorer is probably the single application that's most responsible for me preferring Android to iPhone. It's a file browser, but more than that: it can browse Windows (SMB) network shares, as well as FTP and Bluetooth. That means I can copy music, movies etc. on and off my device to and from my NAS. I find iTunes to be a tedious waste of memory and processes, not to mention it wants to update with huge downloads practically every second week, incorporating media players and web browsers I don't want. Crapware. Being able to take control of the media upload and download experience is wonderful, and makes me feel like my phone is more than a locked-down toy. I complement it with Astro File Manager, which has better support for photo management and a built-in image viewer (which supports image view intents, so it can actually be used by ES File Explorer to view photos). I'm not a big fan of Nexus One's stock Gallery app (by CoolIris) - lots of gloss, but slow to index photos if you've taken a bunch. Astro can also act as a file selection agent for other applications that browse for an image, such as image editors. Another photo viewer is B&B Gallery. It was one of the first to support multi-touch, manually implemented before the OS got support. An advantage it has over the built-in Gallery is that it doesn't downsample loaded photos, so you can zoom in and check the details, rather than quickly getting lost in blur. As a gallery app, however, it's not particularly pretty. I find file management superior, especially as you don't have to wait as thumbnails from all over the place get loaded, but can classify into directories, etc. Act 1 Video Player is an excellent replacement for the stock video player. It doesn't add any more decoding capabilities, but it has better affordances in its UI, especially with its touch-based seeking support. Best feature: swiping left and right in the center of the screen seeks back and forth through the video. NewsRob is an offline RSS reader that synchronizes with Google Reader. It has configurable caching, so you can have it download images which are linked from the RSS (a problem with Google Reader's own offline version using Gears, ironically), up to caching the full web page associated with that RSS item. Excellent for public transport. A major annoyance with the iPod Touch (and also the iPhone) for me was the auto-rotation. I almost never want to rotate the view, and it always ends up rotating when I'm lying down, or otherwise not in front of a computer. I was only able to solve this problem on the iPod Touch by jailbreaking it. Android has a setting for this, but for easier access to it I use the AutoRotate widget. This lets you put a 1x1 widget anywhere which toggles the auto-rotate setting on a tap. Some games are useful for passing idle moments. Robo Defense is quite addictive tower defense with RPG-like elements; you earn what are essentially XP points, and can spend them on incremental upgrades, so there's a campaign-like aspect to the gameplay. Replica Island is a classic-style platformer which is particularly ergonomic on the Nexus One, using the scroll ball for directional control. As an aside, controls are one of the weakest elements of most iPhone games - it badly needs more physical buttons. And Nesoid, an NES emulator, is nice in principle, but a better control system is needed. Artistic diversions: DoodleDroid is a finger-painting app with configurable brush dynamics, so with care, you can get some interesting impressionistic images out of it. Simpler, more like coloured markers than paint, is Draw!. Of course, there are the bar code apps, like ShopSavvy, probably the most integrated when you have buying intent, though its local shop search isn't very localized, even when in London; ZXing Barcode Scanner, which runs more general web searches based on barcodes; Google Shopper and Google Goggles also do barcodes, but I feel they're weaker, and Goggles is mostly a gimmick (IMO). Google Sky Map is pretty neat - the way it uses the accelerometer to overlay constellations etc. is probably the neatest augmented reality-style implementation I've seen, even though it doesn't overlay on a video image from the camera. Layar is the probably the canonical implementation, but I find it to be too gimmicky in practice, having to walk around like an idiot with a phone held out in front of you. At least with stars, you're normally standing still and looking into the sky. Google Translate is another essential app. It's tantalizingly close to real-time speech to speech translation; as it is, you can speak into it and at a button press do text to speech on the translation, providing the speech recognition was good. My girlfriend tells me it can be overly literal for German, however. Wifi Analyzer helped me get better channel placement on my home wifi access points. Really neat live view of signal strength for all the different APs in your area, even ones too faint to actually connect to. Arity is a simple expression-based calculator which can graph simple non-parametric functions in two and three dimensions. By non-parametric, I mean you give it an expression using x, or x and y, and it plots the result of the expression as y, or z, in a 2D plane or 3D volume. You can't plot circles with it, for example. ConnectBot is a SSH client, useful for remote administration when you're really stuck for connectivity. Doing anything serious on the command line without access to a keyboard is insanity, of course. When the job you're trying to do is simpler - a single command over SSH - ServerAssistant is a better approach. If you're interested in programming your life, Locale can trigger events based on conditions. Conditions are one or more of location, time, orientation, calls by contacts and battery state. Settings include wallpaper, ringtone, screen brightness, wifi enabled or not, volume, bluetooth, but also actions published by third-party applications. For example, NewsRob can synchronize based on a Locale trigger. And if you've installed ASE, the Android Scripting Environment, you can run arbitrary scripts - bash, python, ruby, etc. - on a trigger. The sample scripts available for ASE include invoking text to speech to say the time and the current weather, toggling airplane mode, showing notifications, etc. Locale is a lot less useful if you have a more flexible schedule, but if you're tied in to a timetable, it makes a lot of sense. Finally, a battery widget: Battery Left. I don't use task managers or killers; I've found that it's better to let Android do its thing and kill what it needs to kill, when it chooses to do it. I get about 46 hours on average battery, but I tend to recharge before 36 hours have gone past. You can drop this widget as a 1x1 (or 2x1) visual indicator of battery left, with configurable detailed textual data: estimated time before battery dead, estimated time of day of dead battery, estimated battery %, etc. It monitors battery performance, so it should straighten the curve that batteries self-report - I've often seen batteries say they have three-quarters battery for ages, and then run out the remainder quite suddenly, etc. Obviously, I have many more applications installed than I've mentioned here, but they tend to be single-purpose location-based ones that have less general applicability, or ones I don't use as often and can't in good conscience recommend. But I can say that all of the above work pretty well for me, and it's notable that many of them would contravene Apple's developer policy, so for me at least, app availability for the iPhone isn't the killer advantage it's made out to be.
Read More

Random Thoughts on the Passing Scene #156

“The board “highly recommended” switching to Pascal/Delphi because it is stable and was designed to teach programming and problem solving.”
Cary Jensen has an interesting survey up on his blog, asking about how much you use the “non-core” features of RAD Studio.  Now this is an interesting question. He’s asking particularly about the unit testing, audit, […] … Read More

Read More

How Android beats Windows 7 laptops

Lately, many have started to use their phone for tasks, for which they previously used a PC - including updating online spreadsheets etc. The PC features much better input and output devices (keyboard, mouse, screen), so why use the phone for these tasks? Here is a list of why it makes sense to use an typical Android phone instead of a Windows 7 laptop for many tasks: * The laptop does not have
Read More

Google Android reviewed by a Delphi developer

With the major part of my background in Delphi, but also some in C, C++, Assembler, Java, C# and others, I threw myself at the Android platform in order to figure out how it works. The main language is Java, the obvious IDE is Eclipse, and after installing the Android SDK things are well integrated with each other. The fundamental structure is very similar to Delphi - but things have different
Read More

Random Thoughts on the Passing Scene #153

Andreano has a new blog, or at least it is new to me – just found it today.  He has two items there that caught my eye: His RADTweet project – a Delphi-based Twitter client – is online.  He has wisely put it on SourceForge for easy access, downloading, and updating.  A project that shows how to do HTTP authentication with DataSnap While you are at it, check out his post on “Using LINQ to Objetcs in Delphi Prism”. The inestimable Mike Rozlog is on a hot streak, and he continues to give cool and interesting Webinars.  His latest is “Mastering Database Application Development with Delphi”.  Once again, he’ll be giving the webinar at three times during the day of 14 April 2010, so no matter where you are in the world, you should be able to attend one of them.  These marketing people are busy.  What to know about RAD In Action regarding building database applications?  There’s a web page for that.  Julian Bucknall, the CTODX (Chief Technology Officer of Developer Express) has an update for their VCL customers. My mentioning of our move caused a bit of a stir in the comment section.   A couple of more thoughts on it.  I can only speak for myself, but so far it is working out pretty well.  I like the new space.  I like my cube.  I like that everyone is fairly close together but not too close.  Our previous space was waaaaay  to big for us, and you could go weeks without seeing someone from Sales or Support.  Now, we are are all in one space, and it feels more like we are one team, which of course we are.  I like that this new place is a good fit.  I like that this place is significantly more appropriate and significantly less expensive that our previous space.  I like that this place is a new start. I like that we have a gigabit network.  I like that we have projectors hanging from the ceilings in the conference rooms.  I like that it is closer to the shopping mall across the street. But most of all, I like that it represents a significant investment in and commitment to our team. So for me, this is a big win. Share This | Email this page to a friend
Read More

Simplify your Delphi Code using some basic rules, OO techniques and some refactoring (Part 3)

Now that we have a basic idea of what we want to achieve and how we could do it, it's time to write some code and create some classes.Introduction So, basically we need a class / object which we can use to read and write some application settings from and to the Windows Registry. Sounds pretty straightforward ... but we did some additional thinking and found out that we might need to add some stuff in the future. Basic Requirements of the code Compatibility with Delphi 7 Although quite a few new features were added to the language in the last years, we won't be using them 'yet'. Our goal is that the code should compile / work under Delphi 7. You may be asking yourselves "Who going though all that trouble for an old Delphi version ?", well I noticed that even today some of my clients still have older projects which get compiled in Delphi 7. In later articles I might show you how to do it using some newer techniques, but for now lets just stick with something that will compile under Delphi 7 Not limited to the Windows Registry Although we will be writing the code based on using the Windows Registry to Load and Save our data. We want an easy way to adapt our code for some other things like storing the data in XML or an Ini file. After all, we don't know what the future will bring yet. We might be able to build applications for Windows Mobile, the Mac, iPhone or even iPad in the future (Would that be nice), and the Windows Registry might not be available on those devices. For now we will focus on the Windows Registry, but as you notice it is a good idea to keep a few other possibilities ready. In the end, the only thing we care about is that we can store / load some settings. How or where these settings are stored isn't all that important, it just needs to get done ! Additional Things So far, we know we will need something to hold a set of settings. We need to be able to load and save those settings. We will probably be supplying a name for each individual setting, maybe even a default value and a description. We need to be able to store Integers, Strings, but who knows even Passwords, Dates, ... Lets get Coding ! ... well almost ... Well, ... actually before we start coding we could take a look at how some of these things get solved in the VCL. Of course, we could do all the coding ourselves, but it might be a good idea to let our new classes inherit from some existing classes. Since we need a list of things, you might want to take a look at the TList for example. In my case, I knew I want to have a Setting which I will use to store Strings, one to store Integers and another one to store Booleans. Once I had those, I quickly noticed I wanted some others for DateTime values and quite a few other things as well. I actually ended up doing something quite similar to TField and TIntegerField, TStringField, ... So, now that I know I will be using different types of Setting objects and I want to keep a list of those Setting Objects, I quickly decided that the TObjectList was a very good class to start with. Creating the TdvSetting class The explanation Basically I need an object with a few properties like an Identifier (or Name, Caption), a Description (or Hint) and of course a Value. I will need to be able to read the value from the Registry and write it to the registry. Additionally when reading the value from the Registry I want to check if there is already something in the registry for the setting, and if nothing is found, the Default value should be used. Just as with the TField and TStringField, I want to be able to get the Value of the TdvSetting as a String or as a Variant, so I added that code as well. Additionally I want to set the value of the TdvSetting as well. In the end, the descendant classes will implement most of this, but like with the TField in the VCL, I added some code which will raise an exception if a descendant class doens't implement a specific accessor. This might sound a bit complex, but lets compare it with the TField and TStringField again. With a TStringField, you can set the value using aField.Value := theValue or with aField.AsString := theValue. Both things will work, but if aField is in instance of TField instead of TStringField an exception will be raised. What I did was implement that functionality as well. For now we will focus on the Windows Registry, but as you notice it is a good idea to keep a few other possibilities ready. In the end, the only thing we care about is that we can store / load some settings. How or where these settings are stored isn't all that important, it just needs to get done ! The Code TdvSetting = class( TObject ) private FValue : Variant; FDefaultValue : Variant; FIdentifier : String; FCaption : String; procedure SetCaption(const Value: String); procedure SetVisible(const Value: Boolean); protected function GetAsBoolean: Boolean; virtual; function GetAsDateTime: TDateTime; virtual; function GetAsFloat: Double; virtual; function GetAsInteger: Longint; virtual; function GetAsString: string; virtual; function GetAsVariant: Variant; virtual; procedure SetAsBoolean(const Value: Boolean); virtual; procedure SetAsDateTime(const Value: TDateTime); virtual; procedure SetHint(const Value: String); procedure SetAsFloat(const Value: Double); virtual; procedure SetIdentifier(const Value: String); procedure SetAsInteger(const Value: Longint); virtual; procedure SetAsString(const Value: string); virtual; procedure SetAsVariant(const Value: Variant); virtual; protected function AccessError(const TypeName: string): Exception; dynamic; procedure SetVarValue( const Value : Variant ); virtual; public Constructor Create( const aIdentifier, aCaption : String; const aDefaultValue : Variant ); virtual; destructor Destroy; override; procedure SaveToRegIni ( aRegIni : TRegistryIniFile; const aSection : String ); virtual; procedure LoadFromRegIni( aRegIni : TRegistryIniFile; const aSection : String ); virtual; procedure Clear; virtual; property DefaultValue : Variant read FDefaultValue; property AsBoolean : Boolean read GetAsBoolean write SetAsBoolean; property AsDateTime : TDateTime read GetAsDateTime write SetAsDateTime; property AsFloat : Double read GetAsFloat write SetAsFloat; property AsInteger : Longint read GetAsInteger write SetAsInteger; property AsString : string read GetAsString write SetAsString; property AsVariant : Variant read GetAsVariant write SetAsVariant; property Identifier : String read FIdentifier write SetIdentifier; property Caption : String read FCaption write SetCaption; property Value : Variant read GetAsVariant write SetAsVariant; end; ... function TdvSetting.AccessError(const TypeName: string): Exception; resourcestring SSettingAccessError = 'Cannot access Setting ''%s'' (%s) as type %s'; begin Result := Exception.CreateResFmt( @SSettingAccessError, [ Identifier, Caption, TypeName ] ); end; procedure TdvSetting.Clear; begin FValue := Null; end; constructor TdvSetting.Create(const aIdentifier, aCaption: String; const aDefaultValue: Variant); begin Create( aIdentifier, aCaption, aCaption, True, aDefaultValue ); end; function TdvSetting.GetAsBoolean: Boolean; begin raise AccessError('Boolean'); { Do not localize } end; function TdvSetting.GetAsDateTime: TDateTime; begin raise AccessError('DateTime'); { Do not localize } end; function TdvSetting.GetAsFloat: Double; begin raise AccessError('Float'); { Do not localize } end; function TdvSetting.GetAsInteger: Longint; begin raise AccessError('Integer'); { Do not localize } end; function TdvSetting.GetAsString: string; begin Result := ClassName; end; function TdvSetting.GetAsVariant: Variant; begin raise AccessError('Variant'); { Do not localize } end; procedure TdvSetting.LoadFromRegIni(aRegIni: TRegistryIniFile; const aSection: String); begin Assert( Assigned( aRegIni ), 'The aRegIni parameter should contain a TRegIni Instance' ); end; procedure TdvSetting.SaveToRegIni(aRegIni: TRegistryIniFile; const aSection: String); begin Assert( Assigned( aRegIni ), 'The aRegIni parameter should contain a TRegIni Instance' ); end; procedure TdvSetting.SetAsBoolean(const Value: Boolean); begin raise AccessError('Boolean'); { Do not localize } end; procedure TdvSetting.SetAsDateTime(const Value: TDateTime); begin raise AccessError('DateTime'); { Do not localize } end; procedure TdvSetting.SetAsFloat(const Value: Double); begin raise AccessError('Float'); { Do not localize } end; procedure TdvSetting.SetAsInteger(const Value: Longint); begin raise AccessError('Integer'); { Do not localize } end; procedure TdvSetting.SetAsString(const Value: string); begin raise AccessError('String'); { Do not localize } end; procedure TdvSetting.SetAsVariant(const Value: Variant); begin if ( VarIsNull( Value ) ) then begin Clear; end else begin SetVarValue( Value ); end; end; procedure TdvSetting.SetCaption(const Value: String); begin FCaption := Value; end; procedure TdvSetting.SetHint(const Value: String); begin FHint := Value; end; procedure TdvSetting.SetIdentifier(const Value: String); begin FIdentifier := Value; end; procedure TdvSetting.SetVarValue(const Value: Variant); begin raise AccessError('Variant'); { Do not localize } end; What does it do ? Actually this piece of code doesn't do all that much. It just provides us with a base class we can now use as an ancestor for our other classes. Basically we have some ErrorHandling and a skeleton for our specific Setting classes. Continued in Part 4 For some reason I had problems fitting everything into one single post, so I had to split it up an two parts. Go ahead and read the rest in part 4.
Read More

Nexus One: touch screens do not solve all problems

When I first learned about Android and iPhone, it was fascinating to see, but I didn't leave my old Nokia phones before I saw somebody demonstrate to me, that it was actually possible to operate these phones with one hand. My Nexus One is still mostly handled with one hand, meaning that I don't use multitouch pinch-to-zoom much, even though it is available, instead I use the zoom buttons
Read More

Windows Phone 7 – A party pack of sweets for developers

They are playing advertisements for Pascall's Party Pack on TV at the moment. The tag line is "All sweets you love, and one you can't stand." That pretty much sums up Windows Phone 7 for developers. Lots of goodness with a few things that may leave a bad taste in your mouth.Despite the name, Windows Phone 7 Series is not a successor to Windows Mobile 6.x. Instead the name is a marketing nod towards Windows 7. Although both WP7 and WM6 are based on Windows CE, there is no resemblance at a higher level. From a user point of view, WP7 is closer to the iPhone than to WM6. A more accurate name would be Windows Phone 1.After far too much time reading, watching and playing, here are my thoughts on WP7 as relates to development:Tasty:Development is done in c# and Silverlight/XNA. While native code would be nice, C# is easier to get into than Objective C. I haven't used either Silverlight or XNA previously but they look a lot more promising than the XP era Windows Forms used by Windows Mobile.The tools are much better than those for the iPhone, and freePush notifications look at lot easier to do than on the iPhoneApparently xbox live integration is really good (I wouldn't know)There are metric shitloads of tutorials, walk throughs and documentation (Twitter apps are the new hello world)Games programming looks really, really niceMeh:The tools don't work well in virtual machines. You can make it work in a Win 7 32bit VM but it will run like a dog. The tools don't work at all in Win 7 x64. If you don't want to install onto your main system, you can try booting from a virtual hard drive.Yuck:No database support (apparently it's not necessary coz you have xml and the cloud). SQL Server CE is built into the rom, but no access is provided.No multitasking (push notifications only)No access to the file system. Application files all go into Isolated Storage in the app folder.No "sideloading" of applications. Apps can only be installed from the Marketplace, or by Visual StudioNo built in file synchronisation (it's this cloud thing again). No Silverlight support in Windows Mobile 6.x (but there is Silverlight for Symbian wtf) and no Win forms support in WP7. I.e. even if your win mobile app was written in c# it needs to be rewritten for WP7.Limited APIs (no access to contacts, no sockets, ...)No native code - no c, c++, pascal etc (and thus no firefox :()I haven't felt this conflicted since the vfr 1200. Some nice things balanced by some not so nice.As mentioned, so this is essentially version 1. Some of the issues are planned to be fixed after the initial release (copy/paste, database access).LinksMicrosoftWindows Phone 7 toolsCode samplesDeveloper networkMix10 videos (check out the keynote)UI GuideSilverlightGet started in SilverlightGet started with Silverlight in WM7Example appsTwitter clientAnother twitter client (Scott Gu)LabyrinthSqlite databaseBlogsArtificial Ignorance10remMobile development
Read More

Web 2.0 programming with Object Pascal (Part 2)

As I promised in my last article, here I'll show you how to add CRUD (Create, Read, Update and Delete) operations to the sample application.The first step is to add a toolbar to the Grid, with three buttons, btnAdd, btnEdit and btnDelete in charge of Inserting, Updating and Deleting data. Also I'll create a new popup form where the user will work with that data.The new GridInstead of overwriting the files used in the last example, I recommend to create a new directory called samples2, containing all the files showed in this article.NOTE: the file grid2.html, has the same contents of grid1.html, so just copy it from the last example and paste in the new directory, and don't forget to rename to grid2.html. After that, you'll have to rename the div id "grid1" by "grid2".This is the code for grid2.js:Ext.onReady(function(){ var dataStore = new Ext.data.JsonStore({ //url: '/samples/customerslist.json', url: '/cgi-bin/customerslist', root: 'rows', method: 'GET', fields: [ {name: 'id', type: 'int'}, {name: 'firstname', type: 'string'}, {name: 'lastname', type: 'string'}, {name: 'age', type: 'int'}, {name: 'phone', type: 'string'} ] }); var btnAdd = new Ext.Toolbar.Button({ text: 'Add', handler: function(){ var win = new MyPeopleWindow(); // to refresh the grid after Insert win.afterPost = function(){ dataStore.load(); }; win.show(); } }); var btnEdit = new Ext.Toolbar.Button({ text: 'Edit', handler: function(){ var win = new MyPeopleWindow(selRecordStore.id); // to refresh the grid after Update win.afterPost = function(){ dataStore.load(); }; win.show(); } }); var btnDelete = new Ext.Toolbar.Button({ text: 'Delete', handler: function(){ Ext.Msg.confirm( 'Delete customer?', 'Are you sure to delete this customer?', function(btn){ if(btn == 'yes'){ var conn = new Ext.data.Connection(); conn.request({ url: '/cgi-bin/customerslist', method: 'POST', params: {"delete_person": selRecordStore.id}, success: function(response, options) { // refresh the grid after Delete JSonData = Ext.util.JSON.decode(response.responseText); if(JSonData.success) dataStore.load(); else Ext.Msg.alert('Status', JSonData.failure); }, failure: function(response, options) { Ext.Msg.alert('Status', 'An error ocurred while trying to delete this customer.'); } }); } } ); } }); var myGrid1 = new Ext.grid.GridPanel({ id: 'customerslist', store: dataStore, columns: [ {header: "First Name", width: 100, dataIndex: "firstname", sortable: true}, {header: "Last Name", width: 100, dataIndex: "lastname", sortable: true}, {header: "Age", width: 100, dataIndex: "age", sortable: true}, {header: "Phone", width: 100, dataIndex: "phone", sortable: true} ], sm: new Ext.grid.RowSelectionModel({ singleSelect: true, listeners: { rowselect: function(smObj, rowIndex, record){ selRecordStore = record; } } }), tbar: [ btnAdd, btnEdit, btnDelete ], autoLoad: false, stripeRows: true, height: 200, width: 500 }); dataStore.load(); myGrid1.render('grid2');});Now, the editor form:MyPeopleForm = Ext.extend(Ext.FormPanel, { initComponent: function(){ Ext.apply(this, { border:false, labelWidth: 80, defaults: { xtype:'textfield', width: 150 }, items:[ {xtype:'numberfield',fieldLabel:'Id',name:'id'}, {fieldLabel:'First Name',name:'firstname'}, {fieldLabel:'Last Name',name:'lastname'}, {xtype:'numberfield',fieldLabel:'Age',name:'age'}, {fieldLabel:'Phone',name:'phone'} ] }); MyPeopleForm.superclass.initComponent.call(this, arguments); }, setId: function(idPerson) { this.load( { method: 'POST', url: '/cgi-bin/customerslist', params: {'idperson': idPerson} } ); } }); MyPeopleWindow = Ext.extend(Ext.Window, { constructor: function(idPerson){ MyPeopleWindow.superclass.constructor.call(this, this.config); // if idPerson is not null, then edit record // otherwise it's a new record if(idPerson != null) this.form.setId(idPerson); }, afterPost: function(){ this.fireEvent('afterPost', this); }, initComponent: function(){ Ext.apply(this, { title: 'Loading data into a form', bodyStyle: 'padding:10px;background-color:#fff;', width:300, height:270, closeAction: 'close', items: [ this.form = new MyPeopleForm() ], buttons: [ { text:'Save', scope: this, handler: function(){ this.form.getForm().submit({ scope: this, url: '/cgi-bin/customerslist', method: 'POST', // here I add the param save_person // to let the cgi program decide // a course of action (save person data in this case). params: {'save_person':'true'}, success: function(form, action){ // on success I just close the form this.afterPost(); this.close(); }, failure: function(form, action){ Ext.Msg.alert("Error","There was an error processing your request\n" + action.result.message); } }); } }, { text:'Cancel', handler: function(){this.close();}, // important!, without "scope: this" // calling this.close() will try to close the Button!, // and we need to close the Window, NOT the button. scope: this } ] }); MyPeopleWindow.superclass.initComponent.call(this, arguments); } });That's all for the UI part. Now let's create our new customerslist.pp file, containing all the data required for the CGI application.program cgiproject1;{$mode objfpc}{$H+}uses Classes,SysUtils, httpDefs,custcgi, // needed for creating CGI applications fpjson, // needed for dealing with JSon data Db, SqlDb, ibconnection; // needed for connecting to Firebird/Interbase;Type TCGIApp = Class(TCustomCGIApplication) Private FConn: TSqlConnection; FQuery: TSqlQuery; FTransaction: TSqlTransaction; procedure ConnectToDataBase; function GetCustomersList: string; function GetCustomer(AIdPerson: string): string; procedure FillJSONObject(AJson: TJsonObject); function SavePerson(ARequest: TRequest): string; function DeletePerson(ARequest: TRequest): string; Public Procedure HandleRequest(ARequest : Trequest; AResponse : TResponse); override; end;procedure TCGIApp.ConnectToDataBase;begin FConn := TIBConnection.Create(nil); FQuery := TSqlQuery.Create(nil); FTransaction := TSqlTransaction.Create(nil); with FConn do begin DatabaseName := 'TARJETA'; UserName := 'SYSDBA'; Password := 'masterkey'; HostName := '192.168.1.254'; Connected := True; Transaction := FTransaction; FQuery.Database := FConn; end;end;procedure TCGIApp.FillJSONObject(AJson: TJsonObject);begin AJson.Add('id', TJsonIntegerNumber.Create(FQuery.FieldByName('IdCliente').AsInteger)); AJson.Add('firstname', TJsonString.Create(FQuery.FieldByName('Apellido').AsString)); AJson.Add('lastname', TJsonString.Create(FQuery.FieldByName('Nombres').AsString)); AJson.Add('age', TJSONIntegerNumber.Create(FQuery.FieldByName('IdCliente').AsInteger)); AJson.Add('phone', TJsonString.Create(FQuery.FieldByName('TelFijo').AsString));end;function TCGIApp.GetCustomersList: string;var lPerson: TJSONObject; lJson: TJSONObject; lJsonArray: TJSONArray;begin (* Query the database *) FQuery.Close; FQuery.Sql.Text := 'select * from clientes'; FQuery.Open; FQuery.First; lJsonArray := TJSONArray.Create; lJson := TJSONObject.Create; try while not FQuery.Eof do begin lPerson := TJSONObject.Create; fillJsonObject(lPerson); FQuery.Next; (* Fill the array *) lJsonArray.Add(lPerson); end; (* Add the array to rows property *) lJson.Add('rows', lJsonArray); Result := lJson.AsJSON; finally lJson.Free; end;end;function TCGIApp.GetCustomer(AIdPerson: string): string;var lPerson: TJSONObject; lJson: TJSONObject;begin (* Query the database *) FQuery.Close; FQuery.Sql.Text := 'select * from clientes where IdCliente=' + AIdPerson; FQuery.Open; FQuery.First; lJson := TJSONObject.Create; try lPerson := TJSONObject.Create; fillJsonObject(lPerson); (* Add the array to rows property *) lJson.Add('success', 'true'); lJson.Add('data', lPerson); Result := lJson.AsJSON; finally lJson.Free; end;end;function TCGIApp.SavePerson(ARequest: TRequest): string;var lId: string; lFirstName: string; lLastName: string; lPhone: string; lSql: string;begin lId := ARequest.ContentFields.Values['id']; lFirstName := ARequest.ContentFields.Values['firstname']; lLastName := ARequest.ContentFields.Values['lastname']; lPhone := ARequest.ContentFields.Values['phone']; if lId '' then lSql := 'update clientes set ' + 'nombres = ''' + lLastName + ''', ' + 'apellido = ''' + lFirstName + ''', ' + 'telfijo = ''' + lPhone + ''' where idcliente=' + lId else begin lSql := 'insert into clientes(IdCliente, Nombres, Apellido, TelFijo) ' + 'values(Gen_Id(SeqClientes, 1),''' + lFirstName + ''', ''' + lLastName + ''', ''' + lPhone + ''')'; end; try FQuery.Sql.Text := lSql; FConn.Transaction.StartTransaction; FQuery.ExecSql; FConn.Transaction.Commit; Result := '{''success'': ''true''}'; except on E: Exception do Result := '{''message'': "' + E.message + '"}'; end;end;function TCGIApp.DeletePerson(ARequest: TRequest): string;var lId: string;begin lId := ARequest.ContentFields.Values['delete_person']; try FQuery.Sql.Text := 'delete from clientes where idcliente=' + lId; FConn.Transaction.StartTransaction; FQuery.ExecSql; FConn.Transaction.Commit; Result := '{''success'': ''true''}'; except on E: Exception do Result := '{''failure'': ''Error deleting person.''}'; end;end;Procedure TCGIApp.HandleRequest(ARequest : TRequest; AResponse : TResponse);var lIdPerson: string;begin if ARequest.ContentFields.Values['delete_person'] '' then begin AResponse.Content := DeletePerson(ARequest); end else if ARequest.ContentFields.Values['save_person'] '' then begin AResponse.Content := SavePerson(ARequest); end else begin lIdPerson := ARequest.ContentFields.Values['idperson']; if lIdPerson '' then AResponse.Content := GetCustomer(lIdPerson) else AResponse.Content := GetCustomersList; end;end;begin With TCGIApp.Create(Nil) do try Initialize; ConnectToDatabase; Run; finally Free; end;end.To compile this file, I use a simple Bash script, that copies the compiled program to /var/www/cgi-bin directory, where my Apache2 is configured to host executable CGI programs.#!/bin/bashfpc -XX -Xs -b -v ./customerslist.ppcp ./customerslist /var/www/cgi-binIn this article, I showed how to create an HTML page containing a grid, with a toolbar that allow CRUD operations. After I published my last article, some of you told me that this was too much work for showing just a simple grid on a web page, and I agree, but when you start adding complexity to the application, the effort needed to add features is marginal, and the separation of concerns used here allows to use better ways to speed up the code creation. I mean, instead of using a simple text editor to create the ExtJs code as I did (well, VIM is not a simple editor), you could try the new ExtJs Designer, and for the Object Pascal part, it is very easy to replace it with higher level frameworks, like WebBroker or DataSnap.Some of you asked why I didn't use ExtPascal in this article. I didn't use it because I wanted to show all the parts involved in an ExtJs application (HTML, JS, CGI App), and ExtPascal creates the JavaScript code automatically hiding those internals to the programmer, I think it is very powerfull for programmers who already know how to work with plain ExtJs, and after this article, you already know how it works, so now I can write about ExtPascal!.Here are the files for this article.What's next?Before writing about ExtPascal, I'll show you how to replace the CGI part by a DataSnap Server application. See you in my next post!.
Read More