Category: Firemonkey

Mind Mapping

I have been using mind mapping software for many years now and it has often been a great boost to get clarity around thought.I know that I’ve often been told that my mind needs a map to get around and I’ve agreed with them. I know I  think the sam… … Read More

Read More

DataSnap: In-Process Server Method

DataSnap Server Method was introduced in Delphi 2009.  Most video or demo about DataSnap server method available only introduce socket based client server access communication. e.g.: TCP or HTTP protocol.
However, DataSnap was designed as a scalable data access solution that able to work with one, two, three or more tiers model.  All examples we see so far are suitable for 2 or 3 tiers design.  I can’t find any example talking about 1 tier or in-process design.
Indeed, it is very simple to work with in-process server method.  Most steps are similar to out-of-process server methods.

Define a Server Method

Define a well known EchoString() and a Sum() server method:

unit MyServerMethod;
interface
uses Classes, DBXCommon;
type
  {$MethodInfo On}
  TMyServerMethod = class(TPersistent)
  public
    function EchoString(Value: string): string;
    function Sum(const a, b: integer): integer; 
  end;
  {$MethodInfo Off}

implementation
function TMyServerMethod.EchoString(Value: string): string;
begin
  Result := Value;
end;

function TMyServerMethod.Sum(const a, b: integer): integer;
begin
  Result := a + b;
end;
end.

Define a DataModule to access the server method

Drop a TDSServer and TDSServerClass as usual to the data module.  Define a OnGetClass event to TDSServerClass instance.  Please note that you don’t need to drop any transport components like TDSTCPServerTransport or TDSHTTPServer as we only want to consume the server method for in-process only.
object MyServerMethodDataModule1: TMyServerMethodDataModule
  OldCreateOrder = False
  Height = 293
  Width = 419
  object DSServer1: TDSServer
    AutoStart = True
    HideDSAdmin = False
    Left = 64
    Top = 40
  end
  object DSServerClass1: TDSServerClass
    OnGetClass = DSServerClass1GetClass
    Server = DSServer1
    LifeCycle = ‘Server’
    Left = 64
    Top = 112
  end
end


unit MyServerMethodDataModule;
uses MyServerMethod;
procedure TMyServerMethodDataModule.DSServerClass1GetClass(DSServerClass: TDSServerClass;
    var PersistentClass: TPersistentClass);
begin
  PersistentClass := TMyServerMethod;
end;

Generate Server Method Client Classes

It is not easy to generate the server method client classes design for in-process server.  You may try any methods you are familiar with to hook up your server method to TCP or HTTP transport service, start the service and attempt to generate the client class by any means.
//
// Created by the DataSnap proxy generator.
//

unit DataSnapProxyClient;
interface
uses DBXCommon, DBXJSON, Classes, SysUtils, DB, SqlExpr, DBXDBReaders;
type
  TMyServerMethodClient = class
  private
    FDBXConnection: TDBXConnection;
    FInstanceOwner: Boolean;
    FEchoStringCommand: TDBXCommand;
  public
    constructor Create(ADBXConnection: TDBXConnection); overload;
    constructor Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean); overload;
    destructor Destroy; override;
    function EchoString(Value: string): string;
    function Sum(const a, b: integer): integer;
  end;

implementation
function TMyServerMethodClient.EchoString(Value: string): string;
begin
  if FEchoStringCommand = nil then
  begin
    FEchoStringCommand := FDBXConnection.CreateCommand;
    FEchoStringCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FEchoStringCommand.Text := ‘TMyServerMethod.EchoString’;
    FEchoStringCommand.Prepare;
  end;
  FEchoStringCommand.Parameters[0].Value.SetWideString(Value);
  FEchoStringCommand.ExecuteUpdate;
  Result := FEchoStringCommand.Parameters[1].Value.GetWideString;
end;

function TMyServerMethodClient.Sum(a: Integer; b: Integer): Integer;
begin
  if FSumCommand = nil then
  begin
    FSumCommand := FDBXConnection.CreateCommand;
    FSumCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FSumCommand.Text := ‘TMyServerMethod.Sum’;
    FSumCommand.Prepare;
  end;
  FSumCommand.Parameters[0].Value.SetInt32(a);
  FSumCommand.Parameters[1].Value.SetInt32(b);
  FSumCommand.ExecuteUpdate;
  Result := FSumCommand.Parameters[2].Value.GetInt32;
end;

constructor TMyServerMethodClient.Create(ADBXConnection: TDBXConnection);
begin
  inherited Create;
  if ADBXConnection = nil then
    raise EInvalidOperation.Create(‘Connection cannot be nil.  Make sure the connection has been opened.’);
  FDBXConnection := ADBXConnection;
  FInstanceOwner := True;
end;

constructor TMyServerMethodClient.Create(ADBXConnection: TDBXConnection; AInstanceOwner: Boolean);
begin
  inherited Create;
  if ADBXConnection = nil then
    raise EInvalidOperation.Create(‘Connection cannot be nil.  Make sure the connection has been opened.’);
  FDBXConnection := ADBXConnection;
  FInstanceOwner := AInstanceOwner;
end;

destructor TMyServerMethodClient.Destroy;
begin
  FreeAndNil(FEchoStringCommand);
  inherited;
end;

end.

Invoke the server method via in-process

