internal static bool StartInternal(ref StartParams startParams, ref ApiFunctionPointers apiFunctionPointers, string rootDir) { if (!File.Exists(m_configFilePath)) { m_log.Log($"XPNet CLR: Will not load plugin because config file does not exist: (Path = {m_configFilePath})."); return(false); } m_config = GetConfig(m_configFilePath); if (m_config == null) { m_log.Log($"XPNet CLR: Will not load plugin because config file was unusable: (Path = {m_configFilePath})."); return(false); } m_configReloadToken = m_config.GetReloadToken(); m_configReloadTokenDisposer = m_configReloadToken.RegisterChangeCallback(o => { m_log.Log($"XPNet CLR: Config file change detected: (Path = {m_configFilePath})."); m_log.Log($"XPNet CLR: Will reconfigure logging."); m_api.Log = m_log = ReconfigureLogging(forceLogging: false); m_log.Log($"XPNet CLR: Will tell plugin that config changed."); m_api.RaiseConfigChanged(); }, state: null); // Make a local copy of the given set of API function pointers. ApiFunctions = new ApiFunctions(apiFunctionPointers); m_api = new XPlaneApi(m_log, m_config); m_plugin = LoadPlugin(rootDir); if (m_plugin == null) { m_log.Log("XPNet CLR: Failed to find a plugin to load. Will tell X-Plane we failed to start."); return(false); } var typeInfo = m_plugin.GetType().GetTypeInfo(); var xpattr = typeInfo.GetCustomAttribute <XPlanePluginAttribute>(); unsafe { fixed(byte *pc = startParams.Name) Interop.CopyCString(pc, 256, xpattr.Name); fixed(byte *pc = startParams.Signature) Interop.CopyCString(pc, 256, xpattr.Signature); fixed(byte *pc = startParams.Description) Interop.CopyCString(pc, 256, xpattr.Description); } return(true); }
private static unsafe ILog InitLogging(ref StartParams startParams) { // If the calling environment provides a log handle, that's // what we'll use. Otherwise we use configuration to // determine what kind of log file to open. var hLog = (IntPtr)startParams.LogHandle; m_loggingOverriddenByHostEnvironment = hLog != IntPtr.Zero; if (m_loggingOverriddenByHostEnvironment) { var hSafeFile = new SafeFileHandle(hLog, ownsHandle: false); return(new FileHandleLog(hSafeFile)); } else { return(ReconfigureLogging(forceLogging: true)); } }
public static bool Start(ref StartParams startParams, ref ApiFunctionPointers apiFunctionPointers) { // MAINT: This is the initial entry point that gets called from C++ when the plugin system is // initialized. // // * Do not call anything from _this_ method that requires any other DLLs, other than those // that come with the framework itself. (Put such calls into StartInternal instead). // // * Do not introduce anything in a static constructor of this class that requires any // other DLLs, other than those that come with the framework itself. // // Violating either of those rules makes debugging "missing dependency" problems much more // difficult. When the framework finds a missing dependency, it throws an exception. If that // happens at a place where we cannot catch it in C#, then it will make it back to C++-land // as a hard crash. (It is actually an NT exception and could be handled with __try/__except, // but that wouldn't work on other macOS or Linux, and we wouldn't be able to log any information // about the exception, so that's a non-optimal solution). // // The reason you can't call methods that are loaded from other DLLs here, but can in StartInternal, // is to do with how the JIT compiler works. If it encounters a method it can't compile because // of a missing DLL, it will fail to compile _that_ method with an exception. If one of the core // dependencies (like Microsoft.Configuration.*) is missing, we want that failure to occur while // compiling StartInternal, which we call from here. That way, we can catch it with our try/catch // here, and report a good error message. If _this_ method were to fail to JIT-compile, there are // no other .NET methods higher in the stack to catch and properly report the error, and the result // looks to the user like a hard crash. // // Logically, this method should be very small, just a try/catch wrapper around StartInternal. // However, // // 1. Logging is safe, because we specifically make sure that it doesn't require any external // dependencies. (That is one of the reasons that we aren't using something like log4net). // 2. Setting up logging (but nothing else!) at this level lets us pass back to the host environment // a useful message about exactly which DLL is missing, because in practice the host environment // is our C++ X-Plane plugin DLL, which passes a log handle, so it can catch what we log. // Without that, we might as well just be telling the user "¯\_(ツ)_/¯ sucks to be you..." // // NOTE: There's not much we can do right now about unhandled exceptions on other threads. // Plugin writers will have to be conscientious about taking care of that themselves. When/if // .NET Core adds an equivalent to AppDomain.UnhandledException, we could give it a try. // try { var thisAssemblyPath = typeof(PluginBridge).GetTypeInfo().Assembly.Location; var rootDir = Path.GetDirectoryName(thisAssemblyPath); m_configFilePath = Path.Combine(rootDir, "xpnetcfg.json"); m_logFilePath = Path.Combine(rootDir, "xpnet.log"); // We always start out with logging enabled. We may reconfigure it off once config loads. m_log = InitLogging(ref startParams); m_log.Log("XPNet CLR: Start"); return(StartInternal(ref startParams, ref apiFunctionPointers, rootDir)); } catch (Exception exc) { // File.WriteAllText(@"D:\Games\X-Plane\X-Plane 11 Beta\Resources\plugins\XPNetDev\64\Emergency.txt", "Error: " + exc); m_log?.Log(exc); return(false); } }
public static bool Start(ref StartParams startParams, ref ApiFunctionPointers apiFunctionPointers) { try { var thisAssemblyPath = typeof(PluginBridge).GetTypeInfo().Assembly.Location; var rootDir = Path.GetDirectoryName(thisAssemblyPath); m_configFilePath = Path.Combine(rootDir, "xpnetcfg.json"); m_logFilePath = Path.Combine(rootDir, "xpnet.log"); // We always start out with logging enabled. We may reconfigure it off once config loads. m_log = InitLogging(ref startParams); m_log.Log("XPNet CLR: Start"); if (!File.Exists(m_configFilePath)) { m_log.Log($"XPNet CLR: Will not load plugin because config file does not exist: (Path = {m_configFilePath})."); return(false); } m_config = GetConfig(m_configFilePath); if (m_config == null) { m_log.Log($"XPNet CLR: Will not load plugin because config file was unusable: (Path = {m_configFilePath})."); return(false); } m_configReloadToken = m_config.GetReloadToken(); m_configReloadTokenDisposer = m_configReloadToken.RegisterChangeCallback(o => { m_log.Log($"XPNet CLR: Config file change detected: (Path = {m_configFilePath})."); m_log.Log($"XPNet CLR: Will reconfigure logging."); m_api.Log = m_log = ReconfigureLogging(forceLogging: false); m_log.Log($"XPNet CLR: Will tell plugin that config changed."); m_api.RaiseConfigChanged(); }, state: null); // Make a local copy of the given set of API function pointers. ApiFunctions = new ApiFunctions(apiFunctionPointers); m_api = new XPlaneApi(m_log, m_config); m_plugin = LoadPlugin(rootDir); if (m_plugin == null) { m_log.Log("XPNet CLR: Failed to find a plugin to load. Will tell X-Plane we failed to start."); return(false); } var typeInfo = m_plugin.GetType().GetTypeInfo(); var xpattr = typeInfo.GetCustomAttribute <XPlanePluginAttribute>(); unsafe { fixed(byte *pc = startParams.Name) Interop.CopyCString(pc, 256, xpattr.Name); fixed(byte *pc = startParams.Signature) Interop.CopyCString(pc, 256, xpattr.Signature); fixed(byte *pc = startParams.Description) Interop.CopyCString(pc, 256, xpattr.Description); } return(true); } catch (Exception exc) { // File.WriteAllText(@"D:\Games\X-Plane\X-Plane 11 Beta\Resources\plugins\XPNetDev\64\Emergency.txt", "Error: " + exc); m_log?.Log(exc); return(false); } }