From 7244f344414fc4d2f8539db4925bab3fe6bbb161 Mon Sep 17 00:00:00 2001 From: Jorgen Dahl Date: Fri, 20 Oct 2023 15:02:05 -0400 Subject: [PATCH] Add support for v7.x CER Component SDK (#14501) * New CER tool * Make enums internal * Remove unused using statement * Make sure that Send Report is always called. * Don't remove temp files --- src/DynamoCore/Models/DynamoModel.cs | 2 +- src/DynamoCoreWpf/DynamoCoreWpf.csproj | 1 + src/DynamoCoreWpf/Utilities/CerDLL.cs | 379 ++++++++++++++++++ .../Utilities/CrashReportTool.cs | 117 +++--- 4 files changed, 438 insertions(+), 61 deletions(-) create mode 100644 src/DynamoCoreWpf/Utilities/CerDLL.cs diff --git a/src/DynamoCore/Models/DynamoModel.cs b/src/DynamoCore/Models/DynamoModel.cs index e05d3e69c90..e32296b06bc 100644 --- a/src/DynamoCore/Models/DynamoModel.cs +++ b/src/DynamoCore/Models/DynamoModel.cs @@ -475,7 +475,7 @@ public enum DynamoModelState { NotStarted, StartedUIless, StartedUI }; public bool CLIMode { get; internal set; } /// - /// The Autodesk CrashReport tool location on disk (directory that contains the "senddmp.exe") + /// The Autodesk CrashReport tool location on disk (directory that contains the "cer.dll") /// public string CERLocation { get; internal set; } diff --git a/src/DynamoCoreWpf/DynamoCoreWpf.csproj b/src/DynamoCoreWpf/DynamoCoreWpf.csproj index 186771c489f..3e1741d6465 100644 --- a/src/DynamoCoreWpf/DynamoCoreWpf.csproj +++ b/src/DynamoCoreWpf/DynamoCoreWpf.csproj @@ -232,6 +232,7 @@ InPortContextMenu.xaml + diff --git a/src/DynamoCoreWpf/Utilities/CerDLL.cs b/src/DynamoCoreWpf/Utilities/CerDLL.cs new file mode 100644 index 00000000000..c0302e0dd65 --- /dev/null +++ b/src/DynamoCoreWpf/Utilities/CerDLL.cs @@ -0,0 +1,379 @@ +using System; +using System.Runtime.InteropServices; + +namespace Dynamo.Wpf.Utilities +{ + internal class DLL + { + [DllImport("kernel32.dll")] + public static extern IntPtr LoadLibrary(string dllToLoad); + + [DllImport("kernel32.dll")] + public static extern IntPtr GetProcAddress(IntPtr hModule, string procedureName); + + [DllImport("kernel32.dll")] + public static extern bool FreeLibrary(IntPtr hModule); + } + + internal class CerDLL : IDisposable + { + private IntPtr m_dll = IntPtr.Zero; + + public string DllFilePath { get; } + + public CerDLL(string dllFilePath) + { + DllFilePath = dllFilePath; + } + + public void Dispose() + { + if (m_dll != IntPtr.Zero) + { + DLL.FreeLibrary(m_dll); + m_dll = IntPtr.Zero; + } + } + + private static bool Initialized; + private static readonly object InitLocker = new(); + private bool Init() + { + if (!Initialized) + { + lock (InitLocker) + { + if (!Initialized) + { + if (m_dll == IntPtr.Zero) + m_dll = DLL.LoadLibrary(DllFilePath); + Initialized = true; + } + } + } + + return m_dll != IntPtr.Zero; + } + + private TDelegate GetDelegate() where TDelegate : Delegate + + { + IntPtr funcAddr = DLL.GetProcAddress(m_dll, typeof(TDelegate).Name); + if (funcAddr == IntPtr.Zero) + return null; + + return Marshal.GetDelegateForFunctionPointer(funcAddr); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_EnableUnhandledExceptionFilter(); + public void EnableUnhandledExceptionFilter() + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func(); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_ToggleCER(bool enable); + public void ToggleCER(bool enable) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func(enable); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool CER_IsCEREnabled(); + public bool IsCEREnabled() + { + if (!Init()) + return false; + + var func = GetDelegate(); + if (func == null) + return false; + return func(); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetSenddmpPath(string senddmpExePath); + public void SetSenddmpPath(string senddmpExePath) + { + if (!Init()) + return; + + + var func = GetDelegate(); + if (func == null) + return; + func(senddmpExePath); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_RegisterUPI(string upiConfigFilePath); + public void RegisterUPI(string upiConfigFilePath) + { + if (!Init()) + return; + + + var func = GetDelegate(); + if (func == null) + return; + func(upiConfigFilePath); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool CER_SendReport(IntPtr exceptionPointers, bool suspendProcess); + public bool SendReport(Exception e, bool suspendProcess) + { + if (!Init()) + return false; + + var exceptionPointers = Marshal.GetExceptionPointers(); + if (exceptionPointers == IntPtr.Zero) + { + try + { + var exInfo = System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(e); + exInfo.Throw(); + } + catch + { + exceptionPointers = Marshal.GetExceptionPointers(); + } + } + + var func = GetDelegate(); + if (func == null) + return false; + return func(exceptionPointers, suspendProcess); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate bool CER_SendReportWithDump(string dumpFile, bool suspendProcess); + public bool SendReportWithDump(string dumpFile, bool suspendProcess) + { + if (!Init()) + return false; + + var func = GetDelegate(); + if (func == null) + return false; + return func(dumpFile, suspendProcess); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetTheme(int theme); + public void SetTheme(int theme) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func(theme); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetMultiStringParam(int key, string value, int maxCount); + public void SetMultiStringParam(ReportMultiStringParamKey key, string value, int maxCount) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func((int)key, value, maxCount); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetStringParam(int key, string value); + public void SetStringParam(ReportStringParamKey key, string value) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func((int)key, value); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetIntParam(int key, int value); + public void SetIntParam(ReportIntParamKey key, int value) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func((int)key, value); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetBoolParam(int key, bool value); + public void SetBoolParam(ReportBoolParamKey key, bool value) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func((int)key, value); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetAdditionalStringParam(string key, string value, int maxCount); + public void SetAdditionalStringParam(string key, string value, int maxCount) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func(key, value, maxCount); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetAdditionalIntParam(string key, int value, int maxCount); + public void SetAdditionalIntParam(string key, int value, int maxCount) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func(key, value, maxCount); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetAdditionalBoolParam(string key, bool value, int maxCount); + public void SetAdditionalBoolParam(string key, bool value, int maxCount) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func(key, value, maxCount); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_RemoveAdditionalParam(string key); + public void RemoveAdditionalParam(string key) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func(key); + } + + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + private delegate void CER_SetLegacyOptionsByString(string str); + public void SetLegacyOptionsByString(string str) + { + if (!Init()) + return; + + var func = GetDelegate(); + if (func == null) + return; + func(str); + } + } + + internal enum ThemeType : int + { + Light, + Dark, + Blue, + }; + + internal enum ReportStringParamKey : int + { + StringKeyBegin = 10000, + StringKeyUpiToken, // will be deprecated, please call RegisterUPI function as instead + StringKeyCalUptime, + StringKeyProductKey, + StringKeySerialNum, + StringKeyFeatureName, + StringKeyFeatureVer, + StringKeyLicenseBehavior, + StringKeyLicExp, + StringKeyLicUsage, + StringKeyDwg, + StringKeyAutoSend, + StringKeyAppXMLFile, + StringKeyAvailPhysicalMem, + StringKeyAvailPageFile, + StringKeyAvailVirtualMem, + StringKeyLastError, + StringKeyErrNo, + StringKeyUserSubscriptionEmail, + StringKeyErrorDescription, + StringKeyGraphicsDriver, + StringKeyCadSettingsRegPath, + StringKeyOptionsFile, + StringKeyMc3SessionId, + StringKeyMc3UserId, + StringKeyAppIcon, + StringKeyGdiObjects, + StringKeyGdiUserObjects, + StringKeyGdiObjectsPeak, + StringKeyGdiUserObjectsPeak, + StringKeyUnknown = 19998, + StringKeyEnd = 19999 + }; + + internal enum ReportMultiStringParamKey + { + MultiStringKeyBegin = 20000, + MultiStringKeyAppCData, + MultiStringKeyAppName, + MultiStringKeyAppXML, + MultiStringKeyExtraFile, + MultiStringKeyLastCommand, + MultiStringKeyUnknown = 29998, + MultiStringKeyEnd = 29999 + }; + + internal enum ReportIntParamKey : int + { + IntKeyBegin = 30000, + IntKeyCrashCount, + IntKeyAppLocaleId, + IntKeyAppIconId, + IntKeyUnknown = 39998, + IntKeyEnd = 39999 + }; + + internal enum ReportBoolParamKey : int + { + BoolKeyBegin = 40000, + BoolKeyAltForceSend, + BoolKeyUseExceptionTrace, + BoolKeyUnknown = 49998, + BoolKeyEnd = 49999 + }; +} diff --git a/src/DynamoCoreWpf/Utilities/CrashReportTool.cs b/src/DynamoCoreWpf/Utilities/CrashReportTool.cs index 057f84592bd..721b01232e6 100644 --- a/src/DynamoCoreWpf/Utilities/CrashReportTool.cs +++ b/src/DynamoCoreWpf/Utilities/CrashReportTool.cs @@ -16,7 +16,7 @@ namespace Dynamo.Wpf.Utilities internal class CrashReportTool { private static List ProductsWithCER => new List() { "Revit", "Civil", "Robot Structural Analysis" }; - private static readonly string CERExeName = "senddmp.exe"; + private static readonly string CERDllName = "cer.dll"; private static string CERInstallLocation = null; @@ -143,7 +143,7 @@ private static string FindCERToolInInstallLocations() throw new MissingMethodException("Method 'DynamoInstallDetective.Utilities.FindProductInstallations' not found"); } - var methodParams = new object[] { ProductsWithCER, CERExeName }; + var methodParams = new object[] { ProductsWithCER, CERDllName }; var installs = installationsMethod.Invoke(null, methodParams) as IEnumerable; CERInstallLocation = installs.Cast>>().Select(x => x.Key).LastOrDefault() ?? string.Empty; @@ -173,7 +173,7 @@ internal static bool ShowCrashErrorReportWindow(DynamoViewModel viewModel, Crash string cerToolDir = !string.IsNullOrEmpty(model.CERLocation) ? model.CERLocation : FindCERToolInInstallLocations(); - var cerToolPath = Path.Combine(cerToolDir, CERExeName); + var cerToolPath = Path.Combine(cerToolDir, CERDllName); if (string.IsNullOrEmpty(cerToolPath) || !File.Exists(cerToolPath)) { model?.Logger?.LogError($"The CER tool was not found at location {cerToolPath}"); @@ -187,78 +187,75 @@ internal static bool ShowCrashErrorReportWindow(DynamoViewModel viewModel, Crash var cerDir = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), "DynamoCER_Report_" + DateTime.Now.ToUniversalTime().ToString("yyyy-MM-dd-HH-mm-ss"))); - using (Scheduler.Disposable.Create(() => { - try - { - // Cleanup - foreach (FileInfo file in cerDir.EnumerateFiles()) - file.Delete(); - foreach (DirectoryInfo dir in cerDir.EnumerateDirectories()) - dir.Delete(true); - } - catch (Exception ex) - { - model?.Logger?.LogError($"Failed to cleanup the CER directory at {cerDir.FullName} : {ex.Message}"); - } - })) + var filesToSend = new List(); + if (args.SendLogFile && model != null) { - var filesToSend = new List(); - if (args.SendLogFile && model != null) - { - string logFile = Path.Combine(cerDir.FullName, "DynamoLog.log"); + string logFile = Path.Combine(cerDir.FullName, "DynamoLog.log"); - File.Copy(model.Logger.LogPath, logFile); - // might be usefull to dump all loaded Packages into - // the log at this point. - filesToSend.Add(logFile); - } + File.Copy(model.Logger.LogPath, logFile); + // might be usefull to dump all loaded Packages into + // the log at this point. + filesToSend.Add(logFile); + } - if (args.SendSettingsFile && model != null) - { - string settingsFile = Path.Combine(cerDir.FullName, "DynamoSettings.xml"); - File.Copy(model.PathManager.PreferenceFilePath, settingsFile); + if (args.SendSettingsFile && model != null) + { + string settingsFile = Path.Combine(cerDir.FullName, "DynamoSettings.xml"); + File.Copy(model.PathManager.PreferenceFilePath, settingsFile); - filesToSend.Add(settingsFile); - } + filesToSend.Add(settingsFile); + } - if (args.HasDetails()) - { - var stackTracePath = Path.Combine(cerDir.FullName, "StackTrace.log"); - File.WriteAllText(stackTracePath, args.Details); - filesToSend.Add(stackTracePath); - } + if (args.HasDetails()) + { + var stackTracePath = Path.Combine(cerDir.FullName, "StackTrace.log"); + File.WriteAllText(stackTracePath, args.Details); + filesToSend.Add(stackTracePath); + } - if (args.SendRecordedCommands && viewModel != null) - { - filesToSend.Add(viewModel.DumpRecordedCommands()); - } + if (args.SendRecordedCommands && viewModel != null) + { + filesToSend.Add(viewModel.DumpRecordedCommands()); + } + + string appConfig = ""; + if (model != null) + { + var appName = GetHostAppName(model); + appConfig = $""; + } - var extras = string.Join(" ", filesToSend.Select(f => "/EXTRA \"" + f + "\"")); + string dynName = viewModel?.Model.CurrentWorkspace.Name; - string appConfig = ""; - if (model != null) - { - var appName = GetHostAppName(model); - appConfig = $@""; - } + var miniDumpFilePath = CreateMiniDumpFile(cerDir.FullName); + var upiConfigFilePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "upiconfig.xml"); + + using (var cerDLL = new CerDLL(cerToolPath)) + { + cerDLL.ToggleCER(true); + cerDLL.RegisterUPI(upiConfigFilePath); - string dynConfig = string.Empty; - string dynName = viewModel?.Model.CurrentWorkspace.Name; if (!string.IsNullOrEmpty(dynName)) { - dynConfig = $"/DWG {dynName}"; + cerDLL.SetStringParam(ReportStringParamKey.StringKeyDwg, dynName); } - var miniDumpFilePath = CreateMiniDumpFile(cerDir.FullName); - var upiConfigFilePath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "upiconfig.xml"); + foreach (var file in filesToSend) + { + cerDLL.SetMultiStringParam(ReportMultiStringParamKey.MultiStringKeyExtraFile, file, filesToSend.Count); + } - var cerArgs = $"/UPITOKEN \"{upiConfigFilePath}\" /DMP \"{miniDumpFilePath}\" /APPXML \"{appConfig}\" {dynConfig} {extras} /USEEXCEPTIONTRACE"; - - Process.Start(new ProcessStartInfo(cerToolPath, cerArgs) { UseShellExecute = true }).WaitForExit(); - return true; + cerDLL.SetMultiStringParam(ReportMultiStringParamKey.MultiStringKeyAppXML, appConfig, 1); + cerDLL.SetBoolParam(ReportBoolParamKey.BoolKeyUseExceptionTrace, true); + var success = cerDLL.SendReportWithDump(miniDumpFilePath, true); + model?.Logger?.LogError(success + ? $"Successfully sent CER error report" + : $"Failed to send CER error report"); } + + return true; } catch(Exception ex) {