You may see from the following code that there is no different to access the server method for in-process and out-of-process design.
First, you create an instant of datasnap server.  This will register the DSServer to the TDBXDriverRegistry.  e.g. DSServer1 in this case.
You may then use TSQLConnection with DSServer1 as driver name instead of “DataSnap” that require socket connection to initiate in-process communication invoking the server method.
var o: TMyServerMethodDataModule;
    Q: TSQLConnection;
    c: TMyServerMethodClient;
begin
  o := TMyServerMethodDataModule.Create(Self);   Q := TSQLConnection.Create(Self);
  try
    Q.DriverName := ‘DSServer1’;     Q.LoginPrompt := False;
    Q.Open;

    c := TMyServerMethodClient.Create(Q.DBXConnection);
    try
      ShowMessage(c.EchoString(‘Hello’));
    finally
      c.Free;
    end;

  finally
    o.Free;
    Q.Free;
  end;
end;

Troubleshoot: Encounter Memory Leak after consume the in-process server methods

This happens in Delphi 2010 build 14.0.3513.24210.  It may have fixed in future release.  You may check QC#78696 for latest status.  Please note that you need to add “ReportMemoryLeaksOnShutdown := True;” in the code to show the leak report.

The memory leaks has no relation with in-process server methods.  It should be a problem in class TDSServerConnection where a property ServerConnectionHandler doesn’t free after consume.
Here is a fix for the problem:
unit DSServer.QC78696;
interface
implementation
uses SysUtils,
     DBXCommon, DSServer, DSCommonServer, DBXMessageHandlerCommon, DBXSqlScanner,
     DBXTransport,
     CodeRedirect;

type
  TDSServerConnectionHandlerAccess = class(TDBXConnectionHandler)
    FConProperties: TDBXProperties;
    FConHandle: Integer;
    FServer: TDSCustomServer;
    FDatabaseConnectionHandler: TObject;
    FHasServerConnection: Boolean;
    FInstanceProvider: TDSHashtableInstanceProvider;
    FCommandHandlers: TDBXCommandHandlerArray;
    FLastCommandHandler: Integer;
    FNextHandler: TDBXConnectionHandler;
    FErrorMessage: TDBXErrorMessage;
    FScanner: TDBXSqlScanner;
    FDbxConnection: TDBXConnection;
    FTransport: TDSServerTransport;
    FChannel: TDbxChannel;
    FCreateInstanceEventObject: TDSCreateInstanceEventObject;
    FDestroyInstanceEventObject: TDSDestroyInstanceEventObject;
    FPrepareEventObject: TDSPrepareEventObject;
    FConnectEventObject: TDSConnectEventObject;
    FErrorEventObject: TDSErrorEventObject;
    FServerCon: TDSServerConnection;
  end;

  TDSServerConnectionPatch = class(TDSServerConnection)
  public
    destructor Destroy; override;
  end;

  TDSServerDriverPatch = class(TDSServerDriver)
  protected
    function CreateConnectionPatch(ConnectionBuilder: TDBXConnectionBuilder): TDBXConnection;
  end;

destructor TDSServerConnectionPatch.Destroy;
var o: TDSServerConnectionHandlerAccess;
begin
  inherited Destroy;
  o := TDSServerConnectionHandlerAccess(ServerConnectionHandler);
  if o.FServerCon = Self then begin
    o.FServerCon := nil;
    ServerConnectionHandler.Free;
  end;
end;

function TDSServerDriverPatch.CreateConnectionPatch(
  ConnectionBuilder: TDBXConnectionBuilder): TDBXConnection;
begin
  Result := TDSServerConnectionPatch.Create(ConnectionBuilder);
end;

var QC78696: TCodeRedirect;
initialization
  QC78696 := TCodeRedirect.Create(@TDSServerDriverPatch.CreateConnection, @TDSServerDriverPatch.CreateConnectionPatch);
finalization
  QC78696.Free;
end.

Troubleshoot: Encounter “Invalid command handle” when consume more than one server method at runtime for in-process application

This happens in Delphi 2010 build 14.0.3513.24210.  It may have fixed in future release.  You may check QC#78698 for latest status.
To replay this problem, you may consume the server method as:
    c := TMyServerMethodClient.Create(Q.DBXConnection);
    try
      ShowMessage(c.EchoString(‘Hello’));
      ShowMessage(IntToStr(c.Sum(100, 200)));
    finally
      c.Free;
    end;

or this:
    c := TMyServerMethodClient.Create(Q.DBXConnection);
    try
      ShowMessage(c.EchoString(‘Hello’));
      ShowMessage(IntToStr(c.Sum(100, 200)));
      ShowMessage(c.EchoString(‘Hello’));
    finally
      c.Free;
    end;

Here is a fix for the problem
unit DSServer.QC78698;
interface
implementation
uses SysUtils, Classes,
     DBXCommon, DBXMessageHandlerCommon, DSCommonServer, DSServer,
     CodeRedirect;

type
  TDSServerCommandAccess = class(TDBXCommand)
  private
    FConHandler: TDSServerConnectionHandler;
    FServerCon: TDSServerConnection;
    FRowsAffected: Int64;
    FServerParameterList: TDBXParameterList;
  end;

  TDSServerCommandPatch = class(TDSServerCommand)
  private
    FCommandHandle: integer;
    function Accessor: TDSServerCommandAccess;
  private
    procedure ExecutePatch;
  protected
    procedure DerivedClose; override;
    function DerivedExecuteQuery: TDBXReader; override;
    procedure DerivedExecuteUpdate; override;
    function DerivedGetNextReader: TDBXReader; override;
    procedure DerivedPrepare; override;
  end;

  TDSServerConnectionPatch = class(TDSServerConnection)
  public
    function CreateCommand: TDBXCommand; override;
  end;

  TDSServerDriverPatch = class(TDSServerDriver)
  private
    function CreateServerCommandPatch(DbxContext: TDBXContext; Connection:
        TDBXConnection; MorphicCommand: TDBXCommand): TDBXCommand;
  public
    constructor Create(DBXDriverDef: TDBXDriverDef); override;
  end;

