2011. június 7., kedd

Delphi Does ADO


Problem/Question/Abstract:

ADO Overview

Answer:

The New Way to Get to Data

Universal Data Access (UDA) is part of Microsoft's strategy to provide fast access to data in both relational and non-relational data stores. UDA provides a language-independent, easy-to-use API for accessing data in any data source that has a UDA-compatible driver. Like the BDE, this technology makes it easy to access data from multiple data sources in a single program. UDA is implemented using the Microsoft Data Access Components (MDAC), which includes Active Data Objects (ADO), Open Database Connectivity (ODBC), and OLE DB.

ADO is the application programming interface of MDAC, while OLE DB is the system-level interface. OLE DB defines a suite of COM interfaces that provide all the data access capabilities required by any data source, from a relational database to a file system. ODBC is included in MDAC for backward compatibility. While existing ODBC drivers will likely be replaced by OLE DB providers in the future, the Microsoft OLE DB provider for ODBC lets you use any ODBC driver via ADO now. Although ADO is relatively new, OLE DB providers are already available for Microsoft Access, Microsoft SQL Server, and Oracle.

Another major advantage of ADO is that it will be built into all future Microsoft operating systems, including Windows 2000. While this means that today you must install ADO on each PC that will use ADO to access data, that task will vanish in the future. If you want to learn more about UDA and ADO, visit Microsoft's data access Web site at http://www.microsoft.com/data/default.htm. From this page, you can download the ADO redistributable, which allows you to install ADO on Windows 95/98/NT machines, or the MDAC SDK, which contains complete documentation and everything you need to develop your own OLE DB providers. The SDK also includes the ADO redistributable.

Everything you need to use ADO with Delphi is on the Delphi 5 CD, including MDAC. Simply go to the MDAC folder on the Delphi 5 CD and run the installation program, MDAC_TYP.EXE. The MDAC installation program is a single EXE file, so it's easy to install MDAC anywhere you need it. You can also use the MDAC installation program to install MDAC as part of your application's installation if you're using an installation program that supports calling EXEs (InstallShield Express does not). If you're installing MDAC as part of your application's installation, you'll want to use the "silent" mode to suppress all screen displays. To install in silent mode, use the command:

mdac_typ.exe /q:a /c:"setup.exe /qt"

For more information on installing MDAC, including file lists and dependencies, see the MDAC SDK documentation.

Using the ADOConnection and ADODataSet Components

Delphi 5 has a suite of six new components that provide complete ADO support, and an easy way to convert existing applications to ADO. To begin building an ADO application, drop an ADOConnection component on a form or data module. The ADOConnection component is the ADO equivalent of the BDE Database component. It allows you to define a connection to a database using its ConnectionString property.

While it's possible to build a connection string manually, it is difficult. The ADO connection string consists of a semicolon-delimited list of many parameters that can easily exceed 150 characters. Fortunately, Microsoft provides a Connection String Editor to make this job easier. To open the Connection String Editor, shown in Figure 1, click the ellipsis button in the ConnectionString property's edit box, or double-click on the component.


Figure 1: The Connection String Editor.

The easy way to build a connection string is to click the Build button to display the Data Link Properties dialog box, shown in Figure 2. The Provider page lets you choose the driver you want to use.


Figure 2: The Data Link Properties dialog box.

What you see on the Connection page depends on the provider you select. Figure 3 shows the Connection page with the Microsoft Jet provider selected, and the path to an Access database entered.


Figure 3: The Connection page.

The Advanced page, shown in Figure 4, lets you specify the type of access to the database, and the All page (see Figure 5) lets you edit any value in the connection string. The All page is particularly important if you're connecting to an Access database with user-level security, because it's the only place you can enter the path to the system database.


Figure 4: The Advanced page.


Figure 5: The All page.

Once a value has been assigned to the ConnectionString property, you can set the Connected property to True, at design or run time, to connect to the database. The ADOConnection component also provides transaction support through its BeginTrans, CommitTrans, and RollbackTrans methods.

The ADODataSet component is really the only one you need to work with data because it allows you to work directly with a table, execute a SQL statement, work with the result set, or call a stored procedure. After dropping an ADODataSet on a form or data module, the first step is to set its Connection property. The Connection property's drop-down list will display all the available ADOConnection components. Next, you need to set two related properties: CommandType and CommandText. Set CommandType first because it determines how CommandText is interpreted. You can set CommandType to indicate that you want to connect directly to a table, call a stored procedure, or enter a SQL statement as text. Choosing cmdTable as the CommandType causes the drop-down list for the CommandText property to display the tables in the database.

Once CommandType and CommandText have been set, using the ADO components is exactly like working with the BDE dataset components. Drop a DataSource component, a DBNavigator, and some data-aware components on your form. Set the DataSet property of the DataSource to the ADODataSet component, and set the DataSource property of the navigator and data-aware controls.

Figure 6 shows a data module containing an ADOConnection, two ADODataSet components, and two DataSource components modeling a one-to-many relationship between two tables in an Access database. The master table is FailureAdoDs, and the detail table is RepairTimeAdoDs. The datasets were linked by setting the DataSource property of the detail dataset to the DataSource component of the master dataset, then setting the MasterFields property of the detail dataset.


Figure 6: Linked ADODataSet components.

The property editor for the MasterFields property is the Field Link Designer, shown in Figure 7. To link the tables, select the master and detail fields that define the relationship between the tables, and click the Add button. In this example, the TrackingNumber field links the tables. If the relation is defined by more than one field, repeat the process of selecting the corresponding master and detail fields and clicking the Add button.


Figure 7: The Field Link Designer.

To use an ADODataSet with a query result set, change the CommandType to cmdText and enter the SQL statement in the CommandText property. With the CommandType set to cmdText, the property editor for the CommandText property changes to the Command Text Editor, shown in Figure 8.


Figure 8: The Command Text Editor.

The Command Text Editor is a major improvement over the String List Editor, used to edit SQL commands in previous versions of Delphi. It provides a list of tables, and a button to add the table name to the SQL statement, as well as a list of field names for the selected table. Even if you don't use the Add buttons, the list of table and field names is very handy. Creating a one-to-many link between the ADODataSet components that execute SQL statements is exactly the same as linking two BDE Query components. The SQL statement for the detail dataset is:

SELECT *
  FROM RepairTime
WHERE TrackingNumber = :TrackingNumber

The name of the parameter in the WHERE clause, :TrackingNumber, matches the name of the primary key in the master table exactly. The detail dataset's DataSource property is set to the master table's DataSource component. Because these two conditions have been met, each time the master dataset is positioned to a new record, the detail dataset is automatically closed, the new value from the master record is assigned to the query parameter, and the detail dataset is opened to retrieve the new set of detail records.

If you will execute a query more than once with different parameters, set the ADODataSet's Prepared property to True. This will cause the query plan to be prepared and stored the first time the query is executed. The stored plan will be used for each subsequent execution. This eliminates the time required to parse and optimize the query for all executions except the first.

To work with a stored procedure, set the CommandType to cmdStoredProc, and choose the stored procedure from the CommandText property's drop-down list. Use the ADODataSet's Parameters property to assign values to input parameters and retrieve values from output parameters.

Although you can do everything with the ADODataSet component, Delphi 5 also includes the ADOTable, ADOQuery, and ADOStoredProc components. These are designed to resemble the BDE Table, Query, and StoredProc components as closely as possible to make converting an application to ADO easy.

Should You Convert to ADO?

Why convert an existing application from BDE to ADO? Neither the native BDE Access driver nor the Access ODBC driver have been ideal solutions for working with Access databases. Using the ADO Jet driver eliminates these problems. With ADO, your Access applications will correctly detect changes made by other users and warn you when you try to post a record that has been changed by another user since you read it. Also, Autoincrement fields work correctly with default values set for other fields.

The big advantage of using ADO with any database, however, is that you are no longer dependent on Borland to update drivers when new releases of the database appear. When a new version of SQL Server or Oracle is released, the new ADO drivers should be available at the same time, and should work because the database vendor writes them.

The ADOCommand Component

In addition to the components for working with datasets, Delphi 5 also provides the ADOCommand component. The ADOCommand component is most useful for executing commands that don't return a result set, such as SQL DDL (Data Definition Language) commands, or a SQL DELETE query.

If you're using one or more ADOConnection components, click the drop-down button in the Connection property of the ADOCommand component and select the connection you want to use. The ADOCommand component, like all the ADO dataset components, has its own ConnectionString property so you don't have to use an ADOConnection component. However, in most cases, you'll want to. The connection component provides a single central place to change the ConnectionString and any other connection-related properties, as well as providing transaction control methods.

The CommandText property of the ADOCommand component contains the command you want to execute, and the CommandType property determines whether CommandText is interpreted as a text string, table name, or stored procedure name. Set CommandType to ctText to execute a SQL statement. If the SQL statement includes parameters, you can set their properties using the Parameters property editor of the ADOCommand component. Although it makes no sense to use the ADOCommand component to retrieve a dataset from a table, query, or stored procedure, you can do it. The ADOCommand's Execute method returns the recordset generated by the command, if any. You can assign the returned recordset to the RecordSet property of an ADODataSet to view the records.

Cursor Types

If you're accustomed to working with the BDE dataset components, there are a number of things you'll find different when you use ADO. One of the most striking is the choice of four different cursor types, which you can set using the CursorType property of ADODataSet. The first is ctStatic, which provides a static dataset that you cannot edit, and that will not show any changes made by other users. A static cursor behaves like the result set from a BDE Query component with its RequestLive property set to False.

Choosing ctOpenForwardOnly provides a cursor that is identical to a static cursor, except that you can only move forward through the dataset. A forward-only cursor is very efficient and is ideal for generating reports. Setting the CursorType to ctDynamic provides a cursor that allows you to navigate both forward and backward, as well as see all additions, deletions, and changes made by other users. The ctKeySet cursor type is identical to ctDynamic except that you can't see records added by other users.

ADO also provides a CursorLocation property with two possible values: clUseClient and clUseServer. Client cursors are somewhat similar to data provided to a MIDAS ClientDataSet, in that all the data is downloaded to the client immediately. For a large dataset, this can impose a significant penalty in time and memory usage. However, client cursors are almost always updateable, support bookmarks, and allow scrolling in both directions. This may not always be true with server cursors. The features available with server cursors will depend on the database and the OLE DB provider you're using.

Transaction Isolation Levels

ADO supports the ANSI SQL-92 standard transaction isolation levels, which are slightly different than those supported by the Delphi Database component's TransIsolation property. ADO supports the following four isolation levels:

Read Uncommitted. Read Uncommitted is also called Dirty Read or Browse isolation. At this level of isolation, a transaction can see uncommitted changes made by other transactions.
Read Committed. A transaction at this level cannot see uncommitted changes made by other transactions, but can see committed changes. This means that reading the same record twice may give two different values because the record could have been changed by another transaction that has committed. If a query is re-executed within the transaction, it can also return new records that have been added by another committed transaction that it did not see the first time the query ran.
Repeatable Read. A Repeatable Read transaction will not see any changes made by other transactions to records it has read, even if the other transactions have committed. However, if a query is re-executed within the transaction, it will see new records added by other committed transactions.
Serializable. This isolation level requires that all concurrent transactions interact in ways that produce the same result as though the transactions executed sequentially. A transaction at this level will not see either changed or newly inserted records from other committed transactions.

Of course, the isolation level that you actually get when you choose one of these options depends on the isolation levels that the database you're using supports.

Conclusion

ADO support is the single most important feature for database application developers in Delphi 5. As Microsoft builds ADO into its next generation of operating systems, you'll no longer have to install additional software with your application to access databases. Perhaps more important is the range of data that ADO will provide access to in the future. Looking beyond relational databases, ADO will provide access to e-mail system message stores, the file system on your hard disk, and any other data store in a Microsoft product.

