Decimal rounding and printer selection - delphi

I use Delphi RAD Studio 2010 and DecimalRounding_JH1.pas from http://cc.embarcadero.com/item/21909.
It works good, but I don't know why, in some old machines (Pentium IV with Windows XP SP3), the round fails after access to printer.printerindex. I have checked that the problem not is Windows XP due it works in other machines with this OS.
I have made a simple project that round an extended value with DecimalRounding_JH1 (drHalfUp) with two decimals (1.105 -> rounds to 1.11). But If I read printer.printerindex, then 1.105 rounds to 1.10).
I thought it could be the "FDIV bug", but compiling with "FDIV safe" doesn't resolve the problem.
The code:
var d1,d2:extended;
i:integer;
begin
d1:=1.105;
d2:=DecimalRounding_JH1.DecimalRoundExt(d1,2,drHalfUp);
memo1.lines.add(FloatToStr(d2)); // --> shows 1.11 (OK)
i:=Printer.printerindex;
d2:=DecimalRounding_JH1.DecimalRoundExt(d1,2,drHalfUp);
memo1.lines.add(FloatToStr(d2)); // --> shows 1.10 (ERROR!!!)
...
I know that it is very strange, but I've tested it and It's as I said.
What could I do?
Edited:
If I add Printer.printerindex:=1; (for example) before i:=Printer.printerindex; then again it works good. Reading printer unit, the difference is about execute "SetToDefaultPrinter" or not:
function TPrinter.GetPrinterIndex: Integer;
begin
if FPrinterIndex = -1 then SetToDefaultPrinter;
Result := FPrinterIndex;
end;
thanks in advance.

Certain parts of the system printer libraries have a rather nasty habit of modifying the 8087 control word. You should restore it to its default value after using methods and properties of Printer.
For example, you might write it like this:
Set8087CW(Default8087CW);
The comments in my codebase suggest that you only need to do this after the VCL printer code has been initialized for the first time. So you could deal with this in your program's startup. Read Printer.PrinterIndex and then immediately set the control word to its desired value.

Related

SAPI 5.4/11 Japanese TTS in Delphi GUI vs console

