Sunday, June 21, 2009

Unattended Windows XP installation

Unattended Windows XP installation will speed up the installation process without answering dialogs prompted by Windows XP installation service.  It save times waiting for the dialog prompt.  After the installation finish, you have a ready Windows XP instance.  You may walk away when installing.  All dialog prompts’ answer are provided by a unattended answer file.

The answer file in .ini text file.  However, there is a tool to help you generate the answer file:

  1. Extract setupmgr.exe from Windows XP installation CD in folder \support\tools\deploy.cab
  2. Run setupmgr.exe and follow the screen instruction to generate the answer file.

Once the answer file is generated, you may install Windows XP via command line installation:

d:\> i386\winnt32.exe /syspart:c: /makelocalsource /unattend:answer.txt

Saturday, June 20, 2009

Recover damage Windows Vista or Windows 7 installation

In most case, normal Windows users seldom face unbootable windows installation as most user usually install windows in a single partition utilize all available hard drive spaces.

For user who play with multi boot loader like GRUB or partition the hard drive into few and install more than one Windows in each partition, they may face the problem if mistake make during installation or configuration.

Some common errors are:

  1. When booting a windows instance, system prompt:

    BOOTMGR is missing
    Press Ctrl+Alt+Del to restart
  2. When booting a windows instance, system prompt:

    File: \Boot\BCD
    Status: 0xc0000034
    Info: The Windows Boot Configuration Data file is missing required information

  3. When booting a windows, the window start-up screen shown but never complete the booting

The cause of the problems might be:

  1. File BOOTMGR does not exist in root drive (e.g: C:\)
  2. \Boot\BCD is corrupted or missing due to improper configuration during multi boot setup.
  3. MBR has corrupted or invalid.

The most common solution is using Windows Recovery Environment to solve the problem.

Solution 1: Using Windows Recovery Environment with Windows DVD

  1. Put the Windows installation disc in the disc drive, and then start the computer.
  2. Press a key when you are prompted: “Press any key to boot from CD or DVD…”.  Windows installation service will start booting
  3. Select a language, a time, a currency, and a keyboard or another input method, and then click Next.
  4. Click Repair your computer.

    t1
  5. Click the operating system that you want to repair, and then click Next.
  6. In the System Recovery Options dialog box, click Command Prompt

    t2 
  7. In command prompt, type:

    X:\Sources> bootrec /fixmbr
    X:\Sources> bootrec /fixboot
    X:\Sources> bootrec /rebuildbcd

  8. Remove the Windows Installation disc and Restart the computer.
  9. Cross your finger and hope your Windows instance is back to normal.

Solution 2: Using Windows Recovery Environment in PXE environment

  1. Boot your computer into PXE service
  2. use “NET USE” to mount correct version of Windows installation share same as your damage Windows instance:

    e.g: net use k: \\server\windows.vista.share\
  3. Prepare to re-start your computer as Windows installation service
    1. copy k:\bootmgr c:\
    2. xcopy /s /e k:\boot c:\
    3. mkdir c:\sources
    4. copy k:\sources\boot.wim c:\sources
    5. k:\boot\bootsect.exe /nt60 c:
  4. Restart your computer
  5. Refer to step 3 of Solution 1 to continue the Windows Recovery Environment.
  6. If you able to recover the windows instance, you may do the following clean up task:
    1. Delete sources folder
    2. Start CMD in administrator mode and type

      attrib +s +h +r c:\bootmgr
      attrib +s +h –a c:\boot

Reference:

  1. Microsoft knowledgebase: Error message when you start Windows Vista: "The Windows Boot Configuration Data file is missing required information"; URL: http://support.microsoft.com/kb/927391
  2. How to Install Windows 7 or Windows Vista on Physical Machine Without DVD Media; URL: http://keznews.com/5185_How_to_Install_Windows_7_or_Windows_Vista_on_Physical_Machine_Without_DVD_Media

Friday, June 12, 2009

Synergy: Control machines with one set Keyboard and Mouse

Note: Please refer to Input Director for better solution.

If you have more than one machine (PC or notebook) on your desktop, you may use new set of keyboard and mouse for each machine on your desktop.  The Synergy application is here to help you reduce the devices on your desk by using just one set of keyboard and mouse to control all machines.  Synergy works on Windows XP, Windows Vista, Windows 7 or even Linux and Mac.

Example

This example show how to install and configure Synergy for 3 machines name as:

  1. Streamyx (OS: Windows XP)
  2. Optimus (OS: Windows Vista)
  3. Windows7 (OS: Windows 7)

Each machine attached with a LCD monitor individually.  The sequence of machines from Left to Right on desk are Streamyx, Optimus and Windows7.
First, install synergy on these 3 machines.  My keyboard and mouse attached to machine Optimus.  Thus, Optimus will be server.

Server Configuration: Optimus

Start synergy and click "Share this computer's keyboard and mouse (server)".

1

Click “Advance” button to set server’s parameters:

3

Press “Configure” button to configure:

  1. Add three machines’ screen name to “Screens” list box.
  2. A link is a pair of distinct adjacent screen.  Each adjacent pair must configure as 2 links.  For example, “Streamyx is left of Optimus” and “Optimus is right of Streamyx”

2

Click “Test” or “Start” button start synergy service.

Client Configuration: Streamyx and Windows7

Synergy configuration on client machines are simple:

  1. Click “Use another computer’s shared keyboard and mouse (client)”
  2. Enter the server host name (optimus)
  3. Click “Test” or “Start” button to establish connection

4

You may now enjoy using one set of mouse and keyboard to control three machines.

References:

  1. http://synergy2.sourceforge.net/

Friday, June 05, 2009

Firebird: Deal with Unavailable Database

“unavailble database” error prompt connecting to Firebird service is annoying and difficult to trace.  Sometime it work and sometime it doesn’t.  We don’t know when it work and when it doesn’t work.

“unavailable database” happen in TCP/IP connection

Use TCP/IP connection string to connect to a Firebird database is easy to trace.  The connection string looks like

  • 192.168.5.1:/data/db/test.fdb
  • localhost:/data/db/test.fdb

Please note that localhost is not using local protocol connection but it is a TCP/IP connection using loopback address.

If you encounter “unavailable database” error for TCP/IP connection, please make sure:

  1. You network connection on both end are working and the TCP/IP configuration is correct.  Try to run “PING” to the Firebird server will confirm the connection is working.
  2. Make sure Firebird Service is running
  3. Make sure Firewall doesn’t block Firebird listening port.  Default port number is 3050
  4. Make sure the connection string point to valid Firebird database file

Once you got above verified, the “unavailable database” error prompt should gone.

“unavailable database” happen in Local Protocol

The data transmission throughput for local protocol is few times better than TCP/IP connection.  However, it is easy to encounter “unavailable database” error while using Local Protocol when Firebird is running as service.  The connection string is simple:

  • c:\db\test1.fdb
  • c:\db\test2.fdb

The connection string is the database file path itself without any prefix.

Using Local Protocol with Firebird 1.5

  Run as Application Run as Service
Windows XP Yes Yes. Only work for first logon user
Windows Vista Yes No

Using Local Protocol with Firebird 2.X

  Run as Application Run as Service
Windows XP Yes Yes.  Work for any number of logon users
Windows Vista Yes No

