2006. április 30., vasárnap

Can I build "Query By Form" applications using Delphi?


Problem/Question/Abstract:

I used Oracle Forms for a long time to build Query By Form applications. I have recently switched to Delphi, and am wondering if it's possible to build Query By Form applications in it.

Answer:

What is Query By Form (QBF)?

First of all, one of the mistakes many people make about QBF is that is a proprietary feature of a some company's development environment. It's not. Think about QBF as an application design methodology or paradigm. Any programming environment in which you can build windows (forms) and in turn can access a database platform has the ability to build a QBF application. Admittedly, some products provide a relatively easy way to implement QBF over others, but if a product meets the two requirements mentioned above, it can do QBF.

For those of you not familiar with the concept, Query By Form is the act of wrapping an intelligent user interface around a query or group of queries that they might normally have to create by hand. By intelligence I don't mean a program that has cognitive abilities; rather, it's one that can translate and process user input by way of a form and provide result data in a reasonable format such as a printed report or a data grid.

In a nutshell, QBF is a way to hide the complexities of data extraction from the user, thus allowing him/her to focus on a specific business problem rather than being distracted by cryptic commands and keystrokes usually associated with query languages such as SQL. And because QBF is by nature business-problem-centric, QBF applications have the added advantage of restricting the user to a specific problem domain. In other words, it is very unlikely that while using a QBF application, the user could ask the wrong questions. This is because the program has only a limited set of questions which are bound by a specific problem domain.

There are a few people out there that disagree with this concept, saying it's inflexible and contending that users want to perform more ad hoc queries of their data to get their answers. In some cases I will agree with this. But I will counter that almost all business problems are defined by very specific sets of protocols and so have clearly defined and expected results. These protocols can in turn be modeled, then transformed into a seamless automation of the protocols.

Ad hoc querying is not only error-prone, but suffers from the danger of introducing unnecessary, extraneous data that could be perceived as meaningful but in actuality is far from it. Not only that, but most analyses require more than one query to achieve an intelligible answer set, usually starting with some initial extraction, then going through various levels of refinement until the appropriate data set is achieved. Users performing queries by ad hoc means may run their refinement queries out of sequence, or even miss some intermediate steps altogether.

Enter Delphi

Now let's look at how we can implement QBF. The concept of QBF can be applied in numerous ways in Delphi, so I'm not going to talk to much about specific cases of coding. However, I will talk about certain techniques I've used in Delphi when creating QBF applications.

Delphi is an ideal tool for doing QBF for a number of compelling reasons. Among them are:

Delphi applications are built with a form or window design paradigm. Every new project you start has a form and an associated unit that's created along with the project. This puts the developer in the interface design state right away. That's what QBF is all about: building a form to be the interface to your data extraction.
Delphi data-aware VCL components such as TTable and TQuery can make the process of creating QBF applications as easy as dragging and dropping and setting properties. This especially applies to really simple QBF apps that have only one query. Of course, for several sequential queries you'll have to do a bit of coding, but it's still pretty easy.
On top of all that, the Borland Database Engine (BDE) provides connectivity to a variety of database platforms, which means you can create generic QBFs that can go after data on heterogenous platforms.

The above are just a few examples of why I feel Delphi is an ideal tool for creating QBF applications.

Concept Revisited

I mentioned above that QBF implements an intelligent interface that has the ability to tranform user input requests into a data set of some sort. What is implied by QBF is that you use queries to perform the transformations, but I'm going to break stride here and say that you don't necessarily need to use queries to get your answer sets. Why? Think about it for a moment. The whole purpose of QBF is to hide the complexities inherent to data retrieval languages from the user. All users care about is the end product: the answer set. They don't care about the back-end operations. In that light, we open up a bunch of doors to getting data to the user.

For brevity's sake we won't go into all the different ways to do QBF. What I will concentrate on here are two common, useful ways of doing QBF in Delphi: by Dynamic Querying and TTable SetRange.

If there's something bugging you about the whole concept of QBF, it's probably this: You probably already know how to do this! That's right. Anytime you put a front-end form in front of a query or data retrieval operation, you're essentially doing Query By Form.

QBF Techniques: Another Flavor of Dynamic Queries

When you think of dynamic queries, what comes to mind? Usually the parameter-ized variety of placing a query variable within a SQL statement you preprocess with a Prepare, fill with a value, then execute. That's a perfectly valid methodology to employ in many cases. But for a lot of my own applications, I've found using parameter-ized queries limiting in many ways. You can't use a parameter in the FROM clause of a query. This means that you can't apply the query to different tables that have the same structure. For myself, I want to have ultimate flexibility, so what I do is address the SQL property directly.

The SQL property of a TQuery is a TStrings type property. Ah! the old TStrings. That's right folks, this is something many of you have used time and again in your programs. As you may already know, a TStrings object is nothing more than an ordered collection of strings, each accessed by means of a zero-based index (meaning the first string's index is '0'). So what's so special about this respect to the SQL property of a TQuery? It all has to do with strings themselves. The most important thing is that strings can be easily manipulated. You can pretty much dice and slice them any way you choose. With respect to dynamic queries, the ability to manipulate the SQL property is a boon to doing QBF. Let's look at a sample of a real code snippet from one of my larger QBF applications.

