1

I need to call some native DLLs in C# and redirect the stdout and stderr in the native DLLs to files. Have tried a very simple example but couldn't get it working.

My experiment started with building a native DLL (StdStreamRedirect.dll) with below code.

#include <stdio.h>

extern __declspec(dllexport) void Test();

void Test()
{
    fprintf(stderr, "%s", "STDERR\n");

    fprintf(stdout, "%s", "STDOUT\n");
}

Then I created a C# console application as shown below.

namespace StdStreamRedirectionTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var stdErrFilePath = "stderrredirect.txt";
            var stdOutFilePath = "stdoutredirect.txt";
            var errFileStream = File.Open(stdErrFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
            SetStdHandle(-12, errFileStream.SafeFileHandle.DangerousGetHandle());
            var outFileStream = File.Open(stdOutFilePath, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
            SetStdHandle(-11, outFileStream.SafeFileHandle.DangerousGetHandle());

            var dllAddress = LoadLibrary(@"StdStreamRedirect.dll");
            var procAddress = GetProcAddress(dllAddress, "Test");
            var action = Marshal.GetDelegateForFunctionPointer(procAddress, typeof(Action)) as Action;
            action?.Invoke();

            errFileStream.Flush();
            outFileStream.Flush();
        }

        [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi, SetLastError = true)]
        public static extern IntPtr LoadLibrary([MarshalAs(UnmanagedType.LPStr)]string fileName);

        [DllImport("Kernel32.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
        public static extern IntPtr GetProcAddress(IntPtr modulePtr, [MarshalAs(UnmanagedType.LPStr)] string procName);

        [DllImport("Kernel32.dll", SetLastError = true)]
        private static extern int SetStdHandle(int device, IntPtr handle);
    }
}

I was expecting the stdout and stderr from the native DLL would be redirected into stdoutredirect.txt and stderrredirect.txt but nothing was found in the two files after ran the C# console app.

To eliminate the possibility that the native dll wasn't called correctly in the first place, I also tried to comment out the std stream redirect code (the first six lines of Main method), and ran the c# console app, the stdout and stderr were printed in the console correctly.

Would you please help to see what went wrong in my code? Thanks a lot.

Update

I figured it out. It turned out that there was nothing wrong with the C# code which redirects the stdout and stderr. The problem was from the C code. According to this answer, at least the stdout is buffered. That's why I didn't see them in the redirected files because they weren't flushed yet so that there was nothing to redirect. After I forced the stdout and stderr to flush as shown below, it worked!

void Test()
{
    fprintf(stderr, "%s", "STDERR\n");
    fflush(stderr);
    fprintf(stdout, "%s", "STDOUT\n");
    fflush(stdout);
}

I'll still keep this question here. Because it is still not clear to me that why after the the C# app terminated, the buffered stdout and stderr were still not flushed and got redirected?

Shinbo
  • 143
  • 2
  • 8
  • That's not probably the issue, but you are not checking return values from the called PInvoke methods. Also, unless you really need to use PInvoke, I'd recommend you to try https://learn.microsoft.com/en-us/dotnet/api/system.console.setout?view=netframework-4.8 instead. – Eugene Podskal Aug 06 '19 at 06:24
  • @Eugene Podskal I removed the return values check of PInvoke methods on purpose in order to simplify the code pasted here. Thanks for the comment :) – Shinbo Aug 08 '19 at 00:21
  • Very interesting, an identical approach does not work for me. I'm trying to capture from within a C# Console program the stdout & stderr output of a C++ DLL function invocation. @Shinbo after you make the 2 SetStdHandle calls in your program, do you still see the C++ program's output? I do see it in my Console program, which makes me believe that the SetStdHandle redirections do not work. – eugen_nw Jun 21 '23 at 16:30

0 Answers0