constructor TDSServerDriverPatch.Create(DBXDriverDef: TDBXDriverDef);
begin
  FCommandFactories := TStringList.Create;
  rpr;
  InitDriverProperties(TDBXProperties.Create);
  // ” makes this the default command factory.
  //
  AddCommandFactory(”, CreateServerCommandPatch);
end;

function TDSServerDriverPatch.CreateServerCommandPatch(DbxContext: TDBXContext;
    Connection: TDBXConnection; MorphicCommand: TDBXCommand): TDBXCommand;
var
  ServerConnection: TDSServerConnection;
begin
  ServerConnection := Connection as TDSServerConnection;
  Result := TDSServerCommandPatch.Create(DbxContext, ServerConnection, TDSServerHelp.GetServerConnectionHandler(ServerConnection));
end;

function TDSServerCommandPatch.Accessor: TDSServerCommandAccess;
begin
  Result := TDSServerCommandAccess(Self);
end;

procedure TDSServerCommandPatch.DerivedClose;
var
  Message: TDBXCommandCloseMessage;
begin
  Message := Accessor.FServerCon.CommandCloseMessage;
  Message.CommandHandle := FCommandHandle;
  Message.HandleMessage(Accessor.FConHandler);
end;

function TDSServerCommandPatch.DerivedExecuteQuery: TDBXReader;
var
  List: TDBXParameterList;
  Parameter: TDBXParameter;
  Reader: TDBXReader;
begin
  ExecutePatch;
  List := Parameters;
  if (List <> nil) and (List.Count > 0) then
  begin
    Parameter := List.Parameter[List.Count – 1];
    if Parameter.DataType = TDBXDataTypes.TableType then
    begin
      Reader := Parameter.Value.GetDBXReader;
      Parameter.Value.SetNull;
      Exit(Reader);
    end;
  end;
  Result := nil;
end;

procedure TDSServerCommandPatch.DerivedExecuteUpdate;
begin
  ExecutePatch;
end;

function TDSServerCommandPatch.DerivedGetNextReader: TDBXReader;
var
  Message: TDBXNextResultMessage;
begin
  Message := Accessor.FServerCon.NextResultMessage;
  Message.CommandHandle := FCommandHandle;
  Message.HandleMessage(Accessor.FConHandler);
  Result := Message.NextResult;
end;

procedure TDSServerCommandPatch.DerivedPrepare;
begin
  inherited;
  FCommandHandle := Accessor.FServerCon.PrepareMessage.CommandHandle;
end;

procedure TDSServerCommandPatch.ExecutePatch;
var
  Count: Integer;
  Ordinal: Integer;
  Params: TDBXParameterList;
  CommandParams: TDBXParameterList;
  Message: TDBXExecuteMessage;
begin
  Message := Accessor.FServerCon.ExecuteMessage;
  if not IsPrepared then
    Prepare;
  for ordinal := 0 to Parameters.Count – 1 do
    Accessor.FServerParameterList.Parameter[Ordinal].Value.SetValue(Parameters.Parameter[Ordinal].Value);
  Message.Command := Text;
  Message.CommandType := CommandType;
  Message.CommandHandle := FCommandHandle;
  Message.Parameters := Parameters;
  Message.HandleMessage(Accessor.FConHandler);
  Params := Message.Parameters;
  CommandParams := Parameters;
  if Params <> nil then
  begin
    Count := Params.Count;
    if Count > 0 then
      for ordinal := 0 to Count – 1 do
      begin
        CommandParams.Parameter[Ordinal].Value.SetValue(Params.Parameter[Ordinal].Value);
        Params.Parameter[Ordinal].Value.SetNull;
      end;
  end;
  Accessor.FRowsAffected := Message.RowsAffected;
end;

function TDSServerConnectionPatch.CreateCommand: TDBXCommand;
var
  Command: TDSServerCommand;
begin
  Command := TDSServerCommandPatch.Create(FDbxContext, self, ServerConnectionHandler);
  Result := Command;
end;

var QC78698: TCodeRedirect;
initialization
  QC78698 := TCodeRedirect.Create(@TDSServerConnection.CreateCommand, @TDSServerConnectionPatch.CreateCommand);
finalization
  QC78698.Free;
end.

Reference:

  1. QC#78696: Memory Leak in TDSServerConnection for in-process connection
  2. QC#78698: Encounter “Invalid command handle” when consume more than one server method at runtime for in-process application

Read More

Read More

Configure Windows 7 IIS7 for ISAPI DLL

Windows 7 IIS7 require some configurations to get ISAPI DLL works.  It is not that straight forward compare to IIS 5.

Install IIS 7

  1. Go to Control Panel | Programs and Features | Turn on Windows features on or off (require privilege mode).
  2. Check “Internet Information Services and make sure “ISAPI Extensions” and “ISAPI Filters” is checked as well.
  3. Click OK button to start installation.

After finish install IIS 7, open your favorite web browser and enter URL http://localhost/ to make sure the IIS is working and running.  You might need to check your firewall setting and add exception for port 80 TCP traffic if necessary.

