无法连接控制台

时间:2018-11-29 09:18:25

标签: c# visual-studio winapi

如果我是从调试器运行控制台,则尝试将控制台附加到服务。 我已经阅读了几个“有效”的解决方案,但是这些解决方案似乎并没有真正起作用。这是我使用的代码:

    public static void RunService(Func<ServiceBase> factory)
    {
        if (Debugger.IsAttached)
        {
            Utils.AttachConsole();
            Console.Write($"Starting service ");
            var instance = factory();
            Console.WriteLine(instance.GetType().Name);
            //Invoke start Method
            Console.WriteLine("Press [ENTER] to exit");
            Console.ReadLine();
            //Stop service
        }
        else
        {
            ServiceBase.Run(factory());
        }
    }

Alloc控制台是:

    public static void AttachConsole()
    {
        var ret = NativeMethods.AllocConsole();
        IntPtr currentStdout = NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE);
        NativeMethods.SetStdHandle(NativeMethods.STD_OUTPUT_HANDLE, new IntPtr(7));
        TextWriter writer = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
        Console.SetOut(writer);
    }

Interop包括:

internal static class NativeMethods
{
    internal const uint STD_OUTPUT_HANDLE = 0xFFFFFFF5;

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool AllocConsole();

    [DllImport("kernel32.dll")]
    internal static extern IntPtr GetStdHandle(uint nStdHandle);

    [DllImport("kernel32.dll")]
    internal static extern void SetStdHandle(uint nStdHandle, IntPtr handle);
}

