Не могу прикрепить консоль - PullRequest
0 голосов
/ 29 ноября 2018

Я пытаюсь подключить консоль к Сервису, если я запускаю ее из отладчика .Я прочитал пару «рабочих» решений, но они, похоже, не работают.Вот код, который я использую:

    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 Console:

    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 есть какое-то перенаправление, которое я собираюсь здесь преодолеть.

EDIT только для Ганса - вот «полный код»

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

Для проекта задан тип окна приложения.

Ответы [ 3 ]

0 голосов
/ 05 декабря 2018

Мне удалось воспроизвести вашу проблему и заставить ее работать на моей машине.Часть вашего кода выглядит так, как будто он получен из принятого ответа на Нет вывода на консоль при использовании AllocConsole и целевой архитектуры x86 .Если вы прочтете ветку комментариев под этим ответом, вы увидите, что new IntPtr(7) не работает с Windows 7 / Server 2012. «Новое магическое число» для Windows 7 также не сработало для меня.Чтобы решить эту проблему, я пошел по пути переноса данного вызова 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
}
0 голосов
/ 05 декабря 2018

Мне всегда было проще использовать Topshelf для отладки приложений-служб (http://topshelf -project.com / )

0 голосов
/ 04 декабря 2018

Я делаю это для каждой службы 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 вы делаете то, что когда-либо получаете от своего фабричного метода.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...