Configure for ISAPI DLL

Add Virtual Directory

First, you may need to add a virtual directory to host your ISAPI DLL:

  1. Open Internet Information Service Manager (require privilege mode)
  2. Right click on “Default Web Site” node and click “Add Virtual Directory” of popup menu:

Enter “Alias” and “Physical Path” of the virtual directory:

Enable ISAPI for Virtual Directory

To enable ISAPI for the virtual directory:

  1. Select the virtual directory node (e.g.: “ISAPI” in this example). 
  2. Double click the “Handler Mappings” icon. 
  3. Click “Edit Feature Permissions…” in Actions panel
  4. A “Edit Feature Permission” dialog prompt out
  5. Check “Execute”.
  6. Click OK button to commit the changes.

Enable Directory Browsing for Virtual Directory

This is optional but is convenient.  To enable Directory Browsing for a virtual directory:

  1. Select the virtual directory node (e.g.: “ISAPI” in this example). 
  2. Double click the “Directory Browsing” icon.
  3. Click “Enable” in Actions panel.

Edit Anonymous Authentication Credentials

  1. Select the virtual directory node.
  2. Double click the “Authentication” icon.
  3. Click to select “Anonymous Authentication” item.
  4. Click “Edit…” in Actions panel.
  5. A dialog will prompt out.
  6. Checked “Application pool identity” and press OK button to commit changes.

Enable ISAPI modules

  1. Click on the root node.
  2. Double click the “ISAPI and CGI Restrictions” icon.
  3. Click ”Edit Feature Setting …” in Actions panel.
  4. Check “Allow unspecified ISAPI modules” option.  This option allow any ISAPI dll to be executed under IIS.  If you don’t use this option, you will need to specify a list of ISAPI DLLs explicitly.

Edit Permission for Virtual Directory

  1. Select the virtual directory node (e.g.: “ISAPI” in this example). 
  2. Right click on the node and click “Edit Permission” of popup menu.
  3. A Properties dialog prompt out.
  4. Switch to “Security” page
  5. Click Edit button to show Permission dialog.
  6. Add “IIS_IUSRS” into the permission list.

Enable 32 bits ISAPI DLL on IIS 7 x64

This is only require if you are using IIS7 x64 and would like to run 32 bits ISAPI DLL on the IIS.  If your ISAPI DLL and IIS7 is both x86 or both x64, you may skip this step.

  1. Click “Application Pools” node.
  2. Click “DefaultAppPool” item
  3. Click “Advanced Settings …” from Actions panel.
  4. A “Advanced Settings” dialog prompt out
  5. Set “Enable 32-bits Applications” to True
  6. Click OK button to commit changes

If you didn’t enable this options for 32 bits applications, you may encounter the following errors when execute the ISAPI from web browser:

HTTP Error 500.0 – Internal Server Error

The page cannot be displayed because an internal server error has occurred.

HTTP Error 500.0 – Internal Server Error
Module    IsapiModule
Notification    ExecuteRequestHandler
Handler    ISAPI-dll
Error Code    0x800700c1
Requested URL    http://localhost:80/isapi/isapi.dll
Physical Path    C:\isapi\isapi.dll
Logon Method    Anonymous
Logon User    Anonymous 

You may now deploy your ISAPI DLLs into the virtual directory and start execute the library from web browser.

DataSnap and ISAPI DLL

You may create Delphi DataSnap ISAPP DLL library and deploy on IIS.  From time to time, you may encounter compilation error during development or deployment time if you have consume the ISAPI DLL.  This is because the ISAPI DLL invoked will cache in the application pool.  You are not allow to overwrite the ISAPI DLL while it’s being cached.

To overcome this problem, you need to perform Recycle operation:

  1. Click “Application Pools” node.
  2. Right click on “DefaultAppPool” item and click “Recycle…” item.

Deploying as ISAPI DLL is encourage during deployment stage as IIS will cache the ISAPI DLL for performance consideration.

However, the caching might not feasible during development stage as recycling need to be performed while overwrite the ISAPI DLL either by frequent compiling or overwriting.  You may consider compile the server modules as CGI application in development time.  Each invocation of CGI is a separate OS process and won’t be cache by IIS application pool.

Install CGI on IIS

  1. Go to Control Panel | Programs and Features | Turn on Windows features on or off (require privilege mode).
  2. Check “Internet Information Services and make sure “CGI” is checked.
  3. Click OK button to start installation.

Enable CGI Module

  1. Click on the root node.
  2. Double click the “ISAPI and CGI Restrictions” icon.
  3. Click ”Edit Feature Setting …” in Actions panel.
  4. Check “Allow unspecified CGI modules” option.

Consume DataSnap Server Methods via URL

The DataSnap server methods are using JSON as data stream via REST protocol.  For example, a simple EchoString server method defined as:

type
  {$MethodInfo On}
  TMyServerMethod = class(TPersistent)
  public
    function EchoString(Value: string): string;
  end;
  {$MethodInfo Off}

implementation

function TMyServerMethod.EchoString(Value: string): string;
begin
  Result := Value;
end;

To access this method compiled in ISAPI DLL via URL, the URL is something like

http://localhost/datasnap/MyISAPI.DLL/datasnap/rest/TMyServerMethod/EchoString/Hello

and the response text will be:

{“result”:[“Hello”]}

Likewise, a CGI URL is

http://localhost/datasnap/MyCGI.exe/datasnap/rest/TMyServerMethod/EchoString/Hello

 