With the full power of Microsoft behind it, ADO will certainly be adopted by other vendors with products that store data. Finally, ADO relieves Borland of the burden of writing drivers. That is a bigger benefit to you than to Borland because it means you'll get better drivers faster, as new versions of data storage products ship. Best of all, because ADO drivers for Access, SQL Server, and Oracle are already available, and because ADO includes an ODBC provider, you can start using it right now.

2011. június 6., hétfő

How to convert a String To a PChar and PChar to String


Problem/Question/Abstract:

How to convert a String To a PChar and PChar to String

Answer:

function ConvertStringToPChar(StringValue: string): PChar;
var
  PCharString: array[0..255] of Char;
begin
  Result := StrPCopy(PCharString, StringValue);
end;

function ConvertPCharToString(PCharValue: PChar): string;
begin
  Result := StrPas(PCharValue);
end;

2011. június 5., vasárnap

BDE error codes


Problem/Question/Abstract:

BDE error codes

Answer:

Additional information about BDE error codes are available in file \DELPHI\DOC\DBIERRS.INT.


0 0000 Successful completion.
33 0021 System Error
34 0022 Object of Interest Not Found
35 0023 Physical Data Corruption
36 0024 I/O Related Error
37 0025 Resource or Limit Error
38 0026 Data Integrity Violation
39 0027 Invalid Request
40 0028 Lock Violation
41 0029 Access/Security Violation
42 002A Invalid Context
43 002B OS Error
44 002C Network Error
45 002D Optional Parameter
46 002E Query Processor
47 002F Version Mismatch
48 0030 Capability Not Supported
49 0031 System Configuration Error
50 0032 Warning
51 0033 Miscellaneous
52 0034 Compatibility Error
62 003E Driver Specific Error
63 003F Internal Symbol
256 0100 KEYVIOL
257 0101 PROBLEMS
258 0102 CHANGED
512 0200 Production Index file missing, corrupt or cannot interpret index key
513 0201 Open Read Only
514 0202 Open the table in read only mode
515 0203 Open and Detach
516 0204 Open the table and detach the Production Index file
517 0205 Fail Open
518 0206 Do not open the table
519 0207 Convert Non-dBase Index
520 0208 Convert production index to dBase format
521 0209 BLOB file not found
522 020A Open without blob file
523 020B Open the table without the blob file
524 020C Empty all blob fields
525 020D Reinitialize BLOB file and LOSE all blobs
526 020E Fail Open
527 020F Do not open the table
528 0210 Import Non-dBASE BLOB file
529 0211 Import BLOB file to dBASE format
530 0212 Open as Non-dBASE table
531 0213 Open Table and BLOB file in its native format
532 0214 Production Index Language driver mismatch
533 0215 Production Index damaged
534 0216 Rebuild Production Index
535 0217 Rebuild all the Production Indexes
1024 0400 Lookup table not found or corrupt
1025 0401 Blob file not found or corrupt
1026 0402 Open Read Only
1027 0403 Open the table in read only mode
1028 0404 Fail Open
1029 0405 Do not open the table
1030 0406 Remove lookup
1031 0407 Remove link to lookup table
2048 0800 Reading records
2049 0801 Sorting records
2050 0802 Writing records
2051 0803 Merging
2052 0804 Steps Completed
2053 0805 Packing records
2309 0905 LIKE
2310 0906 NOT
2320 0910 INSERT
2321 0911 DELETE
2322 0912 CHANGETO
2323 0913 CHANGE
2324 0914 TO
2325 0915 FIND
2326 0916 CALC
2327 0917 COUNT
2328 0918 SUM
2329 0919 AVERAGE
2330 091A MAX
2331 091B MIN
2332 091C ALL
2333 091D UNIQUE
2334 091E BLANK
2335 091F TODAY
2336 0920 AS
2337 0921 DESCENDING
2338 0922 OR
2339 0923 ONLY
2340 0924 EVERY
2341 0925 NO
2342 0926 EXACTLY
2343 0927 SET
2347 092B %time
2348 092C %date
2353 0931 %lower
2354 0932 %upper
2355 0933 %trim
2356 0934 %substring
2364 093C __QB0000
2365 093D ANSWER
2366 093E DELETED
2367 093F INSERTED
2368 0940 CHANGED
2369 0941 ERRORDEL
2370 0942 ERRORINS
2371 0943 ERRORCHG
2372 0944 __XLTTMP
2373 0945 __QBEDIC
2405 0965 JAN
2406 0966 FEB
2407 0967 MAR
2408 0968 APR
2409 0969 MAY
2410 096A JUN
2411 096B JUL
2412 096C AUG
2413 096D SEP
2414 096E OCT
2415 096F NOV
2416 0970 DEC
2423 0977 INSERTED.DB
2424 0978 CHANGED.DB
2425 0979 DELETED.DB
2426 097A ANSWER.DB
2427 097B blank
2428 097C Sum of
2429 097D Average of
2430 097E Count of
2431 097F Max of
2432 0980 Min of
2433 0981 GROUPBY
2434 0982 FIELDORDER
2435 0983 SORT
2436 0984 ANSWER
2437 0985 TYPE
2438 0986 OPTIONS
2440 0988 GENERATE AUXILIARY TABLES
2441 0989 NO AUXILIARY TABLES
2442 098A SERVER
2443 098B LOCAL
2444 098C CANNED
2445 098D LIVE
2446 098E SPEED
2447 098F %extract
2448 0990 DATE
2449 0991 TIME
2450 0992 YEAR
2451 0993 MONTH
2452 0994 DAY
2453 0995 HOUR
2454 0996 MINUTE
2455 0997 SECOND
8449 2101 Cannot open a system file.
8450 2102 I/O error on a system file.
8451 2103 Data structure corruption.
8452 2104 Cannot find Engine configuration file.
8453 2105 Cannot write to Engine configuration file.
8454 2106 Cannot initialize with different configuration file.
8455 2107 System has been illegally re-entered.
8456 2108 Cannot locate IDAPI01.DLL
8457 2109 Cannot load IDAPI01.DLL
8458 210A Cannot load an IDAPI service library
8705 2201 At beginning of table.
8706 2202 At end of table.
8707 2203 Record moved because key value changed.
8708 2204 Record/Key deleted.
8709 2205 No current record.
8710 2206 Could not find record.
8711 2207 End of BLOB.
8712 2208 Could not find object.
8713 2209 Could not find family member.
8714 220A BLOB file is missing.
8715 220B Could not find language driver.
8961 2301 Corrupt table/index header.
8962 2302 Corrupt file - other than header.
8963 2303 Corrupt Memo/BLOB file.
8965 2305 Corrupt index.
8966 2306 Corrupt lock file.
8967 2307 Corrupt family file.
8968 2308 Corrupt or missing .VAL file.
8969 2309 Foreign index file format.
9217 2401 Read failure.
9218 2402 Write failure.
9219 2403 Cannot access directory.
9220 2404 File Delete operation failed.
9221 2405 Cannot access file.
9222 2406 Access to table disabled because of previous error.
9473 2501 Insufficient memory for this operation.
9474 2502 Not enough file handles.
9475 2503 Insufficient disk space.
9476 2504 Temporary table resource limit.
9477 2505 Record size is too big for table.
9478 2506 Too many open cursors.
9479 2507 Table is full.
9480 2508 Too many sessions from this workstation.
9481 2509 Serial number limit (Paradox).
9482 250A Some internal limit (see context).
9483 250B Too many open tables.
9484 250C Too many cursors per table.
9485 250D Too many record locks on table.
9486 250E Too many clients.
9487 250F Too many indexes on table.
9488 2510 Too many sessions.
9489 2511 Too many open databases.
9490 2512 Too many passwords.
9491 2513 Too many active drivers.
9492 2514 Too many fields in Table Create.
9493 2515 Too many table locks.
9494 2516 Too many open BLOBs.
9495 2517 Lock file has grown too large.
9496 2518 Too many open queries.
9498 251A Too many BLOBs.
9729 2601 Key violation.
9730 2602 Minimum validity check failed.
9731 2603 Maximum validity check failed.
9732 2604 Field value required.
9733 2605 Master record missing.
9734 2606 Master has detail records. Cannot delete or modify.
9735 2607 Master table level is incorrect.
9736 2608 Field value out of lookup table range.
9737 2609 Lookup Table Open operation failed.
9738 260A Detail Table Open operation failed.
9739 260B Master Table Open operation failed.
9740 260C Field is blank.
9741 260D Link to master table already defined.
9742 260E Master table is open.
9743 260F Detail table(s) exist.
9744 2610 Master has detail records. Cannot empty it.
9745 2611 Self referencing referential integrity must be entered one at a time with no other changes to the table
9746 2612 Detail table is open.
9747 2613 Cannot make this master a detail of another table if its details are not empty.
9748 2614 Referential integrity fields must be indexed.
9749 2615 A table linked by referential integrity requires password to open.
9750 2616 Field(s) linked to more than one master.
9985 2701 Number is out of range.
9986 2702 Invalid parameter.
9987 2703 Invalid file name.
9988 2704 File does not exist.
9989 2705 Invalid option.
9990 2706 Invalid handle to the function.
9991 2707 Unknown table type.
9992 2708 Cannot open file.
9993 2709 Cannot redefine primary key.
9994 270A Cannot change this RINTDesc.
9995 270B Foreign and primary key do not match.
9996 270C Invalid modify request.
9997 270D Index does not exist.
9998 270E Invalid offset into the BLOB.
9999 270F Invalid descriptor number.
10000 2710 Invalid field type.
10001 2711 Invalid field descriptor.
10002 2712 Invalid field transformation.
10003 2713 Invalid record structure.
10004 2714 Invalid descriptor.
10005 2715 Invalid array of index descriptors.
10006 2716 Invalid array of validity check descriptors.
10007 2717 Invalid array of referential integrity descriptors.
10008 2718 Invalid ordering of tables during restructure.
10009 2719 Name not unique in this context.
10010 271A Index name required.
10011 271B Invalid session handle.
10012 271C invalid restructure operation.
10013 271D Driver not known to system.
10014 271E Unknown database.
10015 271F Invalid password given.
10016 2720 No callback function.
10017 2721 Invalid callback buffer length.
10018 2722 Invalid directory.
10019 2723 Translate Error. Value out of bounds.
10020 2724 Cannot set cursor of one table to another.
10021 2725 Bookmarks do not match table.
10022 2726 Invalid index/tag name.
10023 2727 Invalid index descriptor.
10024 2728 Table does not exist.
10025 2729 Table has too many users.
10026 272A Cannot evaluate Key or Key does not pass filter condition.
10027 272B Index already exists.
10028 272C Index is open.
10029 272D Invalid BLOB length.
10030 272E Invalid BLOB handle in record buffer.
10031 272F Table is open.
10032 2730 Need to do (hard) restructure.
10033 2731 Invalid mode.
10034 2732 Cannot close index.
10035 2733 Index is being used to order table.
10036 2734 Unknown user name or password.
10037 2735 Multi-level cascade is not supported.
10038 2736 Invalid field name.
10039 2737 Invalid table name.
10040 2738 Invalid linked cursor expression.
10041 2739 Name is reserved.
10042 273A Invalid file extension.
10043 273B Invalid language Driver.
10044 273C Alias is not currently opened.
10045 273D Incompatible record structures.
10046 273E Name is reserved by DOS.
10047 273F Destination must be indexed.
10048 2740 Invalid index type
10049 2741 Language Drivers of Table and Index do not match
10050 2742 Filter handle is invalid
10051 2743 Invalid Filter
10052 2744 Invalid table create request
10053 2745 Invalid table delete request
10054 2746 Invalid index create request
10055 2747 Invalid index delete request
10056 2748 Invalid table specified
10058 274A Invalid Time.
10059 274B Invalid Date.
10060 274C Invalid Datetime
10061 274D Tables in different directories
10062 274E Mismatch in the number of arguments
10063 274F Function not found in service library.
10064 2750 Must use baseorder for this operation.
10065 2751 Invalid procedure name
10241 2801 Record locked by another user.
10242 2802 Unlock failed.
10243 2803 Table is busy.
10244 2804 Directory is busy.
10245 2805 File is locked.
10246 2806 Directory is locked.
10247 2807 Record already locked by this session.
10248 2808 Object not locked.
10249 2809 Lock time out.
10250 280A Key group is locked.
10251 280B Table lock was lost.
10252 280C Exclusive access was lost.
10253 280D Table cannot be opened for exclusive use.
10254 280E Conflicting record lock in this session.
10255 280F A deadlock was detected.
10256 2810 A user transaction is already in progress.
10257 2811 No user transaction is currently in progress.
10258 2812 Record lock failed.
10259 2813 Couldn't perform the edit because another user changed the record.
10260 2814 Couldn't perform the edit because another user deleted or moved the record.
10497 2901 Insufficient field rights for operation.
10498 2902 Insufficient table rights for operation.Password required.
10499 2903 Insufficient family rights for operation.
10500 2904 This directory is read only.
10501 2905 Database is read only.
10502 2906 Trying to modify read-only field.
10503 2907 Encrypted dBASE tables not supported.
10504 2908 Insufficient SQL rights for operation.
10753 2A01 Field is not a BLOB.
10754 2A02 BLOB already opened.
10755 2A03 BLOB not opened.
10756 2A04 Operation not applicable.
10757 2A05 Table is not indexed.
10758 2A06 Engine not initialized.
10759 2A07 Attempt to re-initialize Engine.
10760 2A08 Attempt to mix objects from different sessions.
10761 2A09 Paradox driver not active.
10762 2A0A Driver not loaded.
10763 2A0B Table is read only.
10764 2A0C No associated index.
10765 2A0D Table(s) open. Cannot perform this operation.
10766 2A0E Table does not support this operation.
10767 2A0F Index is read only.
10768 2A10 Table does not support this operation because it is not uniquely indexed.
10769 2A11 Operation must be performed on the current session.
10770 2A12 Invalid use of keyword.
10771 2A13 Connection is in use by another statement.
10772 2A14 Passthrough SQL connection must be shared
11009 2B01 Invalid function number.
11010 2B02 File or directory does not exist.
11011 2B03 Path not found.
11012 2B04 Too many open files. You may need to increase MAXFILEHANDLE limit in IDAPI configuration.
11013 2B05 Permission denied.
11014 2B06 Bad file number.
11015 2B07 Memory blocks destroyed.
11016 2B08 Not enough memory.
11017 2B09 Invalid memory block address.
11018 2B0A Invalid environment.
11019 2B0B Invalid format.
11020 2B0C Invalid access code.
11021 2B0D Invalid data.
11023 2B0F Device does not exist.
11024 2B10 Attempt to remove current directory.
11025 2B11 Not same device.
11026 2B12 No more files.
11027 2B13 Invalid argument.
11028 2B14 Argument list is too long.
11029 2B15 Execution format error.
11030 2B16 Cross-device link.
11041 2B21 Math argument.
11042 2B22 Result is too large.
11043 2B23 File already exists.
11047 2B27 Unknown internal operating system error.
11058 2B32 Share violation.
11059 2B33 Lock violation.
11060 2B34 Critical DOS Error.
11061 2B35 Drive not ready.
11108 2B64 Not exact read/write.
11109 2B65 Operating system network error.
11110 2B66 Error from NOVELL file server.
11111 2B67 NOVELL server out of memory.
11112 2B68 Record already locked by this workstation.
11113 2B69 Record not locked.
11265 2C01 Network initialization failed.
11266 2C02 Network user limit exceeded.
11267 2C03 Wrong .NET file version.
11268 2C04 Cannot lock network file.
11269 2C05 Directory is not private.
11270 2C06 Multiple .NET files in use.
11271 2C07 Unknown network error.
11272 2C08 Not initialized for accessing network files.
11273 2C09 SHARE not loaded. It is required to share local files.
11274 2C0A Not on a network. Not logged in or wrong network driver.
11275 2C0B Lost communication with SQL server.
11521 2D01 Optional parameter is required.
11522 2D02 Invalid optional parameter.
11777 2E01 obsolete
11778 2E02 obsolete
11779 2E03 Ambiguous use of ! (inclusion operator).
11780 2E04 obsolete
11781 2E05 obsolete
11782 2E06 A SET operation cannot be included in its own grouping.
11783 2E07 Only numeric and date/time fields can be averaged.
11784 2E08 Invalid expression.
11785 2E09 Invalid OR expression.
11786 2E0A obsolete
11787 2E0B bitmap
11788 2E0C CALC expression cannot be used in INSERT, DELETE, CHANGETO and SET rows.
11789 2E0D Type error in CALC expression.
11790 2E0E CHANGETO can be used in only one query form at a time.
11791 2E0F Cannot modify CHANGED table.
11792 2E10 A field can contain only one CHANGETO expression.
11793 2E11 A field cannot contain more than one expression to be inserted.
11794 2E12 obsolete
11795 2E13 CHANGETO must be followed by the new value for the field.
11796 2E14 Checkmark or CALC expressions cannot be used in FIND queries.
11797 2E15 Cannot perform operation on CHANGED table together with a CHANGETO query.
11798 2E16 chunk
11799 2E17 More than 255 fields in ANSWER table.
11800 2E18 AS must be followed by the name for the field in the ANSWER table.
11801 2E19 DELETE can be used in only one query form at a time.
11802 2E1A Cannot perform operation on DELETED table together with a DELETE query.
11803 2E1B Cannot delete from the DELETED table.
11804 2E1C Example element is used in two fields with incompatible types or with a BLOB.
11805 2E1D Cannot use example elements in an OR expression.
11806 2E1E Expression in this field has the wrong type.
11807 2E1F Extra comma found.
11808 2E20 Extra OR found.
11809 2E21 One or more query rows do not contribute to the ANSWER.
11810 2E22 FIND can be used in only one query form at a time.
11811 2E23 FIND cannot be used with the ANSWER table.
11812 2E24 A row with GROUPBY must contain SET operations.
11813 2E25 GROUPBY can be used only in SET rows.
11814 2E26 Use only INSERT, DELETE, SET or FIND in leftmost column.
11815 2E27 Use only one INSERT, DELETE, SET or FIND per line.
11816 2E28 Syntax error in expression.
11817 2E29 INSERT can be used in only one query form at a time.
11818 2E2A Cannot perform operation on INSERTED table together with an INSERT query.
11819 2E2B INSERT, DELETE, CHANGETO and SET rows may not be checked.
11820 2E2C Field must contain an expression to insert (or be blank).
11821 2E2D Cannot insert into the INSERTED table.
11822 2E2E Variable is an array and cannot be accessed.
11823 2E2F Label
11824 2E30 Rows of example elements in CALC expression must be linked.
11825 2E31 Variable name is too long.
11826 2E32 Query may take a long time to process.
11827 2E33 Reserved word or one that can't be used as a variable name.
11828 2E34 Missing comma.
11829 2E35 Missing ).
11830 2E36 Missing right quote.
11831 2E37 Cannot specify duplicate column names.
11832 2E38 Query has no checked fields.
11833 2E39 Example element has no defining occurrence.
11834 2E3A No grouping is defined for SET operation.
11835 2E3B Query makes no sense.
11836 2E3C Cannot use patterns in this context.
11837 2E3D Date does not exist.
11838 2E3E Variable has not been assigned a value.
11839 2E3F Invalid use of example element in summary expression.
11840 2E40 Incomplete query statement. Query only contains a SET definition.
11841 2E41 Example element with ! makes no sense in expression.
11842 2E42 Example element cannot be used more than twice with a ! query.
11843 2E43 Row cannot contain expression.
11844 2E44 obsolete
11845 2E45 obsolete
11846 2E46 No permission to insert or delete records.
11847 2E47 No permission to modify field.
11848 2E48 Field not found in table.
11849 2E49 Expecting a column separator in table header.
11850 2E4A Expecting a column separator in table.
11851 2E4B Expecting column name in table.
11852 2E4C Expecting table name.
11853 2E4D Expecting consistent number of columns in all rows of table.
11854 2E4E Cannot open table.
11855 2E4F Field appears more than once in table.
11856 2E50 This DELETE, CHANGE or INSERT query has no ANSWER.
11857 2E51 Query is not prepared. Properties unknown.
11858 2E52 DELETE rows cannot contain quantifier expression.
11859 2E53 Invalid expression in INSERT row.
11860 2E54 Invalid expression in INSERT row.
11861 2E55 Invalid expression in SET definition.
11862 2E56 row use
11863 2E57 SET keyword expected.
11864 2E58 Ambiguous use of example element.
11865 2E59 obsolete
11866 2E5A obsolete
11867 2E5B Only numeric fields can be summed.
11868 2E5C Table is write protected.
11869 2E5D Token not found.
11870 2E5E Cannot use example element with ! more than once in a single row.
11871 2E5F Type mismatch in expression.
11872 2E60 Query appears to ask two unrelated questions.
11873 2E61 Unused SET row.
11874 2E62 INSERT, DELETE, FIND, and SET can be used only in the leftmost column.
11875 2E63 CHANGETO cannot be used with INSERT, DELETE, SET or FIND.
11876 2E64 Expression must be followed by an example element defined in a SET.
11877 2E65 Lock failure.
11878 2E66 Expression is too long.
11879 2E67 Refresh exception during query.
11880 2E68 Query canceled.
11881 2E69 Unexpected Database Engine error.
11882 2E6A Not enough memory to finish operation.
11883 2E6B Unexpected exception.
11884 2E6C Feature not implemented yet in query.
11885 2E6D Query format is not supported.
11886 2E6E Query string is empty.
11887 2E6F Attempted to prepare an empty query.
11888 2E70 Buffer too small to contain query string.
11889 2E71 Query was not previously parsed or prepared.
11890 2E72 Function called with bad query handle.
11891 2E73 QBE syntax error.
11892 2E74 Query extended syntax field count error.
11893 2E75 Field name in sort or field clause not found.
11894 2E76 Table name in sort or field clause not found.
11895 2E77 Operation is not supported on BLOB fields.
11896 2E78 General BLOB error.
11897 2E79 Query must be restarted.
11898 2E7A Unknown answer table type.
11926 2E96 Blob cannot be used as grouping field.
11927 2E97 Query properties have not been fetched.
11928 2E98 Answer table is of unsuitable type.
11929 2E99 Answer table is not yet supported under server alias.
11930 2E9A Non-null blob field required. Can't insert records
11931 2E9B Unique index required to perform changeto
11932 2E9C Unique index required to delete records
11933 2E9D Update of table on the server failed.
11934 2E9E Can't process this query remotely.
11935 2E9F Unexpected end of command.
11936 2EA0 Parameter not set in query string.
11937 2EA1 Query string is too long.
12033 2F01 Interface mismatch. Engine version different.
12034 2F02 Index is out of date.
12035 2F03 Older version (see context).
12036 2F04 .VAL file is out of date.
12037 2F05 BLOB file version is too old.
12038 2F06 Query and Engine DLLs are mismatched.
12289 3001 Capability not supported.
12290 3002 Not implemented yet.
12291 3003 SQL replicas not supported.
12292 3004 Non-blob column in table required to perform operation.
12293 3005 Multiple connections not supported.
12545 3101 Invalid database alias specification.
12546 3102 Unknown database type.
12547 3103 Corrupt system configuration file.
12548 3104 Network type unknown.
12549 3105 Not on the network.
12550 3106 Invalid configuration parameter.
12801 3201 Object implicitly dropped.
12802 3202 Object may be truncated.
12803 3203 Object implicitly modified.
12804 3204 Should field constraints be checked?
12805 3205 Validity check field modified.
12806 3206 Table level changed.
12807 3207 Copy linked tables?
12809 3209 Object implicitly truncated.
12810 320A Validity check will not be enforced.
12811 320B Multiple records found, but only one was expected.
12812 320C Field will be trimmed, cannot put master records into PROBLEM table.
13057 3301 File already exists.
13058 3302 BLOB has been modified.
13059 3303 General SQL error.
13060 3304 Table already exists.
13061 3305 Paradox 1.0 tables are not supported.
13313 3401 Different sort order.
13314 3402 Directory in use by earlier version of Paradox.
13315 3403 Needs Paradox 3.5-compatible language driver.
14849 3A01 SYSTEM
14850 3A02 DRIVERS
14851 3A03 DATABASES
14853 3A05 VERSION
14854 3A06 NET TYPE
14855 3A07 NET DIR
14856 3A08 LOCAL SHARE
14857 3A09 LANGDRIVER
14858 3A0A LANGDRVDIR
14859 3A0B MINBUFSIZE
14860 3A0C MAXBUFSIZE
14861 3A0D LOCKRETRY
14862 3A0E SYSFLAGS
14863 3A0F MAXFILEHANDLES
14864 3A10 SQLQRYMODE
14865 3A11 LOW MEMORY USAGE LIMIT
14866 3A12 AUTO ODBC
14867 3A13 DEFAULT DRIVER
14868 3A14 VERSION
14869 3A15 TYPE
14870 3A16 LANGDRIVER
14871 3A17 FILL FACTOR
14872 3A18 BLOCK SIZE
14873 3A19 LOCKPROTOCOL
14874 3A1A LEVEL
14875 3A1B DRIVER FLAGS
14878 3A1E MEMO FILE BLOCK SIZE
14879 3A1F MDX BLOCK SIZE
14888 3A28 INIT
14889 3A29 DB CREATE
14890 3A2A DB OPEN
14891 3A2B TABLE CREATE
14892 3A2C TABLE OPEN
14898 3A32 DB INFO
14908 3A3C TYPE
14909 3A3D PATH
14910 3A3E DEFAULT DRIVER
14918 3A46 INIT
14919 3A47 TYPE
14920 3A48 STANDARD
14921 3A49 TRUE
14922 3A4A FALSE
14923 3A4B OPEN MODE
14924 3A4C READ/WRITE
14925 3A4D READ ONLY
14926 3A4E SHARE MODE
14927 3A4F EXCLUSIVE
14928 3A50 SHARED
14929 3A51 USER NAME
14930 3A52 SERVER NAME
14931 3A53 DATABASE NAME
14932 3A54 SCHEMA CACHE SIZE
14933 3A55 STRICTINTEGRTY
14938 3A5A ORACLE
14939 3A5B 1.0
14940 3A5C SERVER
14941 3A5D NET PROTOCOL
14942 3A5E DECNET
14943 3A5F NETBIOS
14944 3A60 NAMED PIPES
14945 3A61 SPX/IPX
14946 3A62 TCP/IP
14947 3A63 3270
14948 3A64 VINES
14949 3A65 APPC
14950 3A66 ASYNC
14958 3A6E SYBASE
14959 3A6F 1.0
14960 3A70 SERVER
14961 3A71 BLOB EDIT LOGGING
14962 3A72 CONNECT TIMEOUT
14963 3A73 TIMEOUT
14964 3A74 DATE MODE
14965 3A75 DATE SEPARATOR
14966 3A76 DECIMAL SEPARATOR
14968 3A78 INTRBASE
14969 3A79 1.0
14970 3A7A SERVER
14978 3A82 FORMATS
14979 3A83 DATE
14980 3A84 TIME
14981 3A85 NUMBER
14988 3A8C SEPARATOR
14989 3A8D MODE
14990 3A8E FOURDIGITYEAR
14991 3A8F YEARBIASED
14992 3A90 LEADINGZEROM
14993 3A91 LEADINGZEROD
14994 3A92 TWELVEHOUR
14995 3A93 AMSTRING
14996 3A94 PMSTRING
14997 3A95 SECONDS
14998 3A96 MILSECONDS
15008 3AA0 DECIMALSEPARATOR
15009 3AA1 THOUSANDSEPARATOR
15010 3AA2 DECIMALDIGITS
15011 3AA3 LEADINGZERON
15013 3AA5 ascii
15014 3AA6 DB437US0
15018 3AAA /
15019 3AAB 0
15020 3AAC FALSE
15021 3AAD TRUE
15022 3AAE TRUE
15023 3AAF TRUE
15024 3AB0 TRUE
15025 3AB1 AM
15026 3AB2 PM
15027 3AB3 TRUE
15028 3AB4 FALSE
15029 3AB5 .
15030 3AB6 ,
15031 3AB7 TRUE
15873 3E01 Wrong driver name.
15874 3E02 Wrong system version.
15875 3E03 Wrong driver version.
15876 3E04 Wrong driver type.
15877 3E05 Cannot load driver.
15878 3E06 Cannot load language driver.
15879 3E07 Vendor initialization failed.
16129 3F01 Query By Example
16130 3F02 SQL Generator
16131 3F03 IDAPI
16132 3F04 Lock Manager
16133 3F05 SQL Driver
16134 3F06 IDAPI Services
16135 3F07 dBASE Driver
16138 3F0A Token
16140 3F0C Table
16141 3F0D Field
16142 3F0E Image
16143 3F0F User
16144 3F10 File
16145 3F11 Index
16146 3F12 Directory
16147 3F13 Key
16148 3F14 Alias
16149 3F15 Drive
16150 3F16 Server error
16151 3F17 Server message
16152 3F18 Line Number
16153 3F19 Capability
16154 3F1A Limit
16239 3F6F WORK
16240 3F70 PRIV
16241 3F71 Rec

