增加调试控制台
Unity版本:2019.3.4f1
SLua版本:1.7.0
.NET : 4.7.1
前文已经把Unity和SLua搭建起来了。这次主要是希望为Unity增加一个单独的调试控制台,方便开发,也方便后面策划跑包过程中更容易定位问题(实时调试),原理则是调用了kernel32.dll里的AllocConsole()和FreeConsole()。
控制台存在的意义:
- 根据Debug.Log、Debug.LogWarning和Debug.LogError区分输出,控制台输出不同颜色的字符,便于查看
- 打出PC包后,也能正常查看Log
- 当程序异常触发断点时,能通过控制台进行调试(具体可以参考网上各种debuger的实现)
当前的问题:
我之前项目是使用Unity 5.6,控制台是利用GetStdHandle进行操作的,也能正常调用kernel32.dll里的函数。但是到了2019.3,发现GetStdHandle行不通了,GetLastError也没返回任何错误,估计跟Unity一些底层实现有关。通过谷歌一些资料和尝试,结果是使用CreateFileW能正常操作,但是文字着色等操作依旧无效(跟GetStdHandle相关的操作都无效,即使GetLastError返回是0),所以下面代码只能保证控制台正常使用,而不能保证文字着色。
using UnityEngine;
using System;
using System.IO;
using System.Runtime.InteropServices;
using SLua;
using Microsoft.Win32.SafeHandles;
namespace QGNB
{
internal class ConsoleWindow
{
#if UNITY_EDITOR_WIN || UNITY_STANDLONE_WIN
static ConsoleWindow g_window;
static void HandleLog(string message, string stackTrace, LogType type)
{
if (type == LogType.Warning)
g_window.SetForegroundColor(0x0e);
else if (type == LogType.Error)
g_window.SetForegroundColor(0x0c);
else
g_window.SetForegroundColor(0x0c);
Console.WriteLine(message);
}
static public void DestroyWindow()
{
g_window.Shutdown();
}
static public void CreateWindow()
{
g_window = new ConsoleWindow();
g_window.Initialize();
g_window.SetTitle("QGNB");
Application.logMessageReceived += HandleLog;
}
TextWriter oldOutput;
TextReader oldInput;
IntPtr stdOutHandler;
IntPtr stdInHandler;
public void SetForegroundColor(uint color)
{
//SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
//Debug.Log(">> SetConsoleTextAttribute " + GetLastError());
}
private FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
FileAccess dotNetFileAccess)
{
var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
if (!file.IsInvalid)
{
var fs = new FileStream(file, dotNetFileAccess);
return fs;
}
return null;
}
public StreamReader standardInput;
public StreamWriter standardOutput;
public void Initialize()
{
AllocConsole();
oldOutput = Console.Out;
oldInput = Console.In;
try
{
var fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
if (fs != null)
{
standardOutput = new StreamWriter(fs) { AutoFlush = true };
Console.SetOut(standardOutput);
}
fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
if (fs != null)
{
standardInput = new StreamReader(fs);
Console.SetIn(standardInput);
}
SetConsoleOutputCP(65001);
}
catch (System.Exception e)
{
Debug.Log("Couldn't redirect output: " + e.Message);
}
}
public void Shutdown()
{
standardOutput.Close();
standardInput.Close();
Console.SetOut(oldOutput);
Console.SetIn(oldInput);
FreeConsole();
}
public void SetTitle(string strName)
{
SetConsoleTitle(strName);
}
static public string ReadConsoleString()
{
while (true)
{
string str = g_window.standardInput.ReadLine();
if(str != null)
{
return str;
}
}
}
#region Win API Functions and Constants
[DllImport("kernel32.dll",
EntryPoint = "AllocConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();
[DllImport("kernel32.dll",
EntryPoint = "FreeConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int FreeConsole();
[DllImport("kernel32.dll",
EntryPoint = "AttachConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern UInt32 AttachConsole(UInt32 dwProcessId);
[DllImport("kernel32.dll",
EntryPoint = "GetStdHandle",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr GetStdHandle(Int32 nStdHandle);
[DllImport("kernel32.dll",
EntryPoint = "SetConsoleTextAttribute",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern bool SetConsoleTextAttribute(IntPtr handle, uint color);
[DllImport("kernel32.dll",
EntryPoint = "SetConsoleTitle",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern bool SetConsoleTitle(string title);
[DllImport("kernel32.dll",
EntryPoint = "SetConsoleOutputCP",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern bool SetConsoleOutputCP(uint mode);
[DllImport("kernel32.dll",
EntryPoint = "SetConsoleScreenBufferSize",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern bool SetConsoleScreenBufferSize(IntPtr handle, _coord coord);
[DllImport("kernel32.dll",
EntryPoint = "GetLastError",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int GetLastError();
[DllImport("kernel32.dll",
EntryPoint = "CreateFileW",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr CreateFileW(
string lpFileName,
UInt32 dwDesiredAccess,
UInt32 dwShareMode,
IntPtr lpSecurityAttributes,
UInt32 dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFile
);
private const Int32 STD_OUTPUT_HANDLE = -11;
private const Int32 STD_INPUT_HANDLE = -10;
private const UInt32 GENERIC_WRITE = 0x40000000;
private const UInt32 GENERIC_READ = 0x80000000;
private const UInt32 FILE_SHARE_READ = 0x00000001;
private const UInt32 FILE_SHARE_WRITE = 0x00000002;
private const UInt32 OPEN_EXISTING = 0x00000003;
private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x80;
private const UInt32 ERROR_ACCESS_DENIED = 5;
private const UInt32 ATTACH_PARRENT = 0xFFFFFFFF;
#endregion
#else
static public void ReadConsoleString() { return ""; }
static public void DestroyWindow() {}
static public void CreateWindow() {}
#endif
}
[CustomLuaClass]
public class ConsoleServer : MonoBehaviour
{
static public string ReadConsoleString()
{
return ConsoleWindow.ReadConsoleString();
}
#if UNITY_EDITOR_WIN || UNITY_STANDLONE_WIN
void Awake()
{
DontDestroyOnLoad(gameObject);
ConsoleWindow.CreateWindow();
}
void OnDestroy()
{
ConsoleWindow.DestroyWindow();
}
#endif
}
}