Wednesday, December 19, 2012

ShellExecute as administrator + Drag and Drop in Windows 7

This is a little tip that took me sometime to figure it out:
This kind of issue happened when I was debugging a IntraWeb application created with Delphi XE3 in Windows 7. I often run Delphi XE3 as local administrator. I know, I know... but sometimes it is required for proper ISAPI debugging.
This application renders a control that supports drag and drop from Windows Explorer, when the browser also supports it (Firefox, Chorme, etc.). The issue then is: When you start the browser using ShellExecute (or CreateProcess for that matter), from Delphi XE3 (or any other version or software) running with administrator credentials, the browser is also running as administrator, right? But Windows Explorer IS NOT. So, drag and drop from Windows Explorer to Firefox, for instance, won't work. Applications running with elevated privileges and applications running as regular user just won't do drag and drop... Simple, yes?

Thursday, July 12, 2012

My IntraWeb optimizations are not required anymore

Yes! If you are using the latest 12.2.6 IntraWeb version you won't need my "speed patches" anymore because IntraWeb 12.2.6 are using these same faster routines, and more! :-)

You may download the new IntraWeb 12.2.6 version from here (follow the Test Releases link)

http://www.atozed.com/IntraWeb/Download/Download.EN.aspx

Enjoy!

Sunday, April 15, 2012

Debug ISAPI extensions in Windows 7 and IIS 7.5 using Delphi XE2

Many things have changed in IIS from version 6 to 7 (and 7.5 that comes with Windows 7). If you are not familiarized with IIS 7 (and 7.5) debugging, maybe this post may help you. In fact, debugging ISAPI in Winodws 7 is easy if you follow a few steps:

Setting up IIS

First, you have to create a new application in IIS and set it up as usual. Two things are important during IIS and application setup:

1) In IIS, create the web application under Default Web Site.

2) Every application under Default Web Site should be using the same application pool, DefaultAppPool.

If you ignore these two rules, you will have additional configuration steps to run w3wp later.

Setting up the application in Delphi XE2

1) First and more important: Run Delphi XE2 as administrator! If you don't, Delphi may not be able to start the IIS Worker process (W3WP.exe).

2) In Delphi XE2 Ide, choose Run -> Parameters. Inform Host application and Parameters as you can see in the following picture:



W3WP.exe is the IIS Worker Process executable, and we will run it interactively to debug the ISAPI app. If you didn´t follow rules (1) and (2) of IIS Setup, then you will have to do additional configuration:
- Inform the web site using the parameter: -s "TheSiteId"
- Inform the application pool using the parameter: -ap "TheAppPoolName"
(This doesn't work with IIS 7 and above. The -ap parameter is not recognized by w3wp, so it's better to stay with tip (2) above)

Ready to go

Now that everything is ready, you have to stop the WWW publication service (W3SVC). You may use "net stop W3SVC" from an elevated command prompt, or use the Windows services console. Once the W3SVC is stopped, just run the application from Delphi XE2 IDE and call it from your browser. When the application is loaded all your breakpoints will be activated and you can easily debug the application.

Additional notes

I´ve found some references saying that you have to set the linking options "Map file -> Detailed" and "Include remote symbols -> On", in your project options. Well, in fact, the debuggin of ISAPI apps as explained here works perfeclty without the map file and the remote symbols.

Monday, April 9, 2012

Faster IntToStr functions for Delphi 32 bits