2011. június 4., szombat

List Template In Delphi


Problem/Question/Abstract:

How to create a type specific list in Delphi without reimplementing the entired list for each type ?

Answer:

The C++ language has a nice feature, that's called Templates. It allowes the programer to define a class (or a method) that acts with a none-specific type. At complie time, the programer describes for which type the class will be defined. That is, you can define a general list (list template) and define all of it's methods to work on type A (where 'A' is not defined). For example, the method GetItem will look as follows :

function GetItem(Index: Integer): A;

Then, at compile type you tell the complier that 'A' is actually an Integer, and the complier replaces all of the accurances of 'A' with 'Integer'. That way, you can write one list (for type 'A'), and each time you wan a list (of Strings, Integers, Boolean, Soubles, etc.) you just need to tell the complier to replace 'A' with the type you want.

All of that is very nice, but has nothing to do with Delphi. It's relevent only to C++ programers. So what do Delphi programers do ?

There are 3 majore options. First, write a list of pointers once, and then use it many times by passing to it a pointer to the datatype you are interested in. For Example :
  
TList = class
  ...
  public
  procedure Add(Value: Pointer);

  function GetItem(Index: Integer): Pointer;
  procedure SetItem(Index: Integer; Value: Pointer);

  property Items[Index: Integer]: Pointer read GetItem write SetItem;
  
