we have an delphi application that using tadoconnection for connecting sql server database
a problem is when we open a resource hacker on running exe file it show's connectionstring clearly and any one can connect to our server
a sample code shown below describe problem
You are not using a resource hacker. If your strings were stored in resources, they would be even easier to find and view.
You are using a memory scanner instead, one that looks at the program's raw memory while it is running. Eventually, you would have to create a connection string in memory to pass to the database engine. If a hacker has access to the app and its memory, they have app to its data. A dedicated hacker with such access will be able to grab that string at the time it is being used.
But there are things you can do to make things harder for casual hackers.
For one thing, don't allow non-admin users to have access to your app. When a user runs the scanner app, it is likely to have access to memory in other apps run by the same user. Run your app in a different user context, or in an elevated process. Apps run by a user cannot access the memory of other apps being run by other users, unless he/she is explictly granted permission to them, or he/she is an admin to begin with. If a hacker has permissions to access your app, all bets are off.
Also, break up the string into smaller substrings that you have to concatenate when needed. Build up the connection string dynamically. That way, the substrings are not stored in one place, should the hacker try looking through the EXE file itself on disk.
And by all means, don't store sensitive credentials within the app code to begin with. Store them externally, and keep them encrypted. Retrieve them to memory only when absolutely needed, and securely destroy the content of that memory using SecureZeroMemory() when you are done using it. If you need to hold on to the credentials, or the connection string, in memory for more than a few milliseconds, consider using CryptProtectMemory() to encrypt that memory when you are not actively using it.
The more work it takes you to piece together the connection string, the more work a casual hacker will have to do, too. But a dedicated hacker will just wait until you do all the work, and then just pluck the final string from memory at the moment it is used, so you have to do what you can to prevent access to memory in the first place.
Ensure your password is stored separately and as securely as meets your requirements.*
Ensure you do not persist your password with your connection. I.e. Connection.Properties must include 'Persist Security Info=False'
Set Connection.LoginPrompt to True.
Implement a handler for the OnLogin event in which you load, decrypt and provide your password to the Connection.Open call.
As a final security step, ensure you erase your decrypted password from memory.
*There a plenty of simple encryption libraries available that you might as well encrypt your password. Microsoft's Data Protection API is worth considering. Bear in mind that perfect security is impossible if someone has access to your application. The best you can do is add layers of obfuscation making it more difficult for a hacker to crack your database login credentials.
Some sample code
procedure TAbc.HandleOnLogin(Sender: TObject; Username, Password: string);
var
LPassword: string;
begin
LPassword := GetDecryptedPassword; //Your choice how you do this
Connection.Open(Username, LPassword);
//The next line ensures memory is erased before it is deallocated
//which would otherwise leave the password hanging around.
SecureZeroMemory(Pointer(LPassword), Length(LPassword) * SizeOf(Char));
end;
In addition to other answers what you can do is remove permission to read process memory, this is useful if you control user permissions (say in enterprise environment).
Calling following procedure at the start of your process prevents non-admin user from reading process memory. (You'll need JEDI API units for missing API calls)
uses JwaWinNT, JwaWinBase, JwaAclApi, JwaAccCtrl;
//...
{$SCOPEDENUMS ON}
function ProtectProcess(): DWORD;
type
TSidType = (Everyone, CurrentUser, System, Admin);
var
// Released on exit
ProcessToken: THandle;
TokenInfo: PVOID;
SidCurUser: PSID;
SidEveryone: PSID;
SidSystem: PSID;
SidAdmins: PSID;
ACL: PACL;
SecDesc: PSECURITY_DESCRIPTOR;
Size: DWORD;
TokenSize: DWORD;
BResult: Bool;
SIDAuthEveryone: SID_IDENTIFIER_AUTHORITY;
SIDAuthSystem: SID_IDENTIFIER_AUTHORITY;
SIDAuthAdministrators: SID_IDENTIFIER_AUTHORITY;
SIDArray: array[TSidType] of PSID;
I: TSidType;
const
// Mimic Protected Process
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms684880%28v=vs.85%29.aspx
// Protected processes allow PROCESS_TERMINATE, which is
// probably not appropriate for high integrity software.
DeniedPermissions =
{READ_CONTROL |}
WRITE_DAC or WRITE_OWNER or
PROCESS_CREATE_PROCESS or PROCESS_CREATE_THREAD or
// PROCESS_DUP_HANDLE or // this permission is needed for printing
PROCESS_QUERY_INFORMATION or
PROCESS_SET_QUOTA or PROCESS_SET_INFORMATION or
PROCESS_VM_OPERATION or
PROCESS_VM_READ or PROCESS_VM_WRITE
// In addition to protected process
// or PROCESS_SUSPEND_RESUME or PROCESS_TERMINATE
;
// Standard and specific rights not explicitly denied
AllowedPermissions = ((not DeniedPermissions) and $1FFF) or PROCESS_TERMINATE or SYNCHRONIZE;
begin
ACL := nil;
TokenInfo := nil;
SecDesc := nil;
try
TokenSize := 0;
ProcessToken := 0;
// If this fails, you can try to fallback to OpenThreadToken
if (not OpenProcessToken(GetCurrentProcess(), TOKEN_READ, ProcessToken)) then
begin
Result := GetLastError();
Exit;
end;
BResult := GetTokenInformation(ProcessToken, TokenUser, nil, 0, TokenSize);
Result := GetLastError();
Assert((not BResult) and (ERROR_INSUFFICIENT_BUFFER = Result));
if(not ((BResult = FALSE) and (ERROR_INSUFFICIENT_BUFFER = Result))) then
begin
// failed;
Exit;
end;
if (TokenSize > 0) then
begin
TokenInfo := HeapAlloc(GetProcessHeap(), 0, TokenSize);
Result := GetLastError();
Assert(Assigned(TokenInfo));
if (nil = TokenInfo) then
begin
// failed;
Exit;
end;
end;
BResult := GetTokenInformation(ProcessToken, TokenUser, TokenInfo, TokenSize, TokenSize);
Result := GetLastError();
Assert(BResult and Assigned(TokenInfo));
if not (BResult and Assigned(TokenInfo)) then
begin
Exit;
end;
SidCurUser := (PTokenUser(TokenInfo)).User.Sid;
SIDAuthEveryone := SECURITY_WORLD_SID_AUTHORITY;
BResult := AllocateAndInitializeSid(#SIDAuthEveryone, 1,
SECURITY_WORLD_RID, 0, 0, 0, 0, 0, 0, 0, SidEveryone);
Result := GetLastError();
Assert(BResult and Assigned(SidEveryone));
if not (BResult and Assigned(SidEveryone)) then
begin
Exit;
end;
SIDAuthSystem := SECURITY_NT_AUTHORITY;
BResult := AllocateAndInitializeSid(#SIDAuthSystem, 1,
SECURITY_LOCAL_SYSTEM_RID, 0, 0, 0, 0, 0, 0, 0, SidSystem);
Result := GetLastError();
Assert(BResult and Assigned(SidSystem));
if not (BResult and Assigned(SidSystem)) then
begin
Exit;
end;
SIDAuthAdministrators := SECURITY_NT_AUTHORITY;
BResult := AllocateAndInitializeSid(#SIDAuthAdministrators, 2,
SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS,
0, 0, 0, 0, 0, 0, SidAdmins);
Result := GetLastError();
Assert(BResult and Assigned(SidAdmins));
if not (BResult and Assigned(SidAdmins)) then
begin
Exit;
end;
SIDArray[TSidType.Everyone] := SidEveryone; // Deny most rights to everyone
SIDArray[TSidType.CurrentUser] := SidCurUser; // Allow what was not denied
SIDArray[TSidType.System] := SidSystem; // Full control
SIDArray[TSidType.Admin] := SidAdmins; // Full control
// Determine required size of the ACL
Size := SizeOf(ACL);
// First the DENY, then the ALLOW
Size := Size + GetLengthSid(SIDArray[TSidType.Everyone]);
Size := Size + SizeOf(ACCESS_DENIED_ACE) - SizeOf(DWORD);
for I := TSidType.CurrentUser to High(SIDArray) do
begin
// DWORD is the SidStart field, which is not used for absolute format
Size := Size + GetLengthSid(SIDArray[I]);
Size := Size + SizeOf(ACCESS_ALLOWED_ACE) - SizeOf(DWORD);
end;
Size := Size + SizeOf(DWORD);
ACL := PACL(HeapAlloc(GetProcessHeap(), 0, Size));
Result := GetLastError();
Assert(Assigned(ACL));
if not Assigned(ACL) then
begin
Exit;
end;
BResult := InitializeAcl(ACL, Size, ACL_REVISION);
Result := GetLastError();
Assert(BResult);
if not BResult then
begin
Exit;
end;
BResult := AddAccessDeniedAce(ACL, ACL_REVISION, DeniedPermissions,
SIDArray[TSidType.Everyone]);
Result := GetLastError();
Assert(BResult);
if not BResult then
begin
Exit;
end;
BResult := AddAccessAllowedAce(ACL, ACL_REVISION, AllowedPermissions,
SIDArray[TSidType.CurrentUser]);
Result := GetLastError();
Assert(BResult);
if not BResult then
begin
Exit;
end;
// Because of ACE ordering, System will effectively have dwAllowed even
// though the ACE specifies PROCESS_ALL_ACCESS (unless software uses
// SeDebugPrivilege or SeTcbName and increases access).
// As an exercise, check behavior of tools such as Process Explorer under XP,
// Vista, and above. Vista and above should exhibit slightly different behavior
// due to Restricted tokens.
BResult := AddAccessAllowedAce(ACL, ACL_REVISION, PROCESS_ALL_ACCESS,
SIDArray[TSidType.System]);
Result := GetLastError();
Assert(BResult);
if not BResult then
begin
Exit;
end;
// Because of ACE ordering, Administrators will effectively have dwAllowed
// even though the ACE specifies PROCESS_ALL_ACCESS (unless the Administrator
// invokes 'discretionary security' by taking ownership and increasing access).
// As an exercise, check behavior of tools such as Process Explorer under XP,
// Vista, and above. Vista and above should exhibit slightly different behavior
// due to Restricted tokens.
BResult := AddAccessAllowedAce(ACL, ACL_REVISION, PROCESS_ALL_ACCESS, SIDArray[TSidType.Admin]);
Result := GetLastError();
Assert(BResult);
if not BResult then
begin
SiMain.LogWin32Error('AddAccessAllowedAce failed: ', Result);
Exit;
end;
SecDesc := PSECURITY_DESCRIPTOR(HeapAlloc(GetProcessHeap(), 0, SECURITY_DESCRIPTOR_MIN_LENGTH));
Result := GetLastError();
Assert(Assigned(SecDesc));
if not Assigned(SecDesc) then
begin
Exit;
end;
// InitializeSecurityDescriptor initializes a security descriptor in
// absolute format, rather than self-relative format. See
// http://msdn.microsoft.com/en-us/library/aa378863(VS.85).aspx
BResult := InitializeSecurityDescriptor(SecDesc, SECURITY_DESCRIPTOR_REVISION);
Result := GetLastError();
Assert(BResult);
if not BResult then
begin
Exit;
end;
BResult := SetSecurityDescriptorDacl(SecDesc, TRUE, ACL, FALSE);
Result := GetLastError();
Assert(BResult);
if not BResult then
begin
Exit;
end;
SetSecurityInfo(
GetCurrentProcess(),
SE_KERNEL_OBJECT, // process object
OWNER_SECURITY_INFORMATION or DACL_SECURITY_INFORMATION,
SidCurUser, // Owner SID
nil, // Group SID
ACL,
nil // SACL
);
Result := GetLastError();
Assert(ERROR_SUCCESS = Result);
finally
if (nil <> SecDesc) then
begin
HeapFree(GetProcessHeap(), 0, SecDesc);
end;
if (nil <> ACL) then
begin
HeapFree(GetProcessHeap(), 0, ACL);
end;
if (SidAdmins <> nil) then
begin
FreeSid(SidAdmins);
end;
if (SidSystem <> nil) then
begin
FreeSid(SidSystem);
end;
if (SidEveryone <> nil) then
begin
FreeSid(SidEveryone);
end;
if (nil <> TokenInfo) then
begin
HeapFree(GetProcessHeap(), 0, TokenInfo);
end;
if (0 <> ProcessToken) then
begin
CloseHandle(ProcessToken);
end;
end;
end;
Related
I need a way to find if a user can to write to a binary file.
Users might not have write permission for a directory and its contents. They can, however, create new files and modify them - so testing this by creating a new file in the directory is not accurate.
The following appears to do the trick, is it the best way to do it, and is it safe?
function IsExistingBinaryFileWriteable(const aName : String): Boolean;
var
testFile: File;
begin
Result := False;
if not FileExists(aName) then Exit;
try
AssignFile(testFile, aName);
FileMode := fmOpenWrite;
Reset(testFile); // open
Result := True;
finally
CloseFile(testFile);
end;
end;
If Reset(testFile) fails, result will not be set to True.
It seems to test ok for me, I'm just concerned about the safety of using Reset() like this, and if it modifies the file in any way?
You must change your function because it can't catch IO error
function IsExistingBinaryFileWriteable(const aName : String): Boolean;
var
testFile: File;
begin
Result := False;
if not FileExists(aName) then Exit;
AssignFile(testFile, aName);
FileMode := fmOpenWrite;
try
Reset(testFile); // open
except
Exit(False);
end;
Result := True;
CloseFile(testFile);
end;
But, this function don't check the permission, this is only check you can write on file or not. What is difference, Think you have a file that you have permission to change it but with readonly properties. this function return False instead of True.
I prefer to use this CheckFileAccess function instead.
And for checking write access you can use this condition
CheckFileAccess(yourFileName, FILE_GENERIC_WRITE) = FILE_GENERIC_WRITE
I am trying to find posisition of mykeyword and extract strings from a loaded file (up to 200 MB).
procedure TForm5.Button4Click(Sender: TObject);
var
Stream: TFileStream;
Buffer: array [0 .. 1023] of AnsiChar;
i: Integer;
myKeyword: string;
pullStr: AnsiString;
begin
myKeyword :='anything';
Stream := TFileStream.Create(edtTarget.Text, fmOpenRead);
while Stream.Position < Stream.Size do
begin
Stream.Read(Buffer, 1024);
m1.Lines.Add(Buffer); // no need, just display to evaluate
(* 1. Get address of given keyword *)
// i := Stream.PositionOf(myKeyword); < how to do this?
(* 2. Stream Exract *)
// pullStr := Stream.copy(i,1000); < how to do this ?
end;
end;
I have read other topics regarding file and string. I found a very good answer from here. And i think i want to expand those features.
Something like
TFileSearchReplace.GetStrPos(const KeyWord: string) : Integer;
TFileSearchReplace.ExtractStr (const KeyWord: string; Len : Integer) ;
procedure TForm5.Button4Click(Sender: TObject);
var
Stream: TFileStream;
Buffer: AnsiString;
i, BytesRead, SearchPos: Integer;
myKeyword: string;
pullStr: AnsiString;
Found: Boolean;
begin
myKeyword :='anything';
Found := False;
SetLength(Buffer, 1024);
Stream := TFileStream.Create(edtTarget.Text, fmOpenRead);
while Stream.Position < Stream.Size do
begin
// read some bytes and remember, how many bytes been read actually
BytesRead := Stream.Read(Buffer[1], 1024);
// glue new bytes to the end of the pullStr
pullStr := pullStr + copy(Buffer, 1, BytesRead);
// file is divided to two parts: before myKeyword, and after
// if myKeyword alreay found, there is nothing to do, just repeat reading to pullStr
if Found then
continue;
// if myKeyword is not found yet, pullStr acts like temporary buffer
// search for myKeyword in buffer
SearchPos := Pos(myKeyword, pullStr);
if SearchPos > 0 then
begin //keyword was found, delete from beginning up to and icluding myKeyword
// from now on, pullStr is not tmp buffer, but result
Found := True;
Delete(pullStr, 1, SearchPos + Length(myKeyWord) - 1);
continue;
end;
// myKeyword still not found. Find last line end in buffer
SearchPos := LastDelimiter(#13#10, pullStr);
// and delete everything before it
if SearchPos > 0 then
Delete(pullStr, 1, SearchPos);
// so if myKeyword spans across two reads, it still will be found in next iteration
end;
// if there is no myKeyword in file, clear buffer
if not Found then
pullStr := '';
end;
I have made a sample application in Delphi xe10
and make user id and password and database name encrypted
and decrypt on connecting the problem is when I open exe process in memory by memory scanner I can find all of them easily by searching some part of the connection string
is it such easy to find secure connection data in win applications or I did something wrong?
Don't put the password in the connection string. Instead assign an OnWillConnect event handler to TADOConnection and supply the password there in the supplied parameter.
Try to protect the memory. Use CryptProtectMemory and CryptUnprotectMemory.
https://msdn.microsoft.com/de-de/library/windows/desktop/aa380262(v=vs.85).aspx
Here is an small snippet from my class. Play with it:
uses
Winapi.Windows,
System.SysUtils;
....
TMyMemEncryptBlaBla = class
private
//......
public
function MemEncrypt(const StrInp: String; CryptFlags: DWORD = 0): TBytes;
function MemDecrypt(const EncInp: TBytes; CryptFlags: DWORD = 0): String;
end;
{
BOOL WINAPI CryptProtectMemory(_Inout_ LPVOID pData,
_In_ DWORD cbData,
_In_ DWORD dwFlags );
}
function CryptProtectMemory(Data: Pointer; Size: DWORD; Flags: DWORD) : BOOL; stdcall;
{
BOOL WINAPI CryptUnprotectMemory(_Inout_ LPVOID pData,
_In_ DWORD cbData,
_In_ DWORD dwFlags );
}
function CryptUnProtectMemory(Data: Pointer; Size : DWORD;Flags: DWORD) : BOOL; stdcall;
// CryptProtectMemory and CryptUnprotectMemory.
CRYPTPROTECTMEMORY_SAME_PROCESS = 0; // Set as default
CRYPTPROTECTMEMORY_CROSS_PROCESS = 1;
CRYPTPROTECTMEMORY_SAME_LOGON = 2;
CRYPTPROTECTMEMORY_BLOCK_SIZE = 16;
implementation
function CryptProtectMemory; external 'Crypt32.dll' Name 'CryptProtectMemory';
function CryptUnProtectMemory; external 'Crypt32.dll' Name 'CryptUnprotectMemory';
// encrypt
function TMyMemEncryptBlaBla.MemEncrypt(const StrInp: String; CryptFlags: DWORD): TBytes;
begin
Result := TEncoding.Unicode.GetBytes(StrInp);
try
if Length(Result) mod CRYPTPROTECTMEMORY_BLOCK_SIZE <> 0 then
SetLength(Result, ((Length(Result) div CRYPTPROTECTMEMORY_BLOCK_SIZE) + 1) * CRYPTPROTECTMEMORY_BLOCK_SIZE);
except
on E:Exception do
begin
MessageBox(0, PChar(E.Message), PChar('E_OUTOFMEMORY'), MB_ICONERROR or MB_OK);
end;
end;
try
if not CryptProtectMemory(Result, Length(Result), CryptFlags) then
begin
MessageBox(0, PChar('MemCrypt: ' + SysErrorMessage(GetLastError)), PChar('MemEncrypt failed'), MB_ICONERROR or MB_OK);
ZeroMemory(Result, Length(Result));
end;
except
on E:Exception do
begin
MessageBox(0, PChar(E.Message), PChar('MemEncrypt Exception'), MB_ICONERROR or MB_OK);
end;
end;
end;
//decrypt
function TMyMemEncryptBlaBla.MemDecrypt(const EncInp: TBytes; CryptFlags: DWORD): String;
var
DecTmp: TBytes;
begin
DecTmp := Copy(EncInp);
try
if CryptUnprotectMemory(DecTmp, Length(DecTmp), CryptFlags) then
Result := TEncoding.Unicode.GetString(DecTmp)
else
MessageBox(0, PChar('MemDecrypt: ' + SysErrorMessage(GetLastError)), PChar('MemDecrypt failed'), MB_ICONERROR or MB_OK);
ZeroMemory(DecTmp, Length(DecTmp));
except
on E:Exception do
MessageBox(0, PChar(E.Message), PChar('MemDecrypt Exception'), MB_ICONERROR or MB_OK);
end;
end;
end.
Axel
Thinking out of box....
why you want hide password?
If database is on user's computer, Then he/she can open database simply by windows authentication mode using SQL management studio with out password!
If Database is on a remote server sure it is better to write a web service that fetch data and send result in XML for you program instead of remote opening database .
Is there a way to write a byte array to a file? I have the file name and file extension(like temp.xml).
Sounds like you just want the ioutil.WriteFile function from the standard library.
https://golang.org/pkg/io/ioutil/#WriteFile
It would look something like this:
permissions := 0644 // or whatever you need
byteArray := []byte("to be written to a file\n")
err := ioutil.WriteFile("file.txt", byteArray, permissions)
if err != nil {
// handle error
}
According to https://golang.org/pkg/io/ioutil/#WriteFile, as of Go 1.16 this function is deprecated. Use https://pkg.go.dev/os#WriteFile instead (ioutil.WriteFile simply calls os.WriteFile as of 1.16).
Otherwise, Jeffrey Martinez's answer remains correct:
permissions := 0644 // or whatever you need
byteArray := []byte("to be written to a file\n")
err := os.WriteFile("file.txt", byteArray, permissions)
if err != nil {
// handle error
}
I want to share array data between two applications. In my mind, first program create the array and the second program can read the array from already allocated memory area. The array is not a dynamic array.
I found a way to share pointer using OpenFileMapping and MapViewOfFile. I have no luck to implement array sharing and I think i don't want to use IPC method yet.
Is it possible to plan a scheme like this (sharing array)? My purpose is to minimize memory usage and reading data quickly.
Scratched my head thinking of what a short-but-complete example of sharing memory between two applications might be. The only option is a console application, GUI applications require a minimum of 3 files (DPR + PAS + DFM). So I cooked up a small example where one integers array is shared using a memory mapped file (backed by the page file so I don't need to have a phisical file on disk for this to work). The console application responds to 3 commands:
EXIT
SET NUM VALUE Changes the value at index NUM in the array to VALUE
DUMP NUM displays the value in the array at index NUM
DUMP ALL displays the whole array
Of course, the command processing code takes up about 80% of the whole application. To test this compile the following console application, find the executable and start it twice. Go to the first window and enter:
SET 1 100
SET 2 50
Go to the second console and enter this:
DUMP 1
DUMP 2
DUMP 3
SET 1 150
Go to the first console and enter this:
DUMP 1
There you have it, you've just witnessed sharing memory between two applications.
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils, Windows, Classes;
type
TSharedArray = array[0..10] of Integer;
PSharedArray = ^TSharedArray;
var
hFileMapping: THandle; // Mapping handle obtained using CreateFileMapping
SharedArray: PSharedArray; // Pointer to the shared array
cmd, s: string;
num, value, i: Integer;
L_CMD: TStringList;
function ReadNextCommand: string;
begin
WriteLn('Please enter command (one of EXIT, SET NUM VALUE, DUMP NUM, DUMP ALL)');
WriteLn;
ReadLn(Result);
end;
begin
try
hFileMapping := CreateFileMapping(0, nil, PAGE_READWRITE, 0, SizeOf(TSharedArray), '{C616DDE6-23E2-425C-B871-9E0DA54D96DF}');
if hFileMapping = 0 then
RaiseLastOSError
else
try
SharedArray := MapViewOfFile(hFileMapping, FILE_MAP_READ or FILE_MAP_WRITE, 0, 0, SizeOf(TSharedArray));
if SharedArray = nil then
RaiseLastOSError
else
try
WriteLn('Connected to the shared view of the file.');
cmd := ReadNextCommand;
while UpperCase(cmd) <> 'EXIT' do
begin
L_CMD := TStringList.Create;
try
L_CMD.DelimitedText := cmd;
for i:=0 to L_CMD.Count-1 do
L_CMD[i] := UpperCase(L_CMD[i]);
if (L_CMD.Count = 2) and (L_CMD[0] = 'DUMP') and TryStrToInt(L_CMD[1], num) then
WriteLn('SharedArray[', num, ']=', SharedArray^[num])
else if (L_CMD.Count = 2) and (L_CMD[0] = 'DUMP') and (L_CMD[1] = 'ALL') then
begin
for i:= Low(SharedArray^) to High(SharedArray^) do
WriteLn('SharedArray[', i, ']=', SharedArray^[i]);
end
else if (L_CMD.Count = 3) and (L_CMD[0] = 'SET') and TryStrToInt(L_CMD[1], num) and TryStrToInt(L_CMD[2], value) then
begin
SharedArray^[num] := Value;
WriteLn('SharedArray[', num, ']=', SharedArray^[num]);
end
else
WriteLn('Error processing command: ' + cmd);
finally L_CMD.Free;
end;
// Requst next command
cmd := ReadNextCommand;
end;
finally UnmapViewOfFile(SharedArray);
end;
finally CloseHandle(hFileMapping);
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
A Named File Mapping would be the easiest solution, here is some short example code.
In this sample there is a main program that writes some data and reader(s) that only read from it.
Main:
type
TSharedData = record
Handle: THandle;
end;
PSharedData = ^TSharedData;
const
BUF_SIZE = 256;
var
SharedData: PSharedData;
hFileMapping: THandle; // Don't forget to close when you're done
function CreateNamedFileMapping(const Name: String): THandle;
begin
Result := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
BUF_SIZE, PChar(Name));
Win32Check(Result > 0);
SharedData := MapViewOfFile(Result, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);
Win32Check(Assigned(SharedData));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
hFileMapping := CreateNamedFileMapping('MySharedMemory');
Win32Check(hFileMapping > 0);
SharedData^.Handle := CreateHiddenWindow;
end;
reader:
var
hMapFile: THandle; // Don't forget to close
function GetSharedData: PSharedData;
begin
hMapFile := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, 'MySharedMemory');
Win32Check(hMapFile > 0);
Result := MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, BUF_SIZE);
Win32Check(Assigned(Result));
end;