Last week I was trying to use the excellent "Enhanced Run time library" from A. Bouchez, in a Delphi 2006 project. Unfortunately, due copyright restrictions, the modifications are released as a code patch and for Delphi 7 only. Use all the modifications in a Delphi 2006 project is not an easy task. So I decided to use some of its code in another unit, patching the RTL original functions with new code, as I did a lot lately ;-)
The first two functions ported are faster IntToStr functions written 100% in assembly. The functions are 500 up to 1000% faster than original RTL functions, depending on the compiler (almost 1000% faster in Delphi 2006, almost 500% faster in Delphi XE2 - 32 bits). The code cannot be used in XE2 64 bit projects, because ASM 64 is a different beast. Maybe I will port other functions as well (things that don't violate copyright material), but many of them are already addressed in RtlVclOptimize (from Andreas Hausladen).
I created an unit called uCBRTLPatch.pas, containing two functions:
function FastIntToStr(Value: Integer): string;
function FastInt64ToStr(Value: Int64): string;

These functions will replace (patch) original IntToStr functions in runtime (for Integer and Int64 parameters).
To use it, you just have to add uCBRTLPatch.pas to your .DPR uses clause and you are done. You can donwload uCBRTLPatch.pas here.

Enjoy!

TCodeRedirect rewrite

A few weeks ago I wrote a post about TCodeRedirect. After that, I had a few problems using that code in Delphi 2006, specifically internal compiler errors (Internal Compiler Errors = useless error message!). The errors were occurring in some projects, not in all...
After a few tests I thought that could be something related to naming conflicts with original CodeRedirect function in Andreas Hausladen's RtlVclOptimize.pas (also used in my projects). So I decided to rename everything to avoid naming conflicts and it worked! No more internal compiler errors! Now my unit is named CodePatch.pas. I've updated my IntrawebPatch to use this new unit, and I'm publishing my new code here.

Enjoy!

Monday, March 12, 2012

Show "loading animation" during long Intraweb AJAX requests

Yesterday I was talking to another Intraweb developer and I've created a demo application to show him how I lock the screen, showing the loading AJAX animation (IWLocker), during an AJAX request that takes long time to return. After that, I've improved my demo using 3 different techniques do do that. In this post I will show you all the 3 techniques.

The problem

Suppose you have a IWButton in a form and want to trigger an async (AJAX) request that will take a long time to return. For instance, you will create a complex report, or generate a complex file to be downloaded later. You want your user to click the button and have some kind of feedback that your application is working, yes? At the same time you don't want your user to keep clicking all over the place... The best option, IMO, is tho show the IWLocker (the IW AJAX "loading animation") during the request. I will show you three different techniques to do that. Very similar but you may prefer one over the other.

First solution

For this solution, I will create a JavaScript function that will be called by your button directly, without any OnAsyncClick event handler. First, you have to create the JavaScript function. Let's say:
function myAjaxFunc() { 
  var myData = "Nicole Scherzinger,Scarlett Johansson,Jennifer Lawrence,Doutzen Kroes,Charlize Theron";
  ShowBusy(true);
  executeAjaxEvent("&data="+myData, null,"IWFORM1.DoMyAjaxFunc", false, null, false);
  return true;
}
This is a simple js function: There is a variable myData that holds the data that I want to send to the server, in this case, a comma separated list of names, but it can be whatever you want. Then I call ShowBusy, and after that I call the real AJAX request, executeAjaxEvent.
Note that to show the IWLocker, I'm calling ShowBusy function. ShowBusy is declared in IWPreScript.js, a JavaScript library that is available to all your IWForms. ShowBusy expects a single boolean parameter. If you pass true the IWLocker will be shown, and if you pass false the IWLocker will be hidden. Also note that executeAjaxEvent will call IWFORM1.DoMyAjaxFunc method of your form. This method must be registered so IW core can call it. To register that method, first you have to create the method DoMyAjaxFunc in your IWForm1:
const
  CDATATag = '< ! [CDATA[%s] ] >';  // spaces added so blogger won't mess the CDATA string

procedure TIWForm1.DoMyAjaxFunc(EventParams: TStringList);
var
  ResponseFunc: string;
  sl: TStrings;
  s: string;
begin
  sl := TStringList.Create;
  try
    sl.StrictDelimiter := True;
    sl.CommaText := EventParams.Values['data'];
    s := sl.Strings[Random(sl.Count)];
  finally
    sl.Free;
  end;
  Sleep(5000);  // simulate a long operation
  IWLabel1.Caption := 'The hot chick chosen is: ' + s;
  ResponseFunc := Format(CDATATag, ['ShowBusy(false);']);
  WebApplication.CallBackResponse.AddJavaScriptToExecute(ResponseFunc);
end;
The code above shows the DoMyAjaxFunc. It retrieves the "data" parameter sent from myAjaxFunc() and simulates a long operation (in this case, 5 seconds). When this method finishes, it sends ShowBusy(false) to the browser using WebApplication.CallBackResponse.AddJavaScriptToExecute method.
Then we must register DoMyAjaxFunc callback, using:
WebApplication.RegisterCallBack(UpperCase(Self.Name) + '.DoMyAjaxFunc', DoMyAjaxFunc);  // Self.Name -> IWForm1
Finally you must call the JavaScript function myAjaxFunc from some JavaScript event directly, let's say in the onClick button event handler:
procedure TIWForm1.IWAppFormCreate(Sender: TObject);
begin
  IWButton1.ScriptEvents.HookEvent('onClick', 'myAjaxFunc();');
end;
I hooked myAjaxFunc() to the onClick event handler of IWButton1 in runtime. It can be done in design time, using Object Inspector as well.
That's it! If you run your application and click on the IWButton1, you will see that IWLocker will become visible for 5 seconds (the time that DoMyAjaxFunc() method takes to run) and then will be hidden again.

Second solution

The second solution is very similar to the first, but instead of calling your myAjaxFunc() directly from your onClick event handler, you can use IW async events to call it, in this case, IWButton2 (another button in IWForm1) onAsyncClick event:
const
  CDATATag = '< ! [CDATA[%s] ] >';  // spaces added so blogger won't mess the CDATA string

procedure TIWForm1.IWButton2AsyncClick(Sender: TObject; EventParams: TStringList);
var
  ResponseFunc: string;
begin
  ResponseFunc := Format(CDATATag, ['ShowBusy(true);myAjaxFunc();']);
  WebApplication.CallBackResponse.AddJavaScriptToExecute(ResponseFunc);
end;
Inside IWButton2 OnAsyncClick event handler I add JavaScript to execute, again using AddJavaScriptToExecute method. The JavaScript code that will be executed is a call to ShowBusy (showing the IWLocker) and then a call to myAjaxFunc();.

Third solution

The third solution is a little different from the others. It uses the IWButton3 OnAsyncClick twice. Let's see how:
procedure TIWForm1.IWButton3AsyncClick(Sender: TObject;
  EventParams: TStringList);
var
  ResponseFunc: string;
  FirstCall: boolean;
begin
  FirstCall := (EventParams.Values['SecondCall'] <> 'true');
  if FirstCall then  // this code will run in the first IWButton3AsyncClick call
  begin
    ResponseFunc := Format(CDATATag, ['ShowBusy(true);executeAjaxEvent("&SecondCall=true", null,"' + IWButton3.HTMLName + '.DoOnAsyncClick", false, null, false);']);
  end else   
  begin   // this code will run in the second IWButton3AsyncClick call
    Sleep(5000);  // simulate a long operation
    ResponseFunc := Format(CDATATag, ['ShowBusy(false);']);
    IWLabel1.Caption := 'Result returned';
  end;
  WebApplication.CallBackResponse.AddJavaScriptToExecute(ResponseFunc);
end;
When you click the IWButton3, the OnAsyncClick event is fired. You then search for some parameter, in this case "SecondCall" in the EventParams list. If SecondCall is not there, then this is the first OnAsyncClick call. In the first call we add some JavaScript code to be executed. This code shows the IWLocker (calling ShowBusy(true)), and then calls the OnAsyncClick event again, this time passing the SecondCall parameter to the method.
When the event OnAsyncClick is fired the second time, there will be a SecondCall parameter in EventParams list and then we know that this time we must do the "real" work. When the method finishes, it will then call ShowBusy(false); hiding the IWLocker again.

Conclusions

The three techniques have the same effect: Show IWLocker during a long AJAX async request. Using the first method, your application will use a single AJAX request to your server, but you have to write some JavaScript code. Using the third method you won't have to write a single line of JavaScript but there are two AJAX requests involved. If you are not comfortable writing JavaScript you may prefer this solution. The second has the worst of both worlds: Two AJAX requests involved and some JavaScript coding... ;-)

Download the sample project

You can download a complete project showing all the three methods here.

Enjoy!

Wednesday, March 7, 2012

Crossbrowser, multi-line hints in Intraweb

Yesterday I saw a post in Embarcadero forums asking how to create multi-line hints in a IWGrid, inside an Intraweb application.
Well, this doesn't have a simple answer because IWGrid cells are TD HTML tags, and the hint is the title attribute. There is no reliable way to make the title attribute break lines across browsers. It is doable in IE but not in Firefox, for instance.
I will show you a technique that can be used in your Intraweb application to customize the hints. I will use a small javascript library in this example, but other library can be used as well. I'm using one of the simplest solutions I've found, from Craige Erskine, old qTip. The advantage of this release of qTip is that it is simple, compact and doesn't require another JavaScript library (jQuery, etc). It is composed of two files only: qTip.js and qTip.css. I have modified this release of qTip a little because it was replacing the windows.onload event with its own handler - not a good thing to do in JavaScript...

Include qTip files in your IWForm

Each form rendered that will use qTip hints need to include qTip files (qTip.js and qTip.css). This is done using one of my utility classes TIWContextHelper (file included in the download), in the FormRender event:
uses
  IWContextHelper;

procedure TMyIWForm.IWAppFormRender(Sender: TObject);
begin
  TIWContextHelper.AddCSSFile(Self.PageContext, 'qTip.css');
  TIWContextHelper.AddJavaScriptFile(Self.PageContext, 'qTip.js', True);
end;
Notes:
1) IWContextHelper expect CSS files to be in /files/css/ subfolder, and JavaScript files to be in /files/js/ sub-folder. This can be changed in IWContextHelper.
2) If the default sub-folders are kept, then you have to set AllowSubFolder of your ServerController instance to TRUE.
3) The JavaScript file must be added to the BODY part of the HTML document. That's why the third parameter of TIWContextHelper.AddJavaScriptFile is TRUE.

