Thursday, October 22, 2009

DSiWin32 1.51

It’s been some time since I’ve last updated my open source units… For example, DSiWin32, a collection of Win32 API helpers, was last updated in August 2008! Bad me!

Time to do some housecleaning, then. Let’s see what’s new in DSiWin32 1.51.

There are new dynamic forwarders: DSiWow64DisableWow64FsRedirection, DSiWow64RevertWow64FsRedirection, DSiGetTickCount64 and DSiGlobalMemoryStatusEx. They call appropriate API functions if they are available and return error on older Windows systems. [Click on the function name to see the API specification on the MSDN.]

Function DSiGetGlobalMemoryStatus is not much more than a wrapper to the GlobalMemoryStatusEx API and returns information about global memory status (paging, virtual memory etc). Information is returned in a TMemoryStatusEx record, which is also defined in the DSiWin32 unit.

We implemented six new function in the files section. DSiGetNetworkResource converts drive letter (created by mapping network location) back to the network path. DSiDisconnectFromNetworkResource disconnects drive letter from a network resource. [DSiConnectToNetworkResource was included in the previous public version 1.41.] DSiGetSubstDrive maps drive letter (created by using Subst command) to the associated folder and DSiGetSubstPath does similar for a file path that starts with a subst’ed letter. DSiDisableWow64FsRedirection disables file system redirection (mapping from \Windows\System32 to \Windows\SysWOW64 for 32-bit applications on 64-bit systems) for the current thread and DSiRevertWow64FsRedirection reverts this change.

There are also two new install functions. DSiAddApplicationToFirewallExceptionList adds application to the firewall exception list and DSiAddPortToFirewallExceptionList does the same for a TCP/IP port.

DSiGetCurrentThreadHandle and DSiGetCurrentProcessHandle return (true) handles for the current thread and process. [In contrast to GetCurrentThread and GetCurrentProcess APIs which return pseudo handle which cannot be used outside of the current thread/process context.]