The above tables explains why local protocol sometime work and sometime doesn’t work.

Friday, May 22, 2009

Apache HTTP Server: Configuration

Apache HTTP Server is a famous open source web server.  The default configuration file is located at “/etc/httpd/conf/httpd.conf”

Server Side Includes (SSI)

SSI (Server Side Includes) are directives that are placed in HTML pages, and evaluated on the server while the pages are being served. They let you add dynamically generated content to an existing HTML page, without having to serve the entire page via a CGI program, or other dynamic technology.

To enable SSI in Apache HTTP, add the following in conf file:

AddType text/html .shtml
AddHandler server-parsed .shtml

<Directory /data/www>
    Options +Includes
</Directory>

If the index file is a SSI file, you should add this line too:

DirectoryIndex index.html index.html.var index.shtml

Virtual Host Configuration

The term Virtual Host refers to the practice of maintaining more than one server on one machine, as differentiated by their apparent hostname. For example, it is often desirable for companies sharing a web server to have their own domains, with web servers accessible as www.company1.com and www.company2.com, without requiring the user to know any extra path information.

In order not to mess up the httpd.conf file, we may store the virtual host configuration in a separate file. (eg: /etc/httpd/conf/httpd-vhosts.conf)

/etc/httpd/conf/httpd-vhosts.conf

NameVirtualHost *:80

<Directory /data/www>
    Options +Includes
</Directory>

<VirtualHost *:80>
    DocumentRoot /data/www
    ServerName
www.company1.com
    ScriptAlias /cgi-bin/ /data/www/cgi-bin/ 
</VirtualHost>

We then use Include directive in httpd.conf by adding a line near the VirtualHost section:

Include conf/httpd-vhosts.conf

Restart the Apache HTTP Server

Remember to restart the Apache HTTP Server for changes you did:

# service httpd restart

Wednesday, April 15, 2009

Linux: Install a virtual PDF printer on CUPS

I have been always looking for a virtual PDF printer solution.  The only solution I know is windows PDF printer driver on Internet that need to pay.  I never know CUPS can do the job and it is free.

Service Installation

# yum install cups-pdf