Deploy your application

You will have to deploy qTip.js and qTip.css with your application, inside the sub-folders used in step 1. And you are done!

Testing

This is a test using the default "Features" Intraweb demo. I've set the hints to a multi-line string, for each cell in the grid:

Download simple demo

You can download a simple demo with all required files here.

Further notes

1) The project was created using Delphi 2006 + IW 10.0.23 but should run in newer versions without problems.
2) qTip, as included in the zip file, will "qTip-ize" a, label, input and td HTML tags. If you want to add other tags, modify the qTipTag var, inside qTip.js file.
3) Hint windows style can be customized editing qTip.css file.

Enjoy!

Tuesday, March 6, 2012

Intraweb TextToHTMLStringLiteral in a multi-threaded test

In a recent blog post (Speeding Intraweb up again) I wrote that I suspected that in a multi-threaded test, running in a multi-core computer, the modified version of TextToHTMLStringLiteral would perform even better. So I decided to test it in this context. Now I can tell you that I was right ;-)

The test

Today I created a new multi-threaded test. Two threads running in parallel doing the same thing: Taking a string and using TextToHTMLStringLiteral to convert it multiple times, inside a tight loop. During the first test, both threads used the original TextToHTMLStringLiteral implementation. In the second test, I used my modified version. Both threads execute the following code:
procedure TMyThread.Execute; 
begin
  FCount := 0;
  repeat
    FStr := 'ABCD XPTO %^&* ABCD XPTO %^&* ABCD XPTO %^&*';
    FStr := TextToHTMLStringLiteral(FStr);
    Inc(FCount);
  until FCount = MaxCount;
