I am trying to import around 1500 Excel files to my system.
The code is working in a loop and I am able open and import around 600 Excel files. After that I am getting an error message like: Error calling external object function open at line 55.....
I really stuck with this issue, if anyone can help that will be grateful.
Code posted in reply comments:
For ll_LoopCnt = 1 To Dw_1.rowcount( )
Ls_File_Name = Dw_1.getitemstring( ll_LoopCnt, "file_name")
Ls_Path =Dw_1.getitemstring( ll_LoopCnt, "file_path")
ll_Sr_No= Dw_1.getitemNumber( ll_LoopCnt, "sr_no")
ldt_File_Date= Dw_1.getitemDateTime( ll_LoopCnt, "file_date")
Excel.Application.DisplayAlerts = "False"
Excel.WorkBooks.Open( Ls_Path )
Excel.Application.Visible = False
Excel.windowstate = 2 // 1 : Normal, 2 : Minimize, 3 : Maximize
Excel.Application.CutCopyMode = False
Lb_sheet_rtn = excel.worksheets(7).Activate
Ls_ClipBoard = clipboard()
Excel.Application.ActiveWorkbook.Save()
Excel.Worksheets(7).UsedRange.Copy
ll_cnt = ds_1.importclipboard()
IF ll_cnt <= 1 THEN
Messagebox("Error", "Could not find.")
Else
Dw_1.Scrolltorow( ll_LoopCnt )
Dw_1.SetItem( ll_LoopCnt, "status", 'Success')
For ll_Inner_LoopCnt = 1 To Ds_1.RowCount( )
Ds_1.Object.file_path[ll_Inner_LoopCnt] = Ls_Path
Ds_1.Object.file_name[ll_Inner_LoopCnt] = Ls_File_Name
Ds_1.Object.file_sr_no[ll_Inner_LoopCnt] = ll_Sr_No
Ds_1.Object.file_date[ll_Inner_LoopCnt] = ldt_File_Date
Next
END IF
Clipboard(ls_ClipBoard)
Ds_1.Reset( ) //Reset the data store
Excel.Application.ActiveWorkbook.Save()
Excel.Application.ActiveWorkbook.Close(False);
Excel.Application.Quit
Excel.Application.CutCopyMode = False
IF ll_LoopCnt = ll_Excel_Cnt Then //--->> After 100 files reset the memmory
ll_Excel_Cnt = ll_LoopCnt + 100
Excel.DisConnectObject()
DESTROY excel
DESTROY TEst_Excel
GarbageCollect ( )
Excel = Create OLEObject
Test_Excel = Create OLEObject
Li_rtn = excel.ConnectToNewObject("excel.application")
IF li_rtn <> 0 THEN
MessageBox('Excel error','can not run Excel Program')
DESTROY Excel
RETURN 0
END IF
End IF
Next
Excel.displayalerts = False
Excel.Application.Quit
Excel.displayalerts = True
Excel.DisConnectObject()
DESTROY Excel
DESTROY Test_Excel /* This is the code i written i dont think the OLE is crashing i think the connnectto the OLE is getting lost after some time, but stile its going fine for almost 600 records.. */
Seeing the line of code would help, but this error message typically (in the context of OLE, which I'm guessing is the case here) comes from PowerBuilder making an OLE call which the OLE host rejects. From the information you've supplied, it's impossible to tell if the OLE host has crashed and isn't responding anymore, or if you've got the OLE host into a state where these functions are no longer applicable, or if the OLE object has become invalid, or what.
If it were me, and it was happening consistently, I'd run the app in the debugger to get to the state where the error is about to happen (you can set advanced attributes in breakpoints to not have a breakpoint activate every time it is passed) and try interrogating the OLE objects. I'd expect you'd also have to throw in some test code, since I'm not confident everything you'd want to test would be available to the debugger.
New Feb 21
I'd also change the set of files being processed, so that I could tell if the key to the crash is a specific file, or the quantity of files processed. For example, if you get rid of the first 100 files, does it still crash on the 600th file (same quantity) or the 500th file (same file)?
One possibility is that you're running out of memory. Each "dot" in an OLE reference (attribute access, method call) creates an object in memory that isn't destroyed until the garbage collect. The code clip you've posted will never enter the block where the GarbageCollect() is called (ll_Excel_Cnt is never initialized), so you might want to make sure that part is working. I'd also get rid of unnecessary calls. For example, you've got several calls that maintain the state of Excel within the loop (e.g. Excel.Application.Visible), when they only need to be called once. I'm also not clear from the code clip why you'd need to call a Save(), either time; this could be expendable as well. This clean up should also make your code run faster.
Good luck,
Terry
Around line 30 you have
Excel.Application.ActiveWorkbook.Save()
Excel.Application.ActiveWorkbook.Close(False);
Excel.Application.Quit
You shouldn't call Excel.Application.Quit there. Also, I always recommend to put anything that uses OLE inside a Try..Catch block and catch OleRuntimeError and RuntimeError.
Related
I have a VB.net application which needs to read and write Excel workbooks, using OLEDB and the MS Office Access Database driver. Over time, I have learned a vast amount here on StackOverflow which allowed me to get everything working the way it should be. Those topics covered everything from connectionstrings to the "magic" TypeGuessRows setting / 255 character limitation, now located in the registry. Over time, that issue by itself has morphed due to the evolutions by Microsoft over the years, so I won't reference those (hundreds? thousands?) of posts here. Suffice it to say that I was able to piece it all together and get it working.
For reference: my connectionstring looks like this:
"Provider=Microsoft.ACE.OLEDB.12.0;Data Source='#DBQ';Extended Properties='Excel 12.0 Xml;HDR=YES';"
where '#DBQ' is replaced at runtime by the appropriate pathname of the selected Excel workbook. Note that it does not contain TypeGuessRows (since that has moved to the registry), and IMEX=(whatever) has not effect either, so it does not appear. The result is that everything works quite well, AS LONG AS TYPEGUESSROWS is set to zero in the registry.
Using regedit to manually hunt down and set TypeGuessRows=0 in all occurrences (one of the things I learned about here on StackOverflow), this has worked and avoids the 255-char truncation perfectly.
The problem is that whenever there is a Windows update, it gets set back to the default value, 8. There is no warning or announcement of this; I only know about it when I start seeing the "truncated at 255 characters" symptom showing up again. So then I have to go back and use RegEdit again.
The obvious solution would be that the app does this setting on its own, but that seems to require manipulation of the Security features, which is something I'd prefer to avoid entirely as it seems to be more complex than I want to deal with presently.
So, I thought, "why not just scan the appropriate registry keys, and report if they are not zero", then issue a message to the user to get it sorted using regedit.
I wrote the following function, which looks at all of the various registry keys I have found which contain TypeGuessRows:
Public Function CheckRegistry() As Integer
Dim Val1 As Integer = 0, Val2 As Integer = 0
Dim KeysToCheck As String() = New String() {
"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Jet\4.0\Engines\Excel",
"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Jet\4.0\Engines\Lotus",
"HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Office\14.0\Access Connectivity Engine\Engines\Excel",
"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Wow6432Node\Microsoft\Office\16.0\Access Connectivity Engine\Engines\Excel"
}
'
' try two methods for checking the registry values.
'
' method 1 uses the My.Computer class
'
For Each Key As String In KeysToCheck
Dim aVal As Object = My.Computer.Registry.GetValue(Key, "TypeGuessRows", CType("BOGUS", Object))
If IsNothing(aVal) Then ' did we hit Nothing?
Continue For ' have to skip it
ElseIf aVal.Equals(CType("BOGUS", Object)) Then ' did we hit one not exist?
Val1 = -9999 ' set a crazy value
End If
Val1 += CInt(aVal) ' otherwise, get its value and add to total
Next
'
' method 2 uses the RegistryKey class
'
Dim aKey As Microsoft.Win32.RegistryKey
For Each Key As String In KeysToCheck
Dim LM As Microsoft.Win32.RegistryKey = Microsoft.Win32.Registry.LocalMachine ' point to HKLM
Key = Key.TrimStart("HKEY_LOCAL_MACHINE\".ToCharArray) ' peel off the start (a convenience)
aKey = LM.OpenSubKey(Key, Microsoft.Win32.RegistryKeyPermissionCheck.ReadSubTree) ' try to read the subkey
If IsNothing(aKey) Then Continue For ' did we hit Nothing? have to skip it
'If CInt(aKey.GetValue("TypeGuessRows", 0)) <> 0 Then aKey.SetValue("TypeGuessRows", 0)
Val2 += CInt(aKey.GetValue("TypeGuessRows", 0)) ' otherwise, get its value and add to total
Next
Return Val1 + Val2 ' return the sum of both methods
End Function
This works well enough, EXCEPT FOR THE LAST ONE, the one with "...ClickToRun..." in the key name.
No matter what, this ALWAYS returns as Nothing, so the attempt to check it fails. Even in the first method, where I specifically ask it to return a "BOGUS" object, it always returns as Nothing.
The problem is that it is THIS KEY which gets updated by Windows Update. Thus far I have never seen any of the other three get reset.
If I "walk the tree" of this key (i.e., try looking at "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Office"), it works fine until I hit the ClickToRun branch, at which point it just returns Nothing.
So, I guess I have two questions:
Is there a way to read this key so that it does not return as Nothing?
Can someone provide a (hopefully not too messy / complex) guide to getting the Security settings correct to allow "ReadWriteSubTree" for the permission check? N.B. if I try that now, I get Security exception, telling me that the app does not have the appropriate permission. That's why the aKey.SetValue line is commented out.
Further info:
the machine is Win 10 Pro, 64-bit, with appropriate drivers installed. The app is set to "Enable ClickToRun security settings", and "full trust application". I have played with those settings and the appropriate entries in app.manifest, but thus far that only causes (much) other grief and difficulties elsewhere. That's why I'd prefer to not mess with it.
Any help would be really appreciated.
After some sleep, some more thought and in particular, some hints found [here]
Reading 64bit Registry from a 32bit application I managed to at least get the ability to read the registry keys.
The problem was that I was trying to "read" 64-bit registry keys from my 32-bit app.
Now I can do what I really wanted to do, which was to detect when the TypeGuessRows issue has cropped up again, and warn the user to do something about it.
Here is the modified code:
Public Function CheckRegistry() As Integer
Dim CheckVal As Integer = 0
Dim KeysToCheck As String() = New String() {
"SOFTWARE\WOW6432Node\Microsoft\Jet\4.0\Engines\Excel",
"SOFTWARE\WOW6432Node\Microsoft\Jet\4.0\Engines\Lotus",
"SOFTWARE\WOW6432Node\Microsoft\Office\14.0\Access Connectivity Engine\Engines\Excel",
"SOFTWARE\Microsoft\Office\ClickToRun\REGISTRY\MACHINE\Software\Wow6432Node\Microsoft\Office\16.0\Access Connectivity Engine\Engines\Excel"
}
Dim LMachine As Microsoft.Win32.RegistryKey =
Microsoft.Win32.RegistryKey.OpenBaseKey(Microsoft.Win32.RegistryHive.LocalMachine,
Microsoft.Win32.RegistryView.Registry64)
Dim LKey As Microsoft.Win32.RegistryKey
For Each Key As String In KeysToCheck
Try
LKey = LMachine.OpenSubKey(Key, Microsoft.Win32.RegistryKeyPermissionCheck.ReadSubTree)
CheckVal += CInt(LKey.GetValue("TypeGuessRows", 0))
Catch ex As Exception
LMachine.Close()
Return -9999
End Try
Next
LMachine.Close()
Return CheckVal
End Function
In testing this out, it can successfully check all four of the keys shown. The try-catch is never fired, but I left it there for safety anyway.
I hope that someone else might find this useful.
I have this code from a program that was created to take data and export it into excel it is in vb6 and I dont know much about vb6 I started coding in vb.net could someone tell me why this isnt working with excel 2013 it opens but then closes right away and i am unsure as to why.
Sub GetExcel()
Dim MyExcel As Object ' Variable to hold reference
' to Microsoft Word.
Dim ExcelWasNotRunning As Boolean ' Flag for final release.
' Test to see if there is a copy of Microsoft Excel already running.
10 On Error Resume Next ' Defer error trapping.
' Getobject function called without the first argument returns a
' reference to an instance of the application. If the application isn't
' running, an error occurs.
20 Set MyExcel = GetObject(, "XLMAIN")
30 If Err.Number <> 0 Then ExcelWasNotRunning = True
40 Err.Clear ' Clear Err object in case error occurred.
' Check for Microsoft Excel. If Microsoft Excel is running,
' enter it into the Running Object table.
50 DetectExcel
' Set the object variable to reference the file you want to see.
60 Set MyExcel = GetObject(App.Path & "\test.xls")
' Show Microsoft Word through its Application property. Then
' show the actual window containing the file using the Windows
' collection of the MyWord object reference.
' MyExcel.Application.Visible = True
' MyExcel.document(1).Visible = True
70 MyExcel.Show , f1
'//////////////////////////////////////////////
' Do manipulations of your file here.
'//////////////////////////////////////////////
' ...
' If this copy of Microsoft Excel was not running when you
' started, close it using the Application property's Quit method.
' Note that when you try to quit Microsoft Excel, the
' title bar blinks and a message is displayed asking if you
' want to save any loaded files.
80 If ExcelWasNotRunning = True Then
90 MyExcel.Application.Quit
100 End If
110 Set MyExcel = Nothing ' Release reference to the
' application and spreadsheet.
End Sub
Sub DetectExcel()
' Procedure dectects a running Word and registers it.
Const WM_USER = 1024
Dim hwnd As Long
' If Excel is running this API call returns its handle.
10 hwnd = FindWindow("XLMAIN", 0)
20 If hwnd = 0 Then ' 0 means Word not running.
30 Exit Sub
40 Else
' Word is running so use the SendMessage API
' function to enter it in the Running Object Table.
50 SendMessage hwnd, WM_USER + 18, 0, 0
60 End If
End Sub
even if i can get some direction on how to rewrite this it would be appreciated.
It's been a long time since I've used it, so you may need to address some of the finer points... But:
First and foremost - get rid of the On Error Resume Next it masks whatever happens next.
This is not a situation where you want to have errors ignored. Capture the error, show something useful (not debug information and not details that can be used for hacking) to the user, and resume or return (after cleanup of assigned object variables to avoid memory leaks).
Then step through the code to see what errors you get.
Some of the things that may help:
Change the specification in your GetObject to be "Excel.Application" i.e.
Set MyExcel = GetObject(, "Excel.Application")
Instead of MyExcel.Show which may or may not be supported, use actual Excel objects and their methods and properties, for instance the commented MyExcel.Application.Visible = True works, while I question the MyExcel.document(1).Visible = True.
Look up the Excel object model help for details. And never hardcode the index - obtain the actual reference that you want and use it.
You can still find articles about Excel and VB6 on line. Use your search engine of choice and good luck.
Beware of those who say something is impossible - first see if it works. Some say you can's install VB6 on a 64-bit system - that is not true, but there are some issues getting it to work. Some say you can't interact with 64-bit Office from VB6, but that is not true. Maybe you can't do some things - I haven't done much with it, just enough to know it is possible to do some things.
Consider developing and testing with typed objects in VB6. It can be very helpful, but to make the application version independent, you will need to remove the object typing before final test and deployment.
I'm finding that when I read the GlobalAppointmentID of a recurring event in Outlook via my Excel-VBA code, things start acting odd.
Here's part of the code that I've been using to try to understand the root cause of this behavior:
For Each otItem In resCal
If otItem.Class = olAppointment Then
Set cItm = otItem
If calItem.Subject = "Coffee Date" Then
Debug.Print "Coffee orig " & cItm.Start
Debug.Print "Coffee orig 1.1 " & cItm.Start
Debug.Print cItm.GlobalAppointmentID
Debug.Print cItm.GlobalAppointmentID
Debug.Print "Coffee orig 1.5 " & cItm.Start
End If
End If
Next otItem
This produces the following output:
Coffee orig 4/16/2015 10:00:00 AM
Coffee orig 1.1 4/16/2015 10:00:00 AM
[Global Appointment ID redacted, but identical]
[Global Appointment ID redacted, but identical]
Coffee orig 1.5 2/19/2015 10:00:00 AM
4/16 is the instance of the recurring event that I would like to record. However, when I call the GlobalAppointmentID property, it changes the value of the .Start property to that of the first instance of the recurring event. I've tested and can implement a workaround that involves me storing the start date in a variable before calling GlobalAppointmentID, however I would like to try to diagnose and fix the root cause of the issue to ensure that it isn't causing any problems that I haven't noticed yet. Can anyone shed some light on what is causing this issue?
Thanks!
the problem does not occur when I call the AppointmentItem by index instead of looping via For Each
I'd recommend using the for loop instead and releasing the object as soon as possible. This is particularly important if your code attempts to enumerate more than 256 Outlook items in a collection that is stored on a Microsoft Exchange Server. If you do not release these objects in a timely manner, you can reach the limit imposed by Exchange on the maximum number of items opened at any one time. Declare an object in the function scope (loop scope) and set it to Nothing to release the reference to the object as soon as possible.
I recently took another look at my code and determined the source of the problem. Here’s the snippets of code that were causing the problem:
With oItms
.Sort "[Start]", False
.IncludeRecurrences = True
End With
Set rCal = oItms.Restrict("[Start] >= '" & CStr(date) & "'")
With rCal
.Sort "[Start]", False
.IncludeRecurrences = True
End With
Specifically, running the “.Sort” and the “.IncludeRecurrances” on rCal without calling the “.Restrict” method caused the problem. I determined that the 2nd “With” was redundant as is, so I removed it. I can now call any property in any order, without having to worry about the values changing.
I've got a problem with a vbscript which creates Excel objects and reads from an Excel file.
The vbscript is executed from an Excel macro, and then creates and opens the Excel file and reads from it. The problem is that the Excel object isn't allways closed, even though I'm trying to to it.
Here's the code from the vbscript:
Set ExcelObject = createobject("Excel.Application")
ExcelObject.workbooks.open testWorkBookPath
Set testActionArray = CreateObject( "System.Collections.ArrayList" )
Function getTestsCaseActions (testsPath, esheet, row, col)
Set my_sheet = ExcelObject.sheets.item(esheet)
tempArray = array(my_sheet.cells(row, col-2), "")
testActionArray.Add tempArray
Do While my_sheet.cells(row, col).Value <> ""
tempArray = array(my_sheet.cells(row, col), my_sheet.cells(row+1, col))
testActionArray.Add tempArray
col = col+1
Loop
End Function
getTestsCaseActions testWorkBookPath, testCaseSheet, 3, 4
ExcelObject.Quit
Now, if I run the above code and watch the process explorer, a new Excel process is spawned when the script is started, and then closes, as expected.
However, if I insert this code after running the function, before the ExcelObject.Quit line:
For Each ArrayItem in testActionArray
IF ArrayItem(1) = "" Then
Wscript.Echo ArrayItem(0)
Else
Wscript.Echo ArrayItem(0) & " -> " & ArrayItem(1)
End If
Next
ExcelObject.Quit (STILL HERE)
then the spawned process does NOT quit, and the process list grows until Excel goes completely bananas.
I don't understand this; All the last bit of code does is loop through the ArrayList and print the contents. Why's not the process quitting?
EDIT: At seems that at least some of the Excel objects eventually disappear from the Process Explorer, but this takes about 20-30 minutes. And it's just a few of them - most are still there. At least my list at the moment has shrinked some, but there are still about 15 Excel processes running.
Also, suddenly this message appears:
File Now Available
'filename.xlsm ' is now available for editing. Choose Read-Write to open it for editing.
This line seems to help. It doesn't completely remove all extra processes, but the number grows to five, then goes back to two, and so on. So it works pretty well.
dim book: for each book in ExcelObject.Workbooks: book.saved = true: next
I have created a VSTO plugin with my own RTD implementation that I am calling from my Excel sheets. To avoid having to use the full-fledged RTD syntax in the cells, I have created a UDF that hides that API from the sheet.
The RTD server I created can be enabled and disabled through a button in a custom Ribbon component.
The behavior I want to achieve is as follows:
If the server is disabled and a reference to my function is entered in a cell, I want the cell to display Disabled.
If the server is disabled, but the function had been entered in a cell when it was enabled (and the cell thus displays a value), I want the cell to keep displaying that value.
If the server is enabled, I want the cell to display Loading.
Sounds easy enough. Here is an example of the - non functional - code:
Public Function RetrieveData(id as Long)
Dim result as String
// This returns either 'Disabled' or 'Loading'
result = Application.Worksheet.Function.RTD("SERVERNAME", "", id)
RetrieveData = result
If(result = "Disabled") Then
// Obviously, this recurses (and fails), so that's not an option
If(Not IsEmpty(Application.Caller.Value2)) Then
// So does this
RetrieveData = Application.Caller.Value2
End If
End If
End Function
The function will be called in thousands of cells, so storing the 'original' values in another data structure would be a major overhead and I would like to avoid it. Also, the RTD server does not know the values, since it also does not keep a history of it, more or less for the same reason.
I was thinking that there might be some way to exit the function which would force it to not change the displayed value, but so far I have been unable to find anything like that.
EDIT:
Due to popular demand, some additional info on why I want to do all this:
As I said, the function will be called in thousands of cells and the RTD server needs to retrieve quite a bit of information. This can be quite hard on both network and CPU. To allow the user to decide for himself whether he wants this load on his machine, they can disable the updates from the server. In that case, they should still be able to calculate the sheets with the values currently in the fields, yet no updates are pushed into them. Once new data is required, the server can be enabled and the fields will be updated.
Again, since we are talking about quite a bit of data here, I would rather not store it somewhere in the sheet. Plus, the data should be usable even if the workbook is closed and loaded again.
Different tack=new answer.
A few things I've discovered the hard way, that you might find useful:
1.
In a UDF, returning the RTD call like this
' excel equivalent: =RTD("GeodesiX.RTD",,"status","Tokyo")
result = excel.WorksheetFunction.rtd( _
"GeodesiX.RTD", _
Nothing, _
"geocode", _
request, _
location)
behaves as if you'd inserted the commented function in the cell, and NOT the value returned by the RTD. In other words, "result" is an object of type "RTD-function-call" and not the RTD's answer. Conversely, doing this:
' excel equivalent: =RTD("GeodesiX.RTD",,"status","Tokyo")
result = excel.WorksheetFunction.rtd( _
"GeodesiX.RTD", _
Nothing, _
"geocode", _
request, _
location).ToDouble ' or ToString or whetever
returns the actual value, equivalent to typing "3.1418" in the cell. This is an important difference; in the first case the cell continues to participate in RTD feeding, in the second case it just gets a constant value. This might be a solution for you.
2.
MS VSTO makes it look as though writing an Office Addin is a piece of cake... until you actually try to build an industrial, distributable solution. Getting all the privileges and authorities right for a Setup is a nightmare, and it gets exponentially worse if you have the bright idea of supporting more than one version of Excel. I've been using Addin Express for some years. It hides all this MS nastiness and let's me focus on coding my addin. Their support is first-rate too, worth a look. (No, I am not affiliated or anything like that).
3.
Be aware that Excel can and will call Connect / RefreshData / RTD at any time, even when you're in the middle of something - there's some subtle multi-tasking going on behind the scenes. You'll need to decorate your code with the appropriate Synclock blocks to protect your data structures.
4.
When you receive data (presumably asynchronously on a separate thread) you absolutely MUST callback Excel on the thread on which you were intially called (by Excel). If you don't, it'll work fine for a while and then you'll start getting mysterious, unsolvable crashes and worse, orphan Excels in the background. Here's an example of the relevant code to do this:
Imports System.Threading
...
Private _Context As SynchronizationContext = Nothing
...
Sub New
_Context = SynchronizationContext.Current
If _Context Is Nothing Then
_Context = New SynchronizationContext ' try valiantly to continue
End If
...
Private Delegate Sub CallBackDelegate(ByVal GeodesicCompleted)
Private Sub GeodesicComplete(ByVal query As Query) _
Handles geodesic.Completed ' Called by asynchronous thread
Dim cbd As New CallBackDelegate(AddressOf GeodesicCompleted)
_Context.Post(Function() cbd.DynamicInvoke(query), Nothing)
End Sub
Private Sub GeodesicCompleted(ByVal query As Query)
SyncLock query
If query.Status = "OK" Then
Select Case query.Type
Case Geodesics.Query.QueryType.Directions
GeodesicCompletedTravel(query)
Case Geodesics.Query.QueryType.Geocode
GeodesicCompletedGeocode(query)
End Select
End If
' If it's not resolved, it stays "queued",
' so as never to enter the queue again in this session
query.Queued = Not query.Resolved
End SyncLock
For Each topic As AddinExpress.RTD.ADXRTDTopic In query.Topics
AddinExpress.RTD.ADXRTDServerModule.CurrentInstance.UpdateTopic(topic)
Next
End Sub
5.
I've done something apparently akin to what you're asking in this addin. There, I asynchronously fetch geocode data from Google and serve it up with an RTD shadowed by a UDF. As the call to GoogleMaps is very expensive, I tried 101 ways and several month's of evenings to keep the value in the cell, like what you're attempting, without success. I haven't timed anything, but my gut feeling is that a call to Excel like "Application.Caller.Value" is an order of magnitude slower than a dictionary lookup.
In the end I created a cache component which saves and re-loads values already obtained from a very-hidden spreadsheet which I create on the fly in Workbook OnSave. The data is stored in a Dictionary(of string, myQuery), where each myQuery holds all the relevant info.
It works well, fulfils the requirement for working offline and even for 20'000+ formulas it appears instantaneous.
HTH.
Edit: Out of curiosity, I tested my hunch that calling Excel is much more expensive than doing a dictionary lookup. It turns out that not only was the hunch correct, but frighteningly so.
Public Sub TimeTest()
Dim sw As New Stopwatch
Dim row As Integer
Dim val As Object
Dim sheet As Microsoft.Office.Interop.Excel.Worksheet
Dim dict As New Dictionary(Of Integer, Integer)
Const iterations As Integer = 100000
Const elements As Integer = 10000
For i = 1 To elements + 1
dict.Add(i, i)
Next
sheet = _ExcelWorkbook.ActiveSheet
sw.Reset()
sw.Start()
For i As Integer = 1 To iterations
row = 1 + Rnd() * elements
Next
sw.Stop()
Debug.WriteLine("Empty loop " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")
sw.Reset()
sw.Start()
For i As Integer = 1 To iterations
row = 1 + Rnd() * elements
val = sheet.Cells(row, 1).value
Next
sw.Stop()
Debug.WriteLine("Get cell value " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")
sw.Reset()
sw.Start()
For i As Integer = 1 To iterations
row = 1 + Rnd() * elements
val = dict(row)
Next
sw.Stop()
Debug.WriteLine("Get dict value " & (sw.ElapsedMilliseconds * 1000) / iterations & " uS")
End Sub
Results:
Empty loop 0.07 uS
Get cell value 899.77 uS
Get dict value 0.15 uS
Looking up a value in a 10'000 element Dictionary(Of Integer, Integer) is over 11'000 times faster than fetching a cell value from Excel.
Q.E.D.
Maybe... Try making your UDF wrapper function non-volatile, that way it won't get called unless one of its arguments changes.
This might be a problem when you enable the server, you'll have to trick Excel into calling your UDF again, it depends on what you're trying to do.
Perhaps explain the complete function you're trying to implement?
You could try Application.Caller.Text This has the drawback of returning the formatted value from the rendering layer as text, but seems to avoid the circular reference problem.Note: I have not tested this hack under all possible circumstances ...