Jump to content
sfrazor

Using WriteLn in dll

Recommended Posts

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

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 by sfrazor

Share this post


Link to post

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

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. 

  • Like 1

Share this post


Link to post

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.

  • Like 1

Share this post


Link to post

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

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×