会发生什么,创建并附加了一个控制台,但是没有输出。可以不难,但我很愚蠢地看到它:(

编辑: 问题是Visual Studio,而不是代码“自身”。没有VS,我可以得到一个控制台并在那里接收预期的输出。 VS中有某种重定向,我希望在这里克服。

编辑 仅适用于汉斯-这是“完整代码”

static void Main(string[] args)
{
    ServiceLauncher.RunService(() => new FactoryService();
}

该项目设置为应用程序类型窗口。

3 个答案:

答案 0 :(得分:2)

我能够重现您的问题并使其在我的计算机上正常工作。您的某些代码看起来像来自No console output when using AllocConsole and target architecture x86的公认答案。如果您在该答案下阅读注释主题,您将看到new IntPtr(7)在Windows 7 / Server 2012上不起作用。Windows7的“新魔术数字”对我也不起作用。为了解决这个问题,我开始了将给定的c ++调用从注释移植到c#的过程,这需要对PInvokes进行一些签名更改(所有更改均从PInvoke.net复制和粘贴,所以应该没问题)。我所做的更改几乎完全在PInvoke代码中。这是完整的工作代码集:

Program.cs(不变):

static void Main()
{
    ServiceLauncher.RunService(() => new Service1());
}

ServiceLauncher.cs(不变):

public static void RunService(Func<ServiceBase> factory)
{
    if (Debugger.IsAttached)
    {
        Utils.AttachConsole();
        Console.Write($"Starting service ");
        var instance = factory();
        Console.WriteLine(instance.GetType().Name);
        //Invoke start Method
        Console.WriteLine("Press [ENTER] to exit");
        Console.ReadLine();
        //Stop service
    }
    else
    {
        ServiceBase.Run(factory());
    }
}

Utils.cs(注释中有1处更改):

public static void AttachConsole()
{
    var ret = NativeMethods.AllocConsole();
    IntPtr currentStdout = NativeMethods.GetStdHandle(NativeMethods.STD_OUTPUT_HANDLE);

    // IntPtr(7) was a dangerous assumption that doesn't work on current versions of Windows...
    //NativeMethods.SetStdHandle(NativeMethods.STD_OUTPUT_HANDLE, new IntPtr(7));

    // Instead, get the defaultStdOut using PInvoke
    SafeFileHandle defaultStdOut = NativeMethods.CreateFile("CONOUT$", EFileAccess.GenericRead | EFileAccess.GenericWrite, EFileShare.Write, IntPtr.Zero, ECreationDisposition.OpenExisting, 0, IntPtr.Zero);
    NativeMethods.SetStdHandle(NativeMethods.STD_OUTPUT_HANDLE, defaultStdOut.DangerousGetHandle());    // also seems dangerous... there may be an alternate signature for SetStdHandle that takes SafeFileHandle.

    TextWriter writer = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = true };
    Console.SetOut(writer);
}

NativeMethods.cs(几乎完全不同-注释中给出的链接和解释)。此文件中包含的枚举(超出类范围),但您可以自行决定将其移动到其他文件中:

internal static class NativeMethods
{
    // 0xFFFFFFF5 is not consistent with what I found...
    //internal const uint STD_OUTPUT_HANDLE = 0xFFFFFFF5; 

    // https://www.pinvoke.net/default.aspx/kernel32.getstdhandle
    internal const int STD_OUTPUT_HANDLE = -11;

    [DllImport("kernel32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool AllocConsole();

    // method signature changed per https://www.pinvoke.net/default.aspx/kernel32.getstdhandle
    [DllImport("kernel32.dll", SetLastError = true)]
    internal static extern IntPtr GetStdHandle(int nStdHandle);

    // method signature changed per https://www.pinvoke.net/default.aspx/kernel32.setstdhandle
    [DllImport("kernel32.dll")]
    internal static extern bool SetStdHandle(int nStdHandle, IntPtr hHandle);

    [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
    internal static extern SafeFileHandle CreateFile(
        string lpFileName,
        EFileAccess dwDesiredAccess,
        EFileShare dwShareMode,
        IntPtr lpSecurityAttributes,
        ECreationDisposition dwCreationDisposition,
        EFileAttributes dwFlagsAndAttributes,
        IntPtr hTemplateFile);
}


// ENUMS FROM http://www.pinvoke.net/default.aspx/kernel32/CreateFile.html
[Flags]
public enum EFileAccess : uint
{
    //
    // Standart Section
    //

    AccessSystemSecurity = 0x1000000,   // AccessSystemAcl access type
    MaximumAllowed = 0x2000000,     // MaximumAllowed access type

    Delete = 0x10000,
    ReadControl = 0x20000,
    WriteDAC = 0x40000,
    WriteOwner = 0x80000,
    Synchronize = 0x100000,

    StandardRightsRequired = 0xF0000,
    StandardRightsRead = ReadControl,
    StandardRightsWrite = ReadControl,
    StandardRightsExecute = ReadControl,
    StandardRightsAll = 0x1F0000,
    SpecificRightsAll = 0xFFFF,

    FILE_READ_DATA = 0x0001,        // file & pipe
    FILE_LIST_DIRECTORY = 0x0001,       // directory
    FILE_WRITE_DATA = 0x0002,       // file & pipe
    FILE_ADD_FILE = 0x0002,         // directory
    FILE_APPEND_DATA = 0x0004,      // file
    FILE_ADD_SUBDIRECTORY = 0x0004,     // directory
    FILE_CREATE_PIPE_INSTANCE = 0x0004, // named pipe
    FILE_READ_EA = 0x0008,          // file & directory
    FILE_WRITE_EA = 0x0010,         // file & directory
    FILE_EXECUTE = 0x0020,          // file
    FILE_TRAVERSE = 0x0020,         // directory
    FILE_DELETE_CHILD = 0x0040,     // directory
    FILE_READ_ATTRIBUTES = 0x0080,      // all
    FILE_WRITE_ATTRIBUTES = 0x0100,     // all

    //
    // Generic Section
    //

    GenericRead = 0x80000000,
    GenericWrite = 0x40000000,
    GenericExecute = 0x20000000,
    GenericAll = 0x10000000,

    SPECIFIC_RIGHTS_ALL = 0x00FFFF,
    FILE_ALL_ACCESS =
    StandardRightsRequired |
    Synchronize |
    0x1FF,

    FILE_GENERIC_READ =
    StandardRightsRead |
    FILE_READ_DATA |
    FILE_READ_ATTRIBUTES |
    FILE_READ_EA |
    Synchronize,

    FILE_GENERIC_WRITE =
    StandardRightsWrite |
    FILE_WRITE_DATA |
    FILE_WRITE_ATTRIBUTES |
    FILE_WRITE_EA |
    FILE_APPEND_DATA |
    Synchronize,

    FILE_GENERIC_EXECUTE =
    StandardRightsExecute |
        FILE_READ_ATTRIBUTES |
        FILE_EXECUTE |
        Synchronize
}

[Flags]
public enum EFileShare : uint
{
    /// <summary>
    /// 
    /// </summary>
    None = 0x00000000,
    /// <summary>
    /// Enables subsequent open operations on an object to request read access. 
    /// Otherwise, other processes cannot open the object if they request read access. 
    /// If this flag is not specified, but the object has been opened for read access, the function fails.
    /// </summary>
    Read = 0x00000001,
    /// <summary>
    /// Enables subsequent open operations on an object to request write access. 
    /// Otherwise, other processes cannot open the object if they request write access. 
    /// If this flag is not specified, but the object has been opened for write access, the function fails.
    /// </summary>
    Write = 0x00000002,
    /// <summary>
    /// Enables subsequent open operations on an object to request delete access. 
    /// Otherwise, other processes cannot open the object if they request delete access.
    /// If this flag is not specified, but the object has been opened for delete access, the function fails.
    /// </summary>
    Delete = 0x00000004
}

public enum ECreationDisposition : uint
{
    /// <summary>
    /// Creates a new file. The function fails if a specified file exists.
    /// </summary>
    New = 1,
    /// <summary>
    /// Creates a new file, always. 
    /// If a file exists, the function overwrites the file, clears the existing attributes, combines the specified file attributes, 
    /// and flags with FILE_ATTRIBUTE_ARCHIVE, but does not set the security descriptor that the SECURITY_ATTRIBUTES structure specifies.
    /// </summary>
    CreateAlways = 2,
    /// <summary>
    /// Opens a file. The function fails if the file does not exist. 
    /// </summary>
    OpenExisting = 3,
    /// <summary>
    /// Opens a file, always. 
    /// If a file does not exist, the function creates a file as if dwCreationDisposition is CREATE_NEW.
    /// </summary>
    OpenAlways = 4,
    /// <summary>
    /// Opens a file and truncates it so that its size is 0 (zero) bytes. The function fails if the file does not exist.
    /// The calling process must open the file with the GENERIC_WRITE access right. 
    /// </summary>
    TruncateExisting = 5
}

[Flags]
public enum EFileAttributes : uint
{
    Readonly = 0x00000001,
    Hidden = 0x00000002,
    System = 0x00000004,
    Directory = 0x00000010,
    Archive = 0x00000020,
    Device = 0x00000040,
    Normal = 0x00000080,
    Temporary = 0x00000100,
    SparseFile = 0x00000200,
    ReparsePoint = 0x00000400,
    Compressed = 0x00000800,
    Offline = 0x00001000,
    NotContentIndexed = 0x00002000,
    Encrypted = 0x00004000,
    Write_Through = 0x80000000,
    Overlapped = 0x40000000,
    NoBuffering = 0x20000000,
    RandomAccess = 0x10000000,
    SequentialScan = 0x08000000,
    DeleteOnClose = 0x04000000,
    BackupSemantics = 0x02000000,
    PosixSemantics = 0x01000000,
    OpenReparsePoint = 0x00200000,
    OpenNoRecall = 0x00100000,
    FirstPipeInstance = 0x00080000
}

答案 1 :(得分:0)

我对我开发的每个Windows服务都执行此操作。但是通常我使用Winform,甚至在某些情况下使用命名管道来完成比在控制台中观看输出更多的花哨的事情。

话虽如此,您不必花任何时间就能获得Windows服务的控制台输出。

1)创建Windows服务项目

2)将项目输出更改为控制台应用程序。

3)将“ Service1”类更改为此。

using System.ServiceProcess;

namespace WindowsService1
{
    public partial class Service1 : ServiceBase
    {
        readonly Runner _runner = new Runner();
        static void Main(string[] args)
        {
            var service = new Service1();
            if (Debugger.IsAttached)
            {
                service.OnStart(args);
                Console.WriteLine("Find the any key!");
                Console.Read();
                service.OnStop();
            }
            else
            {
                ServiceBase[] ServicesToRun;
                ServicesToRun = new ServiceBase[]
                {
                    service
                };
                ServiceBase.Run(ServicesToRun);
            }
        }

        public Service1()
        {
            InitializeComponent();
        }

        protected override void OnStart(string[] args)
        {
            _runner.Run();
        }

        protected override void OnStop()
        {
            _runner.Stop();
        }
    }
}

然后在Runner类中,执行从工厂方法获得的任何操作。

答案 2 :(得分:0)

我一直发现使用Topshelf调试服务应用程序(http://topshelf-project.com/)会更容易