InitQuery := TQuery.Create(Application);
with InitQuery do
begin
  DatabaseName := 'PRIVATE';
  Close;
  SQL.Clear;
  SQL.Add('SELECT D.BATCH, D.RECORD, D.ACCOUNT, D.FACILITY, D."INGREDIENT COST",');
  SQL.Add('D."PHARMACY ID", D.DAW, D."DAYS SUPPLY", D."DISPENSING FEE",
        D."MEMBER ID",');
  SQL.Add('D."DOCTOR ID", D.NDC, D.FORMULARY, D."Apr Amt Due",');
  SQL.Add('D1."DEA CODE", D1."GPI CODE", D1."DRUG NAME", D1."GENERIC CODE",
  0 AS D."DAW COUNT"');
  SQL.Add('FROM "' + EncPath + '" D, ":DRUGS:' + DrugTable + '" D1');
  SQL.Add('WHERE (D.' + DateFld + ' = ' + BStart + ' AND D.' + DateFld + ' <= ' + BEnd
    + ') AND');
  SQL.Add('((D."RECORD STATUS" = ''P'') OR (D."RECORD STATUS" = ''R'')) AND ');

  //Get Account List and Medical Group entries. Have to do this conditionally to
  //handle both lists at the same time. A bit of a short-circuit
  if (MainForm.DBRadioGroup1.ItemIndex = 1) then
    if (MainForm.DBRadioGroup2.ItemIndex = 1) then
    begin
      AddSQLList(MainForm.AccountList, SQL, 'Account', True);
      AddSQLList(MainForm.MedGrpList, SQL, 'Facility', True);
    end
    else
      AddSQLList(MainForm.AccountList, SQL, 'Account', True)
  else if (MainForm.DBRadioGroup2.ItemIndex = 1) then
    AddSQLList(MainForm.MedGrpList, SQL, 'Facility', True);

  SQL.Add('(D.FORMULARY <> ''Q'') AND (D.NDC = D1.NDC)');

  SQL.SaveToFile('mgrInit.sql');
  try
    Open;
  except
    Free;
    raise;
    Abort;
  end;
end;

In the code above, I've marked in bold the places I've inserted string variables to be filled in at runtime. Due to the changing nature of user requests, I found this technique far more flexible and it allows me to change the SQL in any number of places in the SQL statement. One thing you should note in the code above is that not only did I just provide fill-in areas with string vars, I also used a remote procedure to load in SQL items using AddSQLList.

This takes advantage of an interesting feature of a TStrings item. While you cannot pass a TStrings item by reference (ie. procedure procName(var _tString: TStrings);), you can pass a TStrings object by constant value to add or delete from the list depending upon what you want to do. That is what the procedure AddSQLList performs. Essentially, it takes what users have entered in a TDBMemo criteria field on the QBF form, turns the list values into a string of comma-separated values, then turns the string into a SQL IN statement. The IN statement is then tacked onto the end of the SQL TStrings object. Let's look at the code:

{=====================================================================================
This procedure will add an IN query statement from a list of values passed from a
TDBMemo into the SQL of a TQuery. Using an IN is far more elegant than several
Field = 'value1' OR Field = 'value2' statements.
====================================================================================}

procedure AddSQLList(lst: TDBMemo; //List you want to read from
  const encSQL: TStrings; //SQL to add to
  fldName: string; //The field to query on
  AddAND: Boolean); //Add an AND to tail end?
var
  I: Word;
  valStr: string;

begin

  //initialize vars;

  valStr := '';

  //Parse the list and make a CSV string out of the values
  for I := 0 to (lst.Lines.Count - 1) do
  begin
    valStr := valStr + '''' + lst.Lines[I] + ''',';
  end;

  //Remove the trailing comma
  valStr := Copy(valStr, 1, Length(valStr) - 1);

  //Append the SQL IN clause with field name. If there is another
  //SQL statement to follow, append an AND to the end.
  if addAND then
    encSQL.Add(' D.' + fldName + ' IN (' + valStr + ') AND ')
  else
    encSQL.Add(' D.' + fldName + ' IN (' + valStr + ')');

end;

The only danger to the procedure above is that I don't know if this is a loophole in the compiler or not. One would assume that to change something, you would pass it by reference. But this is not so with TStrings. I'm waiting to hear replies from Borland and the folks a CompuServe. But rest assured, I've used this technique in both versions of Delphi with no problems. My only concern is what will happen in future versions of the compiler. In any case, the whole point to this discussion is that manipulating the SQL property directly is much more flexible that using parameter-ized queries.

QBF Techniques: TTable SetRange

Remember what I said above, that users don't care how they get their data, they just want to get it? Especially with simple retrieval functions, you don't necessarily need to perform a query. Sometimes a TTable SetRange will do the job for you, and not only that -- but faster.

There are a couple of ways to perform a SetRange. The first is to use the SetRange function itself, which combines the SetRangeStart, SetRangeEnd and ApplyRange functions in one call. This is effective for setting ranges on the first index of a table. For other setting ranges on other index fields, you will need to explicitly use the three functions mentioned previously . The help file explains the usage of these functions in detail, so I won't go into specific coding examples.

Wrapping It Up

I realize that this has been more of a concept discussion rather than a real coding discussion. But you should remember that there's a lot more to progamming than coding. Programming is a really complex process that includes a lot of conceptualization and analysis. Over the years that I have been developing applications, I have found that I've become a much more effective programmer by paying attention to the concepts that have been put before me, and using them as means to approaching a code solution from different perspectives.

2006. április 29., szombat

How to synchronize two controls


Problem/Question/Abstract:

I have a frame with two panels on it (right and left). The left panel is a pretty standard treeview. The right panel is a representation of the data record for the selected node in the tree view. The right panel is dynamic, that is it is constructed on the fly at run time as the users have the ability to define what fields they wish to show and what the labels are for those fields.

I need the right panel to change the treeview node text when the record in the right panel is posted. The general consensus is that it'd best be done with a callback, but I've never done one before. Can anyone give me a simple example of how to do this?

Answer:

What you are describing is a Mediator Pattern. In essence, you set up a class which is aware of your two (or more) components. Set up properties to record changes and then in the appropriate event handler, simply assign the new values. The Mediator is solely responsible for synchronizing the two.

Very simple example (yours will be slightly more complex, but surprisingly probably won't have too much more code):

TEditBoxMediator = class
private
  FIsChanging: boolean;
  FFirstBox: TEdit;
  FSecondBox: TEdit;
  function GetText: string;
  procedure SetText(Value: string);
public
  property Text: string read GetText write SetText;
  constructor Create(const FirstBox, SecondBox: TEdit);
end;

constructor TEditBoxMediator.Create(const FirstBox, SecondBox: TEdit);
begin
  inherited Create;
  FFirstBox := FirstBox;
  FSecondBox := SecondBox;
  FIsChanging := False;
end;

function TEditBoxMediator.GetText: string;
begin
  Result := FFirstBox.Text;
end;

procedure TEditBoxMediator.SetText(Value: string);
begin
  if FIsChanging then
    Exit;
  FIsChanging := True;
  if FFirstBox.Text <> Value then
    FFirstBox.Text := Value;
  if FSecondBox.Text <> Value then
    FSecondBox.Text := Value;
  FIsChanging := False;
end;

procedure TForm1.Create {...}
begin
  FEditBoxMediator := TEditBoxMediator.Create(Edit1, Edit2);
end;

procedure TForm1.Edit1Change(Sender: TObject);
begin
  FEditBoxMediator.Text := Edit1.Text;
end;

and so on.


The idea is that the mediator handles the changes (and uses the internal flag to prevent an endless
loop).

2006. április 27., csütörtök

Construct a class instance from a string


Problem/Question/Abstract:

Is it possible to construct an instance of a class given the name of the class in a string variable? I would also want to test that the so named class actually exists and is a descendent of another known base class before construction.

Answer:

Yes, if some preconditions are met. The first thing you need is a class registry, a construct that allows you to register all the classes you want to create at run-time given the class name. The VCL already contains such a registry for TPersistent-derived classes (actually it is used only for TComponent-derived classes). Look at RegisterClass, GetClass and FindClass in the online help.

If you have your own hierachry of classes it is quite easy to set up your own class registry, using the TClassList class from the Contnrs unit (see online help). You would have one instance (a singleton) of TClasslist as your registry, to which you add your classes at run-time, typically from the Initialization section of the unit that implements the class in question.

Delphi has class reference types (see online help), which are types the values of which are classes. So you define such a type for your base class:

type
  TClassAClass = class of TClassA;

TClassA needs to have a virtual constructor (like Tcomponent) for this scheme to work properly. You can now derive other classes from TClassA, which may override the base classes constructor as appropiate. To find a class in the registry given its classname you iterate over the classlist:

function CreateInstance(const classname: string; minclass: TClassAClass): TClassA;
var
  i: Integer;
  classref: TClassAClass
begin
  Result := nil;
  for i := 0 to classlist.count - 1 do
  begin
    classref := TClassAClass(classlist[i]);
    if classref.ClassnameIs(classname) and classref.InheritsFrom(minclass) then
    begin
      Result := classref.Create;
      Break;
    end;
  end;
end;

If the constructor needs parameters you have to pass these to your CreateInstance function.

2006. április 26., szerda

Boyer-Moore-Horspool pattern matching


Problem/Question/Abstract:

Boyer-Moore-Horspool pattern matching

Answer:

Solve 1:

function search(pat: PATTERN; text: TEXT): integer;
var
  i, j, k, m, n: integer;
  skip: array[0..MAXCHAR] of integer;
  found: boolean;
begin
  found := FALSE;
  search := 0;
  m := length(pat);
  if m = 0 then
  begin
    search := 1;
    found := TRUE;
  end;
  for k := 0 to MAXCHAR do
    skip[k] := m;
  {Preprocessing}
  for k := 1 to m - 1 do
    skip[ord(pat[k])] := m - k;
  k := m;
  n := length(text);
  {Search}
  while not found and (k < = n) do
  begin
    i := k;
    j := m;
    while (j = 1) do
      if text[i] <> pat[j] then
        j := -1
      else
      begin
        j := j - 1;
        i := i - 1;
      end;
    if j = 0 then
    begin
      search := i + 1;
      found := TRUE;
    end;
    k := k + skip[ord(text[k])];
  end;
end;


Solve 2:

unit exbmh;

interface

uses
  Windows, SysUtils;

procedure BMHInit(const pattern: pchar);
function BMHSearch(cstring: pchar; const stringlen: integer): pchar;

var
  found: pchar;

implementation

{Date last modified: 05-Jul-1997
Case-sensitive Boyer-Moore-Horspool pattern match
Public domain by Raymond Gardner 7/92
Limitation: pattern length + string length must be less than 32767
10/21/93 rdg  Fixed bug found by Jeff Dunlop}

const
  Large = 32767;

type
  TSkip = array[0..256] of integer;
  PSkip = ^TSkip;
  TByteArray = array[0..0] of byte;
  PByteArray = ^TByteArray;

var
  patlen: integer;
  skip: TSkip;
  skip2: integer;
  pat: pchar;

procedure BMHInit1(const pattern: pchar);
var
  i, lastpatchar: integer;
begin
  pat := pattern;
  patlen := StrLen(pattern);
  for i := 0 to 255 do
    skip[i] := patlen;
  for i := 0 to patlen - 1 do
    skip[Byte(pat[i])] := patlen - i - 1;
  lastpatchar := byte(pat[patlen - 1]);
  skip[lastpatchar] := Large;
  skip2 := patlen;
  for i := 0 to patlen - 2 do
    if byte(pat[i]) = lastpatchar then
      skip2 := patlen - i - 1;
end;

function BMHSearch1(cstring: pchar; const stringlen: integer): pchar;
var
  i, j: integer;
  s: pchar;
begin
  i := patlen - 1 - stringlen;
  result := nil;
  if i >= 0 then
    exit;
  inc(cstring, stringlen);
  while true do
  begin
    repeat
      inc(i, skip[byte(cstring[i])]);
    until
      i > = 0;
    if i < (Large - StringLen) then
      exit;
    dec(i, Large);
    j := patlen - 1;
    s := cstring + (i - j);
    dec(j);
    while (j >= 0) and (s[j] = pat[j]) do
      dec(j);
    if (j < 0) then
    begin
      result := s;
      exit;
    end;
    inc(i, skip2);
    if (i >= 0) then
      exit;
  end;
end;

procedure BMHInit(const pattern: pchar);
var
  i, lastpatchar: integer;
  len: integer;
  skip: PSkip;
begin
  pat := pattern;
  len := StrLen(pattern);
  patlen := len;
  skip := @BMHSearchs.Skip;
  for i := 0 to 255 do
    skip[i] := len;
  for i := 0 to len - 1 do
    skip[Byte(pattern[i])] := len - i - 1;
  lastpatchar := byte(pattern[len - 1]);
  skip[lastpatchar] := Large;
  skip2 := len;
  for i := 0 to len - 2 do
    if byte(pattern[i]) = lastpatchar then
      skip2 := len - i - 1;
end;

function inner(i: integer; c: PByteArray): integer;
asm
  push ebx
  @L1:
    movzx ebx, byte ptr[edx + eax]
    add eax, [offset skip + ebx]
    jl @l1;
    pop ebx
end;

function BMHSearch(cstring: pchar; const stringlen: integer): pchar;
var
  i, j: integer;
  s: pchar;
  pat: pchar;
begin
  pat := BMHSearchs.pat;
  i := patlen - 1 - stringlen;
  result := nil;
  if i >= 0 then
    exit;
  inc(cstring, stringlen);
  while true do
  begin
    repeat
      inc(i, skip[byte(cstring[i])]);
    until
      i >= 0;
    if i < (Large - StringLen) then
      exit;
    dec(i, Large);
    j := patlen - 1;
    s := cstring + (i - j);
    dec(j);
    while (j >= 0) and (s[j] = pat[j]) do
      dec(j);
    if (j < 0) then
    begin
      result := s;
      exit;
    end;
    inc(i, skip2);
    if (i >= 0) then
      exit;
  end;
end;

const
data = 'of a procedure to find a pattern in a stringThis is a test of a procedure to find a pattern in a string last This is a test of aprocedure to find a pattern in a string';

initialization
  BMHInit('last');
  found := BMHSearch(data, length(data));

end.

2006. április 25., kedd

How to specify a different width for the items list in a TComboBox


Problem/Question/Abstract:

How to specify a different width for the items list in a TComboBox

Answer:

Send it the CB_SETDROPPEDWIDTH message with a new width.

procedure TForm1.Button1Click(Sender: TObject);
var
  W: integer;
begin
  W := 300;
  SendMessage(ComboBox1.Handle, CB_SETDROPPEDWIDTH, W, 0);
end;

2006. április 24., hétfő

Cryptographic Random Numbers


Problem/Question/Abstract:

The time comes in many security related applications to generate random numbers. If done poorly, the entire application will be compromised (think netscape's CSS!). This article demonstrates a TRUE random number generator implemented in 3 layers.

Answer:

Simply calling Randomize and using the Random() procedure is a severe security flaw in application seeking to pretect data with random numbers. A random number generator gets is 'randomness' from entropy. Borlands Random() procedure uses a 32bit seed as entropy, and that seed is generated by the Randomize procedure which gets its entropy the system time and date which are very probabilistic and can be tested for quickly.

To generate random numbers that cannot be differentiated from pure chaos is a VERY difficult task on a computer, mainly because you rely on internal states that are often too predictable. The idea is to gather entropy from the least predictable states of the system and dillute that entropy inside a much larger pool. The pool I refer to is the internal state of the random number generator.

WHAT IT IS

There are important properties that have to be respected when generating random numbers. More specificaly, random numbers intended for encryption. The properties that implicated in this random number gerenartors design are strongly based on Bruce Schneier's Yarrow (www.counterpane.com).

The first property is to ensure there is always anought entropy in the pool before outputing random numbers so that the pool never enters a weakened state where the next random numbers that are output have predictible information.

The next property comes in handy if you're going to be using the generator to make session keys that will change multiple times during a chat session. It is important that one compromised key will not reveal any of the previous keys nor any of the next keys that will be used. To do this we need to eliminate the mathematical relationship between the random numbers that are output and the state of the pool.

The third desired property implies that enven if the entropy gathered from your sources is of poor quality (fairly predictable) the pool must not suffer for the low entropy and the output random numbers must not show any evidence of this.

I have tested this unit extensively. The final and most crucial test centered around the third property. To make an extreme case, I started the pool with nothing but zeros in it and generated ~12MB (100,000,000 bits). I used the DieHard battery of tests (http://stat.fsu.edu/~geo/diehard.html) and it passed all 15 with flying colors... without collecting any entropy. With this I am satisfied of the random number generator's performance and submit it to you to use as a secure alternative to what is commonly seen in programs.


HOW TO IT WORKS

two entropy gatherers are created:

a thread that tracks mouse movement at random intervals taking 4bits of entropy from the mouse position and state of the system's high-resolution timer.
a latency calculator that gets 4bits of entropy from the high-resolution timer when called by the main app (this is used by calling   TKeyGenerator.AddLatency on the OnKeyDown event of an edit box, to count harddrive latency, or irq latency)

When either of the entropy gatherers has accumulated 32bits, it sends it to the entropy pool.

The entroyp pool takes in entropy 32bits at a time and uses it to fill an entropy buffer of 256bits, when the buffer is full, a primary reseed is executed.

The primary reseed updates the primary pool (a Hash Context: internal state of a hash function) with the entropy and XORs it with the pool's seed (this seed is used similarly the way randomize generates randseed). After every primary reseed, the seed (with now 256bits of entropy) is ready to be used to output random numbers if the calling application so desires it, but it will continue to reseed and gather entropy regardless regardless of that. After 8 primary reseeds have taken place, a secondary reseed is executed.

The secondary reseed updates the secondary pool with the contents of the primary pool and then flushes the contents of the primary pool into a state with no entropy. The secondary pool is persistant in that it is never flushed and will carry entropy bits from various reseeds. A completly new seed is generated from the secondary reseed (where as the primary on modifies it with entropy). This secondary reseed prevents backtracking properties (gessing previous states of the pool) and ensures there is entropy in the pool even under conditions where new entropy is of poor quality.

When the calling application needs to generate a key it calls SafeGetKey which ensures that no more than 8 sets of 256bits of random numbers can be generated from a single reseed. To do this a key reserve counter is incremented every primary reseed, and cannot exceed 8. When a you generate a set of random numbers the key reserve is decremented and the function will return fasle if the key reserve is at 0. NOTE: an application can ignore the key reserve and call ForceGetKey. This is very risky practice and I seriously discourage you from using this function.

The random output created by GetKey is a generated with the entropy pool's seed. The seed is used as an encryption key and then permuted (with an expansion-compression). The new seed is used as data to be encrypted. It is encrypted with the previous seed and expanded-compressed in 64 rounds. These rounds ensure that it is impossible to determine the state of the seed, the primary pool, the secondary pool or the entorpy buffer; in turn, preventing anyone from finding the previous or next outputs.

NOTES

Assign a variable of type TKeyGenerator and call it's .Create. This will start the process. When you are done, call .Destropy.

You can use .KeyCount to find out the state of the key reserve (how many GetKey calls can be made before the next reseed). I strongly condone raising the value of MAX_KEY_RESERVE.

You can manipulate the speed at which entropy is gathered from the mouse by setting the MOUSE_INTERVAL constant (in milli-seconds). A value lower than 10 is unrecommended.

No error checking is done to ensure there is a high-frequency counter on the system, this should be verified by the calling application. If there is no such counter, the random number generator will work but will output non-random numbers.

The application must provide 32 BYTES of memory space in a variable to pass to the GetKey functions. No error checking is done here.

You may change KEY_BUILD_ROUNDS to any value greater than or equal to 32, but larger than 64 is quite useless.

IMPORTANT NOTE

The source bellow is part of a library in progress cummulating 3 years of my research. If you want to use it in a program, a little bit of credit would be nice.

NSCrypt.pas contains a pseudo random number generator that comes from an unknown source, and these implementations of Haval and Rijndael are [severe] modifications of David Barton's haval.pas and rijndael.pas found in the DCPcrypt 1.3 component suite.

Download NSCrypt.txt and rename it NSCrypt.pas (silly webhost) I've included the cryptographic functions sepperatly because they total 1300 lines together. My actual website is www.drmungkee.com

//you'll have to deal with the documentation above.

unit NoiseSpunge;

interface

uses Windows, Classes, Controls, NSCrypt;

const
  SEED_SIZE = 8;
  PRIMARY_RESEED = SEED_SIZE;
  SECONDARY_RESEED = SEED_SIZE;

  //parameters
  MAX_KEY_RESERVE = 8;
  KEY_BUILD_ROUNDS = 64;
  MOUSE_INTERVAL = 10;

type
  Key256 = array[0..SEED_SIZE - 1] of longword;

  TNoiseSpungeAddEntropy = procedure(Block: longword) of object;
  TNoiseSpungeProcedure = procedure of object;

  TMouseCollector = class(TThread)
  protected
    PCtx: Prng_CTX;
    x, y: integer;
    Block: longword;
    BitsGathered: longword;
    Interval, Frequency, ThisTime, LastTime: TLargeInteger;
    SendMouseEntropy: TNoiseSpungeAddEntropy;
  public
    constructor Create;
    procedure SyncSendMouseEntropy;
    procedure Execute; override;
  end;

  TLatencyCollector = class
  protected
    Block: longword;
    BitsGathered: longword;
    Time: TLargeInteger;
    SendLatencyEntropy: TNoiseSpungeAddEntropy;
  public
    constructor Create;
    procedure MeasureLatency;
  end;

  TEntropyPool = class
  protected
    Seed: Key256;
    EntropyBuffer: Key256;
    PrimaryPool: Haval_CTX;
    SecondaryPool: Haval_CTX;
    PrimaryReseedCount: byte;
    EntropyCount: byte;
    KeyReserve: byte;
    procedure PermuteSeed;
    procedure PrimaryReseed;
    procedure SecondaryReseed;
    procedure AddEntropy(Block: longword);
  public
    constructor Create;
  end;

  TKeyGenerator = class
  protected
    EntropyPool: TEntropyPool;
    MouseCollector: TMouseCollector;
    LatencyCollector: TLatencyCollector;
  public
    AddLatency: TNoiseSpungeProcedure;
    constructor Create;
    destructor Destroy; override;
    function KeyCount: byte;
    function SafeGetKey(var Key): boolean;
    procedure ForcedGetKey(var Key);
  end;

implementation

constructor TMouseCollector.Create;
begin
  inherited Create(true);
  Randomize;
  PrngInit(@PCtx, RandSeed);
  FreeOnTerminate := true;
  Priority := tpNormal;
  Resume;
end;

procedure TMouseCollector.SyncSendMouseEntropy;
begin
  SendMouseEntropy(Block);
end;

procedure TMouseCollector.execute;
var
  NilHandle: pointer;
  Idled: boolean;
begin
  NilHandle := nil;
  BitsGathered := 0;
  Idled := false;
  QueryPerformanceFrequency(Frequency);
  repeat
    if Idled = false then
    begin
      MsgWaitForMultipleObjects(0, NilHandle, false, MOUSE_INTERVAL, 0);
      Idled := true;
    end;
    QueryPerformanceCounter(ThisTime);
    if ThisTime - LastTime > Interval then
    begin
      if (x <> mouse.cursorpos.x) and (y <> mouse.cursorpos.y) then
      begin
        x := mouse.cursorpos.x;
        y := mouse.cursorpos.y;
        Inc(Block, (((x and 15) xor (y and 15)) xor (ThisTime and 15)) shl
          BitsGathered);
        Inc(BitsGathered, 4);
        if BitsGathered = 32 then
        begin
          PrngInit(@PCtx, Block);
          Synchronize(SyncSendMouseEntropy);
          Block := 0;
          BitsGathered := 0;
        end;
        Interval := ((((Prng(@PCtx) mod MOUSE_INTERVAL) div 2) + MOUSE_INTERVAL)
          * Frequency) div 1000;
        QueryPerformanceCounter(LastTime);
        Idled := false;
      end
      else
      begin
        QueryPerformanceCounter(LastTime);
        Idled := false;
      end;
    end;
  until Terminated = true;
end;

constructor TLatencyCollector.Create;
begin
  inherited Create;
  Block := 0;
  BitsGathered := 0;
end;

procedure TLatencyCollector.MeasureLatency;
begin
  QueryPerformanceCounter(Time);
  Inc(Block, (Time and 15) shl BitsGathered);
  Inc(BitsGathered, 4);
  if BitsGathered = 32 then
  begin
    SendLatencyEntropy(Block);
    Block := 0;
    BitsGathered := 0;
  end;
end;

constructor TEntropyPool.Create;
begin
  inherited Create;
  HavalInit(@PrimaryPool);
  HavalInit(@SecondaryPool);
  FillChar(Seed, SizeOf(Seed), 0);
  EntropyCount := 0;
  PrimaryReseedCount := 0;
  KeyReserve := 0;
end;

procedure TEntropyPool.PermuteSeed;
var
  TempBuffer: array[0..1] of Key256;
  PCtx: Prng_CTX;
  HCtx: Haval_CTX;
  i: byte;
begin
  for i := 0 to SEED_SIZE - 1 do
  begin
    PrngInit(@PCtx, Seed[i]);
    TempBuffer[0, i] := Prng(@PCtx);
    TempBuffer[1, i] := Prng(@PCtx);
  end;
  HavalInit(@HCtx);
  HavalUpdate(@HCtx, TempBuffer, SizeOf(TempBuffer));
  HavalOutput(@HCtx, Seed);
end;

procedure TEntropyPool.PrimaryReseed;
var
  TempSeed: Key256;
  i: byte;
begin
  HavalUpdate(@PrimaryPool, EntropyBuffer, SizeOf(EntropyBuffer));
  if PrimaryReseedCount
  begin
    HavalOutput(@PrimaryPool, TempSeed);
    for i := 0 to SEED_SIZE - 1 do
      Seed[i] := Seed[i] xor TempSeed[i];
    Inc(PrimaryReseedCount);
  end
else
  SecondaryReseed;
  FillChar(EntropyBuffer, SizeOf(EntropyBuffer), 0);
  if KeyReserve EntropyCount := 0;
end;

procedure TEntropyPool.SecondaryReseed;
begin
  HavalOutput(@PrimaryPool, Seed);
  HavalUpdate(@SecondaryPool, Seed, SizeOf(Seed));
  HavalOutput(@SecondaryPool, Seed);
  PermuteSeed;
  HavalInit(@PrimaryPool);
  PrimaryReseedCount := 0;
end;

procedure TEntropyPool.AddEntropy(Block: longword);
begin
  Move(Block, pointer(longword(@EntropyBuffer) + (EntropyCount * SizeOf(Block)))^,
    SizeOf(Block));
  Inc(EntropyCount, 1);
  if EntropyCount = PRIMARY_RESEED then
    PrimaryReseed;
end;

constructor TKeyGenerator.Create;
begin
  inherited Create;
  EntropyPool := TEntropyPool.Create;
  MouseCollector := TMouseCollector.Create;
  MouseCollector.SendMouseEntropy := EntropyPool.AddEntropy;
  LatencyCollector := TLatencyCollector.Create;
  LatencyCollector.SendLatencyEntropy := EntropyPool.AddEntropy;
  AddLatency := LatencyCollector.MeasureLatency;
end;

destructor TKeyGenerator.Destroy;
begin
  MouseCollector.terminate;
  LatencyCollector.destroy;
  EntropyPool.destroy;
  inherited Destroy;
end;

function TKeyGenerator.KeyCount: byte;
begin
  Result := EntropyPool.KeyReserve;
end;

function TKeyGenerator.SafeGetKey(var Key): boolean;
var
  TempSeed: Key256;
  TempBuffer: array[0..1] of Key256;
  RCtx: Rijndael_CTX;
  PCtx: Prng_CTX;
  HCtx: Haval_CTX;
  i, j: byte;
begin
  if EntropyPool.KeyReserve = 0 then
  begin
    Exit;
    Result := false;
  end
  else
    Result := true;
  Move(EntropyPool.Seed, TempSeed, SizeOf(TempSeed));
  EntropyPool.PermuteSeed;
  RijndaelInit(@RCtx, EntropyPool.Seed);
  for i := 0 to KEY_BUILD_ROUNDS - 1 do
  begin
    RijndaelEncrypt(@RCtx, TempSeed[0]);
    RijndaelEncrypt(@RCtx, TempSeed[4]);
    for j := 0 to SEED_SIZE - 1 do
    begin
      PrngInit(@pctx, TempSeed[j]);
      TempBuffer[0, j] := Prng(@PCtx);
      TempBuffer[1, j] := Prng(@PCtx);
    end;
    HavalInit(@HCtx);
    HavalUpdate(@HCtx, TempBuffer, SizeOf(TempBuffer));
    HavalOutput(@HCtx, TempSeed);
  end;
  Move(TempSeed, Key, SizeOf(TempSeed));
  Dec(EntropyPool.KeyReserve, 1);
end;

procedure TKeyGenerator.ForcedGetKey(var Key);
var
  TempSeed: Key256;
  TempBuffer: array[0..1] of Key256;
  RCtx: Rijndael_CTX;
  PCtx: Prng_CTX;
  HCtx: Haval_CTX;
  i, j: byte;
begin
  Move(EntropyPool.Seed, TempSeed, SizeOf(TempSeed));
  EntropyPool.PermuteSeed;
  RijndaelInit(@RCtx, EntropyPool.Seed);
  for i := 0 to KEY_BUILD_ROUNDS - 1 do
  begin
    RijndaelEncrypt(@RCtx, TempSeed[0]);
    RijndaelEncrypt(@RCtx, TempSeed[4]);
    for j := 0 to SEED_SIZE - 1 do
    begin
      PrngInit(@pctx, TempSeed[j]);
      TempBuffer[0, j] := Prng(@PCtx);
      TempBuffer[1, j] := Prng(@PCtx);
    end;
    HavalInit(@HCtx);
    HavalUpdate(@HCtx, TempBuffer, SizeOf(TempBuffer));
    HavalOutput(@HCtx, TempSeed);
  end;
  Move(TempSeed, Key, SizeOf(TempSeed));
  if EntropyPool.KeyReserve < 0 then
    Dec(EntropyPool.KeyReserve, 1);
end;

end.

2006. április 23., vasárnap

Detect if the scrollbars of a TStringGrid are visible


Problem/Question/Abstract:

I have a TStringGrid on my form and enabled the horizontal scrollbar. This only appears when there are more cols than fit on the scroll. When it appears it covers the row at the bottom. I need to have one less row when it is shown. I cannot enable the vertical scrollbar. How do I test if it is visible?

Answer:

{ ... }
if (GetWindowLong(stringgrid.handle, GWL_STYLE) and WS_HSCROLL) <> 0 then
  { ... scrollbar is visible }

2006. április 22., szombat

Get a color string in HTML format


Problem/Question/Abstract:

How can I receive the color string in HTML format

Answer:

If you want to create a HTML-file, you must define a tag for font color or backgroubd color. But you can't insert a Delphi's TColor value - you must convert the color into RGB-format.

function GetHTMLColor(cl: TColor; IsBackColor: Boolean): string;
var
  rgbColor: TColorRef;
begin
  if IsBackColor then
    Result := 'bg'
  else
    Result := '';
  rgbColor := ColorToRGB(cl);
  Result := Result + 'color="#' +
    Format('%.2x%.2x%.2x',
    [GetRValue(rgbColor),
    GetGValue(rgbColor),
      GetBValue(rgbColor)]) + '"';
end;

2006. április 21., péntek

How to get the current row and column of a cursor in a TRichEdit


Problem/Question/Abstract:

How to get the current row and column of a cursor in a TRichEdit

Answer:

procedure TForm1.GetPosition(Sender: TRichEdit);
var
  aX, aY: Integer;
  TheRichEdit: TRichEdit;
begin
  aX := 0;
  aY := 0;
  TheRichEdit := TRichEdit(Sender);
  aY := SendMessage(TheRichEdit.Handle, EM_LINEFROMCHAR, TheRichEdit.SelStart, 0);
  aX := TheRichEdit.SelStart - SendMessage(TheRichEdit.Handle, EM_LINEINDEX, aY, 0);
  Panel1.Caption := IntToStr(aY + 1) + ':' + IntToStr(aX + 1);
end;

procedure TForm1.RichEditMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  GetPosition(RichEdit);
end;

procedure TForm1.RichEditKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  GetPosition(RichEdit);
end;

2006. április 20., csütörtök

How to detect if you can run NT services


Problem/Question/Abstract:

How can I detect if I can install and run a service on the current operating system? I was just going to detect the operating system, but I figured it might be better to directly detect the service control - but not sure how to do that.

Answer:

After installing a service (using the -install command line parameter) you can use the following code to start the service:

function StartService(Name: string): boolean;
var
  Scm: SC_HANDLE;
  Service: SC_HANDLE;
  p: pChar;
begin
  Result := false;
  Scm := OpenSCManager(nil, nil, SC_MANAGER_ALL_ACCESS);
  if Scm <> 0 then
  begin
    Service := OpenService(Scm, pChar(Name), SC_MANAGER_ALL_ACCESS);
    if Service <> 0 then
    begin
      p := nil;
      if WinSvc.StartService(Service, 0, p) then
        Result := True;
      CloseServiceHandle(Service);
    end;
    CloseServiceHandle(Scm);
  end;
end;

2006. április 19., szerda

Preload images


Problem/Question/Abstract:

I have a form with a TPageControl and a number of pages. On some pages, there is a TImage showing a JPEG picture. I want these images to be loaded when the form is created, not when they are going to be drawn. The problem is that it takes a long time to change page in the page control, if the new page hasn't been shown before.

Answer:

Try something like this:

procedure TForm1.FormCreate(Sender: TObject);
begin
  if Image1.Picture.Graphic is TJPEGImage then
  begin
    TJPEGImage(Image1.Picture.Graphic).DIBNeeded;
  end;
end;

It tells the JPEG to decode the image right away and not on demand as it otherwise would.

2006. április 18., kedd

Automatically loading a form "on demand"


Problem/Question/Abstract:

How can I get my forms created automatically "on demand" (when they are referenced)?

Answer:

If you have programmed in Visual Basic, probably you know what we are talking about: when you reference a property or a method of form, it is automatically created if necessary. For example, the following code will generate an exception in Delphi if Form2 was not previously created:

Form2.Show;

However it would work perfectly well in Visual Basic (without the semicolon, of course), and we can make it work it Delphi too with this little trick:

unit Unit2;

interface

uses...;

type
  TForm2 = class(TForm)
    ...
  end;

function Form2: TForm2;

var
  // Form2: TForm2;

implementation

{$R *.DFM}

var
  RealForm2: TForm2;

function Form2: TForm2;
begin
  if RealForm2 <> nil then
    Form2 := RealForm2
  else
    Application.CreateForm(TForm2, Result);
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  RealForm2 := Self;
end;

procedure TForm2.FormDestroy(Sender: TObject);
begin
  RealForm2 := nil;
end;

initialization
  RealForm2 := nil;
end.

What we did was replacing the Form2 variable with a function with the same name and type. This function uses a "hidden" variable (declared in the implementation section) -RealForm2- to check if the form is created or not (and in the latter case, it will create it automatically). We set the value of this hidden variable in the OnCreate and OnDestroy events of the form to the address of the form or nil respectively.

Copyright (c) 2001 Ernesto De Spirito
Visit: http://www.latiumsoftware.com/delphi-newsletter.php

2006. április 17., hétfő

How to parse a TRichEdit for domain names


Problem/Question/Abstract:

How can I parse a TRichEdit for domains ending in .com, .net, .org?

Answer:

Solve 1:

Not extensively tested:

procedure TForm1.Button1Click(Sender: TObject);
const
  charsAllowedInDomain = ['a'..'z', '0'..'9', '.', '_']; {may be more}
  numExts = 4;
  domainExts: array[1..numExts] of Pchar = ('.com', '.net', '.org', '.gov'); {lower case!}
  lens: array[1..numExts] of Integer = (4, 4, 4, 4);
var
  S: string;
  pStartString, pScan, pStartDomain, pEndDomain: Pchar;
  domain: string;
  i: Integer;
begin
  S := AnsiLowerCase(richedit1.text);
  pStartString := PChar(S);
  pScan := pStartString;
  while pScan^ <> #0 do
  begin
    if pScan^ = '.' then
    begin
      for i := Low(domainExts) to High(domainExts) do
        if StrLComp(pScan, domainExts[i], lens[i]) = 0 then
        begin
          {we have a candidate}
          pStartDomain := pScan;
          pEndDomain := pScan + lens[i];
          if not (pEndDomain^ in charsAllowedInDomain) then
          begin
            while (pStartDomain > pStartString) and (pStartDomain[-1] in charsAllowedInDomain) do
              Dec(pStartDomain);
            SetString(domain, pStartDomain, pEndDomain - pStartDomain);
            listbox1.items.add(domain);
            pScan := pEndDomain - 1;
            break;
          end;
        end;
    end;
    Inc(pScan);
  end;
end;


Solve 2:

{ ... }
type {declared in richedit.pas D3}

  TCharRange = record
    cpMin: Longint;
    cpMax: LongInt;
  end;

  TFindTextExA = record {declared in richedit.pas D3}
    chrg: TCharRange;
    lpstrText: PAnsiChar;
    chrgText: TCharRange;
  end;

procedure REFindDomain(RE: TRichEdit; const Target: string; Strs: TStrings);
const
  {maybe more than these?}
  ValidChars: set of char = ['a'..'z', 'A'..'Z', '0'..'9', '.', '/', ':', '_', '-'];
var
  ftx: TFindTextExA;
  flags: longint;
  charpos: longint;
  s: string;
begin
  if (Target = '') then
    exit; {nothing to look for}
  {searches all of the RichEdit}
  ftx.chrg.cpMin := 0;
  ftx.chrg.cpMax := -1;
  ftx.lpstrText := PChar(Target);
  ftx.chrgText.cpMin := 0;
  ftx.chrgText.cpMax := 0;
  flags := 0;
  // EM_FINDTEXTEX = WM_USER + 79;  {declared in richedit.pas D3}
  while SendMessage(RE.Handle, WM_USER + 79, flags, longint(@ftx)) > -1 do
  begin
    RE.SelStart := ftx.chrgText.cpMin; {found at position}
    RE.SelLength := Length(Target);
    {get the line}
    if ftx.chrgText.cpMax >= 255 then
      s := Copy(RE.Lines.Text, ftx.chrgText.cpMax - 254, 255)
    else
      s := Copy(RE.Lines.Text, 1, ftx.chrgText.cpMax);
    {need to find start of domain name}
    charpos := Length(s);
    while (charpos > 1) and (s[charpos] in ValidChars) do
      Dec(charpos);
    if not (s[charpos] in ValidChars) then
      Inc(charpos);
    Strs.Add(Copy(s, charpos, Length(s)));
    ftx.chrg.cpMin := ftx.chrgText.cpMin + 1; {reset to found at pos}
  end;
end;

{ListBox1 contains 3 lines: '.com'  '.net'  '.org',   ListBox2 receives the results}

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  if ListBox1.Items.Count > 0 then
  begin
    ListBox2.Clear;
    for i := 0 to ListBox1.Items.Count - 1 do
    begin
      REFindDomain(RichEdit1, ListBox1.Items[i], ListBox2.Items);
    end;
    Label1.Caption := IntToStr(ListBox2.Items.Count);
  end;
end;

2006. április 16., vasárnap

How to embed binary data in an executable (2)


Problem/Question/Abstract:

Is is possible to somehow embed an external executable file in a Delphi program? Or maybe another type of file for that matter like a zip file for instance. I want the file to be extractable from my program later on so that I can use it on another system.

Answer:

Here is a simple little component where you can save a file in the form file. You can add compression/decompression/encryption or whatever you like, but this demonstrates a possible storage process for data. Quick and simple to use.

unit Unit2;

interface

uses
  Classes, Sysutils;

type
  TFileResourceComponent = class(TComponent)
  private
    FStream: TMemoryStream;
    FFileName: TFileName;
    procedure SetFileName(const Value: TFileName);
    procedure ReadData(Stream: TStream);
    procedure WriteData(Stream: TStream);
  public
    procedure DefineProperties(Filer: TFiler); override;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property FileName: TFileName read FFileName write SetFileName;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('REDSYS', [TFileResourceComponent]);
end;

{ TFileResourceComponent }

constructor TFileResourceComponent.Create(AOwner: TComponent);
begin
  inherited;
  FStream := TMemoryStream.Create;
end;

procedure TFileResourceComponent.DefineProperties(Filer: TFiler);
begin
  inherited;
  Filer.DefineBinaryProperty('Data', ReadData, WriteData, True);
end;

destructor TFileResourceComponent.Destroy;
begin
  inherited;
  FStream.Free;
end;

procedure TFileResourceComponent.ReadData(Stream: TStream);
begin
  if not (csDesigning in ComponentState) then
    FStream.CopyFrom(Stream, Stream.Size);
end;

procedure TFileResourceComponent.SetFileName(const Value: TFileName);
begin
  FFileName := Value;
end;

procedure TFileResourceComponent.WriteData(Stream: TStream);
var
  FS:
  TMemoryStream;
begin
  if FileExists(FFileName) then
  begin
    FS := TMemoryStream.Create;
    try
      FS.LoadFromFile(FFileName);
      Stream.CopyFrom(FS, FS.Size);
    finally
      FS.Free;
    end;
  end;
end;

end.

2006. április 15., szombat

Init the BDE when it is located in another directory than the default one


Problem/Question/Abstract:

I need to use a BDE that is placed in another directory than default. How can I do it? DbiInit(pDbiEnv) doesn't work when pDbiEnv < > nil (not default).

Answer:

pDbiEnv := nil;
check(DbiInit(pDbiEnv));

or if you don't need the pointer simply

check(DbiInit(nil));

2006. április 14., péntek

Load DOS text in a RichEdit


Problem/Question/Abstract:

Load OEM file (any DOS edited file) in a RichEdit.

Answer:

Use the following code, that translates the text through the OemToAnsiBuff function:


procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  linea: PChar;
  txt: TStringList;
begin
  txt := TStringList.Create;
  try
    txt.LoadFromFile('c:\Fichero\a\leer.txt');
    for i := 0 to txt.Count - 1 do
    begin
      linea := PChar(txt.strings[i]);
      OemToAnsiBuff(linea, linea, strlen(linea));
    end;
    RichEdit1.Lines.AddStrings(txt);
  finally
    txt.Free;
  end;
end;

2006. április 13., csütörtök

Implode / Explode methods like in PHP


Problem/Question/Abstract:

In Delphi you can also use the implode and explode methods from PHP:

Answer:

type
  TDynStringArray = array of string;

function Implode(const Glue: string; const Pieces: array of string): string;
var
  I: Integer;
begin
  Result := '';
  for I := 0 to High(Pieces) do
    Result := Result + Glue + Pieces[I];
  Delete(Result, 1, Length(Glue));
end;

function Explode(const Separator, S: string; Limit: Integer = 0): TDynStringArray;
var
  SepLen: Integer;
  F, P: PChar;
begin
  SetLength(Result, 0);
  if (S = '') or (Limit < 0) then
    Exit;
  if Separator = '' then
  begin
    SetLength(Result, 1);
    Result[0] := S;
    Exit;
  end;
  SepLen := Length(Separator);

  P := PChar(S);
  while P^ <> #0 do
  begin
    F := P;
    P := AnsiStrPos(P, PChar(Separator));
    if (P = nil) or ((Limit > 0) and (Length(Result) = Limit - 1)) then
      P := StrEnd(F);
    SetLength(Result, Length(Result) + 1);
    SetString(Result[High(Result)], F, P - F);
    F := P;
    while (P^ <> #0) and (P - F < SepLen) do
      Inc(P); // n�chsten Anfang ermitteln
  end;
end;

2006. április 12., szerda

Debug your ISAPI applications on IIS 5


Problem/Question/Abstract:

Debug your ISAPI applications on IIS 5 without messing around with the registry. Just as simple as it should be!

Answer:

Start IIS WWW service;

Open Internet Services Manager and start your web site;

Open the properties sheet of your scripts directory;

Choose Aplication Protection = Low;

Click on Configuration button and choose the App Debugging tab;

Enable both Debugging Flags;

Close Internet Services Manager and stop IIS WWW service (do not stop your web site before stoping IIS);

Set your Delphi ISAPI Run Parameters as follows:
- host application: C:\WINNT\system32\inetsrv\inetinfo.exe (fix the path if necessary);
- parameters: -e w3svc;

That's it!

Now start your application inside Delphi IDE and it'll start IIS. Open your browser and go to your application. Delphi should start it and "say" it's running.

Good luck!

2006. április 11., kedd

How to read/ write a variable length string from/ to a TFileStream


Problem/Question/Abstract:

How to read/ write a variable length string from/ to a TFileStream

Answer:

Solve 1:

procedure WriteStringToFS(const s: string; const fs: TFileStream);
var
  i: integer;
begin
  i := 0;
  i := Length(s);
  if i > 0 then
    fs.WriteBuffer(s[1], i);
end;

function ReadStringFromFS(const fs: TFileStream): string;
var
  i: integer;
  s: string;
begin
  i := 0;
  s := '';
  fs.ReadBuffer(i, SizeOf(i));
  SetLength(s, i);
  fs.ReadBuffer(s, i);
  Result := s;
end;


Solve 2:

You should be using TWriter and TReader. They make this kind of thing really simple to do. Create a stream, writer and reader object at the form level, then instantiate them in the OnCreate and destroy them in the OnDestroy event.

Stream := TMemoryStream.Create; {Or whatever kind of stream}
Writer := TWriter.Create(Stream, 1024);
Reader := TReader.Create(Stream, 1024);

Once that's done, try something similar to the following...

procedure TForm1.WriteStringToFS(const S: string; Writer: TWriter);
begin
  try
    Writer.WriteString(S);
  except
    raise;
  end;
end;

function TForm1.ReadStringFromFS(Reader: TReader): string;
begin
  try
    Result := Reader.ReadString;
  except
    raise;
  end;
end;

No need to save the length of the string because the writer do this automatically. The only caveat is that you need to be sure to create the stream first and to destroy it last.

2006. április 10., hétfő

How to extract coordinates from a region


Problem/Question/Abstract:

I am trying to do regioning backwards. I am writing an application that will read in a bitmap, allow the user to set a transparent colour, and then calculate the point set that would be needed to make that region transparent. I then want to supply the user with the coordinates as a set of coordinates, i.e. I want to extract it from a region format. Why? Because that's how Winamp takes the data in to make its custom shaped forms. But I can't seem to figure out how to pull the data out.

Answer:

One thing I found is that you must create a region prior to GetWindowRgn. I thought that one was created by default. I made a function that does what you need:

procedure TForm1.ShowRgnInfo(Rgn: HRGN);
type
  RgnRects = array[0..1000] of TRect;
  PRgnRect = ^RgnRects;
var
  RgnData: PRgnData;
  Size: DWORD;
  i: Integer;
  R: TRect;
  RgnPtr: PRgnRect;
begin
  Size := GetRegionData(Rgn, 0, nil);
  Memo1.Lines.Add('Size = ' + IntToStr(Size));
  GetMem(RgnData, Size);
  GetRegionData(Rgn, Size, RgnData);
  Memo1.Lines.Add('Number of Rectangles = ' + IntToStr(RgnData.rdh.nCount));
  RgnPtr := @RgnData.Buffer;
  for i := 0 to RgnData.rdh.nCount - 1 do
  begin
    R := RgnPtr[i];
    Memo1.Lines.Add('Rect ' + IntToStr(i));
    Memo1.Lines.Add(IntToStr(R.Left) + ', ' + IntToStr(R.Top) + ', ' +
      IntToStr(R.Right) + ', ' + IntToStr(R.Bottom));
  end;
end;

2006. április 9., vasárnap

Making a screen shot (Windows has trouble with big resolutions)


Problem/Question/Abstract:

I just want to give you an example of making a screen shot in "tiles" and pasting the results yourself.

Answer:

Sometimes you want to take a screen shot, however often Windows has trouble with big data amounts and becomes very slow. The simple solution is to make many small screen shots and paste the result together. It's not light speed, however often faster than taking the whole screen at once.

const
  cTileSize = 50;

function TForm1.GetScreenShot: TBitmap;
var
  Locked: Boolean;
  X, Y, XS, YS: Integer;
  Canvas: TCanvas;
  R: TRect;
begin
  Result := TBitmap.Create;
  Result.Width := Screen.Width;
  Result.Height := Screen.Height;
  Canvas := TCanvas.Create;
  Canvas.Handle := GetDC(0);
  Locked := Canvas.TryLock;
  try
    XS := Pred(Screen.Width div cTileSize);
    if Screen.Width mod cTileSize > 0 then
      Inc(XS);
    YS := Pred(Screen.Height div cTileSize);
    if Screen.Height mod cTileSize > 0 then
      Inc(YS);
    for X := 0 to XS do
      for Y := 0 to YS do
      begin
        R := Rect(
          X * cTileSize, Y * cTileSize, Succ(X) * cTileSize,
          Succ(Y) * cTileSize
          );
        Result.Canvas.CopyRect(R, Canvas, R);
      end;
  finally
    if Locked then
      Canvas.Unlock;
    ReleaseDC(0, Canvas.Handle);
    Canvas.Free;
  end;
end;

2006. április 8., szombat

How to determine if a field's value has actually changed before posting the new value


Problem/Question/Abstract:

How to determine if a field's value has actually changed before posting the new value

Answer:

{ ... }
var
  sBeforeText: string;

  in the AfterEdit event of the table catch the value:

  SBeforeText := DataSet.FieldByName('Category').AsString;

  in the BeforePost or AfterPost Event(depending on your preference)you can compare the original with the current

  if (sBeforeText <> DataSet.FieldByName('Category').AsString) then
    ShowMessage('Different Values');

2006. április 7., péntek

How to create a Starfield Simulation


Problem/Question/Abstract:

How to create a Starfield Simulation

Answer:

procedure TForm1.Button1Click(Sender: TObject);
var
  tmp: Integer;
begin
  for tmp := 1 to Num_Stars do {Num_Stars is an Integer value}
    Canvas.Pixels[Random(ClientWidth), Random(ClientHeight)] := clWhite;
end;

And you could get more fancy than that (i.e alter star color / greylevel for brightness, vary star
positions for galaxys).

2006. április 6., csütörtök

Add a password to several Paradox tables in one step


Problem/Question/Abstract:

How to add a password to several Paradox tables in one step

Answer:

procedure BDEProtectTable(ATable: TTable; const APassword: string);
var
  CurPrp: CURProps;
  hDB: hDBIdb;
  TableDesc: CRTblDesc;
  DoEncrypt: boolean;
  bExcl, bOpen: boolean;
begin
  Check(DBIGetCursorProps(ATable.Handle, CurPrp));
  DoEncrypt := (APassword > '');
  with ATable do
  begin
    bOpen := Active;
    bExcl := Exclusive;
    if Active and not Exclusive then
      Close;
    if not Exclusive then
      Exclusive := True;
    if not Active then
      Open;
    {supply nulls (=default) for every optional parameter:}
    FillChar(TableDesc, SizeOf(CRTblDesc), 0);
    {supply indispensable parameters:}
    AnsiToNative(DBLocale, TableName, TableDesc.szTblName, DBIMAXTBLNAMELEN - 1);
    TableDesc.szTblType := CurPrp.szTableType;
    {supply parameters for our action here:}
    AnsiToNative(DBLocale, APassword, TableDesc.szPassword, 255);
    TableDesc.bProtected := DoEncrypt; {supply False to decrypt}
    hDB := DBHandle;
    Close;
    {do the restructure:}
    try
      Check(DBIDoRestructure(hDB, 1, @TableDesc, nil, nil, nil, False));
    finally
      Exclusive := bExcl;
      Active := bOpen;
    end;
  end;
end;

2006. április 5., szerda

Send a file from a TServerSocket to a TClientSocket


Problem/Question/Abstract:

How i can send a file from a TServerSocket to a TClientSocket?

Answer:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ScktComp, StdCtrls;

type
  TForm1 = class(TForm)
    ClientSocket1: TClientSocket;
    ServerSocket1: TServerSocket;
    btnTestSockets: TButton;
    procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure ClientSocket1Disconnect(Sender: TObject;
      Socket: TCustomWinSocket);
    procedure ClientSocket1Connect(Sender: TObject;
      Socket: TCustomWinSocket);
    procedure ServerSocket1ClientConnect(Sender: TObject;
      Socket: TCustomWinSocket);
    procedure btnTestSocketsClick(Sender: TObject);
  private
    FStream: TFileStream;

  public

  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.ClientSocket1Read(Sender: TObject;
  Socket: TCustomWinSocket);
var
  iLen: Integer;
  Bfr: Pointer;
begin
  iLen := Socket.ReceiveLength;
  GetMem(Bfr, iLen);
  try
    Socket.ReceiveBuf(Bfr^, iLen);
    FStream.Write(Bfr^, iLen);
  finally
    FreeMem(Bfr);
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FStream := nil;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  if Assigned(FStream) then
  begin
    FStream.Free;
    FStream := nil;
  end;
end;

procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  if Assigned(FStream) then
  begin
    FStream.Free;
    FStream := nil;
  end;
end;

procedure TForm1.ClientSocket1Connect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  FStream := TFileStream.Create('c:\temp\test.stream.html', fmCreate or
    fmShareDenyWrite);
end;

procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
  Socket: TCustomWinSocket);
begin
  Socket.SendStream(TFileStream.Create('c:\temp\test.html', fmOpenRead or
    fmShareDenyWrite));
end;

procedure TForm1.btnTestSocketsClick(Sender: TObject);
begin
  ServerSocket1.Active := True;
  ClientSocket1.Active := True;
end;

end.

2006. április 4., kedd

Implement an inactivity timer with automatic logout


Problem/Question/Abstract:

Anyone have suggestions on how to implement an inactivity timer? My application has passwords for various functions, and I need to be able to automatically logout a user if they've been inactive for 10 minutes, etc.. I suppose I'd tie into the messaging queue, looking at keyboard and mouse events. The implementation should cover activity related to any form in my application, but not any activity outside the application.

Answer:

In your main form, add the integer variables "ShutdownCounter" and "ShutDownDelay". Add a TApplicationEvents and a TTimer control. Set the timer interval to, say, 5000 mSecs. In the form's OnCreate event handler, add:

{ ... }
    {Set up the automatic log off routine. Get the users auto logoff time,
                which defaults to 20 minutes. 0 is never autologoff}
shutDownDelay := UserIni.ReadInteger('Settings', 'Auto Shutdown Delay', 20);
shutDownDelay := shutDownDelay * 60;
ShutdownCounter := 0;
if shutDownDelay > 0 then
  timShutDown.Enabled := true;
    {Enable the timer if you want to use a timeout for this user}

This format allows you to add different logoff times for different users, or completely disable autologoff - I do this on my development system.

In the TApplicationEvents OnMessage event handler, add code to check for keypresses, or left mouse button clicks (or any other message you want to use to keep the app running). Whenever any of these messages are received by the application, reset the ShutDownCounter to zero.

procedure TfrmAutoProMain.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
begin
  case Msg.message of
    WM_KEYDOWN, WM_LBUTTONDOWN:
      ShutdownCounter := 0;
  end;
end;

In the TTimer OnTimer event handler, add code to compare the current value of ShutDownCounter against the ShutDownDelay for this user. If the counter is larger than the delay, then we need to exit the application. In my apps, I actually show another window with a 30 second decrementing progress bar which gives the user notification that the app is about to shutdown, and gives him a chance to keep the app alive - that's the references to dlgAutoLogOff .

procedure TfrmAutoProMain.timShutDownTimer(Sender: TObject);
begin
  Inc(ShutdownCounter, 5);
    {Increase counter by 5 seconds (if TTimer interval was 5000)}
  if ShutdownCounter >= shutDownDelay then
  begin
    timShutDown.Enabled := false;
    {The next block handles a "last chance" warning dialog to allow the user
                to stay alive}
    dlgAutoLogOff := TdlgAutoLogOff.Create(self);
    try
      dlgAutoLogOff.Show;
      repeat
        Application.ProcessMessages;
      until
        (dlgAutoLogOff.ModalResult = mrOK) or (dlgAutoLogOff.ModalResult = mrAbort);
      if dlgAutoLogOff.ModalResult = mrOK then
      begin
        ShutdownCounter := 0;
        timShutDown.Enabled := true;
      end
      else
        Application.Terminate;
    finally
      dlgAutoLogOff.Free;
    end;
  end;
end;

2006. április 3., hétfő

How to determine which combinations of rows and columns have been checked off in an array of TCheckBoxes


Problem/Question/Abstract:

I have a form with checkboxes in 8 rows and 7 columns. I want to be able to determine which combinations of rows and columns have been checked off. Is the best way to do this using a 2-D array? How would I declare this array for the checkboxes?

Answer:

FArray: array[0..7, 0..6] of TCheckBox;

You also you have to create Checkboxes at runtime, like:

FArray[i, j] := TCheckBox.Create(Self);

I would suggest to use the Tag property instead. Assign the same OnClick event handler to all checkboxes and code the Tag for each checkbox. Say 23 (second column, third row), you know like matrix indices in math:

....OnClick(Sender: TObject);
var
  ATag: Integer;
begin
  if (Sender is TComponent) then
  begin
    ATag := TComponent(Sender).Tag;
    ShowMessage(Format('Column %d, Row %d', [ATag div 10, ATag mod 10]));
  end;
end;

2006. április 2., vasárnap

How to identify detail tables linked to a master table


Problem/Question/Abstract:

How can I retrieve the name of the detail tables of some master table? How can I know if a table has a detail table linked? Is there any property or function in the table or query to get the details they have?

Answer:

One way to identify linked detail tables is to scan the form or data module's components array:

for I := 0 to Pred(Component.Count) do
  if Components[I] is TTable then
    if TTable(Components[I]).DataSource <> nil then
      { do whatever }

2006. április 1., szombat

How to embed binary data in an executable (3)


Problem/Question/Abstract:

Does anyone have experience using Delphi to create program that can create standalone exe that contains code and data like Picture2exe? This program creates a stand alone executable exe file that contains image and sound data that plays them in a slideshow. What is the approach and techniques used?

Answer:

Try this code where discclone.res includes the file you want to include:

procedure TMain.mnuCreateClick(Sender: TObject);
var
  MyFile: TFileStream;
  MyAppend: TMemoryStream;
begin
  if diagOpenSelf.Execute then
  begin
    if diagCreateSelf.Execute then
    begin
      CopyFile(PChar(ExtractFilePath(ParamStr(0)) + '\Extractor.exe'),
        PChar(diagCreateSelf.FileName), False);
      {Create a filestream object for the extractor executable}
      MyFile := TFileStream.Create(diagCreateSelf.FileName, $0002);
      try
        MyAppend := TMemoryStream.Create;
        try
          MyAppend.LoadFromFile(diagOpenSelf.FileName);
          MyFile.Seek(0, soFromEnd);
          MyFile.CopyFrom(MyAppend, 0);
          MessageBox(0, 'File was successfully created.', 'File Created',
            MB_OK + MB_ICONINFORMATION);
        finally
          MyAppend.Free;
        end;
      finally
        MyFile.Free;
      end;
    end;
  end;
end;

program Extractor;

{$R DiscClone.res}

uses
  Windows, Classes, ShellAPI, Sysutils;

const
  FileSize = 64512;
  {Or 60416. You may have to change to this number to the size of the
        compiled Extractor executable - minus the appended executable of course.}
var
  {MyExtract: TFileStream;}
  MyFile: TMemoryStream;
  TempStream: TMemoryStream;
  FileExe: string;
  Buffer: array[0..260] of Char;
  Count: DWord;
  Buf: Pointer;
  G: THandle;
  Res: LongBool;
begin
  { ... }
  {ask to make sure}
  { ... }
  {check floppy in drive}
  { ... }
  TempStream := TMemoryStream.Create;
  {Create the memory stream which will hold a copy of this executable in memory}
  MyFile := TMemoryStream.Create;
  try
    SetString(FileExe, Buffer, GetModuleFileName(0, Buffer,
      SizeOf(Buffer))); {What is the name of this executable?}
    MyFile.LoadFromFile(FileExe); {Load a copy of the executable into memory}
    {A filestream which will eventually create the HelloWorld program}
    // MyExtract := TFileStream.Create('dummy.floppy', fmCreate);
    try
      MyFile.Seek(FileSize, 0);
      Move the stream pointer to the start of the appended executable}
        {Copy the appended data to our filestream buffer - this creates the file}
    // MyExtract.CopyFrom(MyFile, MyFile.Size - FileSize);
      TempStream.CopyFrom(MyFile, MyFile.Size - FileSize);
    finally
      //  MyExtract.Free;  {Free the filestream object}
    end;
    {Tell the user that extraction went well and ask to run HelloWorld}
    G := CreateFile('\\.\A:', GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING,
                          FILE_ATTRIBUTE_NORMAL, 0);
    //  F := CreateFile(PChar('\\.\' + location), GENERIC_READ or GENERIC_WRITE,
                0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    GetMem(Buf,1457664);  {1457664}
    //  SetFilePointer(F, 0, nil, FILE_BEGIN);
    //  ReadFile(F, Buf^, 1457664, Count, nil);
    //  Buf:=@MyExtract; //new
    WriteFile(G, Pointer(TempStream)^, 1457664, Count, nil);
    //  WriteFile(G, Buf^, 1457664, Count, nil);
    //  ShowMessage(IntToStr(GetLastError));
    FreeMem(Buf);
    //  CloseHandle(F);
    CloseHandle(G);
    
      {  G := CreateFile(PChar('\\.\C:\Work\Boot\TestCenter\Fred.txt'),
                                GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_ALWAYS,
                                FILE_ATTRIBUTE_NORMAL, 0);}
    G := CreateFile('\\.\A:', GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING,
      FILE_ATTRIBUTE_NORMAL, 0);
    GetMem(Buf, 1474560);
    TempStream.Position := 0;
    TempStream.Read(Buf^, 1474560);
    res := WriteFile(G, Buf^, 1474560, Count, nil);
    if not res then
      MessageBox(0, PChar(IntToStr(GetLastError)),
        PChar(SysErrorMessage(GetLastError)),
        MB_OK);
    CloseHandle(G);
    FreeMem(Buf);
    //  FlushFileBuffers(MyFileStream.Handle);
    MessageBox(0, PChar(IntToStr(TempStream.size)), 'Extraction successful!',
      MB_OK + MB_ICONQUESTION)
  finally
    {Free the memoerystream object}
    MyFile.Free;
  end;
  TempStream.Free;
end.