I have a server written in Delphi that I would like to add a debug logger to so it can log messages passed to Windows.OutputDebugString() while it is deployed, so clients can send me the log when there are issues. In the end, I want functionality similar to DebugView, but built into the server program itself.
I understand how the OutputDebugString works by writing to a shared memory file and using system wide events to synchronize the program and its debugger, and I have found solutions in C# and C++, but have yet to be able to translate those solutions to Delphi.
My largest problem is not knowing how to interact with the DBWIN_BUFFER_READY and DBWIN_DATA_READY synchronization events with Delphi, or how to reference the specific memory mapped file "DBWIN_BUFFER" that OutputDebugString writes to.
Additionally I have found solutions that implement their own method call instead of Windows.OutputDebugString(), but the program already has hundreds of calls, both in the code we have written and third-party modules we have added in, so these are not an option.
The C++ code you linked to can be translated to Delphi as follows:
//////////////////////////////////////////////////////////////
//
// File: WinDebugMonitor.pas
// Description: Interface of class TWinDebugMonitor
// Created: 2007-12-6
// Author: Ken Zhang
// E-Mail: cpp.china#hotmail.com
//
// Translated: 2015-02-13
// Translator: Remy Lebeau
// E-Mail: remy#lebeausoftware.org
//
//////////////////////////////////////////////////////////////
unit WinDebugMonitor;
interface
uses
Windows;
type
PDbWinBuffer = ^DbWinBuffer;
DbWinBuffer = record
dwProcessId: DWORD;
data: array[0..(4096-sizeof(DWORD))-1] of AnsiChar;
end;
TWinDebugMonitor = class
private
m_hDBWinMutex: THandle;
m_hDBMonBuffer: THandle;
m_hEventBufferReady: THandle;
m_hEventDataReady: THandle;
m_hWinDebugMonitorThread: THandle;
m_bWinDebugMonStopped: Boolean;
m_pDBBuffer: PDbWinBuffer;
function Initialize: DWORD;
procedure Uninitialize;
function WinDebugMonitorProcess: DWORD;
public
constructor Create;
destructor Destroy; override;
procedure OutputWinDebugString(const str: PAnsiChar); virtual;
end;
implementation
// ----------------------------------------------------------------------------
// PROPERTIES OF OBJECTS
// ----------------------------------------------------------------------------
// NAME | DBWinMutex DBWIN_BUFFER_READY DBWIN_DATA_READY
// ----------------------------------------------------------------------------
// TYPE | Mutex Event Event
// ACCESS | All All Sync
// INIT STATE | ? Signaled Nonsignaled
// PROPERTY | ? Auto-Reset Auto-Reset
// ----------------------------------------------------------------------------
constructor TWinDebugMonitor.Create;
begin
inherited;
if Initialize() <> 0 then begin
OutputDebugString('TWinDebugMonitor.Initialize failed.'#10);
end;
end;
destructor TWinDebugMonitor.Destroy;
begin
Uninitialize;
inherited;
end;
procedure TWinDebugMonitor.OutputWinDebugString(const str: PAnsiChar);
begin
end;
function WinDebugMonitorThread(pData: Pointer): DWORD; stdcall;
var
_Self: TWinDebugMonitor;
begin
_Self = TWinDebugMonitor(pData);
if _Self <> nil then begin
while not _Self.m_bWinDebugMonStopped do begin
_Self.WinDebugMonitorProcess;
end;
end;
Result := 0;
end;
function TWinDebugMonitor.Initialize: DWORD;
begin
SetLastError(0);
// Mutex: DBWin
// ---------------------------------------------------------
m_hDBWinMutex := OpenMutex(MUTEX_ALL_ACCESS, FALSE, 'DBWinMutex');
if m_hDBWinMutex = 0 then begin
Result := GetLastError;
Exit;
end;
// Event: buffer ready
// ---------------------------------------------------------
m_hEventBufferReady := OpenEvent(EVENT_ALL_ACCESS, FALSE, 'DBWIN_BUFFER_READY');
if m_hEventBufferReady = 0 then begin
m_hEventBufferReady = CreateEvent(nil, FALSE, TRUE, 'DBWIN_BUFFER_READY');
if m_hEventBufferReady = 0 then begin
Result := GetLastError;
Exit;
end;
end;
// Event: data ready
// ---------------------------------------------------------
m_hEventDataReady := OpenEvent(SYNCHRONIZE, FALSE, 'DBWIN_DATA_READY');
if m_hEventDataReady = 0 then begin
m_hEventDataReady := CreateEvent(nil, FALSE, FALSE, 'DBWIN_DATA_READY');
if m_hEventDataReady = 0 then begin
Result := GetLastError;
end;
end;
// Shared memory
// ---------------------------------------------------------
m_hDBMonBuffer := OpenFileMapping(FILE_MAP_READ, FALSE, 'DBWIN_BUFFER');
if m_hDBMonBuffer = 0 then begin
begin
m_hDBMonBuffer := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0, SizeOf(DbWinBuffer), 'DBWIN_BUFFER');
if m_hDBMonBuffer = 0 then begin
Result := GetLastError;
Exit;
end;
end;
m_pDBBuffer := PDbWinBuffer(MapViewOfFile(m_hDBMonBuffer, SECTION_MAP_READ, 0, 0, 0));
if m_pDBBuffer = nil then begin
Result := GetLastError;
Exit;
end;
// Monitoring thread
// ---------------------------------------------------------
m_bWinDebugMonStopped := False;
m_hWinDebugMonitorThread := CreateThread(nil, 0, #WinDebugMonitorThread, Self, 0, nil);
if m_hWinDebugMonitorThread = 0 then begin
m_bWinDebugMonStopped := True;
Result := GetLastError;
Exit;
end;
// set monitor thread's priority to highest
// ---------------------------------------------------------
SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS);
SetThreadPriority(m_hWinDebugMonitorThread, THREAD_PRIORITY_TIME_CRITICAL);
Result := 0;
end;
procedure TWinDebugMonitor.Uninitialize;
begin
if m_hWinDebugMonitorThread <> 0 then begin
m_bWinDebugMonStopped := True;
WaitForSingleObject(m_hWinDebugMonitorThread, INFINITE);
CloseHandle(m_hWinDebugMonitorThread);
m_hWinDebugMonitorThread := 0;
end;
if m_hDBWinMutex <> 0 then begin
CloseHandle(m_hDBWinMutex);
m_hDBWinMutex := 0;
end;
if m_pDBBuffer <> nil then begin
UnmapViewOfFile(m_pDBBuffer);
m_pDBBuffer := nil;
end;
if m_hDBMonBuffer <> 0 then begin
CloseHandle(m_hDBMonBuffer);
m_hDBMonBuffer := 0;
end;
if m_hEventBufferReady <> 0 then begin
CloseHandle(m_hEventBufferReady);
m_hEventBufferReady := 0;
end;
if m_hEventDataReady <> 0 then begin
CloseHandle(m_hEventDataReady);
m_hEventDataReady := 0;
end;
end;
function TCWinDebugMonitor.WinDebugMonitorProcess: DWORD;
const
TIMEOUT_WIN_DEBUG = 100;
begin
// wait for data ready
Result := WaitForSingleObject(m_hEventDataReady, TIMEOUT_WIN_DEBUG);
if Result = WAIT_OBJECT_0 then begin
OutputWinDebugString(m_pDBBuffer^.data);
// signal buffer ready
SetEvent(m_hEventBufferReady);
end;
end;
program Monitor;
{$APPTYPE CONSOLE}
{$R *.res}
uses
WinDebugMonitor;
type
Monitor = class(TWinDebugMonitor)
public
procedure OutputWinDebugString(const str: PAnsiChar); override;
end;
procedure Monitor.OutputWinDebugString(const str: PAnsiChar);
begin
Write(str);
end;
var
mon: Monitor;
begin
WriteLn('Win Debug Monitor Tool');
WriteLn('----------------------');
mon := Monitor.Create;
try
ReadLn;
finally
mon.Free;
end;
end.
program Output;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils, Windows, Messages;
var
hConsoleInput: THandle;
function KeyPressed: boolean;
var
NumberOfEvents: Integer;
begin
GetNumberOfConsoleInputEvents(hConsoleInput, NumberOfEvents);
Result := NumberOfEvents > 0;
end;
procedure KeyInit;
var
mode: Integer;
begin
// get input file handle
Reset(Input);
hConsoleInput := TTextRec(Input).Handle;
// checks/sets so mouse input does not work
SetActiveWindow(0);
GetConsoleMode(hConsoleInput, mode);
if (mode and ENABLE_MOUSE_INPUT) = ENABLE_MOUSE_INPUT then
SetConsoleMode(hConsoleInput, mode xor ENABLE_MOUSE_INPUT);
end;
var
i: Integer;
buf: AnsiString;
begin
KeyInit;
WriteLn('Press any key to stop calling OutputDebugString......');
i := 0;
while not KeyPressed do
begin
Inc(i);
buf := Format('Message from process %d, msg id: %d'#10, [ GetCurrentProcessId(), I]);
OutputDebugStringA(PAnsiChar(buf));
end;
Writeln('Total ', i, ' messages sent.');
end.
Your solution is wrong.
Hint: This function is listed under functions for debugging, and it has "Debug" in its name.
Imagine what if two programs did this. OutputDebugString is a global function. It sends a string from ANY process to the debugger. If two programs would use OutputDebugString as their logging solution - you will get a mess from simultaneous output from two processes, and each log will be mixed with other.
Quote from MSDN (as additional proof that your solution is wrong):
Applications should send very minimal debug output and provide a way for the user to enable or disable its use. To provide more detailed tracing, see Event Tracing.
In other words, OutputDebugString is a debugging solution for development builds; it is not a logging system.
Use this (pseudo-code to illustrate the idea):
unit DebugTools;
interface
procedure OutputDebugString(const AStr: String);
implementation
procedure OutputDebugString(const AStr: String);
begin
if IsDebuggerPresent then
Windows.OutputDebugString(PChar(AStr))
else
begin
CritSect.Enter;
try
GlobalLog.Add(AStr);
finally
CritSect.Leave;
end;
end;
end;
end.
Just add this unit to the uses clause for each of your other units - and you will automatically capture "output OutputDebugString" without need to change source code.
Related
Recently I finally managed to redirect console application output to TMemo text field of another application using an example from Microsoft: https://learn.microsoft.com/en-us/windows/win32/procthread/creating-a-child-process-with-redirected-input-and-output
All the classical examples run a console executable, wait till it ends and then read its STDOUT. I would like to launch a long-running executable that is normally not intended to end, and get its STDOUT stream as soon as new characters become available.
I managed to modify this example so that a read-write part is a loop and runs in a thread (TProcessExecuterThread.Execute). Now I am in doubt whether I should use the thread at all.
Additionally, the host receives not the whole strings till CR-LF even if I get from a pipe one character after other (TProcessExecuterThread.ReadFromPipe).
Finally I am concerned what about ending the host. The guest should then receive a signal to terminate and after some timeout - should be killed. Where (not "how") is it better to organize this?
Here is the console guest application for the test:
{$APPTYPE CONSOLE}
program GuestApp;
uses System.SysUtils;
var i: Integer;
begin
Writeln('Ongoing console output:');
for i := 0 to 65535 do begin //while True do begin
if i mod 2 = 0 then Write('*');
Writeln(Format('Output line %d', [i]));
Sleep(500);
end;
end.
Here is the host application (sorry, it is not short):
unit Executer;
interface
uses Winapi.Windows, System.Classes, System.Generics.Collections;
type
TProcessExecuterThread = class(TThread)
private
FStdInQueue: TQueue<string>;
FhChildStdOutRd: THandle;
FhChildStdInWr: THandle;
FOnStdOutLog: TGetStrProc;
procedure ReadFromPipe();
procedure WriteToPipe();
procedure StdOutLog(msg: string);
protected
procedure Execute(); override;
property hChildStdOutRd: THandle read FhChildStdOutRd write FhChildStdOutRd;
property hChildStdInWr: THandle read FhChildStdInWr write FhChildStdInWr;
property OnstdOutLog: TGetStrProc read FOnStdOutLog write FOnStdOutLog;
end;
TProcessExecuter = class
private const
BUFSIZE = 4096;
private
FhChildStdInRd: THandle;
FhChildStdInWr: THandle;
FhChildStdOutRd: THandle;
FhChildStdOutWr: THandle;
FOnLog: TGetStrProc;
FOnStdOutLog: TGetStrProc;
FExThread: TProcessExecuterThread;
procedure CreateChildProcess(ACmdLine: string);
procedure ErrorExit(AFuncName: string);
procedure Log(msg: string);
procedure StdOutLog(const msg: string);
function KillProcess(dwProcID, Wait: DWORD): Integer;
public
constructor Create();
function RunRedirectedProcess(ACmdLine: string): Integer;
property OnLog: TGetStrProc read FOnLog write FOnLog;
property OnstdOutLog: TGetStrProc read FOnStdOutLog write FOnStdOutLog;
end;
implementation
uses System.SysUtils;
procedure TProcessExecuter.Log(msg: string);
begin
if Assigned(FOnLog) then FOnLog(msg);
end;
procedure TProcessExecuter.StdOutLog(const msg: string);
begin
if Assigned(FOnStdOutLog) then FOnStdOutLog(msg);
end;
// Format a readable error message, display a message box,
// and exit from the application.
procedure TProcessExecuter.ErrorExit(AFuncName: string);
var msg: string;
dw: DWORD;
begin
dw := GetLastError();
msg := Format('%s failed with error %d: %s', [AFuncName, dw, SysErrorMessage(dw)]);
Log(msg);
// ExitProcess(1);
end;
constructor TProcessExecuter.Create();
begin
FhChildStdInRd := 0;
FhChildStdInWr := 0;
FhChildStdOutRd := 0;
FhChildStdOutWr := 0;
FExThread := TProcessExecuterThread.Create();
FExThread.OnstdOutLog := StdOutLog;
end;
// Create a child process that uses the previously created pipes for STDIN and STDOUT.
procedure TProcessExecuter.CreateChildProcess(ACmdLine: string);
var
piProcInfo: TProcessInformation;
siStartInfo: TStartupInfo;
bSuccess: Boolean;
begin
try
bSuccess := False;
FillChar(piProcInfo, SizeOf(TProcessInformation), 0);
FillChar(siStartInfo, SizeOf(TStartupInfo), 0);
siStartInfo.cb := SizeOf(TStartupInfo);
siStartInfo.hStdError := FhChildStdOutWr;
siStartInfo.hStdOutput := FhChildStdOutWr;
siStartInfo.hStdInput := FhChildStdInRd;
siStartInfo.dwFlags := siStartInfo.dwFlags or STARTF_USESTDHANDLES;
bSuccess := CreateProcess(nil, PWideChar(ACmdLine), nil, nil, True, 0, nil, nil, siStartInfo, piProcInfo);
if not bSuccess then begin
ErrorExit('CreateProcess');
Exit;
end
else begin
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
CloseHandle(FhChildStdOutWr);
CloseHandle(FhChildStdInRd);
end;
FExThread.hChildStdOutRd := FhChildStdOutRd;
FExThread.hChildStdInWr := FhChildStdInWr;
except
on ex: Exception do Log(ex.Message);
end;
end;
function TProcessExecuter.RunRedirectedProcess(ACmdLine: string): Integer;
var saAttr: SECURITY_ATTRIBUTES;
i: Integer;
begin
try
Log('->Start of parent execution.');
saAttr.nLength := SizeOf(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle := True;
saAttr.lpSecurityDescriptor := 0;
if not CreatePipe(FhChildStdOutRd, FhChildStdOutWr, #saAttr, 0) then begin
ErrorExit('StdoutRd CreatePipe');
Exit;
end;
if not SetHandleInformation(FhChildStdOutRd, HANDLE_FLAG_INHERIT, 0) then begin
ErrorExit('Stdout SetHandleInformation');
Exit;
end;
if not CreatePipe(FhChildStdInRd, FhChildStdInWr, #saAttr, 0) then begin
ErrorExit('Stdin CreatePipe');
Exit;
end;
if not SetHandleInformation(FhChildStdInWr, HANDLE_FLAG_INHERIT, 0) then begin
ErrorExit('Stdin SetHandleInformation');
Exit;
end;
CreateChildProcess(ACmdLine);
//Read/write loop was here
Log('->End of parent execution.');
if not CloseHandle(FhChildStdInWr) then begin
ErrorExit('StdInWr CloseHandle');
Exit;
end;
Result := 0;
except
on ex: Exception do Log(ex.Message);
end;
end;
procedure TProcessExecuterThread.WriteToPipe();
var dwRead, dwWritten: DWORD;
chBuf: Pointer;
bSuccess: Boolean;
line: string;
bs: Integer;
begin
bSuccess := False;
while FStdInQueue.Count > 0 do begin
line := FStdInQueue.Dequeue();
bs := (Length(line) + 1) * SizeOf(WideChar);
GetMem(chBuf, bs);
try
StrPCopy(PWideChar(chBuf), line);
if not WriteFile(FhChildStdInWr, chBuf^, dwRead, dwWritten, nil) then break;
finally
FreeMem(chBuf, bs);
end;
end;
end;
procedure TProcessExecuterThread.ReadFromPipe();
const BUFSIZE = 1; //4096
var dwRead: DWORD;
//chBuf: array [0 .. BUFSIZE] of CHAR;
chBuf: array [0 .. BUFSIZE] of AnsiChar; // Currently only ANSI is possible
ch: AnsiChar;
bSuccess: Boolean;
begin
bSuccess := False;
while True do begin
//bSuccess := ReadFile(FhChildStdOutRd, chBuf, BUFSIZE, dwRead, nil);
bSuccess := ReadFile(FhChildStdOutRd, ch, 1, dwRead, nil);
if (not bSuccess) or (dwRead = 0) then
break;
//StdOutLog(chBuf);
StdOutLog(ch);
end;
end;
procedure TProcessExecuterThread.StdOutLog(msg: string);
begin
if Assigned(FOnStdOutLog) then
Synchronize(
procedure()
begin
FOnStdOutLog(msg);
end
);
end;
procedure TProcessExecuterThread.Execute;
begin
inherited;
FStdInQueue := TQueue<string>.Create();
try
while not Terminated do begin
WriteToPipe();
ReadFromPipe();
end;
finally
FreeAndNil(FStdInQueue);
end;
end;
end.
I am at a loss here. For some weeks now I tried to get a Kerberos ticket with my Delphi application.
I consulted:
How can I get a Kerberos ticket with Delphi? (I honestly don't see why the bounty was rewarded, btw)
Every page around https://msdn.microsoft.com/en-us/library/aa374713%28v=vs.85%29.aspx
Most importantly: https://msdn.microsoft.com/en-us/library/ee498143.aspx
The last page links to example code that I converted to Delphi - I think. On a form I dropped a TWSocket out of Overbytes Socket components and named it mySocket. Rest is done in code. Problem is, that I seem to get the Kerberos TGT, but can't get the Ticket itself through the UDP connection. The Server just wont answer. I also feel there is something fundamentally wrong here, with the break of media during the communication with the server. How come I can use API to get the TGT but have to switch to UDP to get the ticket itself?
Maybe a good start to discuss this is to ignore the code first and tell me if the way I'm going is right or not. Here are my steps:
Call InitSecurityInterface to get the SecurityFunctionTable
Call QuerySecurityPackageInfo for the Kerberos Package to obtain max message size
Call AcquireCredentialsHandle for Kerberos package
Call InitializeSecurityContext with above received CredentialsHandle and the KerberosServer. Receiving some message, that may contain a KerbTicket a Kerb TGT or anything inbetween
Depending on the result of InitializeSecurityContext either use received KerbTicket or open UDP connection to KerbServer to send received buffer from step 4
Use answer message as parameter for new call of InitilizeSecurityContext
Repeat from Step 4 until result is SEC_E_OK
Have I understood correctly? If so, please consider reading my implementation to find my mistake. If not, please explain how it's really done.
Here is the code:
unit ukerber;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
IdSSPI, IdAuthenticationSSPI, Vcl.StdCtrls, OverbyteIcsWndControl,
OverbyteIcsWSocket;
const
krbServer: PAnsiChar = 'krbtgt/mydomain.int';
type
TForm2 = class(TForm)
Button1: TButton;
Edit1: TEdit;
Button2: TButton;
mySocket: TWSocket;
procedure Button1Click(Sender: TObject);
procedure mySocketDataAvailable(Sender: TObject; ErrCode: Word);
function InitPackage(var maxMessage: Cardinal): Boolean;
function SendUDPMessage(outBuf: Pointer; outsize: Cardinal): Boolean;
function GenClientContext(pIn: Pointer; InputSize: Cardinal; pOut: Pointer; Outputsize: PCardinal; var Done: Boolean): Boolean;
procedure Cleanup;
private
{ Private-Deklarationen }
secfunc: SecurityFunctionTableA;
maxMessageSize: Cardinal;
hCredential: SecHandle;
hContext: CtxtHandle;
pOutBuf, pInBuf: PByteArray;
MessageReceived: Boolean;
public
{ Public-Deklarationen }
end;
procedure Pause(Zeit: Longint);
var
Form2: TForm2;
implementation
{$R *.dfm}
// the main method from the C-example
// this starts the ticket acquisition
procedure TForm2.Button1Click(Sender: TObject);
var
sec_State: SECURITY_STATUS;
pszTargetName: PSEC_CHAR;
hNewContext: CtxtHandle;
Output, Input: SecBufferDesc;
outSecBuf, inSecBuf: SecBuffer;
fContextAttr: ULONG;
cbOut, cbIn: Cardinal;
Done: Boolean;
timeOut: Integer;
begin
Done := False;
if InitPackage(maxMessageSize) then
begin
try
pOutBuf := nil;
pInBuf := nil;
GetMem(pOutBuf, maxMessageSize);
GetMem(pInBuf, maxMessageSize);
SecInvalidateHandle(hCredential);
SecInvalidateHandle(hContext);
if not GenClientContext(nil, 0, pOutBuf, #cbOut, Done) then
begin
Cleanup;
Exit;
end;
// ------------
// up to here everything seem to work just fine
// ------------
if not SendUDPMessage(pOutBuf, cbout) then
begin
Cleanup;
Exit;
end;
timeOut := 0;
while not Done and (timeOut <= 100) do
begin
repeat
Pause(1000);
Inc(timeOut);
until MessageReceived or (timeOut >= 100);
if MessageReceived then
begin
cbOut := maxMessageSize;
if not GenClientContext(pInBuf, cbIn, pOutBuf, #cbout, Done) then
begin
Cleanup;
Exit;
end;
if not SendUDPMessage(pOutBuf, cbout) then
begin
Cleanup;
Exit;
end;
end;
end;
if Done then // <<<----------Sadly... never done
begin
// Kerberos-ticket ---->>>> pInBuf
end
else // this happens every time
ShowMessage('Authentification failed due to server timeout');
finally
Cleanup;
end;
end;
end;
procedure TForm2.Cleanup;
begin
secfunc.DeleteSecurityContext(#hcontext);
secfunc.FreeCredentialsHandle(#hCredential);
FreeMem(pInBuf);
FreeMem(pOutBuf);
end;
function TForm2.GenClientContext(pIn: Pointer; InputSize: Cardinal;
pOut: Pointer; Outputsize: PCardinal; var Done: Boolean): Boolean;
var
sec_State: SECURITY_STATUS;
LifeTime: TimeStamp;
OutBuffDesc: SecBufferDesc;
OutSecBuff: SecBuffer;
InBuffDesc: SecBufferDesc;
InSecBuff: SecBuffer;
ContextAttributes: ULONG;
NewContext: Boolean;
KerberosServer: PAnsiChar;
function SetSecHandle: PSecHandle;
begin
if NewContext then
Result := nil
else
Result := #hContext;
end;
function SetInBuffer: PSecBufferDesc;
begin
if NewContext then
Result := nil
else
Result := #InBuffDesc;
end;
begin
if not Assigned(pIn) then
begin
NewContext := True;
// No user athentication needed, so we'll skip that part of the example
sec_State := secfunc.AcquireCredentialsHandleA(
nil,
PAnsiChar('Kerberos'),
SECPKG_CRED_OUTBOUND,
nil,
nil,
nil,
nil,
#hCredential,
#LifeTime
);
if not SEC_SUCCESS(sec_State) then
begin
ShowMessage('AqcuireCredentials failed, Error#: ' + IntToStr(sec_State));
Result := False;
Exit;
end;
end;
// Prepare buffers
// Output
OutBuffDesc.ulVersion := SECBUFFER_VERSION;
OutBuffDesc.cBuffers := 1;
OutBuffDesc.pBuffers := #OutSecBuff;
OutSecBuff.cbBuffer := Outputsize^;
OutSecBuff.BufferType := SECBUFFER_TOKEN;
OutSecBuff.pvBuffer := pOut;
//Input
InBuffDesc.ulVersion := SECBUFFER_VERSION;
InBuffDesc.cBuffers := 1;
InBuffDesc.pBuffers := #InSecBuff;
InSecBuff.cbBuffer := InputSize;
InSecBuff.BufferType := SECBUFFER_TOKEN;
InSecBuff.pvBuffer := pIn;
// KerberosServer := krbServer; // Tried both krbtgt and following...no change
KerberosServer := PAnsiChar('RestrictedKrbHost/FM-DC01.mydomain.int');
sec_State := secfunc.InitializeSecurityContextA(
#hCredential,
SetSecHandle,
KerberosServer,
ISC_REQ_DELEGATE + ISC_REQ_MUTUAL_AUTH,
0,
SECURITY_NATIVE_DREP,
SetInBuffer,
0,
#hContext,
#OutBuffDesc,
#contextAttributes,
#Lifetime
);
if not SEC_SUCCESS(sec_State) then
begin
ShowMessage('init context failed, Error #: ' + IntToStr(sec_State));
Result := False;
Exit;
end;
if (sec_State = SEC_I_COMPLETE_NEEDED) or
(sec_State = SEC_I_COMPLETE_AND_CONTINUE) then
begin
sec_State := secfunc.CompleteAuthToken(#hContext, #OutBuffDesc);
if not SEC_SUCCESS(sec_State) then
begin
ShowMessage('complete failed, Error #: ' + IntToStr(sec_State));
Result := False;
Exit;
end;
end;
Outputsize^ := OutSecBuff.cbBuffer;
// First call of this method results in sec_state = SEC_I_CONTINUE_NEEDED
// which should be OK, but then I have to switch to UDP communication
// and that seems to be buggy.
Done := not ((sec_State = SEC_I_CONTINUE_NEEDED) or (sec_State = SEC_I_COMPLETE_AND_CONTINUE));
Result := True;
end;
function TForm2.InitPackage(var maxMessage: Cardinal): Boolean;
var
sec_State: SECURITY_STATUS;
pPkgInfo: PSecPkgInfoA;
InitSecurityInterfaceA: function: PSecurityFunctionTableA; stdcall;
begin
Result := False;
MessageReceived := False;
try
InitSecurityInterfaceA := GetProcAddress(GetModuleHandle('secur32.dll'), 'InitSecurityInterfaceA');
if Assigned(InitSecurityInterfaceA) then
secfunc := InitSecurityInterfaceA^;
sec_State := secfunc.QuerySecurityPackageInfoA(
PAnsiChar('Kerberos'),
#pPkgInfo
);
if sec_state = SEC_E_OK then
begin
maxMessage := pPkgInfo^.cbMaxToken;
Result := True;
end;
finally
secfunc.FreeContextBuffer(pPkgInfo);
end;
end;
procedure TForm2.mySocketDataAvailable(Sender: TObject; ErrCode: Word);
var
inBuf: array of Byte;
BufLen: Integer;
Length: Integer;
sentSize: Cardinal;
begin
MessageReceived := False;
// Data should pour in here. Hopefully the Kerberos-ticket
// First DWORD is message size, rest is the message itself
Length := mySocket.Receive(#sentsize, SizeOf(DWORD));
if Length <= 0 then
begin
Exit;
end;
// The rest
SetLength(inBuf, SizeOf(sentSize));
Length := mySocket.Receive(#inBuf, SizeOf(inBuf));
if Length >= 0 then
begin
pInBuf := #inBuf;
MessageReceived := True;
end;
end;
function TForm2.SendUDPMessage(outBuf: Pointer; outsize: Cardinal): Boolean;
begin
mySocket.Proto := 'udp';
mySocket.Addr := 'FM-DC01.mydomain.int';
mySocket.Port := '88';
mySocket.Connect;
// send size of message first, then message itself
Result := (mySocket.Send(PByte(#outsize), SizeOf(outsize)) > -1);
if Result then
if mySocket.State = wsConnected then
Result := (mySocket.Send(outBuf, outsize) > -1);
end;
// small method to wait for action, should not be part of the problem
procedure Pause(Zeit: Longint);
var
Tick: DWORD;
Event: THandle;
begin
Event := CreateEvent(nil, False, False, nil);
try
Tick := GetTickCount + DWORD(Zeit);
while (Zeit > 0) and
(MsgWaitForMultipleObjects(1, Event, False, Zeit, QS_ALLINPUT) <> WAIT_TIMEOUT) do
begin
Application.ProcessMessages;
Zeit := Tick - GetTickCount;
end;
finally
CloseHandle(Event);
end;
end;
end.
I'm working on a multi-threaded component to load and manage a music library, and I have a property defining multiple root directories to include. One thread searches those directories for media files, adds/removes as necessary, and another thread goes through those files and fills in the ID3v2 tag information. I already have a mechanism to detect added/removed files, but I don't know how to detect changes.
How can I detect when changes have been made to any of these files from other outside applications? I'd like an instantaneous response and not have to wait for a thread to get to that file. Is there a way I can receive alerts when any files have been changed in any of these folders recursively?
The function that you need to use is ReadDirectoryChangesW. This is not the easiest function in the world to use and it is worth pointing out that it is not 100% reliable. It will sometimes fail to notify you of modifications. In my experience that is more likely to happen for shares.
This API can be used in synchronous or asynchronous modes. As is always the case, the synchronous version is much easier to code against. But of course it blocks the calling thread. So the way out of that is to put the calls to ReadDirectoryChangesW in different threads. If you have a very large number of directories to watch, then one watching thread per directory is going to be an unworkable burden. If that is so then you would need to grapple with asynchronous usage.
You bWatchSubtree parameter allows you to monitor an entire tree of directories which I think is what you want to do.
For more details I refer you to this article: Understanding ReadDirectoryChangesW.
Try this:
uses
ShlObj, ActiveX;
const
FILE_LIST_DIRECTORY = $0001;
cDir = 'E:\...'; // The directory to monitor
Type
PFileNotifyInformation = ^TFileNotifyInformation;
TFileNotifyInformation = Record
NextEntryOffset: DWORD;
Action: DWORD;
FileNameLength: DWORD;
FileName: Array[0..0] of WideChar;
End;
type
TWaitThread = class(TThread)
private
FForm: TMainForm;
procedure HandleEvent;
protected
procedure Execute; override;
public
constructor Create(Form: TMainForm);
Procedure SendFtp(F: String; AddIfError: Boolean);
end;
procedure TWaitThread.HandleEvent;
Var
FileOpNotification: PFileNotifyInformation;
Offset: Longint;
F: String;
AList: TStringList;
I: Integer;
begin
AList := TStringList.Create;
Try
With FForm Do
Begin
Pointer(FileOpNotification) := #FNotificationBuffer[0];
Repeat
Offset := FileOpNotification^.NextEntryOffset;
//lbEvents.Items.Add(Format(SAction[FileOpNotification^.Action], [WideCharToString(#(FileOpNotification^.FileName))]));
F := cDir + WideCharToString(#(FileOpNotification^.FileName));
if AList.IndexOf(F) < 0 Then
AList.Add(F);
PChar(FileOpNotification) := PChar(FileOpNotification)+Offset;
Until Offset=0;
For I := 0 To AList.Count -1 Do
// do whatever you need
End;
Finally
AList.Free;
End;
end;
constructor TWaitThread.Create(Form: TMainForm);
begin
inherited Create(True);
FForm := Form;
FreeOnTerminate := False;
end;
procedure TWaitThread.Execute;
Var
NumBytes: DWORD;
CompletionKey: DWORD;
begin
While Not Terminated Do
Begin
GetQueuedCompletionStatus( FForm.FCompletionPort, numBytes, CompletionKey, FForm.FPOverlapped, INFINITE);
if CompletionKey <> 0 Then
Begin
Synchronize(HandleEvent);
With FForm do
begin
FBytesWritten := 0;
ZeroMemory(#FNotificationBuffer, SizeOf(FNotificationBuffer));
ReadDirectoryChanges(FDirectoryHandle, #FNotificationBuffer, SizeOf(FNotificationBuffer), False, FNotifyFilter, #FBytesWritten, #FOverlapped, nil);
End;
End
Else
Terminate;
End;
end;
{MainForm}
private
FDirectoryHandle: THandle;
FNotificationBuffer: array[0..4096] of Byte;
FWatchThread: TThread;
FNotifyFilter: DWORD;
FOverlapped: TOverlapped;
FPOverlapped: POverlapped;
FBytesWritten: DWORD;
FCompletionPort: THandle;
procedure TMainForm.FormCreate(Sender: TObject);
begin
FCompletionPort := 0;
FDirectoryHandle := 0;
FPOverlapped := #FOverlapped;
ZeroMemory(#FOverlapped, SizeOf(FOverlapped));
Start;
end;
procedure TMainForm.Start;
begin
FNotifyFilter := 0;
FNotifyFilter := FNotifyFilter or FILE_NOTIFY_CHANGE_FILE_NAME;
FDirectoryHandle := CreateFile(cDir,
FILE_LIST_DIRECTORY,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
Nil,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED,
0);
if FDirectoryHandle = INVALID_HANDLE_VALUE Then
Begin
Beep;
FDirectoryHandle := 0;
ShowMessage(SysErrorMessage(GetLastError));
Exit;
End;
FCompletionPort := CreateIoCompletionPort(FDirectoryHandle, 0, Longint(pointer(self)), 0);
ZeroMemory(#FNotificationBuffer, SizeOf(FNotificationBuffer));
FBytesWritten := 0;
if Not ReadDirectoryChanges(FDirectoryHandle, #FNotificationBuffer, SizeOf(FNotificationBuffer), False, FNotifyFilter, #FBytesWritten, #FOverlapped, Nil) Then
Begin
CloseHandle(FDirectoryHandle);
FDirectoryHandle := 0;
CloseHandle(FCompletionPort);
FCompletionPort := 0;
ShowMessage(SysErrorMessage(GetLastError));
Exit;
End;
FWatchThread := TWaitThread.Create(self);
TWaitThread(FWatchThread).Resume;
end;
procedure TMainForm.Stop;
begin
if FCompletionPort = 0 Then
Exit;
PostQueuedCompletionStatus(FCompletionPort, 0, 0, Nil);
FWatchThread.WaitFor;
FWatchThread.Free;
CloseHandle(FDirectoryHandle);
FDirectoryHandle := 0;
CloseHandle(FCompletionPort);
FCompletionPort := 0;
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
Stop;
end;
Based on this question I've created a small application which is catching all debug strings into my application. Code of the thread is shown below.
I want to get the process id and its name for each debug string I got. After I've made some research I got this article where it says: "The first 4 bytes (32-bit DWORD) is the process ID of the process that wrote the text using OutputDebugString.". I have tried to get the process by calling the functions below, but the result is null.
TempString := '';
CopyMemory(PChar(TempString), SharedMemory, sizeof(SharedMemory)); // - returns 0....
TempString := String(PAnsiChar(SharedMemory) + SizeOf(DWORD));
I do not know what it is wrong.
Also is it possible to get the name of the process which has sent the debug string?
Thread code:
interface
uses Classes,
windows,
Forms,
StdCtrls,
SysUtils;
type
TDebugStringThread = class(TThread)
private
FMemo : TMemo;
protected
procedure Execute; override;
procedure DoShowData;
procedure DoShowErrors;
public
constructor Create(aMemo : TMemo);
end;
implementation
var SharedMessage: string;
ErrsMess : String;
constructor TDebugStringThread.Create(aMemo: TMemo);
begin
FMemo := aMemo;
FreeOnTerminate := True;
inherited Create(False);
end;
procedure TDebugStringThread.DoShowData;
begin
FMemo.Lines.Add(SharedMessage);
end;
procedure TDebugStringThread.DoShowErrors;
begin
FMemo.Lines.Add('Error ' + ErrsMess);
ErrsMess := '';
end;
procedure TDebugStringThread.Execute;
var SharedMem: Pointer;
SharedFile: THandle;
WaitingResult: DWORD;
DataReadyEvent: THandle;
BufferReadyEvent: THandle;
SecurityAttributes: SECURITY_ATTRIBUTES;
SecurityDescriptor: SECURITY_DESCRIPTOR;
SharedMemory : Pointer;
TempString : String;
begin
ErrsMess := '';
SecurityAttributes.nLength := SizeOf(SECURITY_ATTRIBUTES);
SecurityAttributes.bInheritHandle := True;
SecurityAttributes.lpSecurityDescriptor := #SecurityDescriptor;
if not InitializeSecurityDescriptor(#SecurityDescriptor, SECURITY_DESCRIPTOR_REVISION) then
Exit;
if not SetSecurityDescriptorDacl(#SecurityDescriptor, True, nil, False) then
Exit;
BufferReadyEvent := CreateEvent(#SecurityAttributes, False, True, 'DBWIN_BUFFER_READY');
if BufferReadyEvent = 0 then
Exit;
DataReadyEvent := CreateEvent(#SecurityAttributes, False, False, 'DBWIN_DATA_READY');
if DataReadyEvent = 0 then
Exit;
SharedFile := CreateFileMapping(THandle(-1), #SecurityAttributes, PAGE_READWRITE, 0, 4096, 'Global\DBWIN_BUFFER');
if SharedFile = 0 then
begin
ErrsMess := SysErrorMessage(GetLastError);
Synchronize(DoShowErrors);
Exit;
end;
SharedMem := MapViewOfFile(SharedFile, FILE_MAP_READ, 0, 0, 512);
SharedMemory := MapViewOfFile(SharedFile, SECTION_MAP_READ, 0, 0, 1024);
if not Assigned(SharedMem) then
begin
ErrsMess := SysErrorMessage(GetLastError);
Synchronize(DoShowErrors);
Exit;
end;
if not Assigned(SharedMemory) then
begin
ErrsMess := SysErrorMessage(GetLastError);
Synchronize(DoShowErrors);
end;
while (not Terminated) and (not Application.Terminated) do
begin
SetEvent(BufferReadyEvent);
WaitingResult := WaitForSingleObject(DataReadyEvent, INFINITE);
case WaitingResult of
WAIT_TIMEOUT: Continue;
WAIT_OBJECT_0:
begin
try
TempString := '';
//CopyMemory(PChar(TempString), SharedMemory, sizeof(SharedMemory)); // - returns 0....
TempString := String(PAnsiChar(SharedMemory) + SizeOf(DWORD));
SharedMessage := TempString + ' ' + String(PAnsiChar(SharedMem) + SizeOf(DWORD));
Synchronize(DoShowData);
finally
end;
end;
WAIT_FAILED: Continue;
end;
end;
UnmapViewOfFile(SharedMem);
CloseHandle(SharedFile);
end;
end.
To read the first four bytes as DWORD:
var
ProcessID: DWORD;
...
ProcessID := PDWORD(SharedMemory)^;
See here how to get the file name of the process.
If the first four bytes are really a DWord, then the first thing you need to do is stop trying to copy it into a string. Integers aren't strings, and storing one in the other doesn't transform it.
var
ProcessID: DWord;
ProcessID := PDWord(SharedMemory)^;
If you're getting zero from that, then the first four bytes of the memory contain zero. If you're expecting something else, then either your expectations are wrong, or you're not reading from the right place.
in this article delphi.net(prism) support async file io.
Delphi(Native/VCL) has Async File IO Class too?
Have you seen this code? http://pastebin.com/A2EERtyW
It is a good start for ansynchronous file I/O, but personally I would write a wrapper around the standard TStream class to maintain compatibility with VCL/RTL.
EDIT 2: This one looks good, too. http://www.torry.net/vcl/filedrv/other/dstreams.zip
I am posting it here just in case it disappears from Pastebin:
unit xfile;
{$I cubix.inc}
interface
uses
Windows,
Messages,
WinSock,
SysUtils,
Classes;
const
MAX_BUFFER = 1024 * 32;
type
TFileReadEvent = procedure(Sender: TObject; const Buffer; Count: Integer) of object;
TAsyncFile = class
private
FHandle: THandle;
FPosition: Cardinal;
FReadPending: Boolean;
FOverlapped: TOverlapped;
FBuffer: Pointer;
FBufferSize: Integer;
FOnRead: TFileReadEvent;
FEof: Boolean;
FSize: Integer;
function ProcessIo: Boolean;
procedure DoOnRead(Count: Integer);
function GetOpen: Boolean;
public
constructor Create(Filename: string; BufferSize: Integer = MAX_BUFFER);
destructor Destroy; override;
procedure BeginRead;
procedure Seek(Position: Integer);
procedure Close;
property OnRead: TFileReadEvent read FOnRead write FOnRead;
property Eof: Boolean read FEof;
property IsOpen: Boolean read GetOpen;
property Size: Integer read FSize;
end;
function ProcessFiles: Boolean;
implementation
var
Files: TList;
function ProcessFiles: Boolean;
var
i: Integer;
AsyncFile: TAsyncFile;
begin
Result := False;
for i := Files.Count - 1 downto 0 do
begin
AsyncFile := TAsyncFile(Files[i]);
Result := AsyncFile.ProcessIo or Result;
end;
end;
procedure Cleanup;
var
i: Integer;
AsyncFile: TAsyncFile;
begin
for i := Files.Count - 1 downto 0 do
begin
AsyncFile := TAsyncFile(Files[i]);
AsyncFile.Free;
end;
Files.Free;
end;
{ TAsyncFile }
constructor TAsyncFile.Create(Filename: string; BufferSize: Integer);
begin
Files.Add(Self);
FReadPending := False;
FBufferSize := BufferSize;
GetMem(FBuffer, FBufferSize);
FillMemory(#FOverlapped, SizeOf(FOverlapped), 0);
Cardinal(FHandle) := CreateFile(
PChar(Filename), // file to open
GENERIC_READ, // open for reading
0, // do not share
nil, // default security
OPEN_EXISTING, // open existing
FILE_ATTRIBUTE_NORMAL, //or // normal file
//FILE_FLAG_OVERLAPPED, // asynchronous I/O
0); // no attr. template
FSize := FileSeek(FHandle, 0, soFromEnd);
FileSeek(FHandle, 0, soFromBeginning);
FPosition := 0;
end;
destructor TAsyncFile.Destroy;
begin
Files.Remove(Self);
CloseHandle(FHandle);
FreeMem(FBuffer);
inherited;
end;
function TAsyncFile.ProcessIo: Boolean;
var
ReadCount: Cardinal;
begin
Result := False; Exit;
if not FReadPending then
begin
Exit;
end;
if GetOverlappedResult(FHandle, FOverlapped, ReadCount, False) then
begin
FReadPending := False;
DoOnRead(ReadCount);
end
else
begin
case GetLastError() of
ERROR_HANDLE_EOF:
begin
FReadPending := False;
FEof := True;
end;
ERROR_IO_PENDING:
begin
FReadPending := True;
end;
0:
begin
Result := True;
end;
end;
end;
end;
procedure TAsyncFile.BeginRead;
var
ReadResult: Boolean;
ReadCount: Cardinal;
begin
ReadCount := 0;
Seek(FPosition);
ReadResult := ReadFile(FHandle, FBuffer^, FBufferSize, ReadCount, nil);//#FOverlapped);
if ReadResult then
begin
FEof := False;
FReadPending := False;
FPosition := FPosition + ReadCount;
DoOnRead(ReadCount);
end
else
begin
case GetLastError() of
ERROR_HANDLE_EOF:
begin
FReadPending := False;
FEof := True;
end;
ERROR_IO_PENDING:
begin
FReadPending := True;
end;
end;
end;
end;
procedure TAsyncFile.DoOnRead(Count: Integer);
begin
if Assigned(FOnRead) then
begin
FOnRead(Self, FBuffer^, Count);
end;
end;
function TAsyncFile.GetOpen: Boolean;
begin
Result := Integer(FHandle) >= 0;
end;
procedure TAsyncFile.Close;
begin
FileClose(FHandle);
end;
procedure TAsyncFile.Seek(Position: Integer);
begin
FPosition := Position;
FileSeek(FHandle, Position, soFromBeginning);
end;
initialization
Files := Tlist.Create;
finalization
Cleanup;
end.
There is nothing built in to the RTL/VCL that offers asynchronous I/O for files. Incidentally the support in Delphi Prism is down to the .net framework rather than being language based.
You can either code directly against the Win32 API (that's not much fun) or hunt around for a Delphi wrapper to that API. Off the top of my head, I don't know any Delphi wrappers of asynchronous file I/O but they must exist.