end;

CPU Utilization

The fist thread CPU utilization chart can be seen below.

Note that there are two threads but CPU utilization is only slightly above 50%.

Now the second CPU utilization chart:

Note that now CPU utilization is almost 100% (and always above 90%). Of course there are a few LOCKed instructions here and there, but it is clearly more multi-core friendly.

The results

Original TextToHTMLStringLiteral time: 24.4 seconds (using ~ 55% CPU x 2)

Modified TextToHTMLStringLiteral time: 3.6 seconds (using ~ 98% CPU x 2)
The time measures showed that the modified code performs almost 7 times better than the original, using 2 threads (It was 6 times better in a single thread app). We can expect that the more cores you have, the bigger will be time difference.

Conclusion

We can see clearly that Delphi code written with special care, specially code dealing with string writing/concatenation, can greatly improve performance and make the compiled code much more multi-core friendly.

Monday, March 5, 2012

TCodeRedirect redux

In my latests posts I was dealing a lot with Delphi classes runtime patching. Thanks to Andreas Hausladen, Chau Chee Yang (among others) most of the hard work is already done, but...
I've created a new and different version of Yang's TCodeRedirect that I consider easier to work with, because it works as a singleton. This way, you won't need to create a new TCodeRedirect instance for each patch you apply during runtime.
I'm releasing the source code of this new TCodeRedirect class because many Delphi developers may use this technique in their own applications and frameworks, so here is it!
Chau Chee Yang covered most of TCodeRedirect utilization cases in his original blog post. What I've changed is: Using Yang's code you would call TCodeRedirect this way:
TCodeRedirect.Create(@MyOldProc, @MyNewProc);
With my new TCodeRedirect, you may use it with another syntax, like this:
TCodeRedirect.GetInstance.AddPatch(@MyOldProc, @MyNewProc);
This code was tested with Delphi 6, 7, BDS 2006 and XE2 but should work with other versions as well. Enjoy!