Reference:

  1. DataSnap 2010 HTTP support with an ISAPI dll; Author: Tierney, Jim

Read More

Read More

Delphi and RAD Studio Essentials on Nov 16-18, 2009 in Sweden

From Monday, Nov 16nd until Wednesday Nov 184th, 2009, I'll also be in Stockholm, Sweden, for a 2.5-day RAD Studio 2010 Essentials seminar (spoken in English) covering Delphi topics like the IDE and Language enhancements (Generics, Anonymous Methods, Touch/Gesture), UNICODE, VCL Database Development (DBX4 and DataSnap), Web Development (IntraWeb, ASP.NET and AJAX), and XML, SOAP and Web Services. The .NET topics will be covered using Delphi Prism 2010.
Read More

Delphi 2010 Masterclass on Nov 2-4, 2009 in Helsinki, Finland

From Monday, Nov 2nd until Wednesday Nov 4th, 2009, I'll be in Helsinki, Finland, for a 2.5-day RAD Studio 2010 Essentials seminar (spoken in English) covering Delphi topics like the IDE and Language enhancements (Generics, Anonymous Methods, Touch/Gesture), UNICODE, VCL Database Development (DBX4 and DataSnap), Web Development (IntraWeb, ASP.NET and AJAX), and XML, SOAP and Web Services. The .NET topics will be covered using Delphi Prism 2010.
Read More

Delphi Community Edition

I’ve just read Jolyon Smith’s post on a Delphi Community Edition. All good stuff, and something I have been clamouring for, for quite a while. The thing is, I don’t think it will happen. Embarcadero are in the business of making money, not giving things away. Incidentally, I think they’d probably make more money in the long run if they did give some things away, but playing the long game can be very difficult. So here’s a suggestion which is a mix of both the long and short game. I’ve suggested something like this before. Here it is slightly refined. Create a Delphi Community Edition/Turbo Editon, call it what you want, but the most important thing is it’s price. Yep, it’s free, or perhaps even better make it $99. (For some reason people think free stuff equates to poor quality) Do all the things Jolyon suggested. Digital Watermarking for example. Create an app-store on the Embarcadero website, that can accept apps/components from the Community Edition. Embarcadero get a percentage of all sales. (and perhaps they have some utility to remove the watermark when sold through the store) So users get their cheap edition, but they also get a reason to use it. Why do people put them selves through the hassle of learning Objective-C and Cocoa for the IPhone when it’s arguably easier to develop for Windows Mobile? There’s money in it, that’s why! Embarcadero get their $99 for the IDE and their 5-10% cut of sales, but more importantly they get a new user. Someone who would have used C# Express Edition, but saw an opportunity. Apple do have the advantage of a closed system, so perhaps it wouldn’t work for Delphi, but Embarcadero would not lose a thing by trying. The developers who currently would buy Delphi Professional, are not the target, and if done right, would still want the Professional version. Your target is that new developer about to download C# Express.
Read More

Customer Care

I was browsing Delphi tagged questions in stackoverflow yesterday, when I encountered this question. It’s all about generics in Delphi 2009. Whether they are actually usable. Here are a few clips from the answers :   I'm using generics extensively in Delphi 2009, and I can say it's not easy as you are often required to work around an ICE All this trouble seems to be gone in Delphi 2010 Bottom line: if you want to use them, upgrade to 2010.   I have also personally mentioned before that the IDE just doesn’t seem to recognise the existence of generics (try any refactoring or code completion). So basically what we are saying is that if you bought Delphi 2009, and you want to use generics in any meaningful way, you need to fork out more money and get Delphi 2010. Does anyone else find that disturbing? Let me give you a comparison. Let’s say you have just bought a Dell laptop. It cost about the same as Delphi 2009. The (fictional) Dell Longitude 2009. Among it’s many features, is built-in wifi. When you got the laptop, wifi kind of worked, but the laptop didn’t really recognise it had wifi, and you had to do a lot of manual stuff to get it working. When you did get it working, you noticed in some circumstances, it just refused to work, and in others it only kind of works. You complained to Dell, and they released update 2 and update 3 (some kind of bios update), which made things a lot better, and you were kind of happy. Less than a year later, Dell release the Longitude 2010. It looks like they’re not going to fix the existing wifi problems in the Longitude 2009, but if you have a 2009, they’ll give you a special price on a brand new 2010. Would you buy Dell again? Would you not expect Dell to fix your Longitude 2009? And if they can’t or won’t, don’t you expect a refund? I’m not singling out Embarcadero here, it is apparently industry practice. What other industry could get away with it though? What worries me is that I have to go to my boss and ask to upgrade to Delphi 2010. I am lucky, that I work for a company that has no problems in giving us the latest tools, whatever the cost. And in the grand scheme of things, to even a small company, the price of Delphi is insignificant. However, my boss will definitely ask the question, What does Delphi 2010 give us? I can wax lyrical about increased IDE productivity, and improved RTTI, but at some point I’ll have to add the line. Well, we had a few problems with generics in D2009, but it’s all been sorted out in D2010. What kind of confidence does a line like that instil?
Read More

Productivity versus Excitement