end;

Here is the code to use this :

// For Integer;
type
  PInteger = ^Integer;
var
  Item: PInteger;
  List: TList;
begin
  List := TList.Create;
  GetMem(Item, SizeOf(Item));
  Item^ := 1023; // Or what ever value you wish
  List.Add(Item);
  ShowMessage(IntToStr(PInteger(List.Items[0])^));
end;

// For Double;
type
  PDouble = ^Double;
var
  Item: PDouble;
  List: TList;
begin
  List := TList.Create;
  GetMem(Item, SizeOf(Item));
  Item^ := 3.14.15926; // Or what ever value you wish
  List.Add(Item);
  ShowMessage(FloatToStr(PDouble(List.Items[0])^));
end;

As you've probably noticed there are a few drawbacks to this solution. The most obvious one is that you need to typecast the value returned by the List each time you want to use it. That might seem as a mere inconvinouce, but if you plan to uses lists intensivly, you'll get REALY tired of typecasting all the time. The second problem to consider with this design is memory concerns. In the example above, I've allocated memory to Item, but never free it. That's because the TList class I've used doesn't allocate memory by itself. But then arisses the question, how will free the memory ? Probably the TList itself (since the item is now 'owned' by it), but that is a bit unconventional, because usually the object (or method) that allocates the memory is responsibly to freeing it. You can solve this by writing the TList class so it allocates it's own memory and only COPIES the value pointed to by Item. But then there are two other problem.

