protected override object ConnectData(Topic topic, IList <string> topicInfo, ref bool newValues) { Log("ConnectData: TopicId - {0}, topicInfo: {1}", GetTopicId(topic), string.Join(", ", topicInfo)); Logger.Debug(">>>>> ConnectData called."); _topics.Add((RealStockTopic)topic); return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); }
protected override object ConnectData(Topic topic, IList <string> topicInfo, ref bool newValues) { TestArrayTopic testArrayTopic = (TestArrayTopic)topic; _topics.Add(testArrayTopic); Debug.Print("ConnectData - Prefix {0}", testArrayTopic.Prefix); return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); }
protected override object ConnectData(Topic topic, System.Collections.Generic.IList <string> topicInfo, ref bool newValues) { lock (m_TopicMap) { string stopic = topicInfo[0]; int topicId = GetTopicId(topic); Logr.Log(String.Format("~A ConnectData: {0} - {1}", topicId, stopic)); m_Subscriptions.Add(topic, stopic); m_TopicMap.Add(stopic, topic); return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); } }
public object?GetData(string viewServer, string view, string fields, string?where = default, string?orderBy = default, string?groupBy = default, int?limit = default, int?offset = default) { if (this._userManager.User == null) { return("Error: Not logged in."); } string location = DataFunctions.GetLocation(); SelectRequest request = new SelectRequest { QueryId = location, Parameters = new SelectRequestParameters { View = view, Fields = fields, Where = where, OrderBy = orderBy, GroupBy = groupBy, Limit = limit, Offset = offset } }; string requestKey = JsonSerializer.Serialize(request, this._serializerOptions).GetHashCode().ToString(); this._objectCache.Insert(requestKey, request); object result = XlCall.RTD(nameof(ViewServerRtdServer), null, viewServer, requestKey); if (result == null) { return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); } if (result is string dataKey) { DataContainer?dataContainer = (DataContainer?)this._objectCache.Extract(dataKey); if (dataContainer == null || !(dataContainer.Data is IReadOnlyList <object> list) || list.Count == 0) { return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); } string[] columns = fields.Split(','); object[,] output = new object[list.Count, columns.Length]; for (int row = 0; row < list.Count; row++) { IReadOnlyDictionary <string, object?> item = (IReadOnlyDictionary <string, object?>)list[row]; for (int column = 0; column < columns.Length; column++) { output[row, column] = item.TryGetValue(columns[column], out object?value) && value != null ? value : ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNull); } } return(output); } return(result); }
// Understanding newValues // ----------------------- // As input: If Excel has a cached value to display, newValues passed in will be false. // On return: if newValues is now false, Excel will use cached value if it has one, (else #N/A if passed in as true) // if newValues is now true, Excel will use the value returned by ConnectData. object IRtdServer.ConnectData(int topicId, ref Array strings, ref bool newValues) { try { // Check for an active topic with the same topicId // - this is unexpected, but is reported as a bug in an earlier Excel version. // (Thanks ngm) // (Does not address the Excel 2010 bug documented here: // http://social.msdn.microsoft.com/Forums/en-US/exceldev/thread/ba06ac78-7b64-449b-bce4-9a03ac91f0eb/ // fixed by hotfix: http://support.microsoft.com/kb/2405840 and SP1. This problem is fixed by the ExcelRtd2010BugHelper. ) if (_activeTopics.ContainsKey(topicId)) { using (XlCall.Suspend()) { ((IRtdServer)this).DisconnectData(topicId); } } List <string> topicInfo = new List <string>(strings.Length); for (int i = 0; i < strings.Length; i++) { topicInfo.Add((string)strings.GetValue(i)); } Topic topic; using (XlCall.Suspend()) { // We create the topic, but what if its value is set here...? topic = CreateTopic(topicId, topicInfo); } if (topic == null) { Logger.RtdServer.Error("Error in RTD server {0} CreateTopic returned null.", GetType().Name); // Not sure what to return here for error. We try the COM error version of #VALUE !? return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue)); } _activeTopics[topicId] = topic; using (XlCall.Suspend()) { return(ConnectData(topic, topicInfo, ref newValues)); } } catch (Exception e) { Logger.RtdServer.Error("Error in RTD server {0} ConnectData: {1}", GetType().Name, e.ToString()); // Not sure what to return here for error. We try the COM error version of #VALUE !? return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue)); } }
public ViewServerTopic(ViewServerRtdServer server, int topicId, string viewServer, Request request) : base(server, topicId, ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)) { this._connection = Container.Instance.Resolve <ViewServerConnection>(); this._objectCache = Container.Instance.Resolve <IObjectCache>(); this._viewServer = viewServer; switch ((this._request = request).Command) { case Command.Select: this._connection.DataResponseReceived += this.Connection_DataResponseReceived; break; case Command.Metadata: this._connection.MetadataResponseReceived += this.Connection_MetadataResponseReceived; break; } }
protected override object ConnectData(Topic topic, IList <string> topicInfo, ref bool newValues) { // Retrieve and store the GUID from the topic's first info string - used to hook up to the Async state Guid id = new Guid(topicInfo[0]); _topicGuids[topic] = id; // Create a new ExcelRtdObserver, for the Topic, which will listen to the Observable // (Internally also set initial value - #N/A for now) ExcelRtdObserver rtdObserver = new ExcelRtdObserver(topic); // ... and subscribe it AsyncObservableImpl.ConnectObserver(id, rtdObserver); // Return something: #N/A for now. Not currently used. // TODO: Allow customize? return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); }
object FixValue(object value) { if (value is ExcelError) { value = ExcelErrorUtil.ToComError((ExcelError)value); } // Long strings will cause the topic update to fail horribly. // See: http://social.msdn.microsoft.com/Forums/nl-BE/exceldev/thread/436f1aa4-c950-4486-ba58-22a6a12fbf19 // We truncate long strings. string valueString = value as string; if (valueString != null && valueString.Length > 255) { value = valueString.Substring(0, 255); } // CONSIDER: Check valid data types return(value); }
// 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); } }
protected internal Topic(ExcelRtdServer server, int topicId) { _server = server; _topicId = topicId; _value = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA); }
// Understanding newValues // ----------------------- // As input: If Excel has a cached value to display, newValues passed in will be false. // On return: if newValues is now false, Excel will use cached value if it has one, (else #N/A if passed in as true) // if newValues is now true, Excel will use the value returned by ConnectData. object IRtdServer.ConnectData(int topicId, ref Array strings, ref bool newValues) { try { // Check for an active topic with the same topicId // - this is unexpected, but is reported as a bug in an earlier Excel version. // (Thanks ngm) // (Does not address the Excel 2010 bug documented here: // http://social.msdn.microsoft.com/Forums/en-US/exceldev/thread/ba06ac78-7b64-449b-bce4-9a03ac91f0eb/ // fixed by hotfix: http://support.microsoft.com/kb/2405840 and SP1. This problem is fixed by the ExcelRtd2010BugHelper. ) if (_activeTopics.ContainsKey(topicId)) { using (XlCall.Suspend()) { ((IRtdServer)this).DisconnectData(topicId); } } List <string> topicInfo = new List <string>(strings.Length); for (int i = 0; i < strings.Length; i++) { topicInfo.Add((string)strings.GetValue(i)); } Topic topic; using (XlCall.Suspend()) { // We create the topic, but what if its value is set here...? topic = CreateTopic(topicId, topicInfo); } if (topic == null) { Logger.RtdServer.Error("Error in RTD server {0} CreateTopic returned null.", GetType().Name); // Not sure what to return here for error. We try the COM error version of #VALUE !? return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue)); } // NOTE: 2016-11-04 // Before v 0.34 the topic was added to _activeTopics before ConnectData was called // The effect of moving it after (hence that topic is not in _activeTopics during the ConnectData call) // is that a call to UpdateValue during the the ConnectData call will no longer cause an Update call to Excel // (since SetDirty is ignored for topics not in _activeTopics) object value; using (XlCall.Suspend()) { value = ConnectData(topic, topicInfo, ref newValues); } _activeTopics[topicId] = topic; // Now we need to ensure that the topic value does indeed agree with the returned value // Otherwise we are left with an inconsistent state for future updates. // If there's a difference, we do force the update. if (!object.Equals(value, topic.Value)) { // 2020-03-03 v1.1 // Changing from topic.UpdateNotify, which no longer (in recent Excel) seems to work on its own. // NOTE: In the unusual case that the FixValue inside Topic makes value == topic.Value, we won't get an update here // E.g. for long strings that are truncated // topic.UpdateValue(value); } return(value); } catch (Exception e) { Logger.RtdServer.Error("Error in RTD server {0} ConnectData: {1}", GetType().Name, e.ToString()); // Not sure what to return here for error. We try the COM error version of #VALUE !? return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue)); } }
//New RTD subscription callback protected override object ConnectData(Topic topic, IList <string> topicInfo, ref bool newValues) { object result; int topicId = GetTopicId(topic); Logging.Log("ConnectData: {0} - {{{1}}}", topicId, string.Join(", ", topicInfo)); //parse and validate request //--- //count and default parameters if (topicInfo.Count < 2) { return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); //need at least a product and a datapoint } else if (topicInfo.Count == 2) { topicInfo.Add("0"); //default to top level of book } //parse parameters string product = topicInfo[0]; DataPoint dataPoint; bool dataPointOk = Enum.TryParse <DataPoint>(topicInfo[1], out dataPoint); int level; bool levelOk = Int32.TryParse(topicInfo[2], out level); //return error if parameters are invalid if (!dataPointOk || !levelOk) { return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); //incorrect level or datapoint request } if (level > 9 || level < 0) { return(ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA)); //level out of range } //store subscription request //--- //if product has not yet been subscribed, create subscription container if (!_topics.ContainsKey(product)) { _topics[product] = new TopicCollection(); } //store subscription request _topics[product].TopicItems[Tuple.Create(dataPoint, level)] = topic; if (_topicDetails.ContainsKey(topicId)) { Logging.Log("ERROR: duplicate topicId: {0} {1}", topicId, _topicDetails[topicId]); } _topicDetails.Add(topicId, new TopicSubscriptionDetails(product, dataPoint, level)); //return data //-- if (dataPoint == DataPoint.Last) { if (!_cache.Instruments.ContainsKey(product)) { //may be empty when sheet is first loaded result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorGettingData); //return "pending data" to Excel } else { //get data from cache BitMexInstrument inst = _cache.Instruments[product]; switch (dataPoint) { case DataPoint.Last: result = inst.lastPrice; break; default: result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA); break; } } } else //bid/ask etc { if (!_cache.MarketData.ContainsKey(product)) { //if product is not yet in cache, request snapshot //this can happen if a product doesnt update very frequently _api.GetSnapshot(product); result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorGettingData); //return "pending data" to Excel } else { //get data from cache MarketDataSnapshot snap = _cache.MarketData[product]; switch (dataPoint) { case DataPoint.Bid: result = snap.BidDepth[level].Price; break; case DataPoint.BidVol: result = snap.BidDepth[level].Qty; break; case DataPoint.Ask: result = snap.AskDepth[level].Price; break; case DataPoint.AskVol: result = snap.AskDepth[level].Qty; break; default: result = ExcelErrorUtil.ToComError(ExcelError.ExcelErrorNA); break; } } } return(result); }
// Forwarded from XlCall // Loads the RTD server with temporary ProgId. public static object RTD(string progId, string server, params string[] topics) { // Check if this is any of our business. if (!string.IsNullOrEmpty(server) || !registeredRtdServerTypes.ContainsKey(progId)) { // Just pass on to Excel. return(CallRTD(progId, null, topics)); } // Check if already loaded. if (loadedRtdServers.ContainsKey(progId)) { // Call Excel using the synthetic RtdSrv.xxx (or actual from attribute) ProgId return(CallRTD(loadedRtdServers[progId], null, topics)); } // Not loaded already - need to get the Rtd server loaded // TODO: Need to reconsider registration here..... // Sometimes need stable ProgIds. Type rtdServerType = registeredRtdServerTypes[progId]; object 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 sever 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); // 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; if (TryCallRTD(out result, progIdRegistered, null, topics)) { // Mark as loaded - ServerTerminate in the wrapper will remove. // TODO: Consider multithread race condition... loadedRtdServers[progId] = progIdRegistered; } 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)); } }
object?IDataFunctions.GetLastPrice(string?symbol) { return(string.IsNullOrEmpty(symbol) ? ExcelErrorUtil.ToComError(ExcelError.ExcelErrorValue) : this.GetData("marketdata", "marketdataWindow", "last", $"symbol='{symbol}'")); }
public override object ToComError(ExcelError excelError) { return(ExcelErrorUtil.ToComError(excelError)); }