Many of the commentators in my two recent posts pointed out that the IDE additions and fixes in the forthcoming Delphi 2010 will increase productivity. I have no doubt about that. There’s one in particular I can see saving me quite a bit of time, and that’s the uses units. I use that little tool all the time, and could never understand why it automatically always put the unit in the implementation’s uses clause by default. Why can’t I choose? Well apparently now I can. One particular comment went on to say that compiler changes, the thing I said might just excite me, could in fact decrease productivity. It takes time to learn new stuff, and if you are spending more time learning, than actually doing things, then you’d not getting things done. That got me thinking about the Microsoft world, and how it seems that you hardly have started looking at one new technology, before it’s superseded by another. You’ve just about got your head around Winforms, and they spring WPF on you, then you jump on to that bandwagon, and suddenly it’s Silverlight out of the browser that’s the new in thing. Oh wait a minute, WPF and Silverlight doesn’t seem to be doing so good, perhaps we should stick with Winforms. Same on the web development side of things. You start with ASP.NET, get comfortable with ViewState and postbacks, and bam, they bring out ASP.NET MVC and tell you to forget all that (Actually they say they’re keeping both, so you need to decide which is for you). To make things worse, they offer you Silverlight as well. It’s the same wherever you look. Linq to SQL? Well apparently Microsoft now prefer Entity Framework. Well they did. I’m not quite sure anymore. So yeah, all these new innovations to the language and new technologies do have an effect on your productivity, especially if you are trying them all out. The thing is, it’s human nature to want something new and shiny. Look at mobile phones. Most people use their mobile phones for making and receiving calls and that’s it. Oh, they might try a few of the new features when they first get the phone. Maybe take the occasional photo with their phone, but for the most part, they just make calls. So why do they change their phone every 6 months or so? The phone they had 5 years ago still works great for making those calls. The phone manufactures know it’s human nature to always want the new model. So they oblige and keep churning them out. Microsoft also know that they can’t just rest on what they’ve got. They’ve got to keep their audience excited. Keep throwing as much new exciting stuff as is possible to keep them coming back for more. So while I agree, I’m as productive as a Microsoft developer who is using C# and Visual Studio, I admit I do go green with envy when that get some new toy!
Read More

StackOverflow and religious language in programming

I was reading Why I Don’t Listen to the StackOverflow Podcast any More, and in reading the comments, I found it interesting to see the amount of religious language. #21 (sbohlen) I’d have to agree that they do indeed appear successful, but success / failure isn’t a boolean [...] The world is full of people who succeed due to all kinds of factors including dumb-luck, [...] This reminds me intensely of "the race is not always to the swift" etc. Eccl 9:11. I changed adopters to believers to accentuate the effect: "#16: [...] I have also experienced that the non-[believers] aren’t (gnerally) non-[believers] out of choice but are instead non-[believers] largely out of ignorance and inexperience — they just don’t know what they don’t know [...]" #16: "the [types] who aren’t interested in self-improvement; there’s not a damned thing I can suggest to help these people — I cannot MAKE someone want to better themselves, that’s gotta come from within" #16: "I [...] think that Jeff and Joel are indeed preaching [...] they are (IMO) abdicating the unofficial professional responsibility that comes with having a pulpit from which to preach." #14: "I am a strong believer" #13: "@sbohlen. I fully agree with the sentiment that “up and running” is not a valid metric" I'm not going to go on with this, though I could - the about page for the blog is particularly ripe for further examples of religious language - but I do want to make a further point. Could it be that latent religious feelings are responsible for a good portion of the minor angst on display in these comments? Could it be that people are unconsciously annoyed, not with StackOverflow's success, but rather its success even in the face of quite mild contempt at the unit-testing / SOLID religion? My own position: I'm a "believer" in evolution and the market. Neither evolution nor the market have any moral content (they can't say what "ought" to be), and both only measure success by effects rather than processes. In a phrase, "up and running" is not just a valid metric - it's the only metric, until it stops running. If SO continues to thrive, and doesn't die under the weight of its lack of unit testing (which seems to be the logical end result of the beliefs of the believers in the comments), where then is the truth in the proclamations of doom from the prophets of the true religion? I don't think unit-testing is the only way to avoid long-term bit-rot. I think it's a good way, but not the only way; moreover, I don't think unit testing is especially useful in the very early stages of a project, where the design changes often and rewriting unit tests becomes a disincentive to larger refactoring and redesign. I think every one of the concepts embodied in S.O.L.I.D. is debatable in certain scenarios (except perhaps LSP). In my experience, premature abstraction has been responsible for a similar number of ills to premature optimization: abstractions chosen at the wrong boundaries, for the wrong reasons, because designers thought they could foretell the future and anticipate where changes would come. Thus I am wary of merchants of abstractions and patterns, principles and practices. I'll use them when appropriate, but never with religious zeal.
Read More

iPhone Dev: Zombie Mansion post mortem