Modifying Intraweb Patches

Today I received a message from a blog reader, pointing that my patches were not compiling under more recent versions of Delphi, specially Delphi 2010. Really, I haven't checked D2010 compatibility before releasing my code. The problem is not in IntrawebPatch.pas but inside my modified RtlVclOptimize.pas (from Andreas Hausladen). Trying to compile it under Delphi XE2, for instance, showed me that it has many incompatibilities...
So, I decided to create a new patching mechanism. It is not original, but my own version of CodeRedirect, based on Chau Chee Yang TCodeRedirect (his work is also based on Andreas Hausladen, I guess).
This code was tested with BDS 2006 and XE2, and IW 10 and XI but should work with other versions as well.

The downloads

You can download a zip file containing IntrawebPatch.pas, CodeRedirect.pas and other required files here.

Enjoy!

Friday, March 2, 2012

Speeding Intraweb up again

A few weeks ago I wrote about how to hack Intraweb classes to speed it up a little. I've found another method that could be faster, using a similar approach: This method is TextToHTMLStringLiteral, from TIWBaseForm class, declared in IWBaseForm.pas. This method is used inside the TIWForm.GenerateForm method, once for each hidden field:
class function TIWBaseForm.TextToHTMLStringLiteral(aText: string): string;
var
  f : integer;
begin
  Result := '';
  for f := 1 to Length(aText) do begin
    if (AnsiChar(aText[f]) in ['A'..'Z', 'a'..'z', '0'..'9', ',', '.', '-', '_']) then  begin
      Result := Result + aText[f];
    end else begin
      Result := Format('%s&#%d;', [Result, Ord(aText[f])]);
    end;
  end;
end;
Here we can see the same pattern we found in the previous modified methods. We will use the same kind of construction - and the same runtime patching technique - to replace this method with a faster one, during execution. The new TextToHTMLStringLiteral is shown below:
class function TIWBaseForm.TextToHTMLStringLiteral(aText: string): string;
const
  NoConversion = ['A'..'Z', 'a'..'z', '0'..'9', ',', '.', '-', '_'];
var
  Sp, Rp: PChar;
begin
  SetLength(Result, Length(AText) * 8);
  Sp := PAnsiChar(AText);
  Rp := PAnsiChar(Result);
  while Sp^ <> #0 do
  begin
    if (Sp^ in NoConversion) then
    begin
      Rp^ := Sp^;
    end else
    begin
      Inc(Rp, FormatBuf(Rp^, 7, '&#%.4d;', 7, [Ord(Sp^)]) - 1);
    end;
    Inc(Rp);
    Inc(Sp);
  end;
  SetLength(Result, Rp - PAnsiChar(Result));
end;
I will not enter in much detail about the implementation, but I think it is quite simple, and it uses the same approach used in the previous post.

The results

Again, I did a simple benchmark application so I could see if there is some gain using this new method.

Original code: 5.10 seconds

Modified code: 0.82 seconds


for 1,000,000 function calls. Quite impressive, isn't it? Of course, this benchmark used a specific string, and a different string will give different results.

This time, the new method is more than 6 times faster than the original method, in a single thread. I suspect, based on my previous post about String concatenation in Delphi, that the new code can be even faster - and more multi-core friendly - in a multi-threaded application (like IW applications). I will create this benchmark application and write a blog post about the results soon.
Checking one of my IW applications, I've found forms with more than 100 hidden fields in it. The time gain will be insignificant (less than 1 millisecond), but this is not the point. The main point when I try to make my applications faster, specially Intraweb applications, is: make it run as fast as you can AND make it more multi-core friendly.

