Communication between two programs using file - file-io

I want two separate programs written in Pascal communicate between themselves using additional text file. It works fine for the first 2-3 messages but then it gives run-time error in either sender.pas or receiver.pas Do you know where my mistake is or do you have any suggestion?
First program receiver.pas
var
f : text;
s : string;
begin
{I-}
Assign(f,'main.in');
while true do
begin
reset(f);
while IOResult<>0 do //
begin // Wait until the file
close(f); // is closed by sender
reset(f); //
end; //
if eof(f) then
close(f)
else
begin
readln(f,s);
close(f);
rewrite(f);
close(f);
writeln(s);
end;
end;
{I+}
end.
second program sender.pas
var
f : text;
s : string;
begin
{I-}
Assign(f,'main.in');
while true do
begin
readln(s);
rewrite(f); //
while IOResult<>0 do //
begin // Wait until the file
close(f); // is closed by receiver
rewrite(f); //
end; //
writeln(f,s);
close(f);
end;
{I+}
end.

Some things I noticed:
If rewrite fails, afaik the file was not opened and you don't have to close it? Closing an unopened file might cause runtime errors (though I assume assign will init it safely)
depending on how you use these, there might be in the logic. Namely that after closing a file is directly ready for opening by other apps. In general closed filehandles linger several 100ms till several seconds (depending on filesystem busines). This can cause starvation problems in such schemes. (while it would work in plain dos, which didn't linger, at least not that much)
The reader will crash if the file doesn't exist.
I assume that the {I-}/{I+} is a typo and that your sourcecode really reads {$I-} and {$I+} (note the dollar?)
In Windows pascal versions, read only access is not always locking. Put filemode:=2 as first line in everything.
What compiler is this? Delphi, Free Pascal? Which version?
My new (Free Pascal) receiver code becomes:
uses sysutils;
var
f : text;
s : string;
begin
filemode := 2; // read-only
{$I-}
Assign(f,'main.in');
while true do
begin
reset(f);
while IOResult<>0 do //
begin // Wait until the file
close(f); // is closed by sender
sleep(1000);
reset(f);
end;
if eof(f) then
begin
close(f);
sleep(1000);
end
else
begin
readln(f,s);
close(f);
rewrite(f);
close(f);
writeln(s);
end;
end;
{$I+}
end.
The new sender code is:
uses sysutils;
var
f : text;
s : string;
begin
filemode := 2; // read-only
{$I-}
Assign(f,'main.in');
while true do
begin
reset(f);
while IOResult<>0 do //
begin // Wait until the file
close(f); // is closed by sender
sleep(1000);
reset(f);
end;
if eof(f) then
begin
close(f);
sleep(1000);
end
else
begin
readln(f,s);
close(f);
rewrite(f);
close(f);
writeln(s);
end;
end;
{$I+}
end.

A couple of things: Make sure you check IOResult after every file operation, not just rewrite/reset - don't call close if your reset/rewrite failed - and you probably want something like a Sleep(250) in those retry loops.

Related

Delphi Syntax error in FROM clause, but no from clause

I'm really new to Delphi and have not yet worked with SQL (I'm a complete beginner).
I use code to connect my database and tables to my program, but as soon as I run my program, I get a Syntax error in FROM clause message.
When I select break, it highlights end; of a part of the code.
function TADOCommand.Execute(var RecordsAffected: Integer;
const Parameters: OleVariant): _Recordset;
var
VarRecsAffected: OleVariant;
begin
SetConnectionFlag(cfExecute, True);
try
Initialize;
Result := CommandObject.Execute(VarRecsAffected, Parameters,
Integer(CommandObject.CommandType) + ExecuteOptionsToOrd
(FExecuteOptions));
RecordsAffected := VarRecsAffected;
finally
SetConnectionFlag(cfExecute, False);
end;
end;
I have three tables, of which two display on their grids, but one is not displaying on the grid, and also gives me the Syntax error in FROM clause when I want to do anything with it.
This is the code that I used to connect my database in the datamodule:
unit dmChamps_u;
interface
uses
System.SysUtils, System.Classes, ADODB, DB; // add Ado and DB
type
TdmChamps = class(TDataModule)
procedure DataModuleCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
conArchers: TADOConnection;
tblArchers: TADOTable;
tblJT: TADOTable;
tblMatches: TADOTable;
dscArchers: TDataSource;
dscMatches: TDataSource;
dscJT: TDataSource;
end;
var
dmChamps: TdmChamps;
implementation
{%CLASSGROUP 'Vcl.Controls.TControl'}
{$R *.dfm}
procedure TdmChamps.DataModuleCreate(Sender: TObject);
begin
// create objects
conArchers := TADOConnection.Create(dmChamps);
tblArchers := TADOTable.Create(dmChamps);
tblMatches := TADOTable.Create(dmChamps);
tblJT := TADOTable.Create(dmChamps);
dscArchers := TDataSource.Create(dmChamps);
dscMatches := TDataSource.Create(dmChamps);
dscJT := TDataSource.Create(dmChamps);
// setup connection
conArchers.ConnectionString :=
'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=ArchChampsDB.mdb;Mode=ReadWrite;Persist Security Info=False';
conArchers.LoginPrompt := false;
conArchers.Open;
// setup table archers
tblArchers.Connection := conArchers;
tblArchers.TableName := 'Archers';
// setup data source
dscArchers.DataSet := tblArchers;
tblArchers.Open;
// setup table matches
tblMatches.Connection := conArchers;
tblMatches.TableName := 'Matches';
// setup data source
dscMatches.DataSet := tblMatches;
tblMatches.Open;
// setup table JT
tblJT.Connection := conArchers;
tblJT.TableName := 'Judges/Timekeepers';
// setup data source
dscJT.DataSet := tblJT;
tblJT.Open;
end;
end.
I've looked through all of the questions on the From clause error already on the site, but none of the scenarios quite match my problem. I also went to Embarcadero's site and read about TableDirect, which I thought might be a possible solution, but it was already in the code.
Your error is here
tblJT.TableName := 'Judges/Judges';
You can't has a table with that name, or Judges or Timekeepers
You can solve do it:
// setup table J
tblJT.Connection := conArchers;
tblJT.TableName := 'Judges';
// setup data source
dscJT.DataSet := tblJT;
tblJT.Open;
Separated
// setup table T
tblJT.Connection := conArchers;
tblJT.TableName := 'Timekeepers';
// setup data source
dscJT.DataSet := tblJT;
tblJT.Open;
Maybe it is because of the slash in 'Judges/Timekeepers'.
Did you try to debug step by step going to the code in DataModuleCreate?