OverviewI released Zombie Mansion in mid December 08. It is a 1st person shooter, the 2nd available on the iPhone and the first one to be designed for mobile devices rather than being a pc port. ZM was a port and extension of the work I had done previously on the Yeti engine.Seeing as how every other iPhone developer is sharing their tales of mega success, or crushing failure, I though I would add my story.SalesZM has sold steadily, if not spectacularly. Unfortunately I have not made enough to pay off my house and buy a fast car. On the other hand, it sold well enough to pay my mortgage payments for the 1st 3 months. Sales are as you would expect: hundreds sold in the first couple of days, rapidly dropping off as ZM moved off the recent releases page. My first update provided a brief blip but my second update was unnoticeable in terms of sales.I took 2 weeks leave to finish ZM, and I estimate that I spent the equivalent of another 2 weeks previously. This doesn't include the original porting work I did moving the yeti engine to the pocket pc.As a return on time spent, ZM performed slightly better than working at my day job. It was however considerably more fun. It would have performed even better but I made a couple of bad design decisions.ProgrammingUnusually, I did the bulk of the development in windows using visual studio rather than in xcode on OSX. I already had an OpenGL ES port running on Windows Mobile and Windows so an iPhone port wasn't much of a problem.Coding was done in a mixture of C (yeti code) and C++ (my code).On the iPhone, I used the Oolong engine to handle the sound, input and 3d setup. Oolong provides c++ wrapper classes for all this (and more).On the pc and windows mobile side, I used the PowerVr sdk to handle 3d setup. Keyboard handling is trivial, and I used Hekkus to provide sound support.Once I had this all set-up,I did most coding in visual studio. At irregular intervals, I would reboot into OSX and compile with xcode. I mostly used xcode for iPhone specific stuff and performance testing. All my support tools (map editor, 3d importer etc.) are windows based (in c++ builder and Delphi) so that necessitated mostly using windows. I prefer visual studio to xcode, so it wasn't much of a chore.MistakesI made a couple of mistakes. The first, and most major was simplifying the weapons too much. I went with one weapon, a staff, with upgrades rather than with multiple weapons. In retrospect, a bad call.The second mistake was not putting some help tips around controls. The controls work well if you leave your thumb on the d pad and slide it to control. If you pick your thumb up and move it, the controls don't work as well. Some easy way of explaining this to the player would have made their initial impressions better.The underlying problem is that perennial development favourite, I underestimated the time needed to write the software. I ran out of annual leave and thus ran out of time. While I enjoyed the game that I wrote, it would have been better if I had more time to add extra features and polish.Dealing with AppleThere are 3 major drawbacks to iPhone development: Apple, Apple and Apple. I don't know if they are overworked, incompetent or just don't care. Either way, developer support seems to be missing in action.Since I have had children, I have made a concious effort to improve my language, removing the more robust terms I picked up working in the abbottiors. Three months of dealing with Apple has made a mockery of my self-betterment attempts, leaving me swearing like Gordon Ramsey on a bad day.Simply getting a paid developer contract took six weeks. My first payment, for December, didn't get paid until late March. Emails asking when money was going to arrive were ignored, or fobbed off with stock responses or outright lies. Whenever I phoned, I was told they couldn't help and to email another department.Even when I did get paid, there was no notification. Apple can email me every week, trying to sell me a second rate album farted out by a talentless boy band, but they can't be bothered sending me an email saying "We have now condescended to pay you".As a comparison, RegNow emailed me on Jan 4th letting me know my December payment had been released, and the money was in my account on Jan 6th. Even now that Apple has decided to make my monthly payments on a monthly basis, they still take nearly 40 days longer than Regnow.Random thoughtsThere is clearly still money to be made in iPhone dev. ZM hasn't disturbed the sales charts, but it has still sold well enough to cover it's costs and my time. I imagine that there are many developers in a similar position, not making riches, but making enough to get by and keep developing. A small stable of good games/applications could provide a nice living without requiring a hit.It used to be that you could get a large amount of exposure and thus sales by releasing an update every couple of weeks. However the volume of new applications has changed that. There are so many apps being released each day that the initial rush of sales from being in the "what's new" charts is much smaller. You need to get sales the hard way, with marketing; promotion and advertising.I also have a couple of windows shareware applications, Pics Print and Rental Property Manager. While it is not easy to compare windows utilities and business applications with an iPhone, the return on time invested is far greater for these 2, particularly RPM, than for ZM.All this brings me full circle from where I started out six months ago. There is money to be made in iPhone apps, but there is also money to be made in windows shareware as well. If you are a windows developer, particularly if you are a Delphi developer, then you are probably better off developing windows shareware than iPhone apps. I know a lot more developers making money off windows than off iPhones.Oh, and developing for iPhones still sucks! Not as badly as I first thought, but still noticeably.
Read More

Delphi: Design Multi-Threaded SDI Application

