Delphi multiple .ini files operation - delphi

Recently, I encountered a little issue, which causes an access violation when trying to read .ini files.
My question is, can I load multiple .ini files (e.g. settings.ini and data.ini) within one and the same procedure? For instance, I have two ini files, which I write in and read from.
Here is a shortened proc, which writes data:
//writing to file uninstall.ini
try
ini := TIniFile.Create(edPath.Text + '\Uninstall.ini');
ini.WriteString('Uninstall', 'qfProgramName', Label4.Caption);
ini.WriteString('Uninstall', 'qfUninstPath', edPath.Text);
finally
ini.Free;
end;
And then, there is this this code (in the same procedure)
configini := configini.Create(ExtractFilePath(Application.ExeName) + '\quickfix.ini');
sectionsCount := getMaxSectionIndex(ExtractFilePath(Application.ExeName) + '\quickfix.ini');
startmenuLoc := GetProperDir(_STARTMENU);
desktopLoc := GetProperDir(_DESKTOP);
for I := 1 to sectionsCount do begin
currentSection := 'qfShortcut_' + IntToStr(I);
shortcutFile := configini.ReadString(currentSection, 'qfShTarget', '');
shortcutDesc := configini.ReadString(currentSection, 'qfShDescription', '');
shortcutFullPath := installPath + '\' + shortcutFile;
shortcutDest := configini.ReadString(currentSection, 'qfShPath', '');
displayName := configini.ReadString(currentSection, 'qfDisplayName', '');
showmessage(startmenuLoc + '\' + displayName + '.lnk');
showmessage(shortcutFullPath);
if shortcutDest = 'spStartMenu' then CreateLink(shortcutFullPath, startmenuLoc + '\' + displayName + '.lnk', '', '');
if shortcutDest = 'spDesktop' then CreateLink(shortcutFullPath, desktopLoc + '\' + displayName + '.lnk', '', '');
The line with the second loaded file crashes with the access violation. It's not excluded, that the trouble comes from some other location, however, I would like you guys to take a look, maybe someone will see another problem.
This snippet is to create shortcuts on the desktop and in start menu, loading data from .ini files. File "quickfix.ini" is also loaded once at OnCreate, but freed then.

Try replacing
configini := configini.Create(ExtractFilePath(Application.ExeName) + '\quickfix.ini');
with:
configini := TIniFile.Create(ExtractFilePath(Application.ExeName) + '\quickfix.ini');
You are trying to call Create on an uninitialized variable rather than on a class.

Related

Using Delphi JOSE & JWT library, how do you sign a JWT using RS256

All their examples use HS*** with none in RS*** and trying to change the examples to suite dont seem to be working.
My problem seems to be getting the private key loaded for signing. I'm using a PEM in a string, setting up the claims, using this
Procedure RunTest2b;
var
LToken: TJWT;
LSigner: TJWS;
LKey: TJWK;
LAlg: TJOSEAlgorithmId;
s: String;
begin
LToken := TJWT.Create;
try
LToken.Claims.Subject := 'Paolo Rossi';
LToken.Claims.Issuer := 'Delphi JOSE Library';
LToken.Claims.IssuedAt := Now;
LToken.Claims.Expiration := Now + 1;
// Signing algorithm
LAlg := TJOSEAlgorithmId.RS256;
LSigner := TJWS.Create(LToken);
LKey := TJWK.Create(gPrivateKey);
try
// With this option you can have keys < algorithm length
LSigner.SkipKeyValidation := True;
LSigner.Sign(LKey, LAlg);
s := 'Header: ' + LSigner.Header + #13#10 +
'Payload: ' + LSigner.Payload + #13#10 +
'Signature: ' + LSigner.Signature + #13#10 +
'Compact Token: ' + LSigner.CompactToken;
if s = '' then;
finally
LKey.Free;
LSigner.Free;
end;
finally
LToken.Free;
end;
end;
This fails in the sign method saying "Unable to load private key:" and a bunch of weird characters which makes it look like maybe I have a wide string when I should have an ansistring, but changing it doesn't seem to help.
I have also tried using the TBase64.Decode and TBase64.UrlDecode to transform the key before I pass it into the sign method without success.
Can anyone see where I'm making a mistake ?
I recently jump thru a few hoops to do some JWT testing using JOSE. I didn't sign anything, but did have to use the PEM to verify the JWT which was using RS. While doing so I made the mistake of concatenating the PEM string into a single line of characters without preserving the line feeds. I wonder if you made the same mistake with your keys?
i.e. bad PEM format
myPem := '-----BEGIN PUBLIC KEY-----'
+ 'A23BBjhasdfbewisudvnacwerf823rdsvcp2'
+ 'bDenDfsub893rghvsaefawerd'
+ '-----END PUBLIC KEY-----';
i.e. good PEM format
myPem := '-----BEGIN PUBLIC KEY-----'
+ #13#10 + 'A23BBjhasdfbewisudvnacwerf823rdsvcp2'
+ #13#10 + 'bDenDfsub893rghvsaefawerd'
+ #13#10 + '-----END PUBLIC KEY-----';

Delphi XE2 - TIBOQuery calculated field must be at the last order?

I am using TIBOQuery, with "file_full_path" is a calculated field.
First, I open query to get data from database
with qryFiles do
begin
Close;
ParamByName('id_dre').Value := Self.qryListID_DRE.AsString;
ParamByName('id_use').Value := rUser.ID_USE;
Open;
end;
Then I load qrery data into TdxMemData named memFiles:
memFiles.Close;
memFiles.LoadFromDataSet(qryFiles);
memFiles.Open;
And I show value of the "file_full_path" field:
ShowMessage(memFiles.FieldByName('file_full_path').Value);
Code in OnCalcFields of the query:
procedure TfrmDocsGoodRegisterDetail.qryFilesCalcFields(DataSet: TDataSet);
begin
DataSet.FieldByName('file_full_path').Value := WideLowerCase(ExtractFileDir(rSys.PathSDK)
+ '\' + DataSet.FieldByName('id_dre').AsString
+ '\' + DataSet.FieldByName('id_dfo').AsString
+ '\' + DataSet.FieldByName('file_name').AsString);
end;
Problem is when I put "file_full_path" at the last in qrery fields order, it works fine.
But when I change that field to any other positions (not at the bottom), it return null at ShowMessage line of code.
Does anyone know why? Please help, thank you.

Deleting Records in a dbGrid Delphi 2010

I am looking for a solution to a rather serious problem I'm facing;
I want to delete a record in a dbGrid but when I click on my coded button and confirm 'Delete', I find no immediate results. In order to find the result of the deleted record, I have to close the program and re-run it. Only then do I see that the record is deleted.
My coding looks basically as follows
procedure TfrmPunte.btnDeleteClick(Sender: TObject);
var
sName, sLeerderNo : string;
begin
with dmPunte do
begin
sLeerderNo := tblLeerder['LeerderNr'];
sName := tblLeerder['NaamVan'];
if MessageDlg('Is jy seker dat jy ' + sName + ' met Leerder Nommer ' + sLeerderNo + ' wil wis van die rekords? Neem kennis dat al die rekords van sy/haar aktiwiteite ook uitgevee sal word!', mtWarning, [mbOk, mbCancel],0) = mrOK then
tblDeelname.Open;
tblDeelname.First;
while NOT tblDeelname.Eof do
begin
if tblDeelname['LeerderNr'] = sLeerderNo then
tblDeelname.Delete;
tblDeelname.Next;
end;
tblLeerder.Delete;
tblLeerder.Active := False;
tblLeerder.Active := True;
end;
end;
I am using an ADO table connected to ADO connection connected to an ACCESS database. I do appologize, some variable names are in Afrikaans.
What should I do?
Table.refresh does not work in every environment. The safe way is
Table.Active := False;
Table.Active := True;
Be aware of a problem that the actual dataset is lost. You can store it and get back locate.

Clientdataset Append/Post fails intermittently

I have an external message coming in every second.
The message payload is saved in a ClientDataSet and displayed in a dbGrid.
No data base is involved. RAM storage only.
This works fine,
BUT,
I have intermittent problems when the dataset is empty and populated the first time.
The code is as follows:
procedure TCtlCfg_DM_WarningsFaults_frm.DecodeRxFrame(Protocol: TProtocolSelection;
// PROVA UTAN VAR VAR Frame : CAN_Driver.TCAN_Frame);
Frame : CAN_Driver.TCAN_Frame);
var
OldRecNo : integer;
// OldIxname : string;
// bMark : TBookMark;
WasFiltered : Boolean;
IdBitFields : TCanId_IdBitFields;
Msg : TCan_Msg;
MsgType : integer;
GlobalNode : TCanId_GlobalNode;
LocalNode : TCanId_LocalNode;
SubNode : TCanId_SubNode;
EntryType : integer;
SubSystemType : integer;
SubSystemDeviceId : integer;
IsActive : Boolean;
IsAcked : Boolean;
begin
with cdsWarningsFaults do
begin
if not Active then Exit;
Msg := Frame.Msg;
IdBitFields := DecodeCanId(Protocol, Frame.ID);
if IdBitFields.SubNode <> cSubNode_Self then Exit; // Ignore non controller/slave messages
if IdBitFields.AddressMode <> cCanId_AddrMode_CA then Exit;
MsgType := IdBitFields.MessageType;
if MsgType <> cMsg_CTL_CA_Broadcast_WarningAndFaultList then Exit;
if Frame.MsgLength < 5 then Exit;
GlobalNode := IdBitFields.GlobalNode;
LocalNode := IdBitFields.LocalNode;
SubNode := IdBitFields.SubNode;
// Silent exit if wrong node
if GlobalNode <> fNodeFilter.GlobalNode then Exit;
if LocalNode <> fNodeFilter.LocalNode then Exit;
if SubNode <> fNodeFilter.SubNode then Exit;
EntryType := Msg[1];
SubSystemType := Msg[2];
IsActive := (Msg[3] = 1);
SubSystemDeviceId := Msg[4];
IsAcked := (Msg[8] = 1);
DisableControls; // 2007-12-03/AJ Flytta inte scrollbars under uppdatering
OldRecNo := RecNo;
// OldIxName := IndexName; // Save current index
// IndexName := IndexDefs.Items[0].Name;
WasFiltered := Filtered; // Save filter status
Filtered := False;
try
try
if Findkey([GlobalNode, LocalNode, SubNode, EntryType, SubSystemType, SubSystemDeviceId]) then
begin // Update record
Edit;
FieldByName('fIsActive').AsBoolean := IsActive;
FieldByName('fIsAcked').AsBoolean := IsAcked;
FieldByName('fTimeout').AsDateTime := GetDatabaseTimeoutAt;
Post;
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Edit. N=' + IntToStr(GlobalNode) + ' ' +
IntToStr(LocalNode) + ' ' +
IntToStr(SubNode) +
' RecCnt=' + IntToStr(RecordCount) + ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
end
else
begin // Create new record
Append;
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Append. N=' + IntToStr(GlobalNode) + ' ' +
IntToStr(LocalNode) + ' ' +
IntToStr(SubNode) +
' RecCnt=' + IntToStr(RecordCount) + ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
try
FieldByName('fGlobalNode').AsInteger := GlobalNode;
FieldByName('fLocalNode').AsInteger := LocalNode;
FieldByName('fSubNode').AsInteger := SubNode;
FieldByName('fEntryType').AsInteger := EntryType;
FieldByName('fSubSystemType').AsInteger := SubSystemType;
FieldByName('fSubSystemDeviceId').AsInteger := SubSystemDeviceId;
FieldByName('fIsActive').AsBoolean := IsActive;
FieldByName('fIsAcked').AsBoolean := IsAcked;
FieldByName('fTimeout').AsDateTime := GetDatabaseTimeoutAt;
finally
try
Post; // VArför biter inte denna post så att det blir edit nästa gång
except
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Exception efter Post.', True);
end;
MainForm.AddToActivityLog('CtlCfg_DM_WF: DecodeRxFrame: Efter Post. N=' + IntToStr(GlobalNode) + ' ' +
IntToStr(LocalNode) + ' ' +
IntToStr(SubNode) +
' RecCnt=' + IntToStr(RecordCount) + ' ET=' + IntToStr(EntryType) + ' SST=' + IntToStr(subSystemType) + ' SSD=' + IntToStr(SubSystemDeviceId), False);
end;
end;
except
on E: Exception do
begin
MainForm.AddToActivityLog('Post exception message: [' + E.Message + ']', False);
MainForm.AddToActivityLog('Post exception class: [' + E.ClassName + ']', False);
MainForm.AddToActivityLog('Post exception Error code: [' + IntToStr(EDBCLIENT (E).ErrorCode) + ']', False);
MainForm.AddToActivityLog('Post exception ReadOnly is: [' + BoolToStr(ReadOnly) + ']', False);
MainForm.AddToActivityLog('Post exception CanModify is: [' + BoolToStr(CanModify) + ']', False);
MainForm.AddToActivityLog('DecodeRxFrame: Exception inside FindKey block', False);
Cancel;
end;
end;
finally
// IndexName := OldIxName; // Restore previous index
Filtered := WasFiltered; // Restore filter state
if (OldRecNo >= 1) and (OldRecNo <= RecordCount) then RecNo := OldRecNo;
EnableControls;
end;
end;
//MainForm.AddToActivityLog('DecodeRxFrame: Exit ur proceduren', False);
end;
The problem is when the record does not already exist,
and I need to Append a new record.
It often works fine, but many times it seems the POST does not work,
and the append is repeated a few or many times when new data comes in.
Suddenly the append works, and subbsequent updates are done using edit,
and as far as I can tell, after that it then works forever.
The issue is intermittent and the number of tries needed to succeed vary.
It feels like a timing issue, but I cannot figure it out.
Any ideas greatly appreciated.
Thanks,
Anders J
As mentioned in my comment a lot can be figured out about how the code flows using an extract of the logs. (Also as a side-note, sometimes you need to be careful of the reliability of your logging system. Your logging is at least partially home-brew, so I have no idea what it means when you arbitrarily pass True/False values to the AddToActivityLog method.)
However, I am still able to offer some guidance to help you identify your problem. I also have some general comments to help you improve your code.
You're not shy to use logging to narrow down your problem: this is a good thing!
However you technique could use a little improvement. You're trying to determine what's going wrong around the Post method. Given such a clear goal, your logging seems surprisingly haphazard.
You should do the following:
//Log the line before Post is called. It confirms when it is called.
//Log important state information to check you're ready to post "correctly"
//In this it's especially useful to know the Sate of the dataset (dsEdit/dsInsert).
Post;
//Log the line after Post to confirm it completed.
//Note that "completed" and "succeeded" aren't always the same, so...
//Again, you want to log important state information (in this case dsBrowse).
If you had this this logging, you might (for example) be able to tell us that:
Before calling Post dataset is in dsInsert state.
And (assuming no exceptions as you say): after calling Post the dataset is still in dsInsert state.
NOTE: If it were in dsBrowse but Post still considered "unsuccessful", you'd be told to log details of the record before and after Post.
So now: Post "completing" without the record being "posted" would give a few more things to look at:
What events are hooked to the data set? Especially consider events used for validation.
Since you're working with TClientDataSet there's an important event you'll want to take a look at OnPostError. DBClient uses this callback mechanism to notify the client of errors while posting.
If you log OnPostError I'm sure you'll get a better idea of the problem.
Finally I mentioned you have a lot of other problems with your code; especially the error handling.
Don't use with until you know how to use it correctly. When you know how to use it correctly, you'll also know there's never a good reason to use it. As it stands, your code is effectively 2 characters short of a subtle bug that could have been a nightmare to even realise it even existed; but would be a complete non-issue without with. (You declared and used a property called IsActive differing by only 2 characters from TDataSet's Active. I'm sure you didn't realise this; and their difference is but an accident. However, if they had been the same, with would very quietly use the wrong one.)
You need to write smaller methods - MUCH smaller! Blobs of code like you have are a nightmare to debug and are excellent at hiding bugs.
Your exception handling is fundamentally wrong:
Your comment about logging and exception handling suggests that you've been simply adding what you can out of desperation. I think it pays to understand what's going on to keep your logging useful and avoid the clutter. Let's take a close look at the most problematic portion.
/_ try
/_ FieldByName('fGlobalNode').AsInteger := GlobalNode;
/_E FieldByName('fLocalNode').AsInteger := LocalNode;
| FieldByName('fSubNode').AsInteger := SubNode;
| FieldByName('fEntryType').AsInteger := EntryType;
| FieldByName('fSubSystemType').AsInteger := SubSystemType;
| FieldByName('fSubSystemDeviceId').AsInteger := SubSystemDeviceId;
| FieldByName('fIsActive').AsBoolean := IsActive;
| FieldByName('fIsAcked').AsBoolean := IsAcked;
| FieldByName('fTimeout').AsDateTime := GetDatabaseTimeoutAt;
|_ finally
/_ try
/_ Post;
/ except
| MainForm.AddToActivityLog(..., True);
| end;
|_ MainForm.AddToActivityLog(..., False);
/ end;
|
...
So, in the above code:
If no exceptions happen, you'd simply step from one line to the next.
But as soon as an exception happens, you jump to the next finally/except block.
The first problem is: Why would you try to force a Post if you haven't finished setting your field values. It's a recipe for headaches when you end up with records that have only a fraction of the data they should - unless you're lucky and Post fails because critical data is missing.
When finally finishes during an exception, code immediately jumps to the next finally/except in the call-stack.
Except is slightly different, it only gets called if something did go wrong. (Whereas finally guarantees it will be called with/without an exception.
TIPS: (good for 99% of exception handling cases.
Only use try finally for cleanup that must happen in both success and error cases.
Nest your try finally blocks: The pattern is <Get Resource>try .... finally<Cleanup>end (The only place to do another resource protection is inside the .....)
Avoid except in most cases.
The main exception to the previous rule is when cleanup is needed only in the case of an error. In which case: do the cleanup and re-raise the exception.
Other than that: only implement an except block without re-raising if you can fully resolve an error condition. (Meaning the lines of code immediately after the exception swallower truly don't care about the previous exception.

Identifying the server from which a Delphi 7 program is run

Separate versions of a Delphi 7 program have been deployed on various servers.
In order to help troubleshoot reported errors, I'm trying to write a function to identify what server the program is running from.
The following code gets me the local computer name.
sbAll.Panels.Items[1].Text := 'Server: ' + GetEnvironmentVariable('COMPUTERNAME');
Assuming that the absolute path of the program is:
\\Swingline\Programs\Folder\Program.exe
How do I get it to return Server: Swingline regardless of what computer it is run from?
You can probably use Application.ExeName, split it by the slashes and get the second element...
This is the code I ended up using based on #Zdravko's suggestion.
List := TStringList.Create;
try
ExtractStrings(['\'], [], PChar(Application.ExeName), List);
if (List.Text[2] = ':') then // On local computer, Ex. J:\Programs\Foo.exe
sbAll.Panels.Items[1].Text := 'Server: ' + ntComputer.ComputerName
else // In the case of \\Swingline\Programs\Folder\Program.exe
sbAll.Panels.Items[1].Text := 'Server: ' + UpperCase(List[0]);
finally
List.Free;
end;
You can do this without using a string list...
function ExeLocation: String;
var
S: String;
begin
S:= ParamStr(0);
if Copy(S, 2, 2) = ':\' then begin
Result:= GetEnvironmentVariable('COMPUTERNAME');
end else
if Copy(S, 1, 2) = '\\' then begin
Delete(S, 1, 2);
Result:= Copy(S, 1, Pos('\', S)-1);
end;
end;
Keep in mind that if you are referencing the file by the machine's IP address, this will only return the IP address. For example \\192.168.1.123\SomeFolder\SomeFile.exe would just return 192.168.1.123. I looked for other ways but I'm not knowledgeable enough in that department to dig deep enough for the true machine name. It might be possible, but I'm just not seeing it possible.

Resources