The downloads

You can download an updated IntrawebPatch.pas unit containing this new patch (and the old ones) here.

Enjoy!

Tuesday, February 7, 2012

Investigating string concatenation performance in Delphi

I got really puzzled when I read Arnaud Bouchez blog post Delphi doesn't like multi-core CPUs (or the contrary). Delphi's generated code should be fast, shouldn't it?? Maybe it is very fast in a single threaded program or even in a multi-threaded program running on a single core computer. As pointed by Arnaud, the asm LOCK prefix is used to ensure exclusive access to the memory address. In a multi-core CPU, all cores just freeze during LOCK execution.

What about Delphi?

Quoting the original post:
String types and dynamic arrays just use the same LOCKed asm instruction everywhere, i.e. for every access which may lead into a write to the string.
So, if you are writing to a string, there are a few LOCK instructions being generated by the compiler, every single time. Even if your computer is a super-duper multi-core machine, it will behave - during these instructions - if they were a single-core CPU.

What about string concatenation in Delphi?

Well, if you do a lot of string concatenation, then a really big part of the time is being spent writing to strings, isn't it? Maybe, during the string concatenation, your cores are being LOCKed and your software is not using all the CPU power at its disposal... So, I decided to create a simple test case to show me if this is true, and how much this may affect the performance.

The test

I've created 2 different thread types, doing the same work during their execute method (a simple string concatenation inside a tight loop). The first uses a standard string type:
type
  TMyThread_String = class(TThread)
  private
    FStr: string;
  public
    procedure Execute; override;
  end;

procedure TMyThread_String.Execute;    
begin
  FCount := 0;
  repeat
    FStr := 'string 1';
    FStr := FStr + ' + string 2';
    Inc(FCount);
  until FCount = MaxCount;
end;
The second thread type uses a TStrBuilder class (my own TStringBuilder implemenation):
type
  TMyThread_StrBuilder = class(TThread)
  private
    FStr: TStrBuilder;
  public
    procedure Execute; override;
  end;

procedure TMyThread_StrBuilder.Execute;  
begin
  FCount := 0;
  repeat
    FStr.Clear;
    FStr.Append('string 1').Append(' + string 2');
    Inc(FCount);
  until FCount = MaxCount;
end;
Both threads do exactly the same. So I've put both on test: First I've created two instances of TMyThread_String and started both in parallel (using a dual-core machine). Then I repeated the test, but using TMyThread_StrBuilder.

The results

Impressive results! These are the task manager CPU charts:

Running with TMyThread_String:


Running with TMyThread_StrBuilder:


Note that the test with TStrBuilder class is burning 100% of my CPU, while the test with standard string type just can't use all the resources, even if my PC is idle.

More results

The times spent to do the string concatenation are even more impressive:

Running with TMyThread_String: 26.4 seconds
Running with TMyThread_StrBuilder: 7.3 seconds. Almost 4 times faster!

The ShortString case

Following Arnaud advice, I've tried the ShortString type also, so I've created a third thread type, using ShortString concatenation, and not a standard string type. The results are identical to those obtained using standard string type: The same CPU usage chart (ranging from 60-70%) and a slightly worse time (28.5 seconds).

Conclusion

Using my simple test, I've discovered that if you have heavy string concatenation (writing) in a multi-threaded environment (for instance: COM+ servers, Intraweb applications, multi-threaded services, etc.) maybe you should avoid standard string types, and use another approach like a TStringBuilder class.
Please note:
  • I did not use the TStringBuilder class from Delphi's own RTL, but my own TStrBuilder class. AFAIK, standard TStringBuilder has the same problem because it uses a standard string internally...
  • I have to investigate a little further to know why ShortStrings are as bad as standard strings (or even worse).
More on this later!

Tuesday, January 17, 2012

Hack Intraweb and make it faster!

I'm always looking for a way to make my applications faster. During last couple of days I've been investigating an - already fast - Intraweb application.

1) Intraweb rendering implementation