You need to free the memory of Item after adding it to the List (since the List isn't going to free it - it only copied the Items contents).
You need to find a way of telling TList how many byte to copy. Since TList gets a pointer and doesn't know what it points to (a string ? an integer ? a double ?), it has no way of knowing how many bytes to copy.

Those are all very good reasons why NOT to use this solution. Lets have a look at the second solution out of the three.

The second solution is very simple. Write a new list for each type. That is, write a TIntergerList, TStringList, TDoubleList, TWhatEverList. Example :
  
TIntegerList = class
  ...
  public
  procedure Add(Value: Integer);

  function GetItem(Index: Integer): Integer;
  procedure SetItem(Index: Integer; Value: Integer);

  proepry Items[Idnex: Integer]: Integer read GetItem write SetItem;
end;

TDoubleList = class
  ...
  public
  procedure Add(Value: Double);

  function GetItem(Index: Integer): Double;
  procedure SetItem(Index: Integer; Value: Double);

  property Items[Index: Integer]: Double read GetItem write SetItem;
end;

The benefits are obvious. You can use a list and have no memory problems and you need not typecast ! Implementing these lists could be a little time consuming, but if you work a lot with the same types of lists it might be worth while. The only draw back of this design (except for a one time developing cost) is it's not extendable (at least not easly). That is, if you want to add a new function to your List (for example : SaveToFile), you'll have to add the same code for each list you implement. That vrings us to the third and final solution.

This solution is a combination of the first and second solutions. It tries to take the best of each. The first solution was very general (worked for every type without adding code), but you couldn't make it specific (you have to use typecasting inorder to use an Item). The second solution was very specific (no typecasting needed) but you had to write a bunch of code for each new list you wanted to implement.

And here is the third solution : Define a base class that is the same as the TList in the first solution. Then, for each new list you want (for example : TIntegerList) smiply inherite from the base class and add the type specific methods (for example : procedure Add(Value : Integer)). There are a few problems with this design as well, but I'll discuss them later. For now, lets see why this design helps as more than the other two.

First, it allows you to use type specific lists (no need for typecasting). Second it doesn't require you to write a lot of code (five mintues will do) for each new List because most of the methods are already implemented and the new methods that need to be implemented are very short.

Lets look closly at the last suggestion. First we need to define a base class :

TBaseList = class
protected
  procedure AddData(Value: Pointer);
  class function ItemSize: Integer; virtual; abstract;
end;

procedure TBaseLink.AddData(Value: Pointer);
var
  P: Pointer;
begin
  GetMem(P, ItemSize);
  Move(P^, Value^, ItemSize);
  // Here you need to add P to your list.
  // The way that is done may vary by the way you decide
  // to save your data. You may want to save it as an Array
  // or as a linked list, or as a tree, or into a stream, etc.
end;

Now, lets create a TIntegerList :

TIntegerList = class
protected
  class function ItemSize: Integer; override;
public
  procedure Add(Value: Integer);
end;

class fucntion TIntegerList.ItemSize: Integer;
  begin
    Result := SizeOf(Integer);
  end;

procedure TIntegerList.Add(Value: Integer);
var
  P: ^Integer;
  begin
    GetMem(P, SizeOf(Integer));
    try
      P^ := Value;
      AddData(P);
    finally
      FreeMem(P, SizeOf(Integer));
  end;
end;

This example is simplefied. In a real list (with full capabilitys) most of the coding is in the base class, and only a few methods are need to be implemented in the derived classes.

I've attached a full implementation of this concept for TIntegerList and TStringList. Notice a few things about the attached file : a) The IBooleanList is defined but not implemented. b) The marked out methods at the begining of the file are not implemented yet. c) Objects aren't suported yet.

When I finish coding these lists, I'll write another article describing my specific implementation of this idea.

Component Download: http://www.kastu.lt/dkb/downfile/download.php?id=100

2011. június 3., péntek

BDE API Overview


Problem/Question/Abstract:

BDE API Overview

Answer:

Available BDE 4.0 engine functions by type:



1. Database functions

Each function listed below returns information about a specific database, available databases, or performs a database-related task, such as opening or closing a database.



DbiCloseDatabase:
Closes a database and all tables associated with this database handle.

DbiGetDatabaseDesc:
Retrieves the description of the specified database from the configuration file.

DbiGetDirectory:
Retrieves the current working directory or the default directory.

DbiOpenDatabase:
Opens a database in the current session and returns a database handle.

DbiOpenDatabaseList:
Creates an in-memory table containing a list of accessible databases and their descriptions.

DbiOpenFileList:
Opens a cursor on the virtual table containing all the tables accessible by the client application
and their descriptions.

DbiOpenIndexList:
Opens a cursor on an in-memory table listing the indexes on a specified table, along with
their descriptions.

DbiOpenTableList:
Creates an in-memory table with information about all the tables accessible to the client application.




2. Environment and configuration functions

Each function listed below returns information about the client application environment, such as the supported table, field and index types for the driver type, or the available driver types. Functions in this category can also perform tasks that affect the client application environment, such as loading a driver.



DbiAddAlias:
Adds an alias to the BDE configuration file (IDAPI.CFG).

DbiAddDriver:
Adds a driver to the BDE configuration file (IDAPI.CFG). NEW FUNCTION BDE 4.0

DbiAnsiToNative:
Multipurpose translate function.

DbiDebugLayerOptions:
Activates, deactivates, or sets options for the BDE debug layer. OBSOLETE FUNCTION BDE 4.0

DbiDeleteAlias:
Deletes an alias from the BDE configuration file (IDAPI.CFG).

DbiDeleteDriver:
Deletes a driver from the BDE configuration file (IDAPI.CFG). NEW FUNCTION BDE 4.0

DbiDllExit:
Prepares the BDE to be disconnected within a DLL. NEW FUNCTION BDE 4.0

DbiExit:
Disconnects the client application from BDE.

DbiGetClientInfo:
Retrieves system-level information about the client application environment.

DbiGetDriverDesc:
Retrieves a description of a driver.

DbiGetLdName:
Retrieves the name of the language driver associated with the specified object name (table name).

DbiGetLdObj:
Retrieves the language driver object associated with the given cursor.

DbiGetNetUserName:
Retrieves the user's network login name. User names should be available for all networks
supported by Microsoft Windows.

DbiGetProp:
Returns a property of an object.

DbiGetSysConfig:
Retrieves BDE system configuration information.

DbiGetSysInfo:
Retrieves system status and information.

DbiGetSysVersion:
Retrieves the system version information, including the BDE version number, date, and time,
and the client interface version number.

DbiInit:
Initializes the BDE environment.

DbiLoadDriver:
Load a given driver.

DbiNativeToAnsi:
Translates a string in the native language driver to an ANSI string.

DbiOpenCfgInfoList:
Returns a handle to an in-memory table listing all the nodes in the configuration file
accessible by the specified path.

DbiOpenDriverList:
Creates an in-memory table containing a list of driver names available to the client application.

DbiOpenFieldTypesList:
Creates an in-memory table containing a list of field types supported by the table type for
the driver type.

DbiOpenFunctionArgList:
Returns a list of arguments to a data source function.

DbiOpenFunctionList:
Returns a description of a data source function.

DbiOpenIndexTypesList:
Creates an in-memory table containing a list of all supported index types for the driver type.

DbiOpenLdList:
Creates an in-memory table containing a list of available language drivers.

DbiOpenTableList:
Creates an in-memory table with information about all the tables accessible to the client application.

DbiOpenTableTypesList:
Creates an in-memory table listing table type names for the given driver.

DbiOpenUserList:
Creates an in-memory table containing a list of users sharing the same network file.

DbiSetProp:
Sets the specified property of an object to a given value.

DbiUseIdleTime:
Allows BDE to accomplish background tasks during times when the client application is idle.
OBSOLETE FUNCTION BDE 4.0




3. Session functions

Each function listed below returns information about a session or performs a task that affects the session, such as starting a session or adding a password.



DbiAddPassword:
Adds a password to the current session.

DbiCheckRefresh:
Checks for remote updates to tables for all cursors in the current session, and refreshes the cursors
if changed.

DbiCloseSession:
Closes the session associated with the given session handle.

DbiDropPassword:
Removes a password from the current session.

DbiGetCallBack:
Returns a pointer to the function previously registered by the client for the given callback type.

DbiGetCurrSession:
Returns the handle associated with the current session.

DbiGetDateFormat:
Gets the date format for the current session.

DbiGetNumberFormat:
Gets the number format for the current session.

DbiGetSesInfo:
Retrieves the environment settings for the current session.

DbiGetTimeFormat:
Gets the time format for the current session.

DbiRegisterCallBack:
Registers a callback function for the client application.

DbiSetCurrSession:
Sets the current session of the client application to the session associated with hSes.

DbiSetDateFormat:
Sets the date format for the current session.

DbiSetNumberFormat:
Sets the number format for the current session.

DbiSetPrivateDir:
Sets the private directory for the current session.

DbiSetTimeFormat:
Sets the time format for the current session.

DbiStartSession:
Starts a new session for the client application.




4. Error-handling functions

Each function listed below returns error handling information or performs a task that relates to error handling.



DbiGetErrorContext:
After receiving an error code back from a call, enables the client to probe BDE for more specific
error information.

DbiGetErrorEntry:
Returns the error description of a specified error stack entry.

DbiGetErrorInfo:
Provides descriptive error information about the last error that occurred.

DbiGetErrorString:
Returns the message associated with a given error code.




5. Lock functions

Each function listed below returns information about lock status or acquires or releases a lock at the table or record level.



DbiAcqPersistTableLock:
Acquires an exclusive persistent lock on the table preventing other users from using the table
or creating a table of the same name.

DbiAcqTableLock:
Acquires a table-level lock on the table associated with the given cursor.

DbiGetRecord:
Record positioning functions have a lock parameter.

DbiIsRecordLocked:
Checks the lock status of the current record.

DbiIsTableLocked:
Returns the number of locks of a specified type acquired on the table associated with the
given session.

DbiIsTableShared:
Determines whether the table is physically shared or not.

DbiOpenLockList:
Creates an in-memory table containing a list of locks acquired on the table.

DbiOpenUserList:
Creates an in-memory table containing a list of users sharing the same network file.

DbiRelPersistTableLock:
Releases the persistent table lock on the specified table.

DbiRelRecordLock:
Releases the record lock on either the current record of the cursor or only the locks acquired
in the current session.

DbiRelTableLock:
Releases table locks of the specified type associated with the current session (the session in
which the cursor was created).

DbiSetLockRetry:
Sets the table and record lock retry time for the current session.




6. Cursor functions

Each function listed below returns information about a cursor, or performs a task that performs a cursor-related task such as positioning of a cursor, linking of cursors, creating and closing cursors, counting of records associated with a cursor, filtering, setting and comparing bookmarks, and refreshing all buffers associated with a cursor.



DbiActivateFilter:
Activates a filter.

DbiAddFilter:
Adds a filter to a table, but does not activate the filter (the record set is not yet altered).

DbiApplyDelayedUpdates:
When cached updates cursor layer is active, writes all modifications made to cached data to the
underlying database.

DbiBeginDelayedUpdates:
Creates a cached updates cursor layer so that users can make extended changes to temporarily
cached table data without writing to the actual table, thereby minimizing resource locking.

DbiBeginLinkMode:
Converts a cursor to a link cursor. Given an open cursor, prepares for linked access. Returns a
new cursor.

DbiCloneCursor:
Creates a new cursor (clone cursor) which has the same result set as the given cursor
(source cursor).

DbiCloseCursor:
Closes a previously opened cursor.

DbiCompareBookMarks:
Compares the relative positions of two bookmarks in the result set associated with the cursor.

DbiDeactivateFilter:
Temporarily stops the specified filter from affecting the record set by turning the filter off.

DbiDropFilter:
Deactivates and removes a filter from memory, and frees all resources.

DbiEndDelayedUpdates:
Closes a cached updates cursor layer ending the cached updates mode.

DbiEndLinkMode:
Ends linked cursor mode, and returns the original cursor.

DbiExtractKey:
Retrieves the key value for the current record of the given cursor or from the supplied record buffer.

DbiForceRecordReread:
Rereads a single record from the server on demand, refreshing one row only, rather than clearing
the cache.

DbiForceReread:
Refreshes all buffers associated with the cursor, if necessary.

DbiFormFullName:
Returns the fully qualified table name.

DbiGetBookMark:
Saves the current position of a cursor to the client-supplied buffer called a bookmark.

DbiGetCursorForTable:
Finds the cursor for the given table.

DbiGetCursorProps:
Returns the properties of the cursor.

DbiGetExactRecordCount:
Retrieves the current exact number of records associated with the cursor. NEW FUNCTION BDE 4.0

DbiGetFieldDescs:
Retrieves a list of descriptors for all the fields in the table associated with the cursor.

DbiGetLinkStatus:
Returns the link status of the cursor.

DbiGetNextRecord:
Retrieves the next record in the table associated with the cursor.

DbiGetPriorRecord:
Retrieves the previous record in the table associated with the given cursor.

DbiGetProp:
Returns a property of an object.

DbiGetRecord:
Retrieves the current record, if any, in the table associated with the cursor.

DbiGetRecordCount:
Retrieves the current number of records associated with the cursor.

DbiGetRecordForKey:
Finds and retrieves a record matching a key and positions the cursor on that record.

DbiGetRelativeRecord:
Positions the cursor on a record in the table relative to the current position of the cursor.

DbiGetSeqNo:
Retrieves the sequence number of the current record in the table associated with the cursor.

DbiLinkDetail:
Establishes a link between two tables such that the detail table has its record set limited to the
set of records matching the linking key values of the master table cursor.

DbiLinkDetailToExp:
Links the detail cursor to the master cursor using an expression.

DbiMakePermanent:
Changes a temporary table created by DbiCreateTempTable into a permanent table.

DbiOpenTable:
Opens the given table for access and associates a cursor handle with the opened table.

DbiResetRange:
Removes the specified table's limited range previously established by the function DbiSetRange.

DbiSaveChanges:
Forces all updated records associated with the cursor to disk.

DbiSetFieldMap:
Sets a field map of the table associated with the given cursor.

DbiSetProp:
Sets the specified property of an object to a given value.

DbiSetRange:
Sets a range on the result set associated with the cursor.

DbiSetToBegin:
Positions the cursor to BOF (just before the first record).

DbiSetToBookMark:
Positions the cursor to the location saved in the specified bookmark.

DbiSetToCursor:
Sets the position of one cursor (the destination cursor) to that of another (the source cursor).

DbiSetToEnd:
Positions the cursor to EOF (just after the last record).

DbiSetToKey:
Positions an index-based cursor on a key value.

DbiSetToRecordNo:
Positions the cursor of a dBASE table to the given physical record number.

DbiSetToSeqNo:
Positions the cursor to the specified sequence number of a Paradox table.

DbiUnlinkDetail:
Removes a link between two cursors.




7. Index functions

Each function listed below returns information about an index or indexes, or performs a task that affects an index, such as dropping it, deleting it, or adding it.



DbiAddIndex:
Creates an index on an existing table.

DbiCloseIndex:
Closes the specified index on a cursor.

DbiCompareKeys:
Compares two key values based on the current index of the cursor.

DbiDeleteIndex:
Drops an index on a table.

DbiExtractKey:
Retrieves the key value for the current record of the given cursor or from the supplied record buffer.

DbiGetIndexDesc:
Retrieves the properties of the given index associated with the cursor.

DbiGetIndexDescs:
Retrieves index properties.

DbiGetIndexForField:
Returns the description of any useful index on the specified field.

DbiGetIndexSeqNo:
Retrieves the ordinal number of the index in the index list of the specified cursor.

DbiGetIndexTypeDesc:
Retrieves a description of the index type.

DbiOpenIndex:
Opens the index for the table associated with the cursor.

DbiRegenIndex:
Regenerates an index to make sure that it is up-to-date (all records currently in the table
are included in the index and are in the index order).

DbiRegenIndexes:
Regenerates all out-of-date indexes on a given table.

DbiSwitchToIndex:
Allows the user to change the active index order of the given cursor.




8. Query functions

Each function listed below performs a query task, such as preparing and executing a SQL or QBE query.



DbiGetProp:
Returns a property of an object.

DbiQAlloc:
Allocates a new statement handle for a prepared query.

DbiQExec:
Executes the previously prepared query identified by the supplied statement handle and
returns a cursor to the result set, if one is generated.

DbiQExecDirect:
Executes a SQL or QBE query and returns a cursor to the result set, if one is generated.

DbiQExecProcDirect:
Executes a stored procedure and returns a cursor to the result set, if one is generated.

DbiQFree:
Frees the resources associated with a previously prepared query identified by the supplied
statement handle.

DbiQGetBaseDescs:
Returns the original database, table, and field names of the fields that make up the result
set of a query.

DbiQInstantiateAnswer:
Creates a permanent table from the cursor to the result set.

DbiQPrepare:
Prepares a SQL or QBE query for execution, and returns a handle to a statement containing
the prepared query.

DbiQPrepareProc:
Prepares and optionally binds parameters for a stored procedure.

DbiQSetParams:
Associates data with parameter markers embedded within a prepared query.

DbiQSetProcParams:
Binds parameters for a stored procedure prepared with DbiQPrepareProc.

DbiSetProp:
Sets the specified property of an object to a given value.

DbiValidateProp:
Validates a property.




9. Table functions

Each function listed below returns information about a specific table, such as all the locks acquired on the table, all the referential integrity links on the table, the indexes open on the table, or whether or not the table is shared. Functions in this category can also perform a table-wide operation, such as copying and deleting.



DbiBatchMove:
Appends, updates, subtracts, and copies records or fields from a source table to a destination table.

DbiCopyTable:
Duplicates the specified source table to a destination table.

DbiCreateInMemTable:
Creates a temporary, in-memory table.

DbiCreateTable:
Creates a table.

DbiCreateTempTable:
Creates a temporary table that is deleted when the cursor is closed, unless the call is followed
by a call to DbiMakePermanent.

DbiDeleteTable:
Deletes a table.

DbiDoRestructure:
Changes the properties of a table.

DbiEmptyTable:
Deletes all records from the table associated with the specified table cursor handle or table name.

DbiGetTableOpenCount:
Returns the total number of cursors that are open on the specified table.

DbiGetTableTypeDesc:
Returns a description of the capabilities of the table type for the driver type.

DbiIsTableLocked:
Returns the number of locks of a specified type acquired on the table associated with the
given session.

DbiIsTableShared:
Determines whether the table is physically shared or not.

DbiMakePermanent:
Changes a temporary table created by DbiCreateTempTable into a permanent table.

DbiOpenFamilyList:
Creates an in-memory table listing the family members associated with a specified table.

DbiOpenFieldList:
Creates an in-memory table listing the fields in a specified table and their descriptions.

DbiOpenIndexList:
Opens a cursor on an in-memory table listing the indexes on a specified table, along with
their descriptions.

DbiOpenLockList:
Creates an in-memory table containing a list of locks acquired on the table associated with the cursor.

DbiOpenRintList:
Creates an in-memory table listing the referential integrity links for a specified table, along with
their descriptions.

DbiOpenSecurityList:
Creates an in-memory table listing record-level security information about a specified table.

DbiOpenTable:
Opens the given table for access and associates a cursor handle with the opened table.

DbiPackTable:
Optimizes table space by rebuilding the table associated with the cursor and releasing any free space.

DbiQInstantiateAnswer:
Creates a permanent table from a cursor handle.

DbiRegenIndexes:
Regenerates all out-of-date indexes on a given table.

DbiRenameTable:
Renames the table and all of its resources to the new name specified.

DbiSaveChanges:
Forces all updated records associated with the table to disk.

DbiSortTable:
Sorts an opened or closed table, either into itself or into a destination table. There are options to
remove duplicates, to enable case-insensitive sorts and special sort functions, and to control the
number of records sorted.




10. Data access functions

Each function listed below accesses data in a table, such as retrieving data from a specified BLOB field or from the record buffer.



DbiAppendRecord:
Appends a record to the end of the table associated with the given cursor.

DbiDeleteRecord:
Deletes the current record of the given cursor.

DbiFreeBlob:
Closes the BLOB handle located within the specified record buffer.

DbiGetBlob:
Retrieves data from the specified BLOB field.

DbiGetBlobHeading:
Retrieves information about a BLOB field from the BLOB heading in the record buffer.

DbiGetBlobSize:
Retrieves the size of the specified BLOB field in bytes.

DbiGetField:
Retrieves the data contents of the requested field from the record buffer.

DbiGetFieldDescs:
Retrieves a list of descriptors for all the fields in the table associated with the cursor.

DbiGetFieldTypeDesc:
Retrieves a description of the specified field type.

DbiInitRecord:
Initializes the record buffer to a blank record according to the data types of the fields.

DbiInsertRecord:
Inserts a new record into the table associated with the given cursor.

DbiModifyRecord:
Modifies the current record of table associated with the cursor with the data supplied.

DbiOpenBlob:
Prepares the cursor's record buffer to access a BLOB field.

DbiPutBlob:
Writes data into an open BLOB field.

DbiPutField:
Writes the field value to the correct location in the supplied record buffer.

DbiReadBlock:
Reads a specified number of records (starting from the next position of the cursor) into a buffer.

DbiSaveChanges:
Forces all updated records associated with the cursor to disk.

DbiSetFieldMap:
Sets a field map of the table associated with the given cursor.

DbiTruncateBlob:
Shortens the size of the contents of a BLOB field, or deletes the contents of a BLOB field
from the record, by shortening it to zero.

DbiUndeleteRecord:
Undeletes a dBASE record that has been marked for deletion (a "soft" delete).

DbiVerifyField:
Verifies that the data specified is a valid data type for the field specified, and that all validity
checks in place for the field are satisfied. It can also be used to check if a field is blank.

DbiWriteBlock:
Writes a block of records to the table associated with the cursor.




11. Transaction functions

Each function listed below begins, ends, or inquires about the status of a transaction.



DbiBeginTran:
Begins a transaction.

DbiEndTran:
Ends a transaction.

DbiGetTranInfo:
Retrieves the transaction state.




12. Capability or schema functions

Each function listed below returns information about capabilities or the schema.



DbiOpenCfgInfoList:
Returns a handle to an in-memory table listing all the nodes in the configuration file accessible by
the specified path.

DbiOpenDatabaseList:
Creates an in-memory table containing a list of accessible databases and their descriptions.

DbiOpenDriverList:
Creates an in-memory table containing a list of driver names available to the client application.

DbiOpenFamilyList:
Creates an in-memory table listing the family members associated with a specified table.

DbiOpenFieldList:
Creates an in-memory table listing the fields in a specified table and their descriptions.

DbiOpenFieldTypesList:
Creates an in-memory table containing a list of field types supported by the table type for the driver type.

DbiOpenFunctionArgList:
Returns a list of arguments to a data source function.

DbiOpenFunctionList:
Returns a description of a data source function.

DbiOpenIndexList:
Opens a cursor on an in-memory table listing the indexes on a specified table, along with
their descriptions.

DbiOpenIndexTypesList:
Creates an in-memory table containing a list of all supported index types for the driver type.

DbiOpenLockList:
Creates an in-memory table containing a list of locks acquired on the table.

DbiOpenRintList :
Creates an in-memory table listing the referential integrity links for a specified table, along with
their descriptions.

DbiOpenSecurityList:
Creates an in-memory table listing record-level security information about a specified table.

DbiOpenTableList:
Creates an in-memory table with information about all the tables accessible to the client application.

DbiOpenTableTypesList:
Creates an in-memory table listing table type names for the given driver.

DbiOpenVchkList:
Creates an in-memory table containing records with information about validity checks for fields
within the specified table.




13. Date/time/number format functions

Each function listed below sets or retrieves date or time, or decodes/encodes date and time into or from a timestamp.



DbiBcdFromFloat:
Converts FLOAT data to binary coded decimal (BCD) format.

DbiBcdToFloat:
Converts binary coded decimal (BCD) data to FLOAT format.

DbiDateDecode:
Decodes DBIDATE into separate month, day and year components.

DbiDateEncode:
Encodes separate date components into date for use by DbiPutField and other functions.

DbiGetDateFormat:
Gets the date format for the current session.

DbiGetNumberFormat:
Gets the number format for the current session.

DbiGetTimeFormat:
Gets the time format for the current session.

DbiSetDateFormat:
Sets the date format for the current session.

DbiSetNumberFormat:
Sets the number format for the current session.

DbiSetTimeFormat:
Sets the time format for the current session.

DbiTimeDecode:
Decodes time into separate components (hours, minutes, milliseconds).

DbiTimeEncode:
Encodes separate time components into time for use by DbiPutField and other functions.

DbiTimeStampDecode:
Extracts separate encoded date and time components from the timestamp.

DbiTimeStampEncode:
Encodes the encoded date and encoded time into a timestamp.

2011. június 2., csütörtök

Align cells in a TStringGrid (4)


Problem/Question/Abstract:

Anyone know a simple way of vertically centering your text in a TStringGrid cell. Actually, I wish the StringGrid had the ability to align horizontally as well.

Answer:

Below is some Delphi3 code that I wrote for handling left-right alignment of text in string grids. It would be straightforward to change it for vertical instead (or as well). See DT_BOTTOM, DT_VCENTER and DT_TOP in the description of DrawText in Delphi's Win32 help file. The code also handles automatic word-wrapping and font changes on a per cell basis.

procedure DrawSGCell(Sender: TObject; C, R: integer; Rect: TRect;
  Style: TFontStyles; Wrap: boolean; Just: TAlignment; NoEditCols: TNoEditCols);

{formats cell text; call this routine from grid's DrawCell event;
Style is TFontStyles...
TFontStyles = set of TFontStyle;
TFontStyle = (fsBold, fsItalic, fsUnderline, fsStrikeOut);
Wrap is word-wrap on/off,
Just is (taLeftJustify, taRightJustify, taCenter)}

var
  S: string;
  DrawRect: TRect;
begin
  {multi-line wordwrapped cells, with any justification, and any font params}
  {if Row > 0 then
    { only used for column headings}
  exit;
  }
    { get cell contents }
  with (Sender as TStringGrid), Canvas do
  begin
    S := Cells[C, R];
    {erase earlier contents from default drawing }
    Brush.Color := FixedColor;
    if (R >= FixedRows) and (C >= FixedCols) and not (C in NoEditCols) then
      Brush.Color := Color;
    FillRect(Rect);
    if length(S) > 0 then
    begin
      {switch to font style}
      Font.Style := Style;
      {local copy of cell rectangle}
      DrawRect := Rect;
      if Wrap then
      begin
        {get size of text rectangle in DrawRect}
        DrawText(Handle, PChar(S), length(S), DrawRect, dt_calcrect or dt_wordbreak or
          dt_center);
        if (DrawRect.Bottom - DrawRect.Top) > RowHeights[R] then
        begin
          {cell word-wrapped; need to increase row height}
          RowHeights[R] := DrawRect.Bottom - DrawRect.Top;
          SetGridHeight(Sender as TStringGrid);
        end
        else
        begin
          DrawRect.Right := Rect.Right;
          FillRect(DrawRect);
          case Just of
            taLeftJustify:
              begin
                S := ' ' + S;
                DrawText(Handle, PChar(S), length(S), DrawRect, dt_wordbreak or
                  dt_left);
              end;
            taCenter:
              DrawText(Handle, PChar(S), length(S), DrawRect, dt_wordbreak or
                dt_center);
            taRightJustify:
              begin
                S := S + ' ';
                DrawText(Handle, PChar(S), length(S), DrawRect, dt_wordbreak or
                  dt_right);
              end;
          end;
        end;
      end
      else
        {no wrap}
        case Just of
          taLeftJustify:
            begin
              S := ' ' + S;
              DrawText(Handle, PChar(S), length(S), DrawRect, dt_singleline or
                dt_vcenter or dt_left);
            end;
          taCenter:
            DrawText(Handle, PChar(S), length(S), DrawRect, dt_singleline or
                                                         dt_vcenter or dt_center);
          taRightJustify:
            begin
              S := S + ' ';
              DrawText(Handle, PChar(S), length(S), DrawRect, dt_singleline or
                dt_vcenter or dt_right);
            end;
        end;
      {restore no font styles}
      Font.Style := [];
    end;
  end;
end;

2011. június 1., szerda

Simple Thread Example


Problem/Question/Abstract:

This article will show you how to create Threads and show you how to work with global variables within a Thread. You need the unit SyncObjs (part of Delphi Enterprise) for this sample.

Answer:

As mentioned in the Abstract, you will need the unit SyncObjs. This unit, however, is a Delphi Enterprise feature. In another article I will show you how to work around this problem, however, until then you may search the web for workarounds. There are some available already.

THREADS

Threads will allow you to open up one or more additional processes within your application. This allows you to process different tasks parallel. Usually your Delphi applications will accomplish one task after another.

Operationg systems like Windows NT or Windows 2000, however, support multi-tasking, allowing multiple processes to work at virtualyl the same time. Within you applications you can make use of these feature by using threads. This will come in handy in different cases like multiple-processor machines or if one task has to wait for something else (e.q. disk access). A single threaded application (e.q. your normal Delphi app) will have to wait until every task is accomplished before running the next one - mutli-threaded apps work them parallel.

WHEN TO USE THREADS

Either you want to support multi-processor machines or you know your tasks depend on other tasks and not only on your calculations.

Especially on single processor machines the use of threads may actually degrade the performance of your whole application. If some task(s) have to be accomplished before the application can continue and all of these tasks are rather demanding for the processor you may consider not to use threading. However, on a multi-processor machine you will, almost certainly, gain speed using threading.

You should use threading when:

multiple, independent tasks need to be accomplished
your application runs on multi-processor systems
the accomplished tasks run idle some of their working time
you want to learn working with Threads :)

