Delphi SAPI Text-To-Speech - delphi

First of all: this is not a duplicate of Delphi and SAPI. I have a specific problem with the "SAPI in Delphi" subject.
I have used the excellent Import Type-Library guide in Delphi 2009 to get a TSpVoice component in the component palette. This works great. With
var
SpVoice: TSpVoice;
I can write
SpVoice.Speak('This is an example.', 1);
to get asynchronous audio output.
First question
According to the documentation, I would be able to write
SpVoice.Speak('This is an example.', 0);
to get synchronous audio output, but instead I get an EZeroDivide exception. Why's that?
Second question
But more importantly, I would like to be able to create the SpVoice object dynamically (I think this is called to "late-bind" the SpVoice object), partly because only a very small fraction of all sessions of my app will use it, and partly because I do not want to assume the existance of the SAPI server on the end-user's system.
To this end, I tried
procedure TForm1.FormClick(Sender: TObject);
var
SpVoice: Variant;
begin
SpVoice := CreateOleObject('SAPI.SpVoice');
SpVoice.Speak('this is a test', 0);
end;
which apparently does nothing at all! (Replacing the 0 with 1 gives me the EZeroDivide exception.)
Disclaimer
I am rather new to COM/OLE automation. I am sorry for any ignorance or stupidity shown by me in this post...
Update
For the benefit of everyone encountering the same problem as I did, the video by François explained there is a bug in SAPI/Windows (some incompatibility somewhere), which makes the following code raise the EZeroDivide exception:
procedure TForm1.FormClick(Sender: TObject);
var
SpVoice: variant;
begin
SpVoice := CreateOleObject('SAPI.SpVoice');
SpVoice.Speak('This is a text.');
end;
The solution, as presented by the video, is to alter the FPU control word:
procedure TForm1.FormClick(Sender: TObject);
var
SpVoice: variant;
SavedCW: Word;
begin
SpVoice := CreateOleObject('SAPI.SpVoice');
SavedCW := Get8087CW;
Set8087CW(SavedCW or $4);
SpVoice.Speak('This is a text.');
Set8087CW(SavedCW);
end;
And, in addition, if you want to play a sound asynchronously, then you have to make sure that the player doesn't go out of scope!

You may find interesting to see this CodeRage 4 session on "Speech Enabling Delphi Applications (zip)"
You'll get the "how-to" you're looking for...
(and I guess you are on Vista or + as the the zero divide did not happend on XP)

I was having the same problem in Delphi XE2. The Set8087CW(SavedCW or $4) solution presented in the question did not work for me. It merely replaced the division by zero exception with another floating point exception.
What did work for me is this:
SavedCW := Get8087CW;
SetFPUExceptionMask([exInvalidOp, exDenormalized, exZeroDivide, exOverflow, exUnderflow, exPrecision]);
SpVoice.Speak('All floating point exceptions disabled!', 0);
Set8087CW(SavedCW);

Related

Load TGPBitmap from MemoryStream

I have been asked to correct an issue (not related to this question) in a legacy Delphi program. After fixing some issues with missing components, I am now stuck with some GDI Plus functionality, which stops me from compiling the program. One of the functions where this is used is:
function TDownLoadItem.LoadRawBitmapFromStream(var bm: TBitmap): Boolean;
var
image: TGPBitmap;
begin
Result := False;
if Content.Size = 0 then
exit;
// NOTE: Content is a TMemoryStream, declared globally.
image := GDIPlusHelper.LoadBitmapFromStream(Content); // <== This is where the problem is....
try
bm.Width := image.GetWidth;
bm.Height := image.GetHeight;
with TGPGraphics.Create(bm.Canvas.Handle) do
try
DrawImage(image, 0, 0, image.GetWidth, image.GetHeight);
Result := True;
finally
Free;
end;
finally
image.Free;
end;
end;
I think (not sure) the last Delphi version used was 2006, I am on Delphi Rio 10.3.
Online I have managed to find GDI+ 1.2, but this does not solve the problem. The procedure LoadBitmapFromStream does not exit in these libraries. GDIPlusHelper was apparently renamed to GDIPlusHelpers and most code has changed from classes to interfaces. I suspect an older edition of the GDI Plus libraries were used, but I cannot find these.
Reworking the code would be too complex as it would require Content to be an IStream instead of a TMemoryStream. Also, simply using a TBitmap is not feasible either as other code (not shown) uses functionality specific to TGPBitmap (e.g. RotateFlip).
Any suggestions on how to fix/work around this? Thanks in advance!

Is globalalloc with GMEM_MOVEABLE dangerous for local variables in Delphi?