TADOConnection with SQL Server Thread with Delphi

I am working on a project and I need to think about emergency situations.
The main issue is, how to check if the database is connected (circle object = red or green)?
BeforeConnect, AfterDisconnect, they have no good answer.
Inside type:
Create Connection:
procedure TForm1.Button1Click(Sender: TObject);
var
s : String;
begin
ADOConnectionSQL := TADOConnection.Create(nil);
ADOConnectionSQL.LoginPrompt := false;
with ADOSQL do
begin
s := 'Provider=SQLNCLI11.1;'+
'Persist Security Info=False;'+
'User ID='+Edit1.Text+';'+
'Initial Catalog='+Edit2.Text+';'+
'Data Source='+Edit3.Text+';'+
'Initial File Name="";'+
'Server SPN="";'+
'password="'+Edit4.Text+'"';
ADOConnectionSQL.ConnectionString := s;
end;
ADOConnectionSQL.BeforeConnect := SQLConnected;
ADOConnectionSQL.AfterDisconnect := SQLDisconnected;
end;
Try to connect:
procedure TForm1.Button2Click(Sender: TObject);
var
Thread : TThread;
begin
Thread := TThread.CreateAnonymousThread(
procedure
begin
TThread.Synchronize(TThread.CurrentThread,
procedure
begin
try
ADOConnectionSQL.Connected := True;
ADOConnectionSQL.Open;
except
on E: Exception do
begin
ShowMessage('Exception message = '+E.Message);
end;
end;
ADOQuerySQL := TADOQuery.Create(nil);
end);
end);
Thread.OnTerminate := FinishConnected;
Thread.Start;
end;
Green or Red:
procedure TForm1.SQLConnected(Sender: TObject);
begin
Circle1.Fill.Color := $FF00FF00;
end;
procedure TForm1.SQLDisconnected(Sender: TObject);
begin
Circle1.Fill.Color := $FFFF0000;
end;
FinishConnected:
procedure TForm1.FinishConnected(Sender: TObject);
begin
if TThread(Sender).FatalException <> nil then
begin
// something went wrong
ShowMessage ('Failure to connection');
//Exit;
end;
end;
When the SQL Server is online, I would like to see a green circle. When the connection with server goes downs, the circle should be red.
You are creating and opening the ADO connection in the context of the main UI thread, not in the context of the worker thread. So your worker thread is basically useless. You could have just used TThread.ForceQueue() instead to get the same effect.
ADO uses COM technology internally, so you can't really use it across thread boundaries anyway. If you want to use ADO in a thread, give the thread its own ADO Connection and Query objects. Do all your SQL work in the context of the thread, and synchronize status updates with the main UI thread as needed.
Also, you need to initialize the COM library in the worker thread before it can work with ADO.
Try something more like this instead:
procedure TForm1.Button1Click(Sender: TObject);
var
Thread : TThread;
ConnStr: string;
begin
ConnStr := 'Provider=SQLNCLI11.1;'+
'Persist Security Info=False;'+
'User ID='+Edit1.Text+';'+
'Initial Catalog='+Edit2.Text+';'+
'Data Source='+Edit3.Text+';'+
'Initial File Name="";'+
'Server SPN="";'+
'password="'+Edit4.Text+'"';
Thread := TThread.CreateAnonymousThread(
procedure
var
ADOConnectionSQL: TADOConnection;
ADOQuerySQL: TADOQuery;
begin
CoInitialize(nil);
try
ADOConnectionSQL := TADOConnection.Create(nil);
try
ADOConnectionSQL.LoginPrompt := False;
ADOConnectionSQL.ConnectionString := ConnStr;
ADOConnectionSQL.Open;
TThread.Queue(nil,
procedure
begin
Circle1.Fill.Color := TAlphaColorRec.Green;
end
);
ADOQuerySQL := TADOQuery.Create(nil);
try
ADOQuerySQL.Connection := ADOConnectionSQL;
// use ADOQuerySQL as needed...
finally
ADOQuerySQL.Free;
end;
finally
ADOConnectionSQL.Free;
end;
finally
CoUninitialize;
end;
end);
Thread.OnTerminate := SQLFinished;
Thread.Start;
end;
procedure TForm1.SQLFinished(Sender: TObject);
begin
Circle1.Fill.Color := TAlphaColorRec.Red;
if TThread(Sender).FatalException <> nil then
begin
// something went wrong
ShowMessage('Failure! ' + Exception(TThread(Sender).FatalException).Message);
end;
end;