After finish install, you may check if the PDF printer is installed and configured from CUPS web interface (e.g: http://cups-printer:631/)

You may configure where to keep PDF output in /etc/cups/cups-pdf.conf.  Look for “Out ${HOME}/Desktop” string in the file.

Windows Client Installation

  1. Make sure you have or know where is the printer driver files
  2. Click Start | Control Panel | Printers and Faxes
  3. Click Add a Printer
  4. Choose "A network printer, or a printer attached to another computer"
  5. Choose "Connect to a printer on the Internet or on a home or office network".  Type the printer URL (e.g: http://cups-printer:631/printers/cups-printer).
  6. Click Next and select appropriate PostScript (PS) printer driver (e.g: HP Color LaserJet 9500 PS)

Enjoy a new virtual PDF printer on your network.  You may start print document from windows workstation to this printer and collect the PDF output files from your Linux home account.

Reference:

  1. CUPS – PDF Printer

Wednesday, April 08, 2009

Delphi: Using TWebBrowser to display content from stream rather than URL

TWebBrowser is a wrapper for Internet Explorer COM interface.  The most common way to use TWebBrowser is using Navigate method that accept a URL string.  However, this is not convenient for user who already has a rendered HTML string or stream.  One workaround solution for this is save the string or stream to a temporary file and use supply a local file URL to TWebBrowser.Navigate.

Here is a solution for supplying a HTML stream to TWebBrowser:

uses ActiveX;

const IID_IPersistStreamInit : TGUID = '{7FD52380-4E07-101B-AE2D-08002B2EC713}';

procedure TForm19.Button1Click(Sender: TObject);
var S: TStream;
    o: IPersistStreamInit;
begin
  S := TStringStream.Create('<html><body>this is a test</body></html>');
  try
    WebBrowser1.Navigate('about:blank');
    while WebBrowser1.ReadyState <> Shdocvw.READYSTATE_COMPLETE do
      Application.ProcessMessages;

    if Supports(WebBrowser1.Document, IID_IPersistStreamInit, o) then
      o.Load(TStreamAdapter.Create(S));
  finally
    S.Free;
  end;
end;

Reference:

  1. Component to display HTML read from a stream rather than fetched from a URL. Author: Antonio Estevez. URL: https://forums.codegear.com/thread.jspa?threadID=15004&tstart=0

Friday, March 27, 2009

BITs operation in SQL

Introduction

The BIT we talk here is the smallest unit in binary: 0 and 1.

Using bit for attributes

If we want to assign a set of attributes to an item, a common practice is using mutually exclusive bit value to indicate each attribute and using “SUM” or “OR” operator to group assigned attributes as a single integer value.  This solution is neat and fast to be implement in programming.

For example, a file in a file system may have attributes of

  • READ
  • WRITE
  • EXECUTE

We may assign READ, READ and WRITE , READ and EXECUTE, or any combination of attributes to the file.  We may use only a single 32 bits (or 64 bits) integer to indicate these attributes.  This design allow us to expand the attribute set in future.  This is a more practical approach compare to using one field for each attribute design.

In the above example, we may define

  • 1 (0001) – READ
  • 2 (0010) – WRITE
  • 4 (0100) – EXECUTE
  • 8 (1000) - HIDDEN

Note that each attribute should occupy a bit position and it shouldn’t overlap with other attributes else we will not able to identify their actual attributes in later stage.

Thus,

  • 5 (0101) indicate READ + EXECUTE
  • 2 (0010) indicate WRITE
  • 7 (0111) indicate READ + WRITE + EXECUTE
  • 6 (0110) indicate EXECUTE + WRITE
  • 0 (0000) indicate No attributes defined

Using bits for states

In some situation we need a range of bits to indicate some kind of states.  The one bit one attribute design doesn’t fit well here.

For example, a document may have one of the following states but never possess more than one state at a time:

  • Draft
  • Waiting for approval
  • Approved
  • Rejected
  • Canceled

There are 5 states above, we may use 4 bits (3 bits is sufficient for above case) to present the state as

  • 1 (0001) – Draft
  • 2 (0010) – Waiting for approval
  • 3 (0011) – Approved
  • 4 (0100) – Rejected
  • 5 (0101) - Canceled

Programming attributes in programming language

We may use only a single variable of type INTEGER to indicate an attribute sets.  This design allow the attribute sets to expand in future without changing much in old codes.

Define attributes and states

Assume bit 0-3 (4 bits) is reserved for states and bit 4-7 (4 bits) is reserved for attributes.

const
  at_READ    = $10;
  at_WRITE   = $20;
  at_EXECUTE = $40;
  at_HIDDEN  = $80;

  st_Draft    = 1;
  st_Waiting  = 2;
  st_Approved = 3;
  st_Rejected = 4;
  st_Canceled = 5;

Set attributes

Use bit operator OR to set attribute:

  • B := at_READ OR at_WRITE;  // assign READ and WRITE
  • B := B OR at_READ;  // assign READ to B regardless of B READ attribute or not

Unset attribute

Use bit operator AND NOT to unset attribute:

  • B := B AND (NOT at_READ) // unset READ of B
  • B := B AND (NOT at_READ) AND (NOT at_WRITE) // unset READ and WRITE of B

Test an attribute is set

Use AND operator to check if READ attribute is set:

  • (B and at_READ) = at_READ

Test state

Bits 0-3 is reserve for states, to test if the item is canceled:

  • B and $0F = st_Canceled

Set State

Set state to st_Rejected (4):

  • B := B and $F0 + st_Rejected

Programming attributes in SQL

It wasn’t easy to perform the above operations in SQL unless the SQL service support bit operator or function.  The following solution is using normal mathematical operation to achieve the same bit operation as programming language without using special function.

Division operator in SQL

The division operator ( / ) in SQL has different behavior for integer and floating point division.  For example:

  • Integer division:
    • 1 / 2 is 0
    • 2 / 2 is 1
  • Floating point division:
    • 1 / 2.0 is 0.5
    • 2 / 2.0 is 1.0

An integer value divided by 2 is similar as performing a right shift operation for binary value.  For example,

  • 10 / 2 = 5
  • 9 / 2 = 4

Perform a right shift in binary for above numbers:

  • 1010 shr 1 = 101 (5 in decimal)
  • 1001 shr 1 = 100 (4 in decimal)

Likewise,

  • perform 2 right shifts is dividing the value by 4
  • perform 3 right shifts is dividing the value by 8
  • perform n right shifts is dividing the value by 2^n

Multiplication operator in SQL

The multiplication operator ( * ) for integer works similarly as performing a left shift operation for binary value.  For example,

  • 5 * 2 = 10
  • 7 * 2 = 14

Perform a left shift in binary for above numbers:

  • 101 shl 1 = 1010 (10 in decimal)
  • 111 shl 1 = 1110 (14 in decimal)

Likewise,

  • perform 2 left shifts is multiplying the value by 4
  • perform 3 left shifts is multiplying the value by 8
  • perform n left shifts is multiplying the value by 2^n

Test the value of least significant bit

The least significant bit of a number is the right most bit in binary presentation of the number.  For example, the least significant bit of 10 (1010 in binary) is 0 and 9 (1001 in binary) is 1.

We may use both integer or floating point division to check the least significant bit of a number:

i = (n / 2) – (n / 2.0)

i = 0 indicate least significant bit is off
i <> 0 indicate least significant bit is on

For example,

  • 10 / 2 – 10 / 2.0 = 5 – 5.0 = 0 (least significant bit is 0)
  • 9 / 2 – 9 / 2.0 = 4 – 4.5 = –0.5 (least significant bit is 1)

Test an attribute is set

To check if an at_EXECUTE ($40) attribute is set in SQL, we may use

WHERE ((Attribute / 64 / 2) - (Attribute / 64 / 2.0) <> 0

Set attribute

The following SQL set at_EXECUTE ($40) for rows that don’t have at_EXECUTE set:

UPDATE Table
   SET Attribute = Attribute + 64
WHERE ((Attribute / 64 / 2) - (Attribute / 64 / 2.0) = 0

Unset attribute

The following SQL unset at_EXECUTE ($40) for rows that have at_EXECUTE set:

UPDATE Table
   SET Attribute = Attribute – 64
WHERE ((Attribute / 64 / 2) - (Attribute / 64 / 2.0) <> 0

Test States

The following SQL retrieve all rows that has state of st_Canceled (5):

SELECT *
  FROM Table
WHERE Attribute – (Attribute / 16) * 16 = 5

Set States

The following SQL set all rows to state of st_Canceled (5):

UPDATE Table
   SET Attribute = (Attribute / 16) * 16 + 5

Wednesday, March 11, 2009

Using VNC to connect to X-Windows display

Introduction

Before VNC, we may use Xming or XWin32 to connect to a proper configured X-Windows Server.  This requires few X-Windows Server setup to get it done.

Since Fedora 7, there is a new service to connect to X Windows Server much easily.  This service is VNCServer.

Installation

Using yum to query and install for vnc-server rpm packages:

# yum list vnc-server
Installed Packages
vnc-server.i386                          4.1.2-23.fc8           installed

# yum install vnc-server.i386

VNC Server Configuration

  1. Define VNC server instances in /etc/sysconfig/vncservers:

    VNCSERVERS="1:myuser"
    VNCSERVERSARGS[1]="-geometry 1024x768 -nohttpd"

  2. Each VNC server instance listens on port 5900 plus the display number on which the server runs. In our case, myuser’s vnc server would listen on port 5901 (5900 + 1).
  3. You may setup multiple vnc instance as well:

    VNCSERVERS="1:myuser 2:user01 3:user02"
    VNCSERVERARGS[1]="-geometry 1024x768 -depth 16"
    VNCSERVERARGS[2]="-geometry 800x600 -depth 8"
    VNCSERVERARGS[3]="-geometry 1024x768 -depth 16"

  4. In this case, the vnc server would listen on port 5901, 5902 and 5903 respectively.
  5. In myuser's account, setup vnc password for the user:

    # vncpasswd
  6. Start VNC Server:

    # service vncserver restart
    # chkconfig vncserver on

  7. Define a new Firewall rule (/etc/sysconfig/iptables):

    -A RH-Firewall-1-INPUT -m state --state NEW -m tcp -p tcp --dport 5901 -j ACCEPT

More User Configuration

Once vncserver has startup for the first time, it will create a default xstartup config file.  You may define your own startup script:

# vi /home/myuser/.vnc/xstartup

#!/bin/sh

# Uncomment the following two lines for normal desktop:
unset SESSION_MANAGER
exec /etc/X11/xinit/xinitrc

[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup
[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
# xsetroot -solid grey
# vncconfig -iconic &
# xterm -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" &
# twm &

Connect to VNC Server

You may use any vesion of vncviewer to connect to the VNC Server.  For example, download TightVNC viewer for Windows.  The connection string is something like:

myhost:5901

Enter password and you may connect to your X windows service remotely.

Reference:

  1. Set up the VNC Server in Fedora

Friday, February 13, 2009

Accelerate the Performance of Windows Vista

Windows Vista consume much more resources than previous version of Windows.  You will get better performance and experience if you add more RAM to your PC.  Here are some tips you may follow to enhance user experience in Windows Vista if you have limited resources.

Switch to Window Classic Theme

Windows Vista use Windows Vista or AERO Theme by default.  You may switch to Windows Classic Theme for much smooth UI experience with the lose of nice glass effect of Windows Vista Theme.

Turn off Zip Folders

Windows Vista will treat zip file as a zip folder show in Windows Explorer's folder panel.  The ZIP folder slow down the performance of Windows Explorer contain ZIP files.  It may also create temporary files in your %TEMP% folder.  You may disable it if you have other File Compression Utility like WinZIP/WinRAR/7-Zip.

Merge the following Registry Script into Window Registry in privilege account and restart your PC:

Windows Registry Editor Version 5.00

[-HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}]

[-HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}]

To turn on Zip Folder back, run the following script in privilege account and restart your PC:

Windows Registry Editor Version 5.00

[HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}]
@="CompressedFolder"

[HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}\DefaultIcon]
@=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
  00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,7a,00,69,00,\
  70,00,66,00,6c,00,64,00,72,00,2e,00,64,00,6c,00,6c,00,00,00

[HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}\Implemented Categories]

[HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}\Implemented Categories\{00021490-0000-0000-C000-000000000046}]
@=hex(0):

[HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}\InProcServer32]
@=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
  00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,7a,00,69,00,\
  70,00,66,00,6c,00,64,00,72,00,2e,00,64,00,6c,00,6c,00,00,00