Our programming dept just spent about a non-mythical man-month tracking down what we think is a bug in a 3rd party component, here's their copyrighted source code:
function TGDIPPicture.GetImageSizes: boolean;
var
multi: TGPImage;
pstm: IStream;
hGlobal: THandle;
pcbWrite: Longint;
begin
result := false;
if Empty then
Exit;
if FDataStream.Size = 0 then
Exit;
hGlobal := GlobalAlloc(GMEM_MOVEABLE, FDataStream.Size);
if (hGlobal = 0) then
raise Exception.Create('Could not allocate memory for image');
try
pstm := nil;
// Create IStream* from global memory
CreateStreamOnHGlobal(hGlobal, TRUE, pstm);
pstm.Write(FDataStream.Memory, FDataStream.Size,#pcbWrite);
multi := TGPImage.Create(pstm);
FWidth := multi.GetWidth;
FHeight := multi.GetHeight;
Result := true;
multi.Free;
finally
GlobalFree(hGlobal);
end;
end;
We found the problem was with TMS's AdvOfficeTabSet. If we added tabs, then it crashed, if we didn't add tabs then it didn't crash. (the crash was one of those un-debuggable app hangs that hits you 10 steps after the real problem).
Following Raymond Chen's advice I replaced GMEM_MOVEABLE with GPTR and it appears to have fixed the problem.
I'm wondering if anyone can tell me if the above code had any legitimate reason for using GMEM_MOVEABLE. AFAIK it's only for the clipboard and it should always be used with GlobalAlloc.
while I was typing this another programmer got an error in the GlobalFree function using my code. So, apparently this doesn't work either. Could really use some help here!
*CreateStreamOnHGlobal is a Windows API function. (which apparently prefers GMEM_MOVEABLE)
*TGPImage is part of TMS's implementation of the GDI+ library.
Jonathan has identified the obvious problem, that being the double free of the HGLOBAL. But as you have found, the use is GMEM_MOVEABLE is correct.
Frankly, the code seems needlessly complex. I suggest you use the built in stream adapter and avoid any GlobalAlloc. To get an IStream you just need to do this:
pstm := TStreamAdapter.Create(FDataStream);
That's it.

Delphi XE4 gives E2036 when accessing generic list items of 'object's

This is probably similar / continuation on the previous question below:
Why Delphi XE3 gives "E2382 Cannot call constructors using instance variables"?
Now I'm trying Delphi XE4 with the same code (with 'constructor' changed to 'procedure' as per the solution of the above question).
Now I have also these things in a generics list, i.e. I have
TCoordRect = object
public
function Something: Boolean;
end;
and then a list of these in a function parameter, which I loop through and try to access the items directly:
function DoSomething(AList: TList<TCoordRect>): Boolean;
var
i: Integer;
begin
Result := False;
for i := 0 to AList.Count - 1 do
begin
Result := Result or AList[i].Something; // <-- Here comes the compiler error!
end;
end;
This gives the compiler error "E2036 Variable required". However, if I don't access it directly, i.e put instead a local variable and use that first, then it works:
function DoSomething(AList: TList<TCoordRect>): Boolean;
var
i: Integer;
ListItem: TCoordRect;
begin
Result := False;
for i := 0 to AList.Count - 1 do
begin
ListItem := AList[i];
Result := Result or ListItem.Something; // <-- Now this compiles!
end;
end;
And another "workaround" is to remove all these 'object' types and change them to 'class', but I'm curious as to why this does not work like it used to? Is it again just something with "the compiler moving towards mobile development" or is there some more specific reason, or is this even a bug? BTW I also reported this as a QC issue, so will see if something comes from there.
It's a compiler bug, and it's present in all earlier versions of the compiler. The fault is not limited to XE4. Submitting a QC report is the correct response.
I would not be surprised if Embarcadero never attempt to fix it. That's because you are using deprecated object. Switch to using record and the code compiles.
The issue you have uncovered in this question is unrelated to the SO question you refer to at the top of your question.
Incidentally, this really is a case of old meets new. Legacy Turbo Pascal objects, and modern day generic containers. You are mixing oil and water!

Seeking (free) framework for network programming

This is an attempt to rephrase my previous question as a result of the feeedback which it received.
I want a simple network communication which I can use as an underlying framework and never have to look at again. I just want o push a string from one PC to another and get a string in response. I don't want to have to worry about opening conenctions, keeping them open, reopening them if they close, etc.
I want to concentrate on my application and have a simple functional API along the lines of:
SendStringToOtherPc() : String; // Called at PC #1.
// Returns PC #2's result string
// or "" on error (or throws exception)
ProcessReceivedStringAndReply(); // Called at PC # 2. Sends result string
I do need to know if the other PC replied or not; and, if so, what the result string was
also "nice to have" would be for both PCs to initiate communication. If not, I can have one of them (the client poll), or have the other send its communication as a reply to the heartbeat which I need to add.
I presume that those with multiple fprojects under their belts have a "starter" framework which they use for every new project, just adding the application specific log - and it's such a framwork, or abstraction layer, that I want. Can anyone point me at a URL?
I know nothing of socket programming and don't really have time to learn. If I do, some other project will suffer.
While I do respect the argument that I should understand what my software is doing, there is a valid counter-arguement that everyone should not have to develop this particular wheel for himself, and surely there is some FOSS around which does what I want?
Thanks in advance.
Update: I seem to have started a little controversy, with some thinking me lazy or doomed to disaster. So, maybe I should explain a little of my history.
I spent three decades developing telecoms software and we always followed the OSI 7 layer model. I was generally layer 3, the network layer, and no matter whether it was a telephone exchange, base station or hanset, whether the protocol was ISDN, ISUP, DECT, GSM, GPRS, UMTS or a propietary satellite protocol, I could always instuct a Serveice Access Point of Layer 2, the data transport layer, "hey, you! Get this mesage to the other guy and tell me what his reply is". Did I know how it was done? Did I care?
#CosmicPrund, who will probably be awarded the answer unless someone points me at a Layer 2, said "The true answer to this question is that all you need is learn how to use Indy" and I beg to disagree.
Someone will, but not me if I can help it. I already leanred too many skills, programming languages, databse systems, oprerating systems and will always avoid learning more that an overview of another if I can. Like Sir Isaac Newton, I would prefer to stand on the shoulders of giants.
Software is just getting too big for one guy. Surely none of you start each project from scratch? I guess you reuse the networking code from a previous project(?) and that reusable code is my "Layer 2". And my question is where can I download such code and use it without understanding its inner workings?
Does anyone know of such a thing?
Answer: I used Indy and got what I wanted. I will porbably try to build up a library of functions which I can use as a network abstraction layer.
I have a free framework that will do all this. The benefit is that you can use it without any knowledge of sockets whatsoever. You can safely ignore connects and disconnects because this is all handled by the framework (the underlying comms framework keeps a continuous connection via configurable pings, etc). A message queueing threading model is also built into the framework. I have a demo for your exact example as well. The downside is obviously a steep learning curve. Have a look at http://www.csinnovations.com/framework_delphi.htm
The true answer to this question is that all you need is learn how to use Indy. To prove my point I'll give you a 89 lines unit that actually implements all you requested, plus a proof-of-concept sample of how to use it.
Before I show the code I'd like to mention:
89 lines of code can't be called a framework. It's just a thin wrapper that's simply not worth it. Sooner or later you'd run into stuff that requires direct access to the underlying Indy framework.
Someone with more Indy experience would probably write this using even less lines of code.
I could even make it shorter myself, since I included two overloaded "StartServer" methods for ease of demonstration.
Implementing this using components dropped on a form would cut the number of lines further.
Here's the "framework" unit:
unit UTcpIntercom;
interface
uses IdContext, IdCustomTCPServer, IdTCPServer, IdBaseComponent,
IdComponent, IdTCPConnection, IdTCPClient, SysUtils;
type
EIntercomError = class(Exception);
TReceivedText = procedure(const TextFromClient:string; var Response:string) of object;
TReceivedTextProc = procedure(const TextFromClient:string; var Response:string);
TIntercomServer = class(TIdCustomTCPServer)
protected
Event: TReceivedText;
Proc: TReceivedTextProc;
HostGreeting: string;
public
function DoExecute(AContext: TIdContext): Boolean; override;
end;
function SendTextToComputer(const TextToSend, HostToSend, HostGreeting:string; PortNumber: Integer): string;
function StartServer(PortNumber:Integer; const HostGreeting:string; OnReceivedText: TReceivedText):TIntercomServer;overload;
function StartServer(PortNumber:Integer; const HostGreeting:string; OnReceivedText: TReceivedTextProc):TIntercomServer;overload;
implementation
function SendTextToComputer(const TextToSend, HostToSend, HostGreeting:string; PortNumber: Integer): string;
var Id: TIdTCPClient;
begin
Id := TIdTCPClient.Create(nil);
try
Id.Host := HostToSend;
Id.Port := PortNumber;
Id.Connect;
try
if Id.IOHandler.ReadLn <> HostGreeting then
raise EIntercomError.Create('Host is invalid: ' + HostToSend);
Id.IOHandler.WriteLn(TextToSend);
Result := Id.IOHandler.ReadLn;
Id.Disconnect;
finally Id.Disconnect;
end;
finally Id.Free;
end;
end;
function StartServer(PortNumber:Integer; const HostGreeting:string; OnReceivedText: TReceivedText):TIntercomServer;overload;
begin
Result := TIntercomServer.Create(nil);
Result.Bindings.Add.Port := PortNumber;
Result.HostGreeting := HostGreeting;
Result.Event := OnReceivedText;
Result.Active := True;
end;
function StartServer(PortNumber:Integer; const HostGreeting:string; OnReceivedText: TReceivedTextProc):TIntercomServer;overload;
begin
Result := TIntercomServer.Create(nil);
Result.Bindings.Add.Port := PortNumber;
Result.HostGreeting := HostGreeting;
Result.Proc := OnReceivedText;
Result.Active := True;
end;
{ TIntercomServer }
function TIntercomServer.DoExecute(AContext: TIdContext): Boolean;
var Text, Response: string;
begin
AContext.Connection.IOHandler.WriteLn(HostGreeting);
Text := AContext.Connection.IOHandler.ReadLn;
Response := '';
if Assigned(Event) then
Event(Text, Response)
else if Assigned(Proc) then
Proc(Text, Response)
else
Response := 'No handler assigned.';
AContext.Connection.IOHandler.WriteLn(Response);
AContext.Connection.Disconnect;
Result := True;
end;
end.
Here's the code that uses the unit. Notice the DoSomethingWithTextFromClient, that's essentially your ProcessReceivedStringAndReply method. Also notice the use of StartServer and SendTextToComputer.
program Project9;
{$APPTYPE CONSOLE}
uses
SysUtils,
UTcpIntercom in 'UTcpIntercom.pas';
procedure DoSomethingWithTextFromClient(const TextFromClient: string; var Response:string);
var i: Integer;
C: Char;
Len: Integer;
begin
Response := TextFromClient;
Len := Length(Response);
for i:=1 to (Length(Response) div 2) do
begin
C := Response[Len-i+1];
Response[Len-i+1] := Response[i];
Response[i] := C;
end;
end;
begin
try
try
with StartServer(1000, 'Test', #DoSomethingWithTextFromClient) do
begin
WriteLn(SendTextToComputer('12345678', '127.0.0.1', 'Test', 1000));
Free;
end;
Readln;
except on E:Exception do
begin
WriteLn(E.ClassName);
WriteLn(E.Message);
Readln;
end;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.

What is the correct way to check if a value is a date/number in Delphi

What is the correct way to check if a value is a date/number in Delphi?
I know other languages have functions like isDate and isNaN, but what is the Delphi equivalent? at the minute I have this
function isNumeric(s1:string):boolean;
begin
// will throw exception if its not a number
// there must be a better way to do this!!
try
StrTofloat(s1);
result := TRUE ;
except
result := FALSE;
end;
end;
But throwing exceptions cant be good, and it makes debugging hard as I keep seeing the exception dialogue every time the code is called.
For integers, you could use TryStrToInt to check and convert without throwing exceptions:
function TryStrToInt(const s: string; out i : integer): boolean;
I'm not absolutely sure there is a full equivalent for floats, though, so you might need to use StrToFloat() and accept the possibility of a TFormatException.
There's a family of functions like TryStrToFloat, TryStrToDateTime etc. which do that. By the way, StrToFloat and others use the "Try" versions internally before raising exceptions.
Firstly StrToFloatDef function is a useful alternative here if you want to stay in the language as delivered out the box.
However your best option is to deploy the JEDI code libraries (http://www.delphi-jedi.org/) and use the StrIsNumber function from there.
JEDI is open source, highly useful in lots of ways, and pretty much a must anyway.
Catching exceptions is very slow. If you plan on using such a function repeatedly in rapid succession, such as when validating fields during a file import, it might be worth it to roll your own function that does some simple character level checking before falling into a try/except block. I've used something similar before with a huge performance increase when parsing large amounts of data that was not in the correct format.
function IsNumeric(aValue : string): boolean;
var
i : integer;
begin
result := false;
for i := 1 to Length(aValue) do
if (NOT (aValue[i] in ['0'..'9', '.', '-', '+', 'E', 'e'])) then
exit;
try
StrToFloat(aValue);
result := true;
except
end;
end;
Obviously this may not be perfect, and has the limitation of hard-coded values in it. Depends entirely on your needs, this was just something simple that worked well for an internal process.
I use strtointdef(singlecharacter,-1)
procedure TForm1.Button1Click(Sender: TObject);
var
x,i:integer;
teststring:string;
begin
teststring:='1235';
for i:=1 to length(teststring) do begin
x:= strtointdef(teststring[i],-1);
if x=-1 then break;
end;
if x<0 then showmessage('not numeric')
else showmessage('numeric');
end;
You CAN turn off the annoying exceptions you don't want by checking the "ignore this exception" box that pops up. Future exceptions will be then ignored for that exception class. To start asking again, just go to the Options|Debugger Options and uncheck the ones you are ignoring.

Resources