InnoSetup v6 ARC Extraction, Minimize Broken

InnoSetup v6
Extracting from a FreeArc 0.67 (March 15 2014) Archive using temporary file unarc.exe, during the extraction process the minimize window button is broken.
Using FreeARC because it's the best compression software I can find to date.
During the archive decompression, the GUI window with the status message can't be minimized.
Clicking the window makes windows respond with a deny sound. How do I resolve this?
Something to do with "ProgressPage" or executing the arc command line?
Even with this page disabled, it does the same thing, so probably the setup waiting for the process to finish?
I am constantly working on improving and getting this fix because I got files that are in the 100 GB range that need to be compressed to save on disk space. Making a setup program for the extraction process makes it easier to install those files vs relying on the application that I must install first in order to do an extraction.
EDIT: I switch the extraction program from arc.exe to unarc.exe because arc.exe was crashing due to RAM issues.
#define ArcArchive "Test.arc"
[Setup]
AppName=Test App
DefaultDirName=Test App
AppVerName=Test App
WizardStyle=modern
Compression=lzma2
SolidCompression=yes
Uninstallable=no
DisableProgramGroupPage=yes
[Files]
Source: unarc.exe; Flags: dontcopy
[Code]
var
ProgressPage: TOutputProgressWizardPage;
ProgressFileName: string;
procedure ExtractArc;
var
ArcExtracterPath: string;
ArcArchivePath: string;
TempPath: string;
CommandLine: string;
ResultCode: Integer;
S: AnsiString;
Message: string;
begin
ExtractTemporaryFile('unarc.exe');
ProgressPage := CreateOutputProgressPage('Decompression', 'Decompressing archive...please wait');
ProgressPage.Show;
try
TempPath := ExpandConstant('{tmp}');
ArcExtracterPath := TempPath + '\unarc.exe';
ArcArchivePath := ExpandConstant('{src}\{#ArcArchive}');
ProgressFileName := ExpandConstant('{tmp}\progress.txt');
Log(Format('Expecting progress in %s', [ProgressFileName]));
CommandLine :=
Format('"%s" x -o+ -dp"%s" "%s" > "%s"', [
ArcExtracterPath, ExpandConstant('{app}'), ArcArchivePath, ProgressFileName]);
Log(Format('Executing: %s', [CommandLine]));
CommandLine := Format('/C "%s"', [CommandLine]);
if not Exec(ExpandConstant('{cmd}'), CommandLine, '', SW_HIDE,
ewWaitUntilTerminated, ResultCode) then
begin
RaiseException('Cannot start extracter');
end
else
if ResultCode <> 0 then
begin
LoadStringFromFile(ProgressFileName, S);
Message := Format('Arc extraction failed failed with code %d', [ResultCode]);
Log(Message);
Log('Output: ' + S);
RaiseException(Message);
end
else
begin
Log('Arc extraction done');
end;
finally
{ Clean up }
Log('Arc extraction cleanup');
ProgressPage.Hide;
DeleteFile(ProgressFileName);
end;
Log('Arc extraction end');
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssPostInstall then
begin
ExtractArc;
end;
end;