DSiGetWindowsVersion was extended to detect Windows Server 2008, Windows 7 and Windows Server 2008 R2. DSiGetTrueWindowsVersion was also upgraded to return “Windows Server 2008 or Vista SP1” and “Windows 7 or Server 2008 R2”. It looks like it is not possible to discriminate between those operating systems on the API level :( Record TOSVersionInfoEx was also defined as it was used in the DSiGetWindowsVersion.

'Access' parameter was added to the DSiWriteRegistry methods so that user can request writing to the non-virtualized key when running on 64-bit system (KEY_WOW64_64KEY).

DSiExecuteAndCapture got much deserved workover. Now the caller can be informed of each line outputted by the child process.

Delphi 2009/2010 compatibility was fixed for DSiGetFolderLocation, DSiGetNetworkResource, DSiGetComputerName, DSiGetWindowsFolder, DSiExecuteAndCapture and bugs were fixed in DSiGetTempFileName and DSiGetUserName.

All in all, lots of things were changed and improved. If you’re already using DSiWin32 then this is a good time to upgrade. [And if not, start using it!]

Labels: , , , , ,

Thursday, January 10, 2008

Unicode Delphi

The Oracle at Delphi (aka Allen Bauer) has posted some information on Unicode support in the next Delphi (codenamed Tiburon).

Firstly, let me say that I really appreciate that we'll finally get VCL Unicode and reference-counted wide string support in Delphi. I really really really appreciate that.

Secondly, I must say, and I must say this very loud, that I DON'T LIKE HOW IT WILL BE IMPLEMENTED! (And I apologize for yelling in public.)

Allen wrote

This new data type will be mapped to string which is currently an AnsiString underlying type depending on how it is declared.  Any string declared using the [] operator (string[64]) will still be a ShortString which is a length prefixed AnsiChar array.  The UnicodeString payload will match that of the OS, which is UTF16.

And

If your code must still operate on Ansi string data, then simply be more explicit about it.  Change the declaration to AnsiString, AnsiChar, and PAnsiChar.  This can be done today and will recompile unchanged in Tiburon.

I wrote my response in comments to the Allen's post and I'll restate it here.

This is very very bad. This will not work for our company and I presume for many of us working with legacy applications with millions of source lines. We have applications using 3rd party components and libraries (all with source code, thankfully). We have applications using libraries that can be compiled for Win32 and for DOS (with BP7 and plenty of IFDEFs, of course). And we have no illusions that they will continue to work if string suddenly becomes an UTF-16-holding type.

We would like to move to Unicode Delphi, but we would like to do it gradually, when starting work on new applications. That way, we can test all units, components and libraries as they are introduced in the new program and we can fix all problems that will occur.

So please, CodeGear, give as an 'Unicode application' option. It should be disabled for old applications and enabled for new ones. That would be a much better plan.

[If you agree with me, make sure to post a comment in Allen's blog stating your opinion. If you don't agree with me, make sure to post a comment in Allen's blog stating your opinion. He needs as much feedback as possible to make decitions that will be good for us.]

Labels: , ,

Tuesday, November 13, 2007

Calculating accurate 'Now'

You may be aware of the fact that Windows functions that return current time with millisecond precision are not accurate to a millisecond. Then, again, you may be not. In any case, I'll show you how to calculate accurate time. Well, maybe not really accurate, but at least it will be miles better than Windows' functions.

Let's start with the system time. If you take a look at the GetSystemTime function, you'll see that it returns a structure containing years, months, hours ... and so on down to milliseconds. The problem is that it is not incremented in millisecond steps. If you fetch the time, wait for two milliseconds and fetch the time again, chances are that both structures would be completely the same. Raymond Chen states in Precision is not the same as accuracy that the accuracy of typical Windows clock is somewhere from 10 to 55 ms. On most computers I've tested, system time accuracy is approximately 15 ms.

To demonstrate this, I've written this 'complicated' code fragment:

procedure TForm6.btnGetSystemTimeClick(Sender: TObject);
var
i: integer;
st: TSystemTime;
begin
for i := 1 to 15 do begin
Windows.GetSystemTime(st);
outLog.Lines.Add(IntToStr(st.wMilliseconds));
Sleep(1);
end;
end;





imageThe code merely retrieves system time, logs it into TMemo and sleeps for approximately one millisecond. The result (picture on the right) is a little 'jumpy' - most of the time milliseconds stay still and then they are increased by approximately 15 milliseconds.






imageCareful reader would notice that the clock was read less than 16 times between the first '723' result and the first '739' result. That's because Sleep doesn't guarantee that the execution will resume in exactly the specified time.






Similar results (on the left) can be achieved by using GetTickCount instead of GetSystemTime.






If you still think that this doesn't concern you because you're only using Delphi's Now and similar functions, then you're wrong. Delphi calculates Now using GetLocalTime, which exhibits exactly the same symptoms as GetSystemTime.









Can we do better?






Of course we can! Otherwise this blog entry would not exist :)






There is a QueryPerformanceCounter function which returns current value of some Windows' internal counter. Exact interpretation of this value is hardware dependent so you have to use QueryPerformanceFrequency function to determine the speed with which the performace counter is incremented. The accuracy of the performace counter is also hardware-dependant but usually it is much higher than one millisecond.






So here's the plan. We'll take a snapshot of system time and performance counter. When we need an accurate time, we'll query the performance counter and use stored system time, stored performance counter and current performance counter to calculate current system time. The main magic is done in two lines immediately after 'else begin'. The rest of the code just converts milliseconds into the TSystemTime record.



function PerformanceCounterToMS(perfCounter: int64): int64;
begin
if GPerformanceFreq = 0 then
Result := 0
else
Result := Round(perfCounter / GPerformanceFreq * 1000);
end; { PerformanceCounterToMS }


procedure GetSystemTime_Acc(var systemTime: TSystemTime);
var
pcDiff : int64;
perfCount: int64;
sum : cardinal;
begin
if GPerformanceFreq = 0 then
Windows.GetSystemTime(systemTime)
else begin
QueryPerformanceCounter(perfCount);
pcDiff := PerformanceCounterToMS(perfCount - GPerfCounterBase); //milliseconds
sum := cardinal(GSystemTimeBase.wMilliseconds) + (pcDiff mod 1000);
systemTime.wMilliseconds := sum mod 1000;
pcDiff := pcDiff div 1000; //seconds
sum := cardinal(GSystemTimeBase.wSecond) + (pcDiff mod 60) + (sum div 1000);
systemTime.wSecond := sum mod 60;
pcDiff := pcDiff div 60; //minutes
sum := cardinal(GSystemTimeBase.wMinute) + (pcDiff mod 60) + (sum div 60);
systemTime.wMinute := sum mod 60;
pcDiff := pcDiff div 60; //hours
sum := cardinal(GSystemTimeBase.wHour) + (pcDiff mod 24) + (sum div 60);
systemTime.wHour := sum mod 24;
pcDiff := pcDiff div 24; //days
DecodeDateFully(GDateBase + pcDiff, systemTime.wYear, systemTime.wMonth,
systemTime.wDay, systemTime.wDayOfWeek);
end;
end; { GetSystemTime_Acc }





Similar but even simpler code deals with GetTickCount. There is also a 64-bit version of GetTickCount, which doesn't have its problems (wrapping around every 49 days or so).



function GetTickCount64_Acc: int64;
var
perfCount: int64;
begin
if GPerformanceFreq = 0 then
Result := Windows.GetTickCount
else begin
QueryPerformanceCounter(perfCount);
Result := GTickCountBase + PerformanceCounterToMS(perfCount - GPerfCounterBase);
end;
end; { GetTickCount64_Acc }

function GetTickCount_Acc: DWORD;
begin
Result := GetTickCount64_Acc AND $FFFFFFFF;
end; { GetTickCount_Acc }





The only remaining piece of mistery is the initialization code. We have to take a snapshot of system time immediately after it is incremented (to get the most accurate value) and a snapshot of performance counter. We also try to make sure that context switch did not occur between those two measurements.



procedure InitExactTimeBase;
var
perfCount1: int64;
perfCount2: int64;
st1 : TSystemTime;
st2 : TSystemTime;
begin
if GPerformanceFreq = 0 then
Exit;
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_HIGHEST);
Sleep(0);
Windows.GetSystemTime(st2);
repeat
st1 := st2;
QueryPerformanceCounter(perfCount1);
Windows.GetSystemTime(st2);
GTickCountBase := Windows.GetTickCount;
QueryPerformanceCounter(perfCount2);
until (st1.wMilliseconds <> st2.wMilliseconds) and
(Round(perfCount1 / GPerformanceFreq * 10000) = Round(perfCount2 / GPerformanceFreq * 10000));
SetThreadPriority(GetCurrentThread, THREAD_PRIORITY_NORMAL);
GPerfCounterBase := perfCount2;
GSystemTimeBase := st2;
GDateBase := EncodeDate(st2.wYear, st2.wMonth, st2.wDay);
end; { InitExactTimeBase }