"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}\ProgID]
@="CompressedFolder"

[HKEY_CLASSES_ROOT\CLSID\{E88DCCE0-B7B3-11d1-A9F0-00AA0060FA31}\ShellFolder]
"Attributes"=dword:200001a0
"UseDropHandler"=""

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}]
@="Cabinet File"

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\CLSID]
@="{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}"

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\DefaultIcon]
@="cabview.dll,0"

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Implemented Categories]

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Implemented Categories\{00021490-0000-0000-C000-000000000046}]

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\InProcServer32]
@="cabview.dll"
"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Shell]
@="none"

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Shell\find]
"SuppressionPolicy"=dword:00000080

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Shell\find\command]
@=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
  00,5c,00,45,00,78,00,70,00,6c,00,6f,00,72,00,65,00,72,00,2e,00,65,00,78,00,\
  65,00,00,00

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Shell\find\ddeexec]
@="[FindFolder(\"%l\", %I)]"

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Shell\find\ddeexec\application]
@="Folders"

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Shell\find\ddeexec\topic]
@="AppProperties"

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Shell\Open]

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\Shell\Open\Command]
@=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
  00,5c,00,45,00,78,00,70,00,6c,00,6f,00,72,00,65,00,72,00,2e,00,65,00,78,00,\
  65,00,20,00,2f,00,69,00,64,00,6c,00,69,00,73,00,74,00,2c,00,25,00,49,00,2c,\
  00,25,00,4c,00,00,00
"DelegateExecute"="{11dbb47c-a525-400b-9e80-a54615a090c0}"

[HKEY_CLASSES_ROOT\CLSID\{0CD7A5C0-9F37-11CE-AE65-08002B2E1262}\ShellFolder]
"Attributes"=dword:680001a0

Clear your Temporary Files in %TEMP% folders

Temporary files take up your hard drive spaces.  Check and clean it regularly:

  1. Click Start | Run...
  2. Type: %TEMP%, press OK button
  3. A Windows Explorer will show the temporary folders.  Select and delete files and folders that you think should be removed.

Reference:

  1. How do I turn off Compressed Folders (built-in support for ZIP files) in Windows Vista and XP?

Saturday, February 07, 2009

Flash your motherboard BIOS to the updated version

MotherBoard BIOS get updated from time to time from vendor for the fixed.  Some unusual and performance issues happening to your OS may be BIOS's bug.  It is always encourage to update your BIOS to the latest available version.

Performing BIOS update need extra care.  Here are some rules to follow:

  1. During the flashing period, your PC must always stay on with power.  Any power failure in between may make your PC totally un-bootable
  2. Always check current BIOS version with the latest version to make sure you download and updated with correct one.

In old day, the BIOS update file size always fit to a 1.44MB floppy disk.  We just create a floppy image from the BIOS flash disk image and boot from the floppy disk will complete the update task easily.  However, PC do not equipped with 1.44MB floppy drive nowadays.

BIOS flash file size is getting bigger now can't fit into a 1.44MB floppy disk.  Vendor may recommend user create a bootable USB flash drive and copy the files into it and boot your PC from bootable USB flash drive.  Other vendors may have a utility to install a software into your Windows Operating System to perform update.

For me, I like the bare bone method that just run a simple flash .EXE utility on DOS.  However booting from DOS is not easy now.  One easy option is:

  1. Create a PXE environment in your network
  2. Setup a DOS image that support network in PXE boot option.
  3. Copy flash utilities and BIOS file in network share folder
  4. Boot your PC from PXE environment and select the DOS image
  5. Run the flash utility

This purely network BIOS updating solution should works usually.

Thursday, February 05, 2009

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:

  1. If my child form overlap the main form, my main form always stay behind the child form
  2. Minimize main form will minimize all child forms and desktop button shrink to only one.
  3. 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:

  1. Newsgroup: borland.public.delphi.objectpascal. Topic: SDI ShowModal howto? Author: Peter Below (TeamB)
  2. WebSite: About.Com: Delphi Programming. Topic: A more Powerful Delphi Form, TaskBar Button for every Delphi Form. Author: Zarko Gajic
  3. Newsgroup: borland.public.delphi.nativeapi.win32. Topic: How do I add a form to the taskbar?
  4. http://delphi.newswhat.com/geoxml/forumhistorythread?groupname=borland.public.delphi.language.delphi.general&messageid=40a1ffb8@newsgroups.borland.com
  5. Win32 user interface work is inherently single-threaded
  6. Newsgroup: Embarcadero Discussion Forums.Delphi.Language.Win32. Topic: Show a modal form freeze whole SDI application

Wednesday, January 28, 2009

Delphi: Scale your form to work in multi-resolution environment

14" and 15" monitor has became a standard desktop environment for years.  The CRT monitor became obsolete after LCD become a standard equipment now.  End user requires bigger and bigger monitor to show more from desktop application.  15" display no longer fulfill today's desktop application.  17", 19", 20", 22" and 24" and even bigger display show it's value.

The screen resolution ranges from 800x600, 1024x768, 1280x1024, 1680x1050, 1920x1080 and etc are great for users who want to show more and more but it come with a price.  You have more space on screen, but the font image may become smaller.  You get a bigger display but the resolution also increase, your application that was designed for lower resolution occupy only a small rectangle area on your nice big display.

While some application looks nicer on higher resolution display but some not.  Such small font doesn't make your priceless eyes comfort especially for aged end users.  They may think bigger display will show bigger display image for desktop application won't make their eyes pain.  This perception is not right as most desktop application design against resolution.  However, there are end users who has brilliant eye power that like to have more working space to show more on bigger display.  It would be great if we can design our application that able to adjust the form scaling for different requirements.

In Delphi, we can use Anchor and Alignment of GUI controls to make our form-based application work nicer with multi-resolution environment but the font size usually stay same for various resolutions.  This type of design doesn't change font size but the work space get increase for higher resolution display.  End users must have good eye power using this application in higher resolution display.  Although we may change font size but it is hard to align the controls properly for multi-resolution environment.

Delphi introduces other mechanism like ScaleBy, ScaleControls, PixelsPerInch for us make the form's controls scale properly in multi-resolution environment.  However, it has a drawback. It doesn't work well with anchored controls.

Scaling is an acceptable solution to solve all the above problems.  I have learned a straight and easy way to perform the scaling that even work well with anchored controls.  Changing Screen.PixelsPerInch will make all later instantiated forms display better scaled view.  The VCL source reveals that:

  1. TCustomForm.InitializeNewForm set
    FPixelsPerInch := Screen.PixelsPerInch;
  2. TScreen.PixelsPerInch is a readonly property and TScreen.FPixelsPerInch is a private field

If we can change Screen.PixelsPerInch at runtime then all subsequent form instances will get the new Screen.PixelsPerInch value.

The following code shows a possibility:

type
  TScreenHelper = class helper for TScreen
  public
    procedure SetPixelsPerInch(Value: integer);
  end;

  TScreenEx = class(TScreen)
  published
    property PixelsPerInch;
  end;

procedure TScreenHelper.SetPixelsPerInch(Value: integer);
begin
  PInteger(Integer(Self) + (Integer(GetPropInfo(TScreenEx, 'PixelsPerInch').GetProc) and $00FFFFFF))^ := Value;
