public bool TryGetValue(out object value) { // We need to be careful when this is called from an array formula. // In the 'completed' case we actually still have to call xlfRtd, then only skip if for the next (single-cell calller) call. // That gives us a proper Disconnect... ExcelReference caller = XlCall.Excel(XlCall.xlfCaller) as ExcelReference; bool isCallerArray = caller != null && (caller.RowFirst != caller.RowLast || caller.ColumnFirst != caller.ColumnLast); if (_currentObserver == null || isCallerArray || !IsCompleted()) { // NOTE: At this point the SynchronizationManager must be registered! if (!SynchronizationManager.IsInstalled) { Debug.Print("SynchronizationManager not registered!"); throw new InvalidOperationException("SynchronizationManager must be registered for async and observable support. Call ExcelAsyncUtil.Initialize() in an IExcelAddIn.AutoOpen() handler."); } // Ensure that Excel-DNA knows about the RTD Server, since it would not have been registered when loading ExcelObserverRtdServer.EnsureRtdServerRegistered(); // Refresh RTD call // NOTE: First time this will result in a call to ConnectData, which will call Subscribe and set the _currentObserver // For the first array-group call, this returns null (due to xlUncalced error), // but Excel will call us again... (I hope). if (!RtdRegistration.TryRTD(out value, _observerRtdServerProgId, null, _id)) { // This is the special case... // We return false - to the state creation function that indicates the state should not be saved. value = ExcelError.ExcelErrorNA; return(false); } } else if (_currentObserver != null && IsCompleted()) { // Special call for the Excel 2010 bug helper to indicate we are not refreshing (due to completion) if (ExcelRtd2010BugHelper.ExcelVersionHasRtdBug) { ExcelRtd2010BugHelper.RecordRtdComplete(_observerRtdServerProgId, _id); } } // No assumptions about previous state here - could have re-entered this class // We use #N/A as the 'busy' indicator, as RTD does normally. // Add-in creator can remap the 'busy' result in the UDF or another wrapper. if (_currentObserver == null) { value = ExcelError.ExcelErrorNA; return(true); } // Subsequent calls get value from Observer value = _currentObserver.Value; return(true); }
// Forwarded from XlCall // Loads the RTD server with temporary ProgId. // CAUTION: Might fail when called from array formula (the first call in every array-group fails). // When it fails, the xlfRtd call returns xlReturnUncalced. // In that case, this function returns null, and does not keep a reference to the created server object. // The next call should then succeed (though a new server object will be created). public static bool TryRTD(out object result, string progId, string server, params string[] topics) { Debug.Print("### RtdRegistration.RTD " + progId); // Check if this is any of our business. Type rtdServerType; if (!string.IsNullOrEmpty(server) || !registeredRtdServerTypes.TryGetValue(progId, out rtdServerType)) { // Just pass on to Excel. return(TryCallRTD(out result, progId, null, topics)); } // TODO: Check that ExcelRtdServer with stable ProgId case also works right here - // might need to add to loadedRtdServers somehow // Check if already loaded. string loadedProgId; if (loadedRtdServers.TryGetValue(progId, out loadedProgId)) { if (ExcelRtd2010BugHelper.ExcelVersionHasRtdBug && rtdServerType.IsSubclassOf(typeof(ExcelRtdServer))) { ExcelRtd2010BugHelper.RecordRtdCall(progId, topics); } // Call Excel using the synthetic RtdSrv_xxx (or actual from attribute) ProgId return(TryCallRTD(out result, loadedProgId, null, topics)); } // Not loaded already - need to get the Rtd server loaded // TODO: Need to reconsider registration here..... // Sometimes need stable ProgIds. object rtdServer; if (ExcelRtd2010BugHelper.ExcelVersionHasRtdBug && rtdServerType.IsSubclassOf(typeof(ExcelRtdServer))) { Debug.Print("### Creating Wrapper " + progId); rtdServer = new ExcelRtd2010BugHelper(progId, rtdServerType); } else { using (XlCall.Suspend()) { rtdServer = Activator.CreateInstance(rtdServerType); } ExcelRtdServer excelRtdServer = rtdServer as ExcelRtdServer; if (excelRtdServer != null) { // Set ProgId so that it can be 'unregistered' (removed from loadedRtdServers) when the RTD server terminates. excelRtdServer.RegisteredProgId = progId; } else { // Make a wrapper if we are not an ExcelRtdServer // (ExcelRtdServer implements exception-handling and XLCall supension itself) rtdServer = new RtdServerWrapper(rtdServer, progId); } } // We pick a new Guid as ClassId for this add-in... CLSID clsId = Guid.NewGuid(); // ... (bad idea - this will cause Excel to try to load this RTD server while it is not registered.) // Guid typeGuid = GuidUtilit.CreateGuid(..., DnaLibrary.XllPath + ":" + rtdServerType.FullName); // or something based on ExcelDnaUtil.XllGuid // string progIdRegistered = "RtdSrv_" + typeGuid.ToString("N"); // by making a fresh progId, we are sure Excel will try to load when we are ready. // Change from RtdSrv.xxx to RtdSrv_xxx to avoid McAfee bug that blocks registry writes with a "." anywhere string progIdRegistered = "RtdSrv_" + clsId.ToString("N"); Debug.Print("RTD - Using ProgId: {0} for type: {1}", progIdRegistered, rtdServerType.FullName); try { using (new SingletonClassFactoryRegistration(rtdServer, clsId)) using (new ProgIdRegistration(progIdRegistered, clsId)) using (new ClsIdRegistration(clsId, progIdRegistered)) { Debug.Print("### About to call TryCallRTD " + progId); if (TryCallRTD(out result, progIdRegistered, null, topics)) { // Mark as loaded - ServerTerminate in the wrapper will remove. loadedRtdServers[progId] = progIdRegistered; Debug.Print("### Added to loadedRtdServers " + progId); return(true); } return(false); } } catch (UnauthorizedAccessException secex) { Logger.RtdServer.Error("The RTD server of type {0} required by add-in {1} could not be registered.\r\nThis may be due to restricted permissions on the user's HKCU\\Software\\Classes key.\r\nError message: {2}", rtdServerType.FullName, DnaLibrary.CurrentLibrary.Name, secex.Message); result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue); // Return true to have the #VALUE stick, just as it was before the array-call refactoring return(true); } catch (Exception ex) { Logger.RtdServer.Error("The RTD server of type {0} required by add-in {1} could not be registered.\r\nThis is an unexpected error.\r\nError message: {2}", rtdServerType.FullName, DnaLibrary.CurrentLibrary.Name, ex.Message); Debug.Print("RtdRegistration.RTD exception: " + ex.ToString()); result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue); // Return true to have the #VALUE stick, just as it was before the array-call refactoring return(true); } }
// Forwarded from XlCall // Loads the RTD server with temporary ProgId. public static object RTD(string progId, string server, params string[] topics) { Debug.Print("### RtdRegistration.RTD " + progId); // Check if this is any of our business. Type rtdServerType; if (!string.IsNullOrEmpty(server) || !registeredRtdServerTypes.TryGetValue(progId, out rtdServerType)) { // Just pass on to Excel. return CallRTD(progId, null, topics); } // TODO: Check that ExcelRtdServer with stable ProgId case also works right here - // might need to add to loadedRtdServers somehow // Check if already loaded. string loadedProgId; if (loadedRtdServers.TryGetValue(progId, out loadedProgId)) { if (ExcelRtd2010BugHelper.ExcelVersionHasRtdBug && rtdServerType.IsSubclassOf(typeof(ExcelRtdServer))) { ExcelRtd2010BugHelper.RecordRtdCall(progId, topics); } // Call Excel using the synthetic RtdSrv.xxx (or actual from attribute) ProgId return CallRTD(loadedProgId, null, topics); } // Not loaded already - need to get the Rtd server loaded // TODO: Need to reconsider registration here..... // Sometimes need stable ProgIds. object rtdServer; if (ExcelRtd2010BugHelper.ExcelVersionHasRtdBug && rtdServerType.IsSubclassOf(typeof(ExcelRtdServer))) { Debug.Print("### Creating Wrapper " + progId); rtdServer = new ExcelRtd2010BugHelper(progId, rtdServerType); } else { using (XlCall.Suspend()) { rtdServer = Activator.CreateInstance(rtdServerType); } ExcelRtdServer excelRtdServer = rtdServer as ExcelRtdServer; if (excelRtdServer != null) { // Set ProgId so that it can be 'unregistered' (removed from loadedRtdServers) when the RTD server terminates. excelRtdServer.RegisteredProgId = progId; } else { // Make a wrapper if we are not an ExcelRtdServer // (ExcelRtdServer implements exception-handling and XLCall supension itself) rtdServer = new RtdServerWrapper(rtdServer, progId); } } // We pick a new Guid as ClassId for this add-in... CLSID clsId = Guid.NewGuid(); // ... (bad idea - this will cause Excel to try to load this RTD server while it is not registered.) // Guid typeGuid = GuidUtilit.CreateGuid(..., DnaLibrary.XllPath + ":" + rtdServerType.FullName); // or something based on ExcelDnaUtil.XllGuid // string progIdRegistered = "RtdSrv." + typeGuid.ToString("N"); // by making a fresh progId, we are sure Excel will try to load when we are ready. string progIdRegistered = "RtdSrv." + clsId.ToString("N"); Debug.Print("RTD - Using ProgId: {0} for type: {1}", progIdRegistered, rtdServerType.FullName); try { using (new SingletonClassFactoryRegistration(rtdServer, clsId)) using (new ProgIdRegistration(progIdRegistered, clsId)) using (new ClsIdRegistration(clsId, progIdRegistered)) { object result; Debug.Print("### About to call TryCallRTD " + progId); if (TryCallRTD(out result, progIdRegistered, null, topics)) { // Mark as loaded - ServerTerminate in the wrapper will remove. loadedRtdServers[progId] = progIdRegistered; Debug.Print("### Added to loadedRtdServers " + progId); } return result; } } catch (UnauthorizedAccessException secex) { Logging.LogDisplay.WriteLine("The RTD server of type {0} required by add-in {1} could not be registered.\r\nThis may be due to restricted permissions on the user's HKCU\\Software\\Classes key.\r\nError message: {2}", rtdServerType.FullName, DnaLibrary.CurrentLibrary.Name, secex.Message ); return ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue); } catch (Exception ex) { Logging.LogDisplay.WriteLine("The RTD server of type {0} required by add-in {1} could not be registered.\r\nThis is an unexpected error.\r\nError message: {2}", rtdServerType.FullName, DnaLibrary.CurrentLibrary.Name, ex.Message); Debug.Print("RtdRegistration.RTD exception: " + ex.ToString()); return ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue); } }