Pitfalls






This code is useful when you want to measure lenght of some operation in the range of few milliseconds. You would not, however, want to use it as a replacement of standard functions when you just need a real-time clock. There is, for example, no resynchronisation - if user or some time-adjusting program changes the system clock, my 'accurate' code will not notice this. On the other hand, this can be useful when performing interval measurements.






You should also keep in mind that performance counter is not necessary problem-free. For example, Microsoft's knowledge base entry #274323 describes performance counter problems on some (admittedly buggy) hardware platforms.






The Code






GpTime web pages are not up yet so for the time being the only access to the source is here. There is also a small test program available.






Things go better with ... Vista






I was totally surprised when I tested my unit on Vista and found out that both GetSystemTime and GetLocalTime work with a millisecond accuracy! GetTickCount, however, doesn't and is incremented in old 10-55 ms (depending on the platform) intervals.






As the Now function is calculated using GetLocalTime, it also has 1 ms accuracy on Vista.



The proof is below. From left to right: GetSystemTime, GetTickCount, GetLocalTime, Now.

image image image image



2009 Update



Later I found all those hacks much too much unstable for my likings. Now I’m using this trivial approach:



unit GpTime;

interface

///<summary>Returns current time in milliseconds. Does not have well-defined time base.</summary>
function Now64: int64;

implementation

uses
Windows,
SysUtils,
MMSystem,
SyncObjs,
GpStuff;

var
GNowLock : TCriticalSection;
GNowHigh32 : cardinal;
GNowLastLow32: cardinal;

{ exports }

function Now64: int64;
begin
GNowLock.Acquire;
try
Int64Rec(Result).Lo := timeGetTime;
if Int64Rec(Result).Lo < GNowLastLow32 then
Inc(GNowHigh32);
GNowLastLow32 := Int64Rec(Result).Lo;
Int64Rec(Result).Hi := GNowHigh32;
finally GNowLock.Release; end;
end; { Now64 }

initialization
GNowLock := TCriticalSection.Create;
GNowHigh32 := 0;
GNowLastLow32 := 0;
timeBeginPeriod(1);
finalization
timeEndPeriod(1);
FreeAndNil(GNowLock);
end.


Labels: , , , , ,

Thursday, October 25, 2007

Disabling Aero

Few days ago, David J Taylor started a borland.public.delphi.language.delphi.win32 thread on how to disable Aero interface on Vista programmatically.

Yesterday, he found and published an answer. David, thanks!

I know many are still struggling with old applications and their compatibility with Aero. Maybe this will help.

I have repacked David's function into two functions in the DSiWin32 library (DSiAeroEnable, DSiAeroDisable) and added a third one that checks whether Aero interface is enabled (DSiAeroIsEnabled).

Labels: , , ,