end;

begin
  Screen.SetPixelsPerInch(120);

  // Create your form here ...
end.

Reference:

  1. Multi-Resolution Delphi Applications. By Zarko Gajic, About.com
  2. Hack #1: Write access to a read-only property. By Hallvard Vassbotn; Oslo, Norway

Sunday, January 18, 2009

How to create a debug version of VCL Package

Delphi ship the following VCL libraries:

  1. DCU: Both debug and non-debug version
  2. DCP: non-debug version only
  3. BPL: non-debug version only

The only reason we need to build our application with debug version of libraries is we want to debug application in the powerful Delphi IDE.  We can trace from the "Call Stack Window" to know our current program location was derived from which lines or code in which unit:

2

These libraries are enough if we only build standalone application (non-package and only a single .EXE file).  However, if we compile and deploy our application with package, we will have problem perform the debugging activities as how the standalone .EXE does.  Delphi didn't ship debug version of DCP/BPL files.  We are unable to compile our packaged application that will show the debug information we want.  This cause the debugging operation particularly hard and difficult.

1

To overcome this problem, we can create our own debug version of VCL libraries manually.  We may use the following code to extract the files in VCL packages (.BPL files) that can be found in "%windir\system32":

procedure ShowInfoProc(const Name: String; NameType: TNameType; Flags: Byte;
  Param: Pointer);
begin
  if (Name = 'System') or (Name = 'SysInit') then Exit;
  if Flags and ufMainUnit <> 0 then Exit;

  case NameType of
    ntRequiresPackage: 
      ShowMessage(Name);
    ntContainsUnit:
      ShowMessage(Name);
    ntDcpBpiName:
      ShowMessage(Name);
  end;
end;

procedure TPackageInfo2.ExtractInfo(const aPackage: string);
var NeedFreeLibrary: boolean;
    M: HModule;
    Flags: Integer;
begin
  // Extract description
  FDescription := GetPackageDescription(PChar(aPackage));

  // Extract DcpBpiName, requires and contains clause
  NeedFreeLibrary := True;
  M := LoadResourceModule(PChar(aPackage));
  if M = 0 then begin
    M := GetModuleHandle(PChar(aPackage));
    if M = 0 then
      M := LoadLibraryEx(PChar(aPackage), 0, LOAD_LIBRARY_AS_DATAFILE + DONT_RESOLVE_DLL_REFERENCES)
    else
      NeedFreeLibrary := False;

    if M = 0 then raise Exception.Create('Package Error !');
  end;

  GetPackageInfo(M, Self, Flags, ShowInfoProc);

  if NeedFreeLibrary then FreeLibrary(M);
end;

Once you know the contains clause and requires clause for the package file, you may assemble your package file as follow:

  1. Start Delphi IDE
  2. Create a new package
  3. Edit the contains and requires clauses to add the files your found in the package using above code.
  4. Click Project |Option... to bring out project option window
  5. In "Description" page:
    1. make the package as "Runtime Only"
    2. Set Build Control to "Explicit rebuild"
    3. Enter the Lib Suffix value (e.g.: 100 for Delphi 2007 package, 120 for Delphi 2009 package)
  6. In Compiler page, Set "Use Debug DCU" and turn off "Optimization"
  7. In Directories/Conditional page, set the Output, Unit output and DCP output directory of your choice
  8. Save the package file as the name of the VCL package.  For example, VCL100.bpl will save as VCL.dpk
  9. Build the package
  10. It will produce 2 important files: Debug version of DCP and BPL files.

The above steps finish the creation of debug version of DCP and BPL files.

The next steps is how to make use of both the DCP/BPL files:

  1. Open your application that you build with VCL packages
  2. Click Project | Options...
  3. In Directories/Conditional page, add the path of debug version of DCP files to "Search Path"
  4. Compile all packages of your application
  5. Copy the debug version of BPL files to the output folder of your application
  6. Try to run your application in debug mode and you may start enjoying the powerful Delphi IDE debugging for your package built application.

Reference:

  1. Creating a debug version of the vcl package (CodeGear TeamB Blogs: Dave Nottage)

Installing Microsoft SQL Server Express 2008

It requires some efforts to get Microsoft SQL Server Express 2008 to get running on a Vista SP1 machine with Microsoft SQL Server Express 2005 installed.

First, my machine wasn't always synchronize with latest windows updates and hot fixes.  I have Vista SP1 installed.  My machine has SQL Server Express 2005 installed.  I still need it in for my daily development.  I need both 2005 and 2008 services running at same time.

After download the SQL Server Express 2008, it will perform a pre-requisite validation to check if you have extra tools installed.  Here are a list of software that I have to install first before I can pass the SQL Server Express 2008 pre-requisite validation:

  1. .NET Framework 3.5 SP1
  2. Windows PowerShell 1.0 Installation Package for Windows Vista (KB928439)
  3. Windows Installer 4.5 Redistributable
  4. Uninstall SQL Server Management Studio for SQL Server 2005

After all the above tools installed, the SQL Server 2008 Express installation process is smooth.  However, there is one more step to get it right before both 2005 and 2008 services can co-exist in same machine.

By default, the default service instance name for both SQL Server 2005 and 2008  is same: "SQLEXPRESS".  You have to give distinct name for both service name.  For example, I name SQL Server 2005 and 2008 instance name as "SQLEXPRESS2005" AND "SQLEXPRESS2008" respectively.  After that, you may start both services in same machine at the same time.

SQL Server communicate on few network protocols. We may use "SQL Server Configuration Manager" to configure it.  Among them are:

  1. Shared Memory
  2. Named Pipes
  3. TCP/IP
  4. VIA

The TCP/IP protocol is using dynamic port by default.  We may continue using that provided we enable "SQL Server Browser".  We may use a connection string like "<ip-address>\<instance-name>" to make a connection to a SQL Server service bind to dynamic port.  For example: "192.168.0.1\SQLEXPRESS2008".

One excellent feature with SQL Server 2008 I learned is the PowerShell.  It allows us to perform task in batch on the database entities.  For example, to delete all tables at once, we may issue "rm *".  A very good entry point for Unix/Linux users.

There is also SQL Server Compact that target on standalone or embedded desktop application.  It seems that SQL Server is getting more scalable for different application.

Thursday, January 08, 2009

Firebird Installation

Firebird support running as service or application mode.  It always confuse me and I don't know what is the best working mode in my daily development and deployment environment.

Running Firebird as application

Running Firebird as application provide developer greater control during development process.  It allow user to start and stop the application anytime.  It also allow user to run various version of Firebird easily without performing any installation in privilege account.

The following example show deployment strategy of running few instances of Firebird as application.  First, obtain the following zip archive files from Firebird installation repositories:

  1. Firebird-1.5.6.5026-0_win32.zip
  2. Firebird-2.1.3.18185-0_Win32.zip / Firebird-2.1.3.18185-0_x64.zip
  3. Firebird-2.5.0.26074-0_Win32.zip / Firebird-2.5.0.26074-0_x64.zip

Extract the zip archive files in your account profile: e.g: %USERPROFILE% (c:\Users\my-account)

Open Windows Registry Editor, and locate the key:[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run]

Enter the following entries:

"FB156"="C:\Users\my-account\Firebird-1.5.6.5026-0_win32\bin\fbserver -a"
"FB213"="C:\Users\my-account\Firebird-2.1.3.18185-0_x64\bin\fbserver -a -p 3213"
"FB250"="C:\Users\my-account\Firebird-2.5.0.26074-0_x64\bin\fbserver -a -p 3250"

Parameter –a indicates the application should run as application.  Parameter –p specify the port number the application should listen to.  Please note that port number is should be used by application exclusively.

You should logoff from your account and logon again to activate the changes.

The above example deploy 3 instance of Firebird:

  1. Firebird-1.5.6.5026-0_win32 listen on default port 3050
  2. Firebird-2.1.3.18185-0_x64 listen on port 3213
  3. Firebird-2.5.0.26074-0_x64 listen on port 3250

Running Firebird as service

If you run Firebird 1.5 or Firebird 2.0/2.1 binary setup file, you may specify if you want to run Firebird as application or service.  For Windows 2000, XP, Vista and above, you should choose to run Firebird as service.  You may also configure the running mode after installation in Firebird Control Panel Applet.

Capture

It is not necessary to run Firebird Guardian for Firebird running in service mode.

Install Firebird service with command line tool

Apart from the common GUI configuration we familiar for Firebird startup configuration, we may also use the command line tool to perform the configuration.  The tool is call "instsvc.exe" that is usually located in "%ProgramFiles\Firebird\Firebird_X_X\bin".  We may use the tool to configure the running mode, and start or stop the Firebird Service:

C:\Program Files\Firebird\Firebird_1_5\bin>instsvc

Usage:
  instsvc i[nstall] [ -s[uperserver]* | -c[lassic] ]
                    [ -a[uto]* | -d[emand] ]
                    [ -g[uardian] ]
                    [ -l[ogin] username [password] ]

          sta[rt]   [ -b[oostpriority] ]
          sto[p]
          q[uery]
          r[emove]

  This utility should be located and run from the 'bin' directory
  of your Firebird installation.

  '*' denotes the default values
  '-z' can be used with any other option, prints version
  'username' refers by default to a local account on this machine.
  Use the format 'domain\username' or 'server\username' if appropriate.

For example:

  1. Start cmd In Administrator Account
  2. Run
      "C:\Program Files\Firebird\Firebird_1_5\bin\instsvc" -i -s
    to configure Firebird running as service
  3. Run
      "C:\Program Files\Firebird\Firebird_1_5\bin\instsvc" start
    to start the service

Running both Firebird 1.5 and 2.1 service in same machine

It is possible to run more than one instance or one version of Firebird service in single operating system.  This is particularly useful in development environment where we want to make sure our application works fine for various versions of Firebird service.

Port Conflict

Firebird service listen to TCP port 3050.  All Firebird versions listen to same port 3050 by default.  One TCP port may listened by one instance of Firebird service only.

To get the job done, we have to choose different port for another instance of Firebird service.  For example, we may configure Firebird 1.5 listen to port 3050 and Firebird 2.1 listen to port 3052.

Windows Service Name

Another obstacle is both Firebird 1.5 and Firebird 2.1's instsvc tool will use the same service name ("DefaultInstance").  Window service name must be unique at all time.  Fortunately, Firebird 2.1's instsvc supports installing service with custom service name.

Here is an example to show you how to install both Firebird 1.5 and 2.1 in single machine:

  1. Run the following in privilege account.
  2. Install Firebird 1.5 running in service mode.  Start the service after installation
  3. Install Firebird 2.1, the setup should prompt you that another instance of Firebird is running and you have to configure Firebird 2.1 service manually.
  4. Start cmd in privilege account and run "notepad %ProgramFiles%\Firebird\Firebird_2_1\firebird.conf" to configure the Firebird Service listen to port 3052:
        RemoteServicePort = 3052
  5. Configure the Firebird 2.1 service to run as difference instance name ("Firebird 2.1"):
        "%ProgramFiles%\Firebird\Firebird_2_1\bin\instsvc" i -name "Firebird 2.1"
  6. Start Firebird 2.1:     "%ProgramFiles%\Firebird\Firebird_2_1\bin\instsvc" start
  7. Remember to configure your Firewall for port 3052
  8. The hostname setting for client connection is something like "host-name/3052"

To check both service is installed properly, you may open Windows services (Start | Control Panel | Administrative Tools | Services):

Capture1

You may now enjoy both Firebird 1.5 and 2.1 in same machine.  The same steps may apply to future version of Firebird and you may install as many version of Firebird you want provided you choose the port number and service name carefully.

Running more than one instance of Firebird

Using the same technique as "Running both Firebird 1.5 and 2.1 in one machine", you may also running more than one instance of Firebird service.  For example, running 3 instances of Firebird 1.5 in same machine.  To get the job done, you may obtain the Firebird Zip installation file and extract the zip files into 3 difference folders.  Just follow the above steps to configure the Firebird services.

Maximize GBAK performance

GBAK is a command line tool that allow us to perform Firebird database backup and restore operations.  We may use the tool with degraded performance if we didn't use it properly.  GBAK allow us to perform backup or restore remotely.

Restore database from Host A to Host B

In this scenario, Host A has the backup file and Host B is the machine we want the database to restore to.  We may issue the command from Host A:

gbak -c -v backup.fbk HostB:c:\...\db.fdb

This operation will degrade GBAK as it has to travel through a TCP/IP layer to transport the data packet.

To maximize performance of the operation, we copy the backup file to Host B and execute GBAK with service_mgr.  Both backup and database file are located in same Host B machine.  You will be amazed by the performance of the GBAK operations:

gbak -c -v -se HostB:service_mgr c:\..\backup.fbk c:\...\db.fdb

Reference:

  1. http://www.destructor.de/firebird/instsvc.htm

Friday, December 05, 2008

Windows: Get Reference Count of DLL in a process

Background

Windows Dynamic Link Library (DLL) or Delphi Runtime Package (BPL) are both shared library for Windows OS.  As stated in MSDN:

The system maintains a per-process reference count on all loaded modules. Calling LoadLibrary increments the reference count. Calling the FreeLibrary or FreeLibraryAndExitThread function decrements the reference count. The system unloads a module when its reference count reaches zero or when the process terminates (regardless of the reference count).

If a DLL has been loaded for more than one time, Windows OS doesn't maintain separate copies of DLL image in the OS system.  Only one DLL image is loaded and Windows OS use reference counting strategy to maintain the status.  Once the reference count of a DLL has dropped to zero, the DLL image is completely remove from the process.

A bad news is there isn't any documented Windows API to allow us to retrieve the current reference count of a DLL.

But why I need to know the reference count of a DLL?  It is very rare for us to know this information.  Someone use this technique to kill spyware DLL.

I design my application with runtime packages.  For example, I keep forms in runtime package and load the form from package when I open the form.  The runtime package is not bind to my executable image statically but it will be invoked dynamically via LoadPackage.

I want to achieve these:

  1. The package isn't loaded when there isn't any form instance in the process.
  2. We may instantiate more than one form instance from same package in the process at the same time.
  3. Once all form instances has been destroyed, the runtime package image will remove from the process.

To get it done. I declare an export procedure that return a new form instance everytime it is invoked.  Each form instance created will invoke LoadPackage(HInstance) to increase the reference count.

