Can someone give me the code to encrypt and decrypt a Unicode strings in delphi firemonkey Mobile?
I've tried everything with xor with other libraries , and nothing.
There are always characters that are not recognized as the euro symbol € .
If someone could help me , would be appreciated.
Edit:
Thank you Hans, but always I have the same problem with stringstream . This code works perfectly in windows , but ios gives me this error : "No mapping for the Unicode character exists in the target multibyte code page"
unit UMain;
interface
uses
System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants,
FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, ElAES,
FMX.StdCtrls, FMX.Layouts, FMX.Memo, Math;
type
TForm2 = class(TForm)
ToolBar1: TToolBar;
Label1: TLabel;
Label2: TLabel;
Memo1: TMemo;
Layout1: TLayout;
Button1: TButton;
Button3: TButton;
procedure Button1Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
const
PASSWORD = '1234';
var
Form2: TForm2;
implementation
{$R *.fmx}
{$R *.iPhone.fmx IOS}
function StringToHex(S: string): string;
var
i: integer;
begin
Result := '';
// Go throught every single characters, and convert them
// to hexadecimal...
for i := 1 to Length( S ) do
Result := Result + IntToHex( Ord( S[i] ), 2 );
end;
function HexToString(S: string): string;
var
i: integer;
begin
Result := '';
// Go throught every single hexadecimal characters, and convert
// them to ASCII characters...
for i := 1 to Length( S ) do
begin
// Only process chunk of 2 digit Hexadecimal...
if ((i mod 2) = 1) then
Result := Result + Chr( StrToInt( '0x' + Copy( S, i, 2 )));
end;
end;
procedure TForm2.Button1Click(Sender: TObject);
var
Source: TStringStream;
Dest: TStringStream;
Key: TAESKey128;
begin
try
Source := TStringStream.Create( Memo1.Text );
Dest := TStringStream.Create('');
FillChar( Key, SizeOf(Key), 0 );
Move( PChar(PASSWORD)^, Key, Min( SizeOf( Key ), Length( PASSWORD )));
EncryptAESStreamECB( Source, 0, Key, Dest );
//Memo1.Lines.BeginUpdate;
Memo1.Text := Dest.DataString;
//Memo1.Lines.EndUpdate;
Label2.Text := 'Texto Encriptado';
Source.Free;
Dest.Free;
except on E: Exception do
begin
ShowMessage(e.ToString);
Source.Free;
Dest.Free;
Memo1.Text :='';
end;
end;
end;
procedure TForm2.Button3Click(Sender: TObject);
var
Source: TStringStream;
Dest: TStringStream;
Key: TAESKey128;
Size: integer;
begin
try
Source := TStringStream.Create(Trim(Memo1.Text) );
Dest := TStringStream.Create('');
Size := Source.Size;
Source.ReadBuffer(Size, SizeOf(Size));
FillChar(Key, SizeOf(Key), 0);
Move(PChar(PASSWORD)^, Key, Min(SizeOf(Key), Length(PASSWORD)));
Source.Position := 0;
DecryptAESStreamECB(Source, Source.Size - Source.Position, Key, Dest);
Memo1.Text := Trim(Dest.DataString);
Label2.Text := 'Texto Original';
Source.Free;
Dest.Free;
except on E: Exception do
begin
ShowMessage(e.ToString);
Source.Free;
Dest.Free;
Memo1.Text :='';
end;
end;
end;
end.
I've also tried to create stringstream with this:
Source := TStringStream.Create(Trim(Memo1.Text) , TEncoding.Unicode) ;
and sometimes works well and sometimes gives me the following error:"Los surrogate char without a preceding high surrogate char at index: 8. chaeck that the string is encoded properly.
Any ideas?
Use standardized libraries instead of trying to make your own encryption solution. There are for example several implementation of AES encryption available for Delphi (e.g. Eldos which is included in the NativeXML library).
Write your string (MyString) to a stream and encrypt it:
var
lSourceStream: TStringStream;
lDestinationStream: TMemoryStream;
begin
lSourceStream := TStringStream.Create(MyString);
lDestinationStream := TMemoryStream.Create;
AESencrypt(lSourceStream,lDestinationStream);
lDestinationStream.SaveToFile(<filename>);
end;
Related
I have written a simple loader to install my program and its help file.
unit PSInstaller;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Registry, Vcl.StdCtrls, HTMListB,
HTMLabel, System.Zip;
type
TfmPDSInstaller = class(TForm)
HTMLabel1: THTMLabel;
HTMListBox1: THTMListBox;
btnNext: TButton;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure btnNextClick(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
function InstallFile(ResID: integer; pName: String): Boolean;
public
{ Public declarations }
end;
var
fmPDSInstaller: TfmPDSInstaller;
implementation
{$R 'ProtonStudio32.res' 'ProtonStudio32.rc'}
{$R *.dfm}
Var IDEDirectory: String;
Const APP = 100;
HELP = 200;
procedure TfmPDSInstaller.btnNextClick(Sender: TObject);
begin
HTMListBox1.AddItem('Copying Proton Studio to Proton IDE directory',nil);
if InstallFile(APP, 'Studio Application') then begin
HTMListBox1.AddItem('Copying Proton Studio Help to Proton IDE directory',nil);
If InstallFile(HELP, 'Studio Help') then
HTMListBox1.AddItem('Proton Studio Installed', nil);
end;
end;
function TfmPDSInstaller.InstallFile(ResID: integer; pName: String): Boolean;
Var rs: TResourceStream;
Zip: TZipFile;
s: String;
begin
Result := false;
try
Rs := TResourceStream.CreateFromID(HInstance, ResID, RT_RCDATA);
Zip := TZipFile.Create;
try
Zip.Open(Rs,zmRead);
Zip.ExtractAll(IDEDirectory);
finally
Rs.Free;
Zip.Free;
Result := true;
end;
except
on EFOpenError do
s := 'Unable to Open resource ' + pName;
else
s := 'Unable to Copy file from resource ' + pName;
end;
HTMListBox1.AddItem(s, nil);
end;
procedure TfmPDSInstaller.Button1Click(Sender: TObject);
begin
Close;
end;
procedure TfmPDSInstaller.FormCreate(Sender: TObject);
Var Reg: TRegistry;
begin
btnNext.Enabled := false;
Reg := TRegistry.Create;
HTMListBox1.AddItem('Checking for ProtonIDE',nil);
if Reg.OpenKey('Software\MecaniqueUK\ProtonIDE\Install', false) then begin
IDEDirectory := Reg.ReadString('IDE');
Reg.CloseKey;
end;
Reg.Free;
end;
procedure TfmPDSInstaller.FormShow(Sender: TObject);
begin
btnNext.Enabled := false;
if DirectoryExists(IDEDirectory) then begin
HTMListbox1.AddItem('Click Next to install Proton Studio in ' + IDEDirectory, nil);
btnNext.Enabled := true;
end
else
HTMListBox1.AddItem('Proton IDE must be installed first', nil);
end;
end.
I have created a .rc script to load my program and help
#100 RT_RCDATA "D:\Data\Documents\RAD Studio\Projects\ProtonNewIDE\Win32\Debug\ProtonNewIDE.zip"
#200 RT_RCDATA "D:\Data\Documents\RAD Studio\Projects\ProtonNewIDE\Win32\Debug\Proton Studio.zip"
I'm working in Delphi Berlin 10.1, Build resulted in my resource file being generated and I can open it in my Resource Editor but when I try and open the resource:
Rs := TResourceStream.CreateFromID(Application.Handle, ResID, RT_RCDATA);
I get an Address violation. It breaks in System.Classes at this point:
HResInfo := FindResource(Instance, Name, ResType);
and both the Name and ResType are empty.
I would appreciate a pointer to what am I doing wrong?
You are passing a window handle instead of a module handle. Pass HInstance instead, the handle to the module containing this code.
I'm out of ideas so I appeal to the immense StackOverflow supermind. What I want is to have a a tedit or whatever text control that lets me input text with the following format: "nnnnnn:nn" where n is an integer. Examples: If I type "100" I want a "100:00" text property. If I type "123:1" I should get "123:01". May be I should only type numbers in a calculator style with the ":" separator at a fixed position. I want rejected something like this "10 : 1", "10:95" (minutes 0-59), "0100:10", etc. Any ideas or component?
Greetings, Marcelo.
Any formatting that changes in input text during entry is bad, so the following shows you how to do it, but input is only changed on exiting the field or pressing the enter key:
Edit 1 is the edit field in question. Edit2 is simply there to allow the tab key to exit Edit1.
Note that I am using standard evets and event name (OnKeyPress and OnExit)
unit Unit10;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, System.StrUtils, Vcl.Mask;
type
TForm10 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
procedure Edit1KeyPress(Sender: TObject; var Key: Char);
procedure Edit1Exit(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
function EntryValid( const pVal : string ) : boolean;
end;
var
Form10: TForm10;
implementation
{$R *.dfm}
{ TComboBox }
procedure TForm10.Edit1Exit(Sender: TObject);
var
iPos : integer;
iCheck : string;
i1 : string;
begin
iPos := Pos( ':', Edit1.Text );
if iPos > 0 then
begin
// we already know there can only be one ':'
i1 := Copy( Edit1.Text, 1, iPos );
iCheck := Copy(Edit1.Text, iPos + 1 );
if iCheck = '' then
begin
Edit1.Text := i1 + '00';
end
else if StrToInt( iCheck ) < 10 then
begin
Edit1.Text := i1 + '0' + iCheck;
end
else
begin
// already correct, so ignore
end;
end
else
begin
Edit1.Text := Edit1.Text + ':00';
end;
end;
procedure TForm10.Edit1KeyPress(Sender: TObject; var Key: Char);
begin
// only allow numbers and a single :
case Key of
'0'..'9': ;
':':
begin
if Pos( ':', Edit1.Text ) <> 0 then
begin
Key := #0; // don't allow
Beep;
end;
end;
#13:
begin
Key := #0; // silently this time
Edit1Exit( Sender );
end
else
begin
Key := #0;
Beep;
end;
end;
end;
function TForm10.EntryValid(const pVal: string): boolean;
var
iPos : integer;
iCheck : string;
begin
iPos := Pos( ':', pVal );
if iPos > 0 then
begin
// we already know there can only be one ':'
iCheck := Copy( pVal, iPos + 1 );
if iCheck = '' then
begin
Result := TRUE;
end
else if StrToIntDef( iCheck, 60 ) < 60 then
begin
Result := TRUE;
end
else
begin
Result := FALSE;
end;
end
else
begin
Result := TRUE;
end;
end;
end.
As DataSnap users will know, its ServerMethods return values to their callers
as DataSnap parameters.
There have been a number of reports on SO and elsewhere relating to a problem with
DataSnap servers returning ServerMethod results as ftStream parameters, that the stream is truncated
prematurely or returned empty. An example is here:
Can't retrieve TStreams bigger than around 260.000 bytes from a Datasnap Server
I have put together a reproducible test case of this that I intend submitting to
Emba's Quality Portal as an MCVE, but before I do I'd like some help pinning down
where the problem occurs. I'm using Delphi Seattle on Win64, compiling to 32-bits, btw.
My MCVE is completely self-contained (i.e. includes both server and client) and does
not depend on any database data. Its ServerMethods module contains a function
(BuildString in the code below) which returns a string of a caller-specified length
and two ServerMethods GetAsString and GetAsStream which return the result
as parameters of types ftString and ftStream, respectively.
Its GetString method successfully returns a string of any requested length up to
the maximum I've tested, which is 32000000 (32 million) bytes.
Otoh, the GetStream method works up to a requested size of 30716; above that,
the returned stream has a size of -1 and is empty. The expected behaviour of course
that it should be capable of working with much larger sizes, just as GetString does.
On the outbound (server) side, at some point the returned stream is passed into
DataSnap's JSon layer en route to the tcp/ip transport layer and on the inbound side, similarly, the stream is retrieved
from the JSon layer. What I'd like to be able to do, and what this q is about,
is to capture the outbound and inbound JSon representations of the AsStream
parameter value in human-legible form so that I identify whether the unwanted
truncation of its data occurs on the server or client side. How do I do that?
the reason I'm asking this is that despite hours of looking I've been unable to identify exactly
where the JSon conversions occur. It's like looking for a needle in a haystack.
If you take a look at the method TDBXJSonStreamWriter.WriteParameter in Data.DBXStream,
the one thing it doesn't write is the stream's contents!
One thing I have been able to establish is regarding line 4809 in Data.DBXStream
Size := ((FBuf[IncrAfter(FOff)] and 255) shl 8) or (FBuf[IncrAfter(FOff)] and 255)
in the function TDBXRowBuffer.ReadReaderBlobSize. On entry to
this method, Size is initialised to zero, and it is this line which sets Size to 30716
for all requested stream sizes >= that value. But I don't know whether this is cause or effect,
i.e. whether the stream trucation has already taken place or whether it's this line
which causes it.
My code is below; apologies for the length of it, but DataSnap projects require
quite a lot of baggage at the best of times and I've included some code which
initialises some of the components to avoid having to post .DFMs too.
ServerMethods code:
unit ServerMethods2u;
interface
uses System.SysUtils, System.Classes, System.Json, variants, Windows,
Datasnap.DSServer, Datasnap.DSAuth, DataSnap.DSProviderDataModuleAdapter;
{$MethodInfo on}
type
TServerMethods1 = class(TDSServerModule)
public
function GetStream(Len: Integer): TStream;
function GetString(Len: Integer): String;
end;
{$MethodInfo off}
implementation
{$R *.dfm}
uses System.StrUtils;
function BuildString(Len : Integer) : String;
var
S : String;
Count,
LeftToWrite : Integer;
const
scBlock = '%8d bytes'#13#10;
begin
LeftToWrite := Len;
Count := 1;
while Count <= Len do begin
S := Format(scBlock, [Count]);
if LeftToWrite >= Length(S) then
else
S := Copy(S, 1, LeftToWrite);
Result := Result + S;
Inc(Count, Length(S));
Dec(LeftToWrite, Length(S));
end;
if Length(Result) > 0 then
Result[Length(Result)] := '.'
end;
function TServerMethods1.GetStream(Len : Integer): TStream;
var
SS : TStringStream;
begin
SS := TStringStream.Create;
SS.WriteString(BuildString(Len));
SS.Position := 0;
Result := SS;
end;
function TServerMethods1.GetString(Len : Integer): String;
begin
Result := BuildString(Len);
end;
ServerContainer code:
unit ServerContainer2u;
interface
uses System.SysUtils, System.Classes, Datasnap.DSTCPServerTransport,
Datasnap.DSServer, Datasnap.DSCommonServer, Datasnap.DSAuth, IPPeerServer,
DataSnap.DSProviderDataModuleAdapter;
type
TServerContainer1 = class(TDataModule)
DSServer1: TDSServer;
DSTCPServerTransport1: TDSTCPServerTransport;
DSServerClass1: TDSServerClass;
procedure DataModuleCreate(Sender: TObject);
procedure DSServerClass1GetClass(DSServerClass: TDSServerClass;
var PersistentClass: TPersistentClass);
end;
var
ServerContainer1: TServerContainer1;
implementation
{$R *.dfm}
uses ServerMethods2u;
procedure TServerContainer1.DataModuleCreate(Sender: TObject);
begin
DSServerClass1.Server := DSServer1;
DSTCPServerTransport1.Server := DSServer1;
end;
procedure TServerContainer1.DSServerClass1GetClass(
DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
PersistentClass := TServerMethods1;
end;
end.
ServerForm code:
unit ServerForm2u;
interface
uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
Vcl.StdCtrls, DBXJSON, Data.DBXDataSnap, IPPeerClient,
Data.DBXCommon, Data.FMTBcd, Data.DB, Data.SqlExpr, Data.DbxHTTPLayer,
DataSnap.DSServer;
type
TForm1 = class(TForm)
btnGetStream: TButton;
edStreamSize: TEdit;
SQLConnection1: TSQLConnection;
SMGetStream: TSqlServerMethod;
Memo1: TMemo;
Label1: TLabel;
btnGetString: TButton;
Label2: TLabel;
edStringSize: TEdit;
SMGetString: TSqlServerMethod;
procedure FormCreate(Sender: TObject);
procedure btnGetStreamClick(Sender: TObject);
procedure btnGetStringClick(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
SqlConnection1.ConnectionData.Properties.Values['CommunicationProtocol'] := 'tcp/ip';
SqlConnection1.ConnectionData.Properties.Values['BufferKBSize'] := '64';
SMGetStream.Params.Clear;
SMGetStream.Params.CreateParam(ftInteger, 'Len', ptInput);
SMGetStream.Params.CreateParam(ftStream, 'Result', ptOutput);
SMGetString.Params.Clear;
SMGetString.Params.CreateParam(ftInteger, 'Len', ptInput);
SMGetString.Params.CreateParam(ftString, 'Result', ptOutput);
end;
procedure TForm1.btnGetStreamClick(Sender: TObject);
var
SS : TStringStream;
S : TStream;
begin
Memo1.Lines.Clear;
SS := TStringStream.Create;
try
SMGetStream.Params[0].AsInteger := StrtoInt(edStreamSize.Text);
SMGetStream.ExecuteMethod;
S := SMGetStream.Params[1].AsStream;
S.Position := 0;
if S.Size > 0 then begin
try
SS.CopyFrom(S, S.Size);
Memo1.Lines.BeginUpdate;
Memo1.Lines.Text := SS.DataString;
Memo1.Lines.Insert(0, IntToStr(S.Size));
finally
Memo1.Lines.EndUpdate;
end;
end
else
ShowMessage(IntToStr(S.Size));
finally
SS.Free;
end;
end;
procedure TForm1.btnGetStringClick(Sender: TObject);
var
S : String;
Size : Integer;
begin
Memo1.Lines.Clear;
Size := StrtoInt(edStringSize.Text);
SMGetString.Params[0].AsInteger := Size;
SMGetString.ExecuteMethod;
S := SMGetString.Params[1].AsString;
if Length(S) > 0 then begin
try
Memo1.Lines.BeginUpdate;
Memo1.Lines.Text := S;
Memo1.Lines.Insert(0, IntToStr(Length(S)));
finally
Memo1.Lines.EndUpdate;
end;
end;
end;
end.
If you have two random files e.g. .txt, .csv, .jpg, and you wanted to open two of them and make them take up the screen 50% 50%.
How would you find the window handle that was opened so that you can re size the right one?
I have edited below to be closer to the answer thanks to suggestions from David Heffernan and Rob Kennedy
The code below kind of works if everything goes right but i'm sure there are ways to improve the code.
Using ShellExecuteEx can return a process ID, you can get a window handle off the process ID by using EnumWindows checking against the process id. Then if everything works you can re size the form using MoveWindow
i have an example in the unit uFileStuff below
There are a few issues that i'm not sure can be resolved
Files can be opened in the same application e.g. notepad++.
ShellExecuteEx may not return a process id
EnumWindows may not find the window
Unit uFileStuff
unit uFileStuff;
interface
uses Winapi.Windows, System.SysUtils, Generics.Collections, shellapi, Winapi.Messages, Vcl.Dialogs, Vcl.Forms;
type
PWindowSearch = ^TWindowSearch;
TWindowSearch = record
TargetProcessID: DWord;
ResultList: TList<HWnd>;
end;
TMyFile = class
private
sFileNameAndPath : String;
MyProcessID : DWord;
MyParentProcessID : Dword;
Procedure OpenFile(sFile: String);
procedure UpdateWindowListByProcessID;
public
WindowsLinkedToProcessID : TList<HWnd>;
function GetWindowInformation(Wnd: HWnd) : String;
function GetAllWindowInformation : String;
property ProcessID : Dword read MyProcessID;
property ParentProcessID : Dword read MyParentProcessID;
constructor Create(sFile : String);
destructor Destroy; override;
end;
implementation
constructor TMyFile.Create(sFile: String);
begin
MyProcessID := 0;
MyParentProcessID := 0;
sFileNameAndPath := sFile;
WindowsLinkedToProcessID := TList<HWnd>.Create;
if (sFile <> '') and FileExists(sFile) then
OpenFile(sFileNameAndPath);
end;
destructor TMyFile.Destroy;
begin
WindowsLinkedToProcessID.Free;
Inherited;
end;
function TMyFile.GetAllWindowInformation: String;
var i : Integer;
sMessage : String;
begin
result := '';
for I := 0 to WindowsLinkedToProcessID.Count -1 do begin
sMessage := sMessage + #13#10 + GetWindowInformation(WindowsLinkedToProcessID[i]);
end;
result := result + sMessage;
end;
function TMyFile.GetWindowInformation(Wnd: HWnd): String;
var Buffer: array[0..255] of char;
begin
result := inttostr(Wnd);
SendMessage(Wnd, WM_GETTEXT, 255, LongInt(#Buffer[0]));
if Buffer <> '' then begin
result := result + ', ' + Buffer;
end;
end;
procedure TMyFile.OpenFile(sFile: String);
var i : Integer;
SEInfo: TShellExecuteInfo;
ExitCode: DWORD;
ExecuteFile, ParamString, StartInString, sMessage: string;
begin
ExecuteFile:=sFile;
FillChar(SEInfo, SizeOf(SEInfo), 0);
SEInfo.cbSize := SizeOf(TShellExecuteInfo);
with SEInfo do
begin
fMask := SEE_MASK_NOCLOSEPROCESS;
Wnd := Application.Handle;
lpFile := PChar(sFile);
nShow := SW_SHOWNORMAL;
end;
if ShellExecuteEx(#SEInfo) then
begin
if SEInfo.hProcess > 0 then begin
Sleep(100);
WaitForInputIdle(SEInfo.hProcess, 10000 );
MyProcessID := GetProcessId( SEInfo.hProcess );
UpdateWindowListByProcessID;
end else begin
ShowMessage('No Process ' + SysErrorMessage(GetLastError) );
end;
end else
ShowMessage('Error starting "'+ sFile +'"' + #13#10 + SysErrorMessage(GetLastError));
end;
procedure TMyFile.UpdateWindowListByProcessID;
function SelectWindowByProcessID(Wnd: HWnd; Param: LParam): Bool; stdcall;
var
pSearchRec: PWindowSearch;
WindowPid: DWord;
begin
pSearchRec := PWindowSearch(Param);
Assert(Assigned(pSearchRec));
GetWindowThreadProcessID(Wnd, WindowPid);
if (WindowPid = pSearchRec.TargetProcessID) and IsWindowVisible(Wnd) then
pSearchRec.ResultList.Add(Wnd);
Result := True;
end;
var
SearchRec: TWindowSearch;
begin
if MyProcessID > 0 then begin
SearchRec.TargetProcessID := MyProcessID;
SearchRec.ResultList := WindowsLinkedToProcessID;
EnumWindows(#SelectWindowByProcessID, LParam(#SearchRec));
end;
end;
end.
Form Creating Files on create - has button to open them
unit fFileOpen;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TfrmFileOpen = class(TForm)
btn1: TButton;
procedure btn1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
sApplicationPath : String;
sFile1, sFile2 : String;
public
{ Public declarations }
end;
var
frmFileOpen: TfrmFileOpen;
implementation
uses uFileStuff;
{$R *.dfm}
procedure TfrmFileOpen.btn1Click(Sender: TObject);
var File1 : TMyFile;
File2 : TMyFile;
begin
File1 := TMyFile.Create( sFile1 );
try
if sFile2 <> sFile1 then
File2 := TMyFile.Create( sFile2 )
else
File2 := TMyFile.Create( '' );
try
if (File1.ProcessID > 0) and (File2.ProcessID > 0) then begin
if (File1.ParentProcessID > 0) and (File2.ParentProcessID > 0) and (File1.ParentProcessID = File2.ParentProcessID) then begin
showmessage('Both Files opened in same process');
end else if (File1.WindowsLinkedToProcessID.Count > 0) and (File2.WindowsLinkedToProcessID.Count > 0) then begin
if File1.WindowsLinkedToProcessID.Count > 1 then
ShowMessage('Warning returned more than 1 window Moving the first window' + #13#10 + File1.GetAllWindowInformation);
MoveWindow(File1.WindowsLinkedToProcessID[0], 0, 0, Trunc(Screen.WorkAreaWidth / 2), Screen.WorkAreaHeight, True);
if File2.WindowsLinkedToProcessID.Count > 1 then
ShowMessage('Warning returned more than 1 window Moving the first window' + #13#10 + File2.GetAllWindowInformation);
MoveWindow(File2.WindowsLinkedToProcessID[0], Round(Screen.WorkAreaWidth / 2)+1, 0, Trunc(Screen.WorkAreaWidth / 2), Screen.WorkAreaHeight, True);
end;
end;
finally
File2.Free;
end;
finally
File1.Free;
end;
end;
procedure TfrmFileOpen.FormCreate(Sender: TObject);
var slTemp : TStringList;
img : TBitmap;
begin
ReportMemoryLeaksOnShutdown := true;
sApplicationPath := ExtractFileDir(application.ExeName);
sFile1 := IncludeTrailingPathDelimiter( sApplicationPath ) + 'File1.txt';
sFile2 := IncludeTrailingPathDelimiter( sApplicationPath ) + 'File2.csv';
{
if not FileExists( sFile1 ) then begin
img := TBitmap.Create;
img.SetSize(300,300);
img.SaveToFile( sFile1 );
img.Free;
end; }
if not FileExists(sFile1) then begin
slTemp := TStringList.Create;
slTemp.Add('File1');
slTemp.SaveToFile(sFile1);
slTemp.Free;
end;
if not FileExists(sFile2) then begin
slTemp := TStringList.Create;
slTemp.Add('File2');
slTemp.SaveToFile(sFile2);
slTemp.Free;
end;
end;
end.
I have a Delphi 7 DLL function that returns large string and it works fine but in Delphi XE5 I get an access violation after a specific size.
I have written a sample demo, that reflects my actual code, that generates also a AV in Delphi XE5 that returns also a large string but again after a specific size, I get an Access Violation ?
13000 lines of 20 chars, it works fine but with 14000 lines it crashes.
I did some tests with Delphi 7 and it works fine also.
What am I doing wrong ? Can anyone help me out ?
Thanks.
Here is the code of my DLL :
function RetLargeStr(Buffer : pAnsiChar; var BufferSize: Integer) : boolean ; stdcall;
var l_ansiStr : string;
loop : integer;
begin
Result := False;
//13000 ok 14000+ fail ???
for loop := 1 to 15000 do
begin
l_AnsiStr := l_AnsiStr + 'String of 20 chars' + Char($0D) + Char($0A) ;
end;
if Assigned(Buffer) and (BufferSize >= Length(l_ansiStr) + 1) then
begin
//Buffer := pAnsiChar(AnsiString(l_AnsiStr));
move(l_AnsiStr, Buffer^, length(l_AnsiStr) + 1);
Result := True;
end;
//Return actual size of output string.
BufferSize := Length(l_AnsiStr) + 1;
end ;
Here’s the call from my EXE :
procedure TForm1.Button7Click(Sender: TObject);
var l_StrOut : pAnsiChar;
l_Str : ansistring;
p_Size : integer;
begin
p_Size := 600000;
SetLength(l_Str, p_Size);
l_strout := pAnsiChar(l_str);
Memo2.Lines.Clear;
if RetLargeStr(l_StrOut, p_Size)
then Memo2.Lines.Add( l_StrOut );
end;
The way you have it here it's probably just luck that it works at all.
In the DLL, when you do this:
Buffer := pAnsiChar(AnsiString(l_AnsiStr));
you are actually returning the string buffer allocated in the DLL to the calling EXE, even though you've explicitly allocated a receive buffer before the call. That receive buffer pointer gets overwritten.
The crash most likely occurs because the heap manager in the EXE is unprepared for freeing a memory block, which was allocated somewhere else (in the DLL).
Instead of assigning to buffer, you might try copying the content of the string to it, like this:
if Assigned(Buffer) and (BufferSize >= Length(l_ansiStr) + 1) then
begin
move(AnsiStr[1], Buffer^, length(AnsiStr) + 1));
Result := True;
end;
Test code (DLL):
library Project2;
uses
SysUtils,
Classes;
function RetLargeStr(Buffer : pAnsiChar; var BufferSize: Integer) : boolean ; stdcall;
var l_ansiStr : string;
loop : integer;
begin
Result := False;
//13000 ok 14000+ fail ???
for loop := 1 to 15000 do
begin
l_AnsiStr := l_AnsiStr + 'String of 20 chars' + Char($0D) + Char($0A) ;
end;
if Assigned(Buffer) and (BufferSize >= Length(l_ansiStr) + 1) then
begin
//Buffer := pAnsiChar(AnsiString(l_AnsiStr));
move(l_AnsiStr[1], Buffer^, length(l_AnsiStr) + 1);
Result := True;
end;
//Return actual size of output string.
BufferSize := Length(l_AnsiStr) + 1;
end ;
exports
RetLargeStr;
begin
end.
Test code (EXE):
unit Unit3;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TForm3 = class(TForm)
Button1: TButton;
Memo1: TMemo;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
function RetLargeStr(Buffer : pAnsiChar; var BufferSize: Integer) : boolean ; stdcall; external 'project2.dll';
procedure TForm3.Button1Click(Sender: TObject);
var l_StrOut : pAnsiChar;
l_Str : ansistring;
p_Size : integer;
begin
p_Size := 600000;
SetLength(l_Str, p_Size);
l_strout := pAnsiChar(l_str);
Memo1.Lines.Clear;
if RetLargeStr(l_StrOut, p_Size)
then Memo1.Lines.Add( l_StrOut );
end;
end.