У RunUO есть хорошая штука под названием CrashGuard, которая позволяет ей перезапускать саму себя в случае возникновения исключения. Это действительно полезно для реального сервера, но вызывает неудобства при отладке и тестирования, т.к. после подобного перезапуска сервер не подключается к отладчику, что вынуждает вручную его перезапускать. На все это уходит время... Конечно наиболее простым решением было бы просто отключение CrashGuard в DEBUG конфигурации, но так как мы легких путей не ищем, то задача свелась к тому чтобы заставить RunUO аттачить саму себя к отладчику MVS. Возможно это может показаться безумным и сложным, но на самом деле все достаточно просто, ведь MVS предлагает нам средства для взаимодействия с IDE. Собственно нам потребуется сама MVS версии 2010/2012 или позднее (в принципе возможно использовать и более старые версии, но тогда надо изменить используемые версии интерфейсов, в принципе их также лучше обновить если доступны более новые). После чего добавляем в референсы ядра следующие сборки: envdte, EnvDTE100, envdte80, envdte90, EnvDTE90a. Ну и добавляем сей код в пространство имен Server.Diagnostics:
Код
/***************************************************************************
* QServer (UO: Quintessense) Created by : StaticZ
* url : http://dev.uoquint.ru
* file: VisualStudio.cs (2012.08.16) email : staticz@uoquint.ru
***************************************************************************/
using System.IO;
using EnvDTE;
using EnvDTE80;
using EnvDTE90;
using EnvDTE90a;
using EnvDTE100;
using DTEVStudio = EnvDTE80.DTE2;
using DTEProcess = EnvDTE90a.Process4;
using DTEDebuger = EnvDTE100.Debugger5;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Process = System.Diagnostics.Process;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace Server.Diagnostics
{
public class VisualStudio
{
#region DllImport
[DllImport("User32")]
private static extern int ShowWindow(int hwnd, int nCmdShow);
[DllImport("ole32.dll")]
public static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);
[DllImport("ole32.dll")]
public static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetFocus(IntPtr hWnd);
#endregion
protected static string GetSolutionForVisualStudio(Process visualStudioProcess)
{
DTEVStudio visualStudioInstance;
if (TryGetVsInstance(visualStudioProcess.Id, out visualStudioInstance)) {
try {
return visualStudioInstance.Solution.FullName;
} catch (Exception) { }
}
return null;
}
protected static Process GetAttachedVisualStudio(Process applicationProcess)
{
IEnumerable<Process> visualStudios = GetVisualStudioProcesses();
foreach (Process visualStudio in visualStudios) {
DTEVStudio visualStudioInstance;
if (TryGetVsInstance(visualStudio.Id, out visualStudioInstance)) {
try {
foreach (Process debuggedProcess in visualStudioInstance.Debugger.DebuggedProcesses) {
if (debuggedProcess.Id == applicationProcess.Id) {
return debuggedProcess;
}
}
} catch (Exception) { }
}
}
return null;
}
protected static void AttachVisualStudioToProcess(Process visualStudioProcess, Process applicationProcess)
{
if (TryGetVsInstance(visualStudioProcess.Id, out visualStudioInstance))
{
while (true) try {
var localProcesses = ((DTEDebuger)visualStudioInstance.Debugger).LocalProcesses;
processToAttachTo = (DTEProcess)localProcesses.Cast<EnvDTE.Process>().FirstOrDefault(process => process.ProcessID == applicationProcess.Id);
break;
} catch { System.Threading.Thread.Sleep(100); }
if (processToAttachTo != null) {
if (processToAttachTo.IsBeingDebugged || GetAttachedVisualStudio(applicationProcess) != null)
return;
processToAttachTo.Attach();
ShowWindow((int)visualStudioProcess.MainWindowHandle, 3);
SetForegroundWindow(visualStudioProcess.MainWindowHandle);
} else {
throw new InvalidOperationException("Visual Studio process cannot find specified application '" + applicationProcess.Id + "'");
}
}
}
protected static Process GetVisualStudioForSolutions(List<string> solutionNames)
{
foreach (string solution in solutionNames) {
Process visualStudioForSolution = GetVisualStudioForSolution(solution);
if (visualStudioForSolution != null) {
return visualStudioForSolution;
}
}
return null;
}
protected static Process GetVisualStudioForSolution(string solutionName)
{
IEnumerable<Process> visualStudios = GetVisualStudioProcesses();
foreach (Process visualStudio in visualStudios) {
DTEVStudio visualStudioInstance;
if (TryGetVsInstance(visualStudio.Id, out visualStudioInstance)) {
try {
string actualSolutionName = Path.GetFileName(visualStudioInstance.Solution.FullName);
if (string.Compare(actualSolutionName, solutionName, StringComparison.InvariantCultureIgnoreCase) == 0) {
return visualStudio;
}
} catch (Exception) { }
}
}
return null;
}
private static IEnumerable<Process> GetVisualStudioProcesses()
{
Process[] processes = Process.GetProcesses();
return processes.Where(o => o.ProcessName.Contains("devenv"));
}
private static bool TryGetVsInstance(int processId, out DTEVStudio instance)
{
IntPtr numFetched = IntPtr.Zero;
IRunningObjectTable runningObjectTable;
IEnumMoniker monikerEnumerator;
IMoniker[] monikers = new IMoniker[1];
GetRunningObjectTable(0, out runningObjectTable);
runningObjectTable.EnumRunning(out monikerEnumerator);
monikerEnumerator.Reset();
while (monikerEnumerator.Next(1, monikers, numFetched) == 0) {
IBindCtx ctx;
CreateBindCtx(0, out ctx);
string runningObjectName;
monikers[0].GetDisplayName(ctx, null, out runningObjectName);
object runningObjectVal;
runningObjectTable.GetObject(monikers[0], out runningObjectVal);
if (runningObjectVal is DTEVStudio && runningObjectName.StartsWith("!VisualStudio")) {
int currentProcessId = int.Parse(runningObjectName.Split(':')[1]);
if (currentProcessId == processId) {
instance = (DTEVStudio)runningObjectVal;
return true;
}
}
}
instance = null;
return false;
}
private static DTEVStudio visualStudioInstance;
private static DTEProcess processToAttachTo;
public static bool Attached {
get { return (visualStudioInstance != null && processToAttachTo != null); }
}
[Conditional("DEBUG")]
public static void Attach(string solution)
{
AttachVisualStudioToProcess(GetVisualStudioForSolution(solution), Process.GetCurrentProcess());
Timer.DelayCall(TimeSpan.FromSeconds(1D), TimeSpan.FromSeconds(1D), new TimerCallback(CheckAttaching_Callback));
}
private static void CheckAttaching_Callback()
{
try {
if (!processToAttachTo.IsBeingDebugged) {
visualStudioInstance = null;
Core.Kill(false);
}
} catch {;}
}
//private static void OnStopDebugging(string Guid, int ID, object CustomIn, object CustomOut, ref bool CancelDefault)
//{
// OnStopDebugging(Guid, ID, CustomIn, CustomOut);
//}
//private static void OnStopDebugging(string Guid, int ID, object CustomIn, object CustomOut)
//{
// visualStudioInstance = null;
// Core.Kill(false);
//}
[Conditional("DEBUG")]
public static void Dettach()
{
try {
if (processToAttachTo != null && visualStudioInstance != null)
processToAttachTo.Detach(false);
} catch {
Console.WriteLine("Error ocuires while dettaching debug process");
} finally {
visualStudioInstance = null;
processToAttachTo = null;
}
}
[Conditional("DEBUG")]
public static void Breakpoint(bool conditional = true)
{
if (!conditional || !Attached) return;
var currentFram = new StackTrace(true).GetFrame(1);
visualStudioInstance.Debugger.Break(false);
}
}
}
Все что нам теперь надо это лишь вызвать метод Attach при запуске сервера и передать ему в качестве параметра имя файла решения вашего сервера:
Код
public static void Main(string[] args)
{
VisualStudio.Attach("UOQuintessence.sln");
// ... Original code behind
}
Итак, теперь при запуске в DEBUG конфигурации RunUO будет автоматически присоединяться к отладчику студии, в которой открыто решение с указанным названием файла. Это легко проверить запустив проект без отладки, вы увидите что не смотря на это он все равно перейдет в режим отладки, тоже самое будет и при рестарте сервера.
В качестве небольшого бонуса еще небольшая мелочь это метод - Breakpoint(bool conditional = true), что ставит брейкпоинт в случае выполнения условия. Зачем оно нужно? Часто требуется во время отладки посмотреть что-то конкретное, к примеру мы хотим проверить проверку при хотьбе и ставим брекпоинт и начинаем сходить с ума, т.к. он вызывается каждым мобайлом, но использовав:
Код
VisualStudio.Breakpoint(from.Type = typeof(PlayerMobile));
Мы будем получать брекпоинты только при обработке движения игрока. Конечно брекпоинт фактически ставится внутри этого метода, но через стек вызова можно легко и быстро перепрыгнуть в интересующее место. Вообще конечно это можно реализовать и лучше, чтобы не пришлось прыгать по стеку, но об этом какнибудь в следующий раз.
Оригинальный текст статьи находиться тут:
board.uoquint.ruСообщение отредактировал StaticZ - 3.7.2014, 20:29