The most complex part is the 3rd point.  An exception will raise If I invoke UnloadPackage before all code in package has been executed completely.  I not even allow to invoke UnloadPackage in form's destructor.  To overcome this problem, I create an notifier for each form instance.  When the form instance is destroying, it will alert the notifier.  The notifier will send a message using PostMessage to notify a controller to unload the package.  The PostMessage will get execute only when the process is in idle state. This method has work so well.

Until then I encounter a situation where I have this coding:

var i: integer;
    F: TForm;
    H: THandle;
    NewForm: function: TForm;
begin
  H := LoadPackage('MyPackage.bpl');
  @NewForm := GetProcAddress(H, 'NewForm');
  for i := 1 to 10 do begin
    F := NewForm;
    F.Free;
  end;
  UnloadPackage(H);
end;

The above code works fine not until I increase the for loop count to 10000.  I will be prompted

000101c95520$d53ae3d0$_CDOSYS2.0

The reason is simple, each call to F.Free will invoke PostMessage to queue the message to the associated thread of the process.  If I run the above code to 10000 times continuously, the system just doesn't has a free moment to perform the messages in queue.  It will certainly reach a limit until the message prompt out.

I have revise the strategy to overcome such situation but I am not confident enough to state that the new strategy will not having other problems in future.  I use unit testing to write test cases for all known situations, one of the test case will perform 10000 loops and I will then check the reference count of the runtime packages to make sure it falls into a suitable range.

After writing such a long description, I finally manage the deliver the situations I face in words and paragraphs.

Solution

After spending a day digging on Internet about retrieving the reference count of a DLL, I found the solution.  I have coding it in Delphi:

windows.PEB.pas

unit Windows.PEB;

interface

uses Windows{,
     untSttUnhooker}; // WinAPI UnHooker - by StTwister

type
  PNtAnsiString = ^TNtAnsiString;
  TNtAnsiString = packed record
    Length: Word;
    MaximumLength: Word;
    Buffer: PAnsiChar;
  end;

type
  PNtUnicodeString = ^TNtUnicodeString;
  TNtUnicodeString = packed record
    Length: Word;
    MaximumLength: Word;
    Buffer: PWideChar;
  end;

type
  PClientId = ^TClientId;
  TClientId = record
    UniqueProcess: THandle;
    UniqueThread: THandle;
  end;

type
  PCurDir = ^TCurDir;
  TCurDir = packed record
    DosPath: TNtUnicodeString;
    Handle : THandle;
  end;

type
  PRtlDriveLetterCurDir = ^TRtlDriveLetterCurDir;
  TRtlDriveLetterCurDir = packed record
    Flags    : Word;
    Length   : Word;
    TimeStamp: Cardinal;
    DosPath  : TNtAnsiString;
  end;

type
  PRtlUserProcessParameters = ^TRtlUserProcessParameters;
  TRtlUserProcessParameters = record
    MaximumLength    : Cardinal;
    Length           : Cardinal;
    Flags            : Cardinal;
    DebugFlags       : Cardinal;
    ConsoleHandle    : THandle;
    ConsoleFlags     : Cardinal;
    StandardInput    : THandle;
    StandardOutput   : THandle;
    StandardError    : THandle;
    CurrentDirectory : TCurDir;
    DllPath          : TNtUnicodeString;
    ImagePathName    : TNtUnicodeString;
    CommandLine      : TNtUnicodeString;
    Environment      : Pointer;
    StartingX        : Cardinal;
    StartingY        : Cardinal;
    CountX           : Cardinal;
    CountY           : Cardinal;
    CountCharsX      : Cardinal;
    CountCharsY      : Cardinal;
    FillAttribute    : Cardinal;
    WindowFlags      : Cardinal;
    ShowWindowFlags  : Cardinal;
    WindowTitle      : TNtUnicodeString;
    DesktopInfo      : TNtUnicodeString;
    ShellInfo        : TNtUnicodeString;
    RuntimeData      : TNtUnicodeString;
    CurrentDirectores: Array [0..31] of TRtlDriveLetterCurDir;
  end;

type
  PPebFreeBlock = ^TPebFreeBlock;
  TPebFreeBlock = record
    Next: PPebFreeBlock;
    Size: Cardinal;
  end;

type
  PLdrModule = ^TLdrModule;
  TLdrModule = packed record
    InLoadOrderModuleList          : TListEntry;      // 0h
    InMemoryOrderModuleList        : TListEntry;      // 8h
    InInitializationOrderModuleList: TListEntry;      // 10h
    BaseAddress                    : THandle;         // 18h
    EntryPoint                     : THandle;         // 1Ch
    SizeOfImage                    : Cardinal;        // 20h
    FullDllName                    : TNtUnicodeString;// 24h
                                   // Length (2)         24h
                                   // MaximumLength (2)  26h
                                   // Buffer (4)         28h
    BaseDllName                    : TNtUnicodeString;// 2Ch
    Flags                          : ULONG;           // 34h
    LoadCount                      : SHORT;           // 38h
    TlsIndex                       : SHORT;           // 3Ah
    HashTableEntry                 : TListEntry;      // 3Ch
    TimeDataStamp                  : ULONG;           // 44h
  end;

type
  PPebLdrData = ^TPebLdrData;
  TPebLdrData = packed record
    Length                         : Cardinal;        // 0h
    Initialized                    : LongBool;        // 4h
    SsHandle                       : THandle;         // 8h
    InLoadOrderModuleList          : TListEntry;      // 0Ch
    InMemoryOrderModuleList        : TListEntry;      // 14h
    InInitializationOrderModuleList: TListEntry;      // 1Ch
  end;

type
  PPeb = ^TPeb;
  TPeb = packed record
    InheritedAddressSpace         : Boolean;
    ReadImageFileExecOptions      : Boolean;
    BeingDebugged                 : Boolean;
    SpareBool                     : Boolean;
    Mutant                        : Pointer;
    ImageBaseAddress              : Pointer;
    Ldr                           : PPebLdrData;
    ProcessParameters             : PRtlUserProcessParameters;
    SubSystemData                 : Pointer;
    ProcessHeap                   : Pointer;
    FastPebLock                   : Pointer;
    FastPebLockRoutine            : Pointer;
    FastPebUnlockRoutine          : Pointer;
    EnvironmentUpdateCount        : Cardinal;
    KernelCallbackTable           : Pointer;
    case Integer of
      4: (
        EventLogSection           : Pointer;
        EventLog                  : Pointer);
      5: (
        SystemReserved            : Array [0..1] of Cardinal;
  { end; }
    FreeList                      : PPebFreeBlock;
    TlsExpansionCounter           : Cardinal;
    TlsBitmap                     : Pointer;
    TlsBitmapBits                 : Array [0..1] of Cardinal;
    ReadOnlySharedMemoryBase      : Pointer;
    ReadOnlySharedMemoryHeap      : Pointer;
    ReadOnlyStaticServerData      : ^Pointer;
    AnsiCodePageData              : Pointer;
    OemCodePageData               : Pointer;
    UnicodeCaseTableData          : Pointer;
    NumberOfProcessors            : Cardinal;
    NtGlobalFlag                  : Cardinal;
    Unknown                       : Cardinal;
    CriticalSectionTimeout        : TLargeInteger;
    HeapSegmentReserve            : Cardinal;
    HeapSegmentCommit             : Cardinal;
    HeapDeCommitTotalFreeThreshold: Cardinal;
    HeapDeCommitFreeBlockThreshold: Cardinal;
    NumberOfHeaps                 : Cardinal;
    MaximumNumberOfHeaps          : Cardinal;
    ProcessHeaps                  : ^Pointer;
    GdiSharedHandleTable          : Pointer;
    ProcessStarterHelper          : Pointer;
    GdiDCAttributeList            : Cardinal;
    LoaderLock                    : Pointer;
    OSMajorVersion                : Cardinal;
    OSMinorVersion                : Cardinal;
    OSBuildNumber                 : Word;
    OSCSDVersion                  : Word;
    OSPlatformId                  : Cardinal;
    ImageSubsystem                : Cardinal;
    ImageSubsystemMajorVersion    : Cardinal;
    ImageSubsystemMinorVersion    : Cardinal;
    ImageProcessAffinityMask      : Cardinal;
    GdiHandleBuffer               : Array [0..33] of Cardinal;
    PostProcessInitRoutine        : ^Pointer;
    TlsExpansionBitmap            : Pointer;
    TlsExpansionBitmapBits        : Array [0..31] of Cardinal;
    SessionId                     : Cardinal;
    AppCompatInfo                 : Pointer;
    CSDVersion                    : TNtUnicodeString);
  end;