Lazarus (freepascal) Reading large output from TProcess

I' m reading large process output data in Lazarus using the TProcess and the suggestions from this freepascal wiki page.
The wiki page suggests to create a loop to read the process output data like this:
// ... If you want to read output from an external process, this is the code you should adapt for production use.
while True do
begin
MemStream.SetSize(BytesRead + 2024); // make sure we have room
NumBytes := OurProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
if NumBytes > 0
then begin
Inc(BytesRead, NumBytes);
Write('.') //Output progress to screen.
end else
BREAK // Program has finished execution.
end;
// "Then read the MemStream to do your job"
The wiki page also mentions that the calling program should read from the output pipe to prevent it from getting full.
So, how much data makes the output pipe full?
Why we should use a MemStream (TMemoryStream) and not directly read from OurProcess.Output stream (using the bytesAvailable, etc) in the above loop?
I'm reading 80MB of wav data from a process and I have noticed that both MemStream and OurProcess.Output streams have the same amount of data! The memory usage gets doubled. So the suggested method from the wiki cannot be considered as efficient or optimized. Or there is something I'm missing?
Afaik output/input streams are a stream form of a pipe, not memory streams. The values you see are retrieved from the OS handle, not from memory allocated to the FPC app per se.
It is just like you can ask for the .size of a file on disk without reading the whole file.
procedure RunExternalAppInMemo(DosApp:String;AMemo:TMemo);
const READ_BYTES = 2048;
var
aProcess: TProcess; //TProcess is crossplatform is best way
MemStream: TMemoryStream;
NumBytes: LongInt;
BytesRead: LongInt;
Lines: TStringList;
begin
// A temp Memorystream is used to buffer the output
MemStream := TMemoryStream.Create;
Lines :=TStringList.Create;
BytesRead := 0;
aProcess := TProcess.Create(nil);
aProcess.CommandLine := DosApp;
aprocess.ShowWindow := swoHIDE;
AProcess.Options := AProcess.Options + [poUsePipes];
aProcess.Execute;
while aProcess.Running do
begin
// make sure we have room
MemStream.SetSize(BytesRead + READ_BYTES);
// try reading it
NumBytes := aProcess.Output.Read((MemStream.Memory + BytesRead)^, READ_BYTES);
if NumBytes > 0 // All read() calls will block, except the final one.
then Inc(BytesRead, NumBytes)
else
BREAK // Program has finished execution.
end;
MemStream.SetSize(BytesRead);
Lines.LoadFromStream(MemStream);
AMemo.lines.AddStrings(Lines);
aProcess.Free;
Lines.Free;
MemStream.Free;
end;
I was dealing with this problem today, I've modified Georgescu answer, as I wanted Memo to display output stream on the fly
procedure RunExternalAppInMemo(DosApp:String;AMemo:TMemo);
const READ_BYTES = 2048;
var
aProcess: TProcess; //TProcess is crossplatform is best way
NumBytes: LongInt;
Buffer: array of byte;
begin
// set the size of your buffer
SetLength(Buffer,READ_BYTES);
aProcess := TProcess.Create(nil);
aProcess.CommandLine := DosApp;
aprocess.ShowWindow := swoHIDE;
AProcess.Options := AProcess.Options + [poUsePipes];
aProcess.Execute;
while aProcess.Running do
begin
// try reading it
NumBytes := aProcess.Output.Read(Buffer[0], length(buffer)*sizeof(byte)); // I usually do it that way, so I can change Buffer size on if needed
AProcess.Suspend; //I have no experience with pipes, but it seems way I won loose eny output?
if NumBytes > 0 then // All read() calls will block, except the final one.
begin
AMemo.Lines.Add(Pchar(Buffer);
application.ProcessMessages;
AProcess.Resume;
end
else
BREAK; // Program has finished execution.
end;
setlength(Buffer,0);
aProcess.Free;
end;

Is it possible to 'Pin to start menu' using Inno Setup?

I'm using the excellent Inno Setup installer and I notice that some Applications (often from Microsoft) get installed with their launch icon already highly visible ('pinned?') in the start menu (in Windows 7). Am I totally reliant on the most-recently-used algorithm for my icon to be 'large' in the start menu, or is there a way of promoting my application from the installer please?
It is possible to pin programs, but not officially. Based on a code posted in this thread (which uses the same way as described in the article linked by #Mark Redman) I wrote the following:
[Code]
#ifdef UNICODE
#define AW "W"
#else
#define AW "A"
#endif
const
// these constants are not defined in Windows
SHELL32_STRING_ID_PIN_TO_TASKBAR = 5386;
SHELL32_STRING_ID_PIN_TO_STARTMENU = 5381;
SHELL32_STRING_ID_UNPIN_FROM_TASKBAR = 5387;
SHELL32_STRING_ID_UNPIN_FROM_STARTMENU = 5382;
type
HINSTANCE = THandle;
HMODULE = HINSTANCE;
TPinDest = (
pdTaskbar,
pdStartMenu
);
function LoadLibrary(lpFileName: string): HMODULE;
external 'LoadLibrary{#AW}#kernel32.dll stdcall';
function FreeLibrary(hModule: HMODULE): BOOL;
external 'FreeLibrary#kernel32.dll stdcall';
function LoadString(hInstance: HINSTANCE; uID: UINT;
lpBuffer: string; nBufferMax: Integer): Integer;
external 'LoadString{#AW}#user32.dll stdcall';
function TryGetVerbName(ID: UINT; out VerbName: string): Boolean;
var
Buffer: string;
BufLen: Integer;
Handle: HMODULE;
begin
Result := False;
Handle := LoadLibrary(ExpandConstant('{sys}\Shell32.dll'));
if Handle <> 0 then
try
SetLength(Buffer, 255);
BufLen := LoadString(Handle, ID, Buffer, Length(Buffer));
if BufLen <> 0 then
begin
Result := True;
VerbName := Copy(Buffer, 1, BufLen);
end;
finally
FreeLibrary(Handle);
end;
end;
function ExecVerb(const FileName, VerbName: string): Boolean;
var
I: Integer;
Shell: Variant;
Folder: Variant;
FolderItem: Variant;
begin
Result := False;
Shell := CreateOleObject('Shell.Application');
Folder := Shell.NameSpace(ExtractFilePath(FileName));
FolderItem := Folder.ParseName(ExtractFileName(FileName));
for I := 1 to FolderItem.Verbs.Count do
begin
if FolderItem.Verbs.Item(I).Name = VerbName then
begin
FolderItem.Verbs.Item(I).DoIt;
Result := True;
Exit;
end;
end;
end;
function PinAppTo(const FileName: string; PinDest: TPinDest): Boolean;
var
ResStrID: UINT;
VerbName: string;
begin
case PinDest of
pdTaskbar: ResStrID := SHELL32_STRING_ID_PIN_TO_TASKBAR;
pdStartMenu: ResStrID := SHELL32_STRING_ID_PIN_TO_STARTMENU;
end;
Result := TryGetVerbName(ResStrID, VerbName) and ExecVerb(FileName, VerbName);
end;
function UnpinAppFrom(const FileName: string; PinDest: TPinDest): Boolean;
var
ResStrID: UINT;
VerbName: string;
begin
case PinDest of
pdTaskbar: ResStrID := SHELL32_STRING_ID_UNPIN_FROM_TASKBAR;
pdStartMenu: ResStrID := SHELL32_STRING_ID_UNPIN_FROM_STARTMENU;
end;
Result := TryGetVerbName(ResStrID, VerbName) and ExecVerb(FileName, VerbName);
end;
The above code first reads the caption of the menu item for pinning or unpinning applications from the string table of the Shell32.dll library. Then connects to the Windows Shell, and for the target app. path creates the Folder object, then obtains the FolderItem object and on this object iterates all the available verbs and checks if their name matches to the one read from the Shell32.dll library string table. If so, it invokes the verb item action by calling the DoIt method and exits the iteration.
Here is a possible usage of the above code, for pinning:
if PinAppTo(ExpandConstant('{sys}\calc.exe'), pdTaskbar) then
MsgBox('Calc has been pinned to the taskbar.', mbInformation, MB_OK);
if PinAppTo(ExpandConstant('{sys}\calc.exe'), pdStartMenu) then
MsgBox('Calc has been pinned to the start menu.', mbInformation, MB_OK);
And for unpinning:
if UnpinAppFrom(ExpandConstant('{sys}\calc.exe'), pdTaskbar) then
MsgBox('Calc is not pinned to the taskbar anymore.', mbInformation, MB_OK);
if UnpinAppFrom(ExpandConstant('{sys}\calc.exe'), pdStartMenu) then
MsgBox('Calc is not pinned to the start menu anymore.', mbInformation, MB_OK);
Please note that even though this code works on Windows 7 (and taskbar pinning also on Windows 8.1 where I've tested it), it is really hacky way, since there is no official way to programatically pin programs to taskbar, nor start menu. That's what the users should do by their own choice.
There's a reason there's no programmatic way to pin things to the taskbar/start menu. In my experience, I have seen the start menu highlight newly-created shortcuts, and that's designed to handle exactly this situation. When you see a newly-installed program show up on the start menu, it's probably because of that algorithm and not because the installer placed it there.
That said, if a new shortcut does not appear highlighted, it may be because the installer extracts a pre-existing shortcut and preserves an old timestamp on it, rather than using the API function to create a shortcut in the start menu.
Have a look at: http://blogs.technet.com/deploymentguys/archive/2009/04/08/pin-items-to-the-start-menu-or-windows-7-taskbar-via-script.aspx