SDI (Single Document Interface) has been pushed by Microsoft Office years ago.  Previously, it was MDI (Multiple Document Interface) in few older Office Suite.  Delphi Win32 VCL Form Application support MDI natively.  It requires some works to get SDI done in Delphi. A new document interface model: Tabbed Browsing has been pushed by Firefox that grab lot of attention too.  A more recent Google Chrome can even undock a tab to make it as a separate desktop window. It is no doubt that SDI has some advantages over MDI.  Research shows that using more than one desktop do improve productivity of user.  Dual-View environment will become popular in end user's day to day work.  MDI application can't utilize multi-desktop environment.  It may only show on one desktop only.  However, SDI doesn't has the restriction and may fully utilize multi-desktop environment.  Perhaps, the only drawback is the over created SDI window flow on desktop may irritate user's eye. Show form as a separate desktop button By default, all newly form create form may just show as is flowing on desktop.  There is only one desktop button for the VCL application you launch regardless of how many forms you created.  Running the following code for each form instance will create a desktop button: procedure TChildForm.CreateParams(var Params: TCreateParams); begin   inherited CreateParams(Params);   with Params do begin     ExStyle := ExStyle or WS_EX_APPWINDOW;   end; end; Now, all TChildForm instances will have separate desktop button.  That makes your application start looks like SDI. Make it more like SDI After play around with the above application for while, you start notice few things: If my child form overlap the main form, my main form always stay behind the child form Minimize main form will minimize all child forms and desktop button shrink to only one. Press Alt-Tab to bring the window selector doesn't show all correct number of main form and child form windows. This is because all child form instance's WndParent handle is set to Application.Handle or Application.MainformHandle.  To overcome above problems, we can code like this: procedure TChildForm.CreateParams(var Params: TCreateParams); begin   inherited CreateParams(Params);   with Params do begin     ExStyle := ExStyle or WS_EX_APPWINDOW;     WndParent := GetDesktopwindow;   end; end; Now all main form and child form behave similar and independent on your desktop.  Only different is closing the main form will close all child forms and end the process. Child Form invoke modal form freeze whole application So far, you should happy with the new SDI looks.  It works great.  But if your child form run the following codes: begin   ShowMessage('Modal Form Launched'); end; begin   raise Exception.Create('Exception raised'); end; You will notice that you can't activate other child form or main form anymore.  This reason behind this is simple.  TCustomForm.ShowModal is running in a repeat until loop waiting for the form to return a ModalResult: function TCustomForm.ShowModal: Integer; begin   ...   Application.ModalStarted;   try     ...     try       Show;       try         ModalResult := 0;         repeat           Application.HandleMessage;           if Application.Terminated then ModalResult := mrCancel else             if ModalResult <> 0 then CloseModal;         until ModalResult <> 0;         ...       finally         Hide;       end;     finally       ...     end;   finally     Application.ModalFinished;   end; end; For simple message dialog like ShowMessage or exception dialog, we may use Windows.MessageBox to prevent the freezing by introduce a parent form handle to the API function. Other modal dialog may need extra coding to prevent the freezing is mimic TForm.ShowModal by using TForm.Show method instead.  The following code reveals a possible solution: procedure ShowOwnModal(const aParentForm: TCustomForm; const aForm: TForm;     const Proc: TProc<TModalResult>); begin   aForm.PopupParent := aParentForm;   aForm.Show;   aParentForm.Enabled := False;   try     while aForm.Visible do       Application.HandleMessage;   finally     aParentForm.Enabled := True;   end;   Proc(aForm.ModalResult); end; procedure TChildForm.btnModalFormClick(Sender: TObject); var F: TForm; begin   F := TModalForm.Create(Self);   try     ShowOwnModal(Self, F,       procedure(M: TModalResult)       begin         if M = mrOK then           ShowMessage('OK pressed')         else           ShowMessage('Cancel pressed');       end     );   finally     F.Free;   end; end; We may reuse procedure ShowOwnModal else where in code that need to mimic ShowModal.  Furthermore, the ShowOwnModal strategy allow us to retain the classic coding style of using ShowModal in try...finally block: F := TModalForm.Create(Self); try   if F.ShowModal = mrOK then     ... finally   F.Free; end; Child form running lengthy task freeze whole application If the child form is running a task that takes a while to complete.  Whole application will freeze.  This doesn't seems to be a acceptable behavior as user may have a perception that each child window should work independently from others. A straight solution to the problem is using thread to run the task. Hybrid Mode: Working with SDI and MDI together There is an obvious disadvantage with SDI mode.  If too many SDI form has been open on desktop, user desktop getting messy and become harder for user to find and switch form.  It is also harder for user to identify a window was instantiated from which application.  In this scenarios, user may miss the classic MDI mode that collect all related forms under one roof. Perhaps a solution would be mixing both mode together.  If we make MDI as a primary mode, all new instantiated window will become MDI child form by default.  There is a gadget to switch the MDI child form to SDI form floating on desktop window and vice versa allowing user working on multi-desktop environment.  This design may be a favor solution for user who need SDI in ad-hoc manner. The following code reveal a possible design: type   TfmMDIChild = class(TForm)     Button1: TButton;     Button2: TButton;     procedure Button1Click(Sender: TObject);     procedure Button2Click(Sender: TObject);   protected     procedure CreateParams(var Params: TCreateParams); override;   end; procedure TfmMDIChild.Button1Click(Sender: TObject); begin   Self.FormStyle := fsNormal; end; procedure TfmMDIChild.Button2Click(Sender: TObject); begin   Self.Hide;   // Hide the form first else CreateParams will invoke twice   Self.FormStyle := fsMDIChild; end; procedure TfmMDIChild.CreateParams(var Params: TCreateParams); begin   inherited;   if FormStyle = fsNormal then begin     with Params do begin       ExStyle := Params.ExStyle or WS_EX_APPWINDOW;       WndParent := GetDesktopWindow;     end;   end else if FormStyle = fsMDIChild then begin     Params.Width := Self.Width;     Params.Height := Self.Height;   end; end; The form provide 2 buttons allowing user to switch from MDI to SDI and SDI to MDI.  CreateParams will be invoked if FormStyle has changed. Reference: Newsgroup: borland.public.delphi.objectpascal. Topic: SDI ShowModal howto? Author: Peter Below (TeamB) WebSite: About.Com: Delphi Programming. Topic: A more Powerful Delphi Form, TaskBar Button for every Delphi Form. Author: Zarko Gajic Newsgroup: borland.public.delphi.nativeapi.win32. Topic: How do I add a form to the taskbar? http://delphi.newswhat.com/geoxml/forumhistorythread?groupname=borland.public.delphi.language.delphi.general&messageid=40a1ffb8@newsgroups.borland.com Win32 user interface work is inherently single-threaded Newsgroup: Embarcadero Discussion Forums.Delphi.Language.Win32. Topic: Show a modal form freeze whole SDI application
Read More