Trying to use MS Speech API v11 with a Japanese engine (MS Haruka) in Delphi 10.3.
I have a sample app with a form and a button. The click handler goes:
uses SpeechLib11_TLB; // Imported from "Microsoft Speech Object Library" v.B.0
procedure TForm1.Button1Click(Sender: TObject);
var
v: ISpeechVoice;
begin
v := CoSpVoice.Create();
//5.4 only but won't hurt
v.Voice := v.GetVoices('language=411', '').Item(0);
v.Speak('時間', SVSFDefault);
end;
That causes an error, "Catastrophic failure" (HRESULT 0x8000FFFF, E_UNEXPECTED). The code that I think should be equivalent works in a C++ project:
#include <windows.h>
#import "libid:d3c4a7f2-7d27-4332-b41f-593d71e16db1" rename_namespace("SAPI") //v11
//#import "libid:C866CA3A-32F7-11D2-9602-00C04F8EE628" rename_namespace("SAPI") //v5.4
int wmain()
{
CoInitializeEx(0, COINIT_APARTMENTTHREADED);
{
SAPI::ISpeechVoicePtr v;
v.CreateInstance(__uuidof(SAPI::SpVoice));
//Needed for 5.4 only, but won't hurt
SAPI::ISpeechObjectTokensPtr voices(v->GetVoices(L"language=411", L""));
v->Voice = voices->Item(0);
v->Speak(L"時間", SAPI::SVSFDefault);
}
CoUninitialize();
return 0;
}
That works and speaks. So the SAPI per se is not broken on the machine. The platform is Win32 in both projects, not Win64. The Japanese voice is the default one (no need to set it explicitly).
Same result with SAPI 5.4 proper (not OneCore), although the Japanese voice is not the default one and I had to add a couple of lines to set it as the default.
Further debugging reveals that on the Delphi side, as much as calling the v.Voice property getter immediately after the first line causes the same error, E_UNEXPECTED. Meanwhile, the Voice setter works if you pass it a valid voice token object from GetVoices(). It looks as if the voice object initializes itself to its defaults correctly in C++, but somehow skips that in the Delphi project.
Requesting v.Voice right after construction works though in Delphi with SAPI 5.4. Calling Speak() still throws a E_UNEXPECTED.
What could be the difference in process/threadwide execution context between C++ and Delphi? It's not the thread locale. The COM thread model is apartment in both.
The same Delphi code works with an English phrase and an English voice (MS Helen). So whatever init failure there might be, it's probably specific to Haruka.
The SAPI 11 runtime is available here. The language data for TTS are here.
Another data point. I've rewritten the SAPI logic in Delphi to use SAPI 5.4 OneCore instead (not SAPI 5.4 proper). Unlike 5.4 and 11, it doesn't expose an IDispatch-based interface, and it's somewhat clumsier specifically in Delphi, but Japanese TTS works. The question, as initially posed, is still unanswered, but at least there's a workaround. I'll write up an answer, but I won't accept it.
However, it's not the custom vs. dual distinction that's to blame. I've changed the logic to use custom interfaces instead of automation ones with SAPI 5.4 proper (the typelib defines both), still got E_UNEXPECTED from Speak(). There's no error info.
Here's another beautiful data point: SAPI 5.4 TTS with automation based API works and talks as expected in a Delphi console app. So it's not even Delphi specific, it's somehow VCL specific. What is it with Delphi GUI? Needless to say, I immediately retested the C++ snippet in a C++ GUI application, with a form and a button. The C++ one talks.
Not an answer, but a workaround.
Windows 10 comes with two flavors of 32-bit SAPI. There's SAPI 5.4 proper (in system32\speech), and also SAPI 5.4 OneCore (in system32\speech_onecore). The latter, even though it's superficially the same, exposes a different typelib - there's no automation support, all interfaces are custom instead of dual. What's more important, when you download the Japanese TTS voice in the Windows 10 Settings app, you end up with 3 voices under OneCore (Sayaka, somehow, missing) and only one, Haruka, under 5.4 proper.
Delphi can consume custom interfaces in a typelib, but the methods look somewhat clumsier. Also, the enumeration of voices in the automation API is cleaner. Anyway, here goes.
uses SpeechLib54Core_TLB; // This time it's OneCore
procedure TForm1.Button1Click(Sender: TObject);
var
v: ISpVoice;
cat: ISpObjectTokenCategory;
toks: IEnumSpObjectTokens;
tok: ISpObjectToken;
sno: LongWord;
hr: HResult;
n: Cardinal;
begin
v := CoSpVoice.Create();
hr := v.GetVoice(tok);
tok.GetCategory(cat);
cat.EnumTokens('language=411', '', toks); //411 means Japanese
toks.GetCount(n);
if n = 0 then
exit; // No Japanese voices installed
toks.Item(0, tok); //Take the first one - typically Ayumi
v.SetVoice(tok);
v.Speak('時間', 0, sno);
end;
Note that passing a Japanese string literal to a COM method works without an explicit cast to a wide string.

Delphi : Field 'False' not found. occurred only on my computer

I have a TCustomClientDataSet (in memory) which is used to do some filtering in a Delphi program.
My filtering code look like :
CDSFilteredGroup.Filter := 'ACP_type = 1 AND ACP_by_default <> False';
CDSFilteredGroup.Filtered := True;
When I compile and run it, I got the following error only with Delphi (XE4) installed on my computer :
CDSFilteredGroup: Champ 'False' non trouvé.
Translation: Field 'False' not found. The same code compile and run perfectly fine on my colleagues computers, but not on mine. There must be an option somewhere that I miss, any idea ?
Remember, the code is ok and working...
The TExprParser instance used for handling the filter string relies on the resourcestrings given as STextTrue and STextFalse. You probably have another language than English configured for your IDE library. Try switching to English and see if the error disappears.
To workaround that problem in general replace the text "False" with the content of the resourcestring STextFalse.
BTW, this was corrected in later Delphi versions that also accept the literal "False" even in non English environments.