PROBLEMS WITH THREADS

Using Threads can open little "trouble cans." In single-threaded apps you have control over the execution order of your tasks and the way they access global variables. In multi-threaded applications you do not have this kind of control anymore, because multiple threads can access the variable at the same time. Reading global variables out of a thread will, usually, not create problems. Writing, itself, is no problem either. However, reading a variable, working with it and writing the new value back to the variable, will certainly bring you in trouble if two threads do this at the same time.

NOTE: For all of you how "hate" global variables, I do too. However, in threaded applications you will often have to use them in order to exchange processing informations.

CRITICAL SECTIONS

The problem mentioned before is the "critical section" of your thread. Once you decide to access a global variable, work with it, change its value and write it back you have to ensure that no other thread will do the same with this variable at the same time. Windows offers CriticalSections as a mean of thread control. Only one thread at a time can be within the critical section. Therefore, only one thread at a time can manipulate the value of the variables.

Critical Sections are not depending on their position in the code. You can use on Critical Section for different areas of your application. A critical section is a specific variable that holds references for every thread accessing in, allowing only one thread at a time to pass through it. Therefore, a thread should only use critical sections where needed and release it as soon as possible. Additionally, you have to ensure that the thread will leave the critical section or no other thread will be able to enter it - your application will not continue processing any data.