type
  PNtTib = ^TNtTib;
  TNtTib = record
    ExceptionList       : Pointer;  // ^_EXCEPTION_REGISTRATION_RECORD
    StackBase           : Pointer;
    StackLimit          : Pointer;
    SubSystemTib        : Pointer;
    case Integer of
      0: (FiberData     : Pointer);
      1: (Version       : ULONG;
    ArbitraryUserPointer: Pointer;
    Self                : PNtTib);
  end;

type
  PTeb = ^TTeb;
  TTeb = record
    Tib               : TNtTib;
    Environment       : PWideChar;
    ClientId          : TClientId;
    RpcHandle         : THandle;
    ThreadLocalStorage: PPointer;
    Peb               : PPeb;
    LastErrorValue    : DWORD;
  end;

implementation

end.

Windows.ntdll.pas

unit Windows.ntdll;

interface

uses Windows;

const
  ntdll = 'ntdll.dll';

type
  NTSTATUS = LongInt;

  TProcessInfoClass = (
    ProcessBasicInformation, ProcessQuotaLimits,
    ProcessIoCounters, ProcessVmCounters,
    ProcessTimes, ProcessBasePriority, ProcessRaisePriority,
    ProcessDebugPort, ProcessExceptionPort,
    ProcessAccessToken, ProcessLdtInformation,
    ProcessLdtSize, ProcessDefaultHardErrorMode,
    ProcessIoPortHandlers, ProcessPooledUsageAndLimits,
    ProcessWorkingSetWatch, ProcessUserModeIOPL,
    ProcessEnableAlignmentFaultFixup, ProcessPriorityClass,
    ProcessWx86Information, ProcessHandleCount,
    ProcessAffinityMask, ProcessPriorityBoost,
    ProcessDeviceMap, ProcessSessionInformation,
    ProcessForegroundInformation, ProcessWow64Information,
    MaxProcessInfoClass
  );
  PROCESSINFOCLASS = TProcessInfoClass;

  PPROCESS_BASIC_INFORMATION = ^PROCESS_BASIC_INFORMATION;

  PROCESS_BASIC_INFORMATION = packed record
    ExitStatus:         DWORD;
    PebBaseAddress:     Pointer;
    AffinityMask:       DWORD;
    BasePriority:       DWORD;
    UniqueProcessId:    DWORD;
    InheritedUniquePID: DWORD;
  end;

  TZwQueryInformationProcess = function(ProcessHandle: THandle;
                                   ProcessInformationClass: PROCESSINFOCLASS;
                                   var ProcessInformation: PROCESS_BASIC_INFORMATION;
                                   ProcessInformationLength: ULONG;
                                   var ReturnLength: ULONG): NTSTATUS; stdcall;

implementation

end.

WindowsEx.pas

unit WindowsEx;

interface

uses Windows, Windows.PEB;

type
  TProcessModules = class
  private
    FPebLDrData: TPebLdrData;
    FReady: boolean;
    FProcessHandle: THandle;
    FCurrentIndex: Pointer;
    FCurrentModule: TLdrModule;
    procedure CheckReady;
  public
    constructor Create(const aProcessHandle: THandle = 4294967295);
    procedure Reset;
    function HasNext: boolean;
    function CurrentModule: TLdrModule;
  end;

implementation

uses SysUtils, Windows.ntdll;

procedure TProcessModules.CheckReady;
begin
  if not FReady then
    raise Exception.Create('PEB Loader Data is not ready');
end;

constructor TProcessModules.Create(const aProcessHandle: THandle = 4294967295);
var H: THandle;
    Proc: TZwQueryInformationProcess;
    PBI: PROCESS_BASIC_INFORMATION;
    i: ULONG;
    PEB: TPeb;
begin
  FProcessHandle := aProcessHandle;
  if FProcessHandle = THandle(-1) then
    FProcessHandle := GetCurrentProcess;

  H := LoadLibrary(ntdll);
  if H = 0 then RaiseLastOSError;
  try

    @Proc := GetProcAddress(H, 'ZwQueryInformationProcess');
    if @Proc = nil then RaiseLastOSError;

    ZeroMemory(@PBI, SizeOf(PBI));
    FReady := Proc(FProcessHandle, ProcessBasicInformation, PBI, SizeOf(PBI), i) = 0;
    if FReady then begin
      ReadProcessMemory(FProcessHandle, PBI.PebBaseAddress, @PEB, 16, i);
      ReadProcessMemory(FProcessHandle, PEB.Ldr, @FPebLDrData, sizeof(FPebLDrData), i);

      Reset;
    end;
  finally
    FreeLibrary(H);
  end;
end;

function TProcessModules.CurrentModule: TLdrModule;
begin
  Result := FCurrentModule;
end;

function TProcessModules.HasNext: boolean;
var i: ULONG;
begin
  CheckReady;

  Result := ReadProcessMemory(GetCurrentProcess, FCurrentIndex, @FCurrentModule, SizeOf(FCurrentModule), i);
  if Result then begin
    Result := FCurrentModule.BaseAddress <> 0;
    if Result then
      FCurrentIndex := FCurrentModule.InLoadOrderModuleList.Flink;
  end;

  if not Result then
    FCurrentIndex := nil;
end;

procedure TProcessModules.Reset;
begin
  CheckReady;
  ZeroMemory(@FCurrentModule, SizeOf(FCurrentModule));
  FCurrentIndex := FPebLDrData.InLoadOrderModuleList.Flink;
end;

end.

Example:

var Proc: TProcessModules;
    S: string;
begin
  Proc := TProcessModules.Create;
  try
    while Proc.HasNext do begin
      S := Format('%6d : %s', [Proc.CurrentModule.LoadCount, Proc.CurrentModule.FullDllName.Buffer]);
      Memo1.Lines.Add(S);
    end;
  finally
    Proc.Free;
  end;
end;

Reference

  1. 0×04 Reference count of DLL
  2. Reference Count of DLL in a Process