Lots of Intraweb controls use an utility function to render their HTML. This function called TextToHTML is implemented as a class function of TIWBaseHTMLControl class. This function is used because if you have an IWText component with a caption containing a special character, lets say, an ampersand (&) it must be properly encoded to be shown by the browser. An ampersand declares the beginning of an entity reference (a special character) in HTML, so to be shown in the page it must be replaced by "&amp;" (without quotes). If you have IW sources you can search for TextToHTML and see that it is used in a lot of places, most inside RenderHTML and RenderAsync methods.

2) TextToHTML implementation

Well, TextToHTML is not as fast as it could be. If you imagine that a lot of strings should be processed by TextToHTML before they get to the browser you may improve your application performance if you make that function faster, isn't it? Let's look at TextToHTML code:
class function TIWBaseHTMLControl.TextToHTML(const AText: string; 
  const AConvertEOLs: Boolean; 
  const AConvertSpaces: Boolean): string;
var
  f : integer;
  xIsCallBack: Boolean;
begin
  Result := '';
  xIsCallBack := GGetWebApplicationThreadVar.IsCallBack;
  for f := 1 to Length(AText) do begin
    case AText[f] of
      '<'  : Result := Result + '&lt;';
      '>'  : Result := Result + '&gt;';
      '"'  : Result := Result + '&quot;';
      '''' : Result := Result + '&#39;';
      '&'  : Result := Result + '&amp;';
    else

      {$ifdef UNICODE}
      if (Char(AText[f]) in [#10, #13]) then begin
      {$else}
      if (AnsiChar(AText[f]) in [#10, #13]) then begin
      {$endif}
        if AConvertEOLs then begin
          case AText[f] of
            #10 : Result := Result + '< br >';
            #13 : Result := Result + '';
          end;
        end else begin
          Result := Result + AText[f];
        end;
      end else begin
        if (AText[f] = #32) and AConvertSpaces then begin
          if xIsCallBack then begin
            Result := Result + '&nbsp;';
          end else begin
            Result := Result + ' '
          end;
        end else begin
          Result := Result + AText[f];
        end;
      end;
    end;
  end;
end;
We can see that there is a main loop interating throught all characters of the AText string, concatenating char by char to generate the result. This approach has two drawbacks: (1) Every string concatenation requires a new memory allocation and (2) it is SLOW!

3) A New TextToHTML implementation

We can implement TextToHTML using another approach, used by Delphi's own RTL in functions like HTMLEncode (unit HTTPApp.pas):
class function TIWBaseHTMLControlHack.TextToHTML(const AText: string; 
  const AConvertEOLs: Boolean; 
  const AConvertSpaces: Boolean): string;
var
  Sp, Rp: PChar;
  xIsCallBack: Boolean;
begin
  xIsCallBack := GGetWebApplicationThreadVar.IsCallBack;
  SetLength(Result, Length(AText) * 10);
  Sp := PChar(AText);
  Rp := PChar(Result);
  while Sp^ <> #0 do
  begin
    case Sp^ of
      '&':
        begin
          FormatBuf(Rp^, 5, '&amp;', 5, []);
          Inc(Rp, 4);
        end;
      '<',
        '>':
        begin
          if Sp^ = '<' then
            FormatBuf(Rp^, 4, '&lt;', 4, [])
          else
            FormatBuf(Rp^, 4, '&gt;', 4, []);
          Inc(Rp, 3);
        end;
      '"':
        begin
          FormatBuf(Rp^, 6, '&quot;', 6, []);
          Inc(Rp, 5);
        end;
      '''':
        begin
          FormatBuf(Rp^, 5, '&#39;', 5, []);
          Inc(Rp, 4);
        end;
      '\':
        begin
          FormatBuf(Rp^, 5, '&#92;', 5, []);
          Inc(Rp, 4);
        end;
      #10:
        if AConvertEOLs then
        begin
          FormatBuf(Rp^, 4, '< br >', 4, []);
          Inc(Rp, 3);
        end
        else
          Rp^ := Sp^;
      #13:
        if AConvertEOLs then
        begin
          Dec(Rp);
        end
        else
          Rp^ := Sp^;
      #32:
        if AConvertSpaces then
        begin
          if xIsCallBack then
          begin
            FormatBuf(Rp^, 10, '&amp;nbsp;', 10, []);
            Inc(Rp, 9);
          end else
          begin
            FormatBuf(Rp^, 6, '&nbsp;', 6, []);
            Inc(Rp, 5);
          end;
        end
        else
          Rp^ := Sp^;
    else
      Rp^ := Sp^
    end;
    Inc(Rp);
    Inc(Sp);
  end;
  SetLength(Result, Rp - PChar(Result));