Pseudo-Code without a CS
Pseudo-Code with a CS
-
-
Load global variable
-
Work with gl. var.
-
Save global variable
-
-
-
Enter CS
try
   Load global variable
   -
   Work with gl. var.
   -
   Save global variable
finally
   Leave CS
end



NOTE: The use of critical sections will, slightly, slow down you application because of the processing (Enter/Leave) of the critical section as well as the fact that only one process can be within the code area of the critical section.

ENOUGH THEORY - A SAMPLE

The following sample will not be "great," it will be simple to show you the facts addressed before. The use of the variables isn't the best, however, do not mind - it just simple to learn.

THE FORM

Create a new application. Name the Form frmMain. To the form add an SpinEdit (sedtThreadCnt) from the Samples page. There we can choose how many threads will be started from our application. Add a Check Box (chkCS) allowing the user to choose whether the thread-safe (with a critical section) model is used or not. Add a Label (lblResult) to show the final Result of our Threads and a Button (btnStart) allowing the us to start the Thread Test. For the Form add an OnCreate and an OnDestroy, for the Button an OnClick event using the Object Inspector.

The code snippet below shows the full declaration of the form. Adapt the private and the public section.

NOTE: Add the unit "SyncObjs" to your global uses clause - it is needed for the TCriticalSection class.

TfrmMain = class(TForm)
  Label1: TLabel;
  sedtThreadCnt: TSpinEdit;
  btnStart: TButton;
  lblResult: TLabel;
  chkCS: TCheckBox;
  procedure btnStartClick(Sender: TObject);
  procedure FormCreate(Sender: TObject);
  procedure FormDestroy(Sender: TObject);
private
  { Private declarations }
  FThreadCount: Integer;
  FCriticalSection: TCriticalSection;
  FGlobalVariable: Integer;
  procedure ThreadDone(Sender: TObject);
  procedure SetGlobalVariable(const Value: Integer);
public
  { Public declarations }
  property GlobalVariable: Integer
    read FGlobalVariable
    write SetGlobalVariable;
  property CriticalSection: TCriticalSection
    read FCriticalSection;
end;

THE THREAD CLASSES

The following code snippet shows the declarations of both the unsafe and the safe version. Besides the class names they ar identical.

Every running Thread will increment a global variable 1000 times by one. Therefore, after running exactly one thread the global variable should be 1000, after 2 threads 2000, after 3 threads 3000, and so on ... or ? Well depending on the use of the critical section - one thread model will return the result as expected the other will not...

TUnsafeSampleThread = class(TThread)
private
  FLocalVariable: Integer;
protected
public
  procedure Execute; override;
end;

TSafeSampleThread = class(TThread)
private
  FLocalVariable: Integer;
protected
public
  procedure Execute; override;
end;

THE FULL SOURCE CODE

Below you can see the full source code. One, not nice part, is the Execute part of both Thread versions. You will see quite often the line:

Application.ProcessMessages;

I had to add this line in order to allow the other threads to execute as well. Our way of adding "idle time" to the threads.

RUNNING THE APPLICATION

The application will allow you to choose the number of threads running concurrently and whether to use the safe version or not. Have fun...

unit uMainForm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Spin, SyncObjs;

type
  TUnsafeSampleThread = class(TThread)
  private
    FLocalVariable: Integer;
  protected
  public
    procedure Execute; override;
  end;

  TSafeSampleThread = class(TThread)
  private
    FLocalVariable: Integer;
  protected
  public
    procedure Execute; override;
  end;

  TfrmMain = class(TForm)
    Label1: TLabel;
    sedtThreadCnt: TSpinEdit;
    btnStart: TButton;
    lblResult: TLabel;
    chkCS: TCheckBox;
    procedure btnStartClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
    FThreadCount: Integer;
    FCriticalSection: TCriticalSection;
    FGlobalVariable: Integer;
    procedure ThreadDone(Sender: TObject);
    procedure SetGlobalVariable(const Value: Integer);
  public
    { Public declarations }
    property GlobalVariable: Integer
      read FGlobalVariable
      write SetGlobalVariable;
    property CriticalSection: TCriticalSection
      read FCriticalSection;
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.DFM}

{ TUnsafeSampleThread }

procedure TUnsafeSampleThread.Execute;
var
  I: Integer;
begin
  for I := 1 to 1000 do
  begin
    Application.ProcessMessages;
    FLocalVariable := frmMain.GlobalVariable;
    Application.ProcessMessages;
    Inc(FLocalVariable);
    Application.ProcessMessages;
    frmMain.GlobalVariable := FLocalVariable;
  end;
end;

{ TSafeSampleThread }

procedure TSafeSampleThread.Execute;
var
  I: Integer;
begin
  for I := 1 to 1000 do
  begin
    Application.ProcessMessages;
    frmMain.CriticalSection.Acquire;
    try
      FLocalVariable := frmMain.GlobalVariable;
      Application.ProcessMessages;
      Inc(FLocalVariable);
      Application.ProcessMessages;
      frmMain.GlobalVariable := FLocalVariable;
    finally
      frmMain.CriticalSection.Release;
    end;
  end;
end;

{ TfrmMain }

procedure TfrmMain.ThreadDone(Sender: TObject);
begin
  Dec(FThreadCount);
  if FThreadCount = 0 then
  begin
    btnStart.Enabled := True;
    lblResult.Caption := 'GlobalVariable: ' + IntToStr(GlobalVariable);
  end;
end;

procedure TfrmMain.btnStartClick(Sender: TObject);
var
  I: Integer;
begin
  GlobalVariable := 0;
  FThreadCount := sedtThreadCnt.Value;
  for I := 0 to FThreadCount - 1 do
    if chkCS.Checked then
      with TSafeSampleThread.Create(False) do
        OnTerminate := ThreadDone
    else
      with TUnsafeSampleThread.Create(False) do
        OnTerminate := ThreadDone;
  btnStart.Enabled := False;
end;

procedure TfrmMain.SetGlobalVariable(const Value: Integer);
begin
  FGlobalVariable := Value;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  FCriticalSection := TCriticalSection.Create;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  FCriticalSection.Free;
end;

end.

Time Zones, Daylight Savings, and other Delights

Problem/Question/Abstract:

Time Zones, Daylight Savings, and other Delights

Answer:

If your application uses dates and times, what will happen when it's deployed to users in different time zones? Have you taken the effects of Daylight Savings Time into consideration?

We know that many locations do not recognize Daylight Savings Time; but what about locations in the Southern Hemisphere, such as Brazil and portions of Australia, where its implementation is the opposite of that in the Northern Hemisphere? Your particular application may not be affected, but if you need to convert between local time and Universal Coordinated Time (UTC) for any reason, you should consider the effects that time-zone changes will have on your program.

Who cares about changes in time zones? Your users might. Consider the relatively trivial example of a telephone dialer: wouldn't it be nice if users were notified of the local time when calling a phone number outside of the local calling area? This would require a lookup table of area codes to time zones, but it would certainly be a reasonable enhancement to a dialing program. Some types of programs are vitally concerned with time-zone changes, particularly technical programs, or those relating to navigation and astronomy, to name a few.

You could ask your users to specify time-zone settings when installing your product, but they already specified their date, time, and time zone when they set up their computers. Besides, mobile computing is so pervasive, a user might easily work in multiple time zones in a single day. It seems intrusive to ask the user for information already in the registry. At the same time, it's your responsibility to confirm that their settings make sense, and to offer alternatives if necessary.

A computer running Windows 95/98/NT maintains its internal time as Universal Coordinated Time, and displays the local time based on the user's time-zone setting, and the current state of Daylight Savings Time.

If all your application needs is the current UTC or local time, we could simply call the Win32 API procedures GetSystemTime or GetLocalTime. These functions return a data structure of type SystemTime:

type
SystemTime = record
wYear: Word;
wMonth: Word;
wDayOfWeek: Word;
wDay: Word;
wHour: Word;
wMinute: Word;
wSecond: Word;
wMilliseconds: Word;
end;

Most of the elements of the SystemTime record are self-explanatory; wDayOfWeek is an unsigned 16-bit integer (i.e. Word) value in the range 0-6, which identifies the day of the week, corresponding to Sunday through Saturday.

The elements of SystemTime can be used to assign a Delphi DateTime variable:

var
ST: SystemTime;
DT: TDateTime;
begin
// Get Universal Coordinated Time (UTC).
ST := GetSystemTime(ST);
with ST do
DT := EncodeDate(wYear, wMonth, wDay) +
EncodeTime(wHour, wMinute, wSecond, wMilliseconds);
end;