Unit source code does not match code execution path when breakpoint hit

I am debugging a DirectShow filter I created with the DSPACK code library using Delphi 6 Pro. When a breakpoint I set is hit in one particular unit named BaseClass.pas, and I begin tracing, the Execution Point jumps to strange places in the source code. This usually indicates that the source code being traced does not match the source code that was compiled into one of the packages being used by the Delphi application. Oddly enough it is only the BaseClass unit since I have traced other units belonging to the DSPACK code library and they do not exhibit this problem. I am not using run-time packages.
I scanned my disk and found only one copy of BaseClass.dcu with a modification date equal to the last time I built the program. I have not modified the source for that unit or any other belonging to DSPACK. Since my Filter is part of the main application this indicates that BaseClass.pas would be subject to a dual use situation since it is used to build the DSPACK component package (dpk), and is also referenced by my main application directly via the TBCSource object my Filter descends from. Note, I did try adding the unit PAS file directly to my Project but that didn't fix anything.
I also went back and re-opened each of the DSPACK package files and did a full re-build. None of this helped. Is there something else I can try to get the source synchronized with the compiled image of the BaseClass unit? Or is a different problem altogether and if so, what is it and how can I fix it?
Sometimes this happens when code is copied/pasted from web pages or other sources, and the lines don't end with CR/LF pairs (#13#10 or 0x0D0A, standard for Windows) but end in only LF (#10 or 0x0A, typically the line ending in *nix systems) or CR (#13 or 0x0D, typical with Mac OSX/iOS). The incorrect line terminators confuse the debugger - this has been an issue for the past several Delphi versions.
You can sometimes fix this by opening the source file using a text editor like Notepad, making a small meaningless change (insert and then delete a blank line, for instance), and then save the file.
I had same problem and made a similar utility. Fixed it.
Basically, just this:
procedure adjustCRLF(filename : String);
var
strList : TStringList;
begin
strList := TStringList.Create;
try
strList.LoadFromFile(filename);
strList.Text := AdjustLineBreaks(strList.Text);
strList.SaveToFile(filename);
finally
strList.Free;
end;
end;
There is another way this can happen: if the IDE erroneously opens another source file with the same name (but different, such as an earlier version) then all the debug points will be incorrect, and the debugger will even allow you to step through the incorrect file.
I've seen Delphi 7 do this once.
Make sure that when you rebuild it, that in the compiler options for your project that you have "Debug Information" turned on. In fact, most of the options under Debugging should be set in your project's Compiler options.
Also, if you haven't already, restart Delphi.

JclLastExceptStackListToStrings produces an empty string list

I've installed JCL into Delphi 2010. In the following code
try
raise Exception.Create('Error Message');
except
on E: Exception do
begin
ResultStatus := JclLastExceptStackListToStrings(sl, True, True, True, True);
end;
end;
Also I have this call in the initialization section:
initialization
JclStartExceptionTracking;
ResultStatus is false and sl is empty. I have set options to generate map and .jdbg files. Other functions, like ProcByLevel work fine. Is there something else I need to do to make JclLastExceptStackListToStrings work?
From JclDebug.pas file:
Last modified: $Date:: 2010-09-07 19:43:19 +0200 (mar., 07 sept. 2010)
Revision: $Rev:: 3331
Try calling
JclStartExceptionTracking;
at the very start of your application.
[Edit]You already are. Then I'm currently out of suggestions. [/Edit]
I use:
JclStackTrackingOptions := [stStack, stExceptFrame, stRawMode, stAllModules,
stStaticModuleList];
I solve this problem in Delphi XE 1.
open Project Options menu
open Delphi Compiler tree node
select Linking tree node
enable Debug Information checkbox
I've been stuck with this problem for a few days. JCL stack tracking was working with my programs and I was able to pinpoint the error location. Somehow it just stopped working.
Then I worked with JCL's demo to see if it is working without any problem. It worked very well. I set all the project options same with the demo and yet my program did not dump stack while the demo was working everytime.
Suddenly I noticed that the only difference between my program and the demo is CPU architecture. Demo is 32 bits and my program is 64 bits.
I compiled the JCL demo as 64 bits and it stopped working too. It is working only with 32 bits code. Not 64 bits.
Unfortunatelly my program needs 6-7 gigabytes of memory and I cannot use the 32 bits mode. I suggest to use Eurekalog since it is working for both architectures.