end;
This new implemenation has two main advantages over the former: (1) There is less overhead due memory allocations, and (2) it is FASTER! :)

4) The problem

TextToHTML is a public method of TIWBaseHTMLControl but it is not virtual. Even if it were virtual, we would have to create descendant classes to override this method and it is not acceptable.

5) The solution: Hack it!

Thanks to great code (RtlVclOptimize.pas) created and made available by Andreas Hausladen we can change TIWBaseHTMLControl implementation on the fly, patching the class. I will not enter in detail about Andreas code, but the hack is done replacing, in memory, the TIWBaseHTMLControl.TextToHTML method by another method from some other class. I needed to change RtlVclOptimize.pas a little, putting the declaration of the function "CodeRedirect" in the interface section, so it could be used by my code, outside that unit.
Update: I'm not using Andreas RtlVclOptimize.pas anymore because it has a few incompatibilities with recent Delphi versions. I'm using another unit CodeRedirect.pas, included in the download file.

6) Speed comparison

The use of Andy's RtlVclOptimize unit alone can significantly improve IW applications performance. Moreover, in my tests, my TextToHTML implemenation is more than 4 times faster than the original. Original code: 14.7 seconds versus 3.4 seconds using my code (1,000,000 function calls). Another advantage is less memory allocations for strings (less memory fragmentation and less LOCK+multicore related issues - read more about it here).

7) More Hacks!

Well, not satisfied hacking TextToHTML code, I did the same thing with another TIWBaseHTMLControl method, TextToJSStringLiteral . Finally I've packed all these little things inside a unit. To speed up your IWApplication a bit just declare RtlVclOptimize (from Andreas) and my unit, IntrawebPatch.pas inside your .DPR file and you are done!

8) Finally, the downloads!

You can download my patch unit (IntrawebPatch.pas) and other required files here.

Enjoy!

Friday, January 13, 2012

Delphi 5 Update packs available again!

Sorry folks, my links to Delphi 5 Update packs were broken. Now I've fixed them :-) You may find my original post with download links here.

Tuesday, January 10, 2012

BDE and Windows 7, 32 and 64 bits

I know, I know... BDE is discontinued and stuff. But many people still need BDE running in new Windows 7 32 and 64 machines. Here is a working Setup for BDE 5.2.0.2 (the latest version released by Borland) plus all SQL Links.
Notes:
  • Installs BDE 5.2.0.2 + all SQL Links
  • During setup there is an option to remove previous BDE registry entries.If you have an old corrupted BDE installation, maybe this option can fix it (It will remove EVERYTHING below HKEY_LOCAL_MACHINE\SOFTWARE\Borland\Database Engine registry entry, and you have been warned!).
  • This setup was tested and works perfectly under Windows 2000, 2003 Server, XP, 2008 Server, Vista and Seven, 32 and 64 bits.
  • The setup correctly prompts for UAC elevation. By the way, it was created using Jordan Russell's excellent installer creator Inno Setup (also created with Delphi)!

Download BDE 5.2.0.2 Setup in English:
http://www.alex7691.com/download/Setup_BDE52.zip

Download BDE 5.2.0.2 Setup in brazilian portuguese:
http://www.alex7691.com/download/Setup_BDE52_PTB.zip

Note: During last months the number of downloads increased a LOT. Some users were receiving messages like "bandwidth limit exceeded". Now I have upgraded my host service plan, so you can easily download it!Enjoy! :-)

Some user gave a false alert about malware/trojan/virus/whatever inside the files. According to VirusTotal (http://www.virustotal.com) this is completely untrue.
Latest scan report of this file: https://www.virustotal.com/en/file/7d37616710f8d5da8d2f859560814fd89b22aef7b6fb47902161bc9f7289145a/analysis/

In short: 0 detections!

Please feel free to re-submit it ;-)

PS: Rene Nussbaum, from Germany wrote me asking about the setup license model/type. Well... This setup is 100% free including its usage in commercial software. You can include this setup in your deployment package, and you can also redistribute it.

Enjoy! :-)