Note that SysUtils.Now is implemented in exactly this fashion, using GetLocalTime (SysUtils.Now replaces GetCurrentTime, which is obsolete).

Similarly, to determine if Standard or Daylight Time is in effect, we can call the Win32 API function GetTimeZoneInformation:

var
Error: Double;
TZInfo: TTimeZoneInformation;
begin
Error := GetTimeZoneInformation(TZInfo);
case Error of
0: { Unknown };
1: { Standard Time };
2: { Daylight Time };
end;
end;

Time Zone Information

GetTimeZoneInformation also returns a data structure (a record) that contains the current time-zone settings, and the information needed to convert between local and UTC times:

type
TTIMEZONEINFORMATION = record
Bias: Longint;
StandardName: array[0..31] of WCHAR;
StandardDate: TSystemTime;
StandardBias: Longint;
DaylightName: array[0..31] of WCHAR;
DaylightDate: TSystemTime;
DaylightBias: Longint;
end;

Elements of TTimeZoneInformation are shown in Figure 1.
Bias
Difference in minutes between local time and UTC, based on the formula UTC = local time + Bias.
StandardName
Null-terminated string identifying Standard Time, e.g. Pacific Standard Time. May be empty, or set to user's preference with SetTimeZoneInformation.
StandardDate
Record of type SystemTime that specifies the date and time when Standard Time begins.
StandardBias
Minutes added to Bias during Standard Time (normally zero).
DaylightName
Null-terminated string identifying Daylight Time, e.g. Pacific Daylight Time. May be empty, or set to user's preference with SetTimeZoneInformation.
DaylightDate
Record of type SystemTime that specifies the date and time when Daylight Time begins.
DaylightBias
Minutes added to Bias during Daylight Time (normally 60).
Figure 1: Elements of TTimeZoneInformation.

The dates and times represented by StandardDate and DaylightDate are implemented as a set. It's not permissible to have only one or the other specified; either both dates are specified (meaning Daylight Savings Time is implemented), or both are unspecified, in which case wMonth must be set to zero as a flag. Dates may be stored in either of two formats:

Absolute. wYear, wMonth, wDay, wHour, wMinute, wSecond, and wMilliseconds are combined to refer to a specific date and time.
Relative. Also called "day-in-month" format, refers to a particular occurrence of a day of the week, e.g. the last Sunday of the month.

Relative dates are by far the more common, and are implemented as shown here:

wYear must be set to zero as a flag.
wMonth identifies the month in which the change occurs.
wDayOfWeek identifies the day of the week (0-6 corresponding to Sunday-Saturday).
wDay identifies which occurrence of wDayOfWeek (1-5 where 1 is the first occurrence, and 5 means "last wDayOfWeek in the month").

The resulting Relative date is combined with the encoded time from the SystemTime record to specify the exact date and time when the change occurs.

Using this information, we can create a function that will tell us whether Daylight time is in effect for a given date and time. We'll assume for this discussion that TZInfo is a global variable of type TTimeZoneInformation that was returned by an earlier call to GetTimeZoneInformation, perhaps during FormCreate, as shown in Listing One.

Converting between Local Time and Universal Time

We now have enough information to convert local times to UTC, and vice versa. On Windows NT, we might use SystemTimeToTzSpecificLocalTime, which converts UTC to local time in a specific time zone, but this function isn't available to users of Windows 95. We'll continue to assume that TZInfo is a global variable of type TTimeZoneInformation returned by an earlier call to GetTimeZoneInformation, as shown in Listing Two.

We have one more step to make the program complete. Our program can decipher time-zone information, and can convert local times to UTC and back, but how do we handle a situation where the time zone changes while our program is running? The answer is found in the Borland FAQ database (FAQ2020D). First, add the following code to the private declaration section of your application:

private

procedure WMTIMECHANGE(var Message: TWMTIMECHANGE);
message WM_TIMECHANGE;

Then, add this wmTimeChange procedure to the implementation section:

procedure TFormName.wmTimeChange(
var Message: TWMTIMECHANGE);
begin
// Get new Time Zone information.
Error := GetTimeZoneInformation(TZInfo);
// Use value returned by GetTimeZoneInformation
// for error trapping.
case Error of
0: { Unknown };
1: { Standard Time };
2: { Daylight Time };
end;
// Update the form using new date/time settings...
end;

The wmTimeChange procedure can perform whatever action is necessary to update your application with the newly-changed time zone.

Conclusion

Hopefully, you will find this little foray into the Win32 API to be time well spent. Your application is now ready for prime time. Feel free to check out my Time Zone application, discussed in the sidebar TIMEZONE.EXE.

TIMEZONE.EXE is an example application designed to demonstrate using Delphi to access time-zone information, to convert between local time and UTC, and to respond to system-wide changes in the time-zone setting (see Figure A).


Figure A: The Time Zone application.

The program starts up initialized to the current local date and time, and lists the current time-zone setting (top), as well as the UTC and date that correspond to the local time and date in the edit controls.

To keep the program simple, user interaction is limited to two edit controls and two buttons.

The user may select a date using the DateTimePicker. The user may enter a time in the Time edit control. If the string entered by the user fails to convert to a valid time, the exception handler clears the control and sets the time to midnight.

The display is updated when any of the following actions occur:

The user clicks one of the controls.
The user selects a date, and the pop-up calendar closes.
The user tabs from one field to another.
The user presses [Enter] after making an entry in the Time edit control.

Two buttons are provided: Now sets the date and time to the computer's current date and time; while Exit terminates the program.

Begin Listing One
function DaylightSavings(DT: TDateTime): Boolean;
var
D, M, Y, WeekNo: Word;
DTBegins, STBegins: TDateTime;
begin
// Get Year/Month/Day of DateTime passed as parameter.
DecodeDate(DT, Y, M, D);
// If TZInfo.DaylightDate.wMonth is zero,
// Daylight Time not implemented.
if (TZInfo.DaylightDate.wMonth = 0) then
Result := False
else //Daylight Time is implemented.
begin
// If wYear is zero, use relative SystemTime format.
if (TZInfo.StandardDate.wYear = 0) then
// Relative SystemTime format.
// Calculate DateTime Daylight Time begins using
// relative format. wDay defines which wDayOfWeek
// is used for time change: wDay of 1 identifies
// the first occurrence of wDayOfWeek in the month;
// 2..4 identify the second through fourth
// occurrence. A value of 5 identifies the last
// occurrence in the month.
begin
// Start at beginning of Daylight month.
DTBegins :=
EncodeDate(Y, TZInfo.DaylightDate.wMonth, 1);
case TZInfo.DaylightDate.wDay of
1, 2, 3, 4:
begin
// Get to first occurrence of wDayOfWeek.
// Key point: SysUtils.DayOfWeek is
// unary-based; TZInfo.Daylight.wDay is
// zero-based
while (SysUtils.DayOfWeek(DTBegins) - 1) <>
TZInfo.DaylightDate.wDayOfWeek do
DTBegins := DTBegins + 1;
WeekNo := 1;
if TZInfo.DaylightDate.wDay <> 1 then
repeat
DTBegins := DTBegins + 7;
Inc(WeekNo);
until WeekNo = TZInfo.DaylightDate.wDay;
// Encode time Daylight Time begins.
with TZInfo.DaylightDate do
DTBegins := DTBegins + EncodeTime(
wHour, wMinute, 0, 0);
end;
5:
begin
// Count down from end of month to day of
// week. Recall that we set DTBegins to the
// first day of the month; go to the first
// day of the next month and decrement.
DTBegins := IncMonth(DTBegins, 1);
DTBegins := DTBegins - 1;
// Find the last occurrence of
// the day of the week.
while SysUtils.DayOfWeek(DTBegins) - 1 <>
TZInfo.DaylightDate.wDayOfWeek do
DTBegins := DTBegins - 1;
// Encode time Daylight Time begins.
with TZInfo.DaylightDate do
DTBegins := DTBegins + EncodeTime(
wHour, wMinute, 0, 0);
end;
end; // case.
// Calculate DateTime Standard Time begins using
// relative format. Start at beginning of
// Standard month.
STBegins :=
EncodeDate(Y, TZInfo.StandardDate.wMonth, 1);
case TZInfo.StandardDate.wDay of
1, 2, 3, 4:
begin
while (SysUtils.DayOfWeek(STBegins) - 1) <>
TZInfo.StandardDate.wDayOfWeek do
STBegins := STBegins + 1;
WeekNo := 1;
if TZInfo.StandardDate.wDay <> 1 then
repeat
STBegins := STBegins + 7;
Inc(WeekNo);
until (WeekNo = TZInfo.StandardDate.wDay);
// Encode time Standard Time begins.
with TZInfo.StandardDate do
STBegins := STBegins + EncodeTime(
wHour, wMinute, 0, 0);
end;
5:
begin
// Count down from end of month to day of
// week. Recall we set DTBegins to first
// day of the month; go to the first day of
// the next month and decrement.
STBegins := IncMonth(STBegins, 1);
STBegins := STBegins - 1;
// Find last occurrence of day of the week.
while SysUtils.DayOfWeek(STBegins) - 1 <>
TZInfo.StandardDate.wDayOfWeek do
STBegins := STBegins - 1;
// Encode time at which Standard Time begins.
with TZInfo.StandardDate do
STBegins := STBegins + EncodeTime(
wHour, wMinute, 0, 0);
end;
end; // case.
end
else
begin // Absolute SystemTime format.
with TZInfo.DaylightDate do
begin
DTBegins := EncodeDate(wYear, wMonth, wDay) +
EncodeTime(wHour, wMinute, 0, 0);
end;
with TZInfo.StandardDate do
begin
STBegins := EncodeDate(wYear, wMonth, wDay) +
EncodeTime(wHour, wMinute, 0, 0);
end;
end;
// Finally! How does DT compare to DTBegins and
// STBegins?
if (TZInfo.DaylightDate.wMonth <
TZInfo.StandardDate.wMonth) then
// For Northern Hemisphere...
Result := (DT >= DTBegins) and (DT < STBegins)
else
// For Southern Hemisphere...
Result := (DT < STBegins) or (DT >= DTBegins);
end;
end;
end Listing One

begin  Listing Two
function LocalTimeToUniversal(LT: TDateTime): TDateTime;
var
UT: TDateTime;
TZOffset: Integer;
// Offset in minutes.
begin
// Initialize UT to something,
// so compiler doesn't complain.
UT := LT;
// Determine offset in effect for DateTime LT.
if DaylightSavings(LT) then
TZOffset := TZInfo.Bias + TZInfo.DaylightBias
else
TZOffset := TZInfo.Bias + TZInfo.StandardBias;
// Apply offset.
if (TZOffset > 0) then
// Time zones west of Greenwich.
UT := LT + EncodeTime(TZOffset div 60,
TZOffset mod 60, 0, 0)
else if (TZOffset = 0) then
// Time Zone = Greenwich.
UT := LT
else if (TZOffset < 0) then
// Time zones east of Greenwich.
UT := LT - EncodeTime(Abs(TZOffset) div 60,
Abs(TZOffset) mod 60, 0, 0);
// Return Universal Time.
Result := UT;
end;

function UniversalTimeToLocal(UT: TDateTime): TDateTime;
var
LT: TDateTime;
TZOffset: Integer;
begin
LT := UT;
// Determine offset in effect for DateTime UT.
if DaylightSavings(UT) then
TZOffset := TZInfo.Bias + TZInfo.DaylightBias
else
TZOffset := TZInfo.Bias + TZInfo.StandardBias;
// Apply offset.
if (TZOffset > 0) then
// Time zones west of Greenwich.
LT := UT - EncodeTime(TZOffset div 60,
TZOffset mod 60, 0, 0)
else if (TZOffset = 0) then
// Time Zone = Greenwich.
LT := UT
else if (TZOffset < 0) then
// Time zones east of Greenwich.
LT := UT + EncodeTime(Abs(TZOffset) div 60,
Abs(TZOffset) mod 60, 0, 0);
// Return Local Time.
Result := LT;
end;
End Listing Two