ZeroConf/Bonjour Code that works in Delphi 7 not working in 2009

I have the following declaration for DNSServiceRegister:
function DNSServiceRegister
(
var sdRef: TDNSServiceRef;
const flags: TDNSServiceFlags;
const interfaceIndex: uint32_t;
const name: PUTF8String; //* may be NULL */
const regType: PUTF8String;
const domain: PUTF8String; //* may be NULL */
const host: PUTF8String; //* may be NULL */
const port: uint16_t;
const txtLen: uint16_t;
const txtRecord: Pointer; //* may be NULL */
const callBack: TDNSServiceRegisterReply; //* may be NULL */
const context: Pointer //* may be NULL */
): TDNSServiceErrorType; stdcall; external DNSSD_DLL;
In my Bonjour framework I have the following response to an announced service being made active (i.e. to actually start announcing itself, via Bonjour):
procedure TAnnouncedService.Activate;
var
flags: Cardinal;
name: UTF8String;
svc: UTF8String;
pn: PUTF8String;
ps: PUTF8String;
begin
fPreAnnouncedServiceName := ServiceName;
inherited;
if AutoRename then
flags := 0
else
flags := kDNSServiceFlagsNoAutoRename; { - do not auto-rename }
if (ServiceName <> '') then
begin
name := ServiceName;
pn := PUTF8String(name);
end
else
pn := NIL;
svc := ServiceType;
ps := PUTF8String(svc);
CheckAPIResult(DNSServiceRegister(fHandle,
flags,
0 { interfaceID - register on all interfaces },
pn,
ps,
NIL { domain - register in all available },
NIL { hostname - use default },
ReverseBytes(Port),
0 { txtLen },
NIL { txtRecord },
DNSServiceRegisterReply,
self));
TBonjourEventHandler.Create(fHandle);
end;
This is more verbose than I think it strictly needs to be, certainly it was working perfectly well in Delphi 7 in a much less verbose form. I have expanded a lot of operations into explicit steps to facilitate debugging, e.g. to be able to identify any implicit transforms of string payloads that may be occuring "under the hood" in Delphi 2009.
Even in this untidy expanded form this code compiles and works perfectly well in Delphi 7, but if I compile and run with Delphi 2009 I get no announcement of my service.
For example, if I run this code as part of a Delphi 7 application to register a _daap._tcp service (an iTunes shared library) I see it pop-up in a running instance of iTunes. If I recompile the exact same application without modification in Delphi 2009 and run it, I do not see my service appearing in iTunes.
I get the same behaviour when monitoring with the dns-sd command line utility. That is, service code compiled with Delphi 7 behaves as I expect, compiled in Delphi 2009 - nothing.
I am not getting any errors from the Bonjour API - the DNSServiceRegisterReply callback is being called with an ErrorCode of 0 (zero), i.e. success, and if I supply a NIL name parameter with AutoRename specified in the flags then my service is allocated the correct default name. But still the service does not show up in iTunes.
I am at a loss as to what is going on.
As you might be able to tell from the expansion of the code, I have been chasing potential errors being introduced by the Unicode implementation in Delphi 2009, but this seems to be leading me nowhere.
The code was originally developed against version 1.0.3 of the Bonjour API/SDK. I've since updated to 1.0.6 in case that was somehow involved, without any success. afaict 1.0.6 merely added a new function for obtaining "properties", which currently supports only a "DaemonVersion" property for obtaining the Bonjour version - this is working perfectly.
NOTE: I'm aware that the code as it stands is not technically UTF8-safe in Delphi 7 - I have eliminated explicit conversions as far as possible so as to keep things as simple as possible for the automatic conversions that Delphi 2009 applies. My aim now is to get this working in Delphi 2009 then work backward from that solution to hopefully find a compatible approach for earlier versions of Delphi.
NOTE ALSO: I originally also had problems with browsing for advertised services, i.e. identifying an actual iTunes shared library on the network. Those issues were caused by the Unicode handling in Delphi 2009 and have been resolved. My Delphi 2009 code is just as capable of identifying an actual iTunes shared library and querying it's TXT records. It's only this service registration that isn't working.
I must be missing something stupid and obvious.
Does anyone have any ideas?!
UPDATE
Having returned to this problem I have now discovered the following:
If I have a pre-D2009 and a D2009+ IDE open (e.g D2006 and D2010) with the same project loaded into both IDE's concurrently:
Build and run under 2006: It works - my service announcement is picked up by iTunes
Switch to D2010 and run (without building): It does a minimal compile, runs and works.
Do a full build in D2010: It stops working
Switch back to D2006 and run (without building): It doesn't work
Do a full build in D2006: It works again
Does this give anyone any other ideas?
The answer to this is mind boggling. On the one hand I made a completely stupid, very simple mistake, but on the other hand it should never - as far as I can see - have worked in ANY version of Delphi!
The problem was nothing what-so-ever to do with the Unicode/non-unicodeness of any strings, but was actually due to a type mismatch in the PORT parameter.
I was passing in the result of ReverseBytes(Port) - that parameter expected a uint16_t, i.e. a Word value. My Port property was however declared (lazily) as an Integer!!
Once I fixed this and had Port declared as a Word, it now works on both D2007- and D2009+ versions of Delphi.
Very weird.
I can only think that some other edge-case behaviour of the compiler that might have somehow affected this was changed when Unicode support was introduced.
Based on the information that we have available here, the situation is this:
When calling the DLL with your code in Delphi 2007, it gives one result.
When calling the same DLL with your code in Delphi 2009, it gives another result.
The suspicion is, that it is related to the Delphi 2009 compiler.
Logically, the difference must therefore be, that Delphi 2009 sends different values as parameters. In order to make the debugging truly Delphi-independent, you therefore need to create a dummy DLL, which reports the values it gets. Other Delphi-dependent methods may be applied, like looking at the disassembly of the function-call into the DLL, and debugging it so that we know exactly what values are passed, and how, to the DLL, in both compilers.
I can't find the declaration instruction for the vars "ServiceName" and "ServiceType" in your code sample.
Assuming a String type (thus a unicode string), I guess (yes... no D2009 available to test this) lazy typecasting could be an issue:
name := ServiceName;
Why not use the following?
name := PAnsiChar(AnsiString(ServiceName))
Anyhow... just my 2 cts.
BTW:
I always use the pre defined "EmptyStr", "EmptyWideStr" ... so the test would look like:
if (ServiceName <> EmptyStr) then
which should be safe and avoid the confusion of types.
On the other side, Delphi may interpret the '' as an ANSIChar like the following declaration do:
const
MyParagraphChar = '§';
Not sure... I'm confused - should go home now ;)
If the DLL is not written using Delphi 2009, you may want to use something else than PUTF8String. The Delphi 2009 Utf8String type is different from Delphi 2007's UTF8String type.
If the DLL was written using C/C++, I strongly suggest to use PAnsiChar() instead of PUtf8String.

Resources