sfrazor 3 Posted January 9, 2023 I have a common.pas file that contains a procedure dbgWriteLn. It would seem I would have to get a valid Handle for the current cmd.exe process and write to that. The procedure looks like this: Atach Console is defined thus: function AttachConsole(dwProdessID: Integer): boolean; stdcall external 'kernel32.dll'; function FreeConsole(): Boolean; stedall; external 'kernel32.dll' procedure DbgWriteLn(const outstr: string); begin {$IFDEF DEBUG} AttachConsole(-1); WriteLn(outstr); FreeConsloe; {$ENDIF} end; Using a simple exe that loads the dll via LoadLIbrary and GetProcAddress, I call the entry point and all is fine. Except I need to have debug output on the cmd.exe console when the loader loads and runs the dll. The loader works fine. There is no output from the dbdWriteLn within the dll. After a bit of searching I don't find anything definitive. I read that WriteLn is not a typical replacement for something like printf. Writeln output is given special handling within the compiler if I understand it properly. The dll gets passed to a tester for review and the release and debug version are included. He needs to see the debug outpout from the dll. In C printf works regardless. I need something similar in delphi. Or is there a better way to achieve the output to the console (stdout) from within a dll that is run via a cmd.exe console? Share this post Link to post
sfrazor 3 Posted January 9, 2023 (edited) Update: After some reading and trying to understand why WriteLn is not working in the above code, I tried to this with some success: Still not sure if this is the best way to accomplish this. procedure DbgWriteLn(const outstr: string); var WinHandle: THandle; written: cardinal; begin if outstr.length = 0 then Exit; {$IFDEF DEBUG} WinHandle:= GetStdHandle(std_Output_Handle); if (WinHandle <> Invalid_Handle_Value) then begin WriteConsole(WinHandle,Pointer(outstr),outstr.length,written,nil); end; {$ENDIF} end; Edited January 9, 2023 by sfrazor Share this post Link to post
Fr0sT.Brutal 900 Posted January 10, 2023 Why you mess with attaching a console? It is attached automatically by OS. I have no issues: library Lib; uses System.SysUtils, System.Classes; {$R *.res} procedure Log; begin writeln(TimeToStr(Now)); end; exports Log; begin end. program Caller; {$APPTYPE CONSOLE} {$R *.res} uses System.SysUtils, Windows; var LogFn: procedure; hLib: THandle; begin try hlib := LoadLibrary('Lib.dll'); @LogFn := GetProcAddress(hLib, 'Log'); LogFn(); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; readln; end. Output is visible for both direct run and run via cmd. As a side note, this kind of logging seems non-optimal to me. It's more adaptable to let caller set logger callback function so that output could be anything. Share this post Link to post
David Heffernan 2345 Posted January 10, 2023 For a DLL that can be used by hosts outside your control, it's wrong to force the caller to be in console mode. You are hosted by another process which makes its own decisions. 1 Share this post Link to post
darnocian 84 Posted January 10, 2023 Maybe better to have a callback function that can be used for debugging. That would be controllable by the application, UI or console. e.g. in the DLL, have a proceudure SetDebugCallback(const ACallback:TDebugCallback); where TDebugCallback = procedure (const AMessage: PChar); stdcall; Note: use PChar and not a string (maybe be even more explicit like PWideChar, PAnsiChar). That way, in the application, console app could do writeln, where the UI app could show a dialog, add to a list, whatever... Also, I wouldn't trust sending a Delphi managed type across a DLL boundary. It limits what type of apps can consume and use the function as they would need to emulate the string interface, knowing the internals of the type, where using zero terminated strings is supported by the Delphi compiler. 1 Share this post Link to post
Lars Fosdal 1792 Posted January 10, 2023 Another alternative is to use OutputDebugString, which can be seen in debuggers and with a debug output watcher app. I went a step further and added a DebugOut wrapper, so that I can use a switch to silence OutputDebugString as well as redirect the strings to a buffer which is flushed to a log file. Share this post Link to post