dllimport stdout gets eaten

  • Thread starter Thread starter not_a_commie
  • Start date Start date
N

not_a_commie

I changed the stdout in my C# app using Console.SetOut. It works fine
for all my Console.Out.Write calls and with log4net. However, I don't
see any output from native dlls that write to stdout. What am I doing
wrong?
 
not_a_commie said:
I changed the stdout in my C# app using Console.SetOut. It works fine
for all my Console.Out.Write calls and with log4net. However, I don't
see any output from native dlls that write to stdout. What am I doing
wrong?

Nothing.

Why should changing something inside the Console class
change what some native code does ?

You need to do something in your native code to
get output where you want it.

Arne
 
Arne said:
Nothing.

Why should changing something inside the Console class
change what some native code does ?

If for example, that managed code was a wrapper around SetStdHandle. But
it's not, it changes Console.Out rather than changing stdout underneath.
You need to do something in your native code to
get output where you want it.

SetStdHandle, most likely. And then it doesn't have to be a change to the
native code, as long as it's called from the same process.
 
Ben said:
If for example, that managed code was a wrapper around SetStdHandle. But
it's not, it changes Console.Out rather than changing stdout underneath.

I can not see how it could be a wrapper around SetStdHandle. Out is a
TextWriter. If I write my own class that extends TextWriter and call
SetOut with it. There are no handle to pass to native and no way
for the native code to call my class.
SetStdHandle, most likely. And then it doesn't have to be a change to the
native code, as long as it's called from the same process.

I was thinking freopen, but if it is documented that SetStdHandle
interacts "nicely" with the C RTL, then that can obviously be
used.

Arne
 
I got this to work. The setvbuf is critical. Here's my test code.
First, the dll:

#include <stdio.h>

__declspec(dllexport) void PrintMsg();

void PrintMsg()
{
setvbuf(stdout, NULL, _IONBF, 0);
printf("blah blah blah\n");
}

Second, the C# code:

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using System.Windows.Controls;

namespace TestPrintf
{
class Program
{
[DllImport("kernel32.dll", SetLastError = true)]
protected static extern bool SetStdHandle(int nStdHandle, IntPtr
hConsoleOutput);

[DllImport("PrintDll.dll", CallingConvention =
CallingConvention.Cdecl)]
protected static extern void PrintMsg();

[STAThread]
public static void Main(string[] args)
{
var id = Process.GetCurrentProcess().Id; // make this instance
unique
var serverPipe = new NamedPipeServerStream("consoleRedirect" + id,
PipeDirection.In, 1);
var clientPipe = new NamedPipeClientStream(".", "consoleRedirect" +
id, PipeDirection.Out, PipeOptions.WriteThrough);
ThreadPool.QueueUserWorkItem(state =>
{
serverPipe.WaitForConnection();
using (var stm = new StreamReader(serverPipe))
{
while (serverPipe.IsConnected)
{
try
{
var txt = stm.ReadLine();
if (!string.IsNullOrEmpty(txt))
MessageBox.Show("Got stdout with: " + txt);
}
catch (IOException)
{
break; // normal disconnect
}
}
}
}, null);
clientPipe.Connect();
var hr11 = new HandleRef(clientPipe,
clientPipe.SafePipeHandle.DangerousGetHandle());
SetStdHandle(-11, hr11.Handle); // redirect stdout to my pipe


var app = new Application();
var win = new Window { Width = 300, Height = 200 };
var sp = new StackPanel { Orientation = Orientation.Horizontal };
win.Content = sp;
var b1 = new Button { Content = "Direct", Width = 100 };
sp.Children.Add(b1);
var b2 = new Button { Content = "Indirect", Width = 100 };
sp.Children.Add(b2);
var b3 = new Button { Content = "DllImport", Width = 100 };
sp.Children.Add(b3);

b1.Click += (sender, e) => Console.Out.WriteLine("Direct Button was
clicked");
b2.Click += (sender, e) =>
{
using (var stdout = Console.OpenStandardOutput())
{
var bytes = Console.OutputEncoding.GetBytes("Indirect Button
was clicked" + Console.Out.NewLine);
stdout.Write(bytes, 0, bytes.Length);
}
};
b3.Click += (sender, e) => PrintMsg();

app.Run(win);
clientPipe.Dispose();
serverPipe.Dispose();
}

}
}
 
Arne said:
I can not see how it could be a wrapper around SetStdHandle. Out is a
TextWriter. If I write my own class that extends TextWriter and call
SetOut with it. There are no handle to pass to native and no way
for the native code to call my class.

I agree. I was just trying to show that it wasn't an entirely unreasonable
expectation though, by mentioning a function that would behave as the OP
expected. And, as you point out, it is limited to redirecting to an OS
handle, not an arbitrary TextWriter.
I was thinking freopen, but if it is documented that SetStdHandle
interacts "nicely" with the C RTL, then that can obviously be
used.

I didn't see any assurance that the native code was written in C, or that
the OP knew which RTL was being used. If the DLL was statically linked, it
would be next-thing-to-impossible to call the "right" freopen.
 
Ben said:
I didn't see any assurance that the native code was written in C, or that
the OP knew which RTL was being used. If the DLL was statically linked, it
would be next-thing-to-impossible to call the "right" freopen.

Good point.

Arne
 
Back
Top