I got switched to other project at work and I noticed that Delphi XE2 debugger does not show the line that raised exception. When i got back at home i started to investigate. Then I found out that it can be disabled in Tools -> Options -> Debugger options and check Integrated debugging. Also I unchecked everything under Language exceptions in Exception types to ignore list. Notify on Language Exceptions left checked. Project -> Options -> Compiling, I have defaults there and Overflow and Range cheking enabled. I am running Debug build. I Cleaned it.
I have not noticed before, but now Delphi debugger doesn't give me the line when I call this code:
procedure TForm1.BitBtn1Click(Sender: TObject);
var
_List: TStringList;
begin
_List := TStringList.Create;
try
Caption := _List[0]; // 'List index out of bounds (0)' here
finally
FreeAndNil(_List);
end;
end;
but this works (provided only to show that debugger does show the line for some things):
{$R+} // Range check is ON
procedure TForm1.BitBtn2Click(Sender: TObject);
var
_myArray: array [1 .. 5] of string;
i: integer;
begin
for i := 0 to 5 do
begin
_myArray[i] := 'Element ' + IntToStr(i); // Range check error here
ShowMessage('myArray[' + IntToStr(i) + '] = ' + _myArray[i]);
end;
end;
What is happening here? How to make the debugger to show as much as possible?
Thank you.
Let me answer the question first.
How to make the compiler show as much as possible
The compiler shows you that the error is in the call to the btnclick.
The trick is to put a breakpoint on the first line of the btnclick proc with F5.
Then rebuild (!) the application and run again.
The execution will stop that the breakpoint.
Step through the code using F8 until the error shows up.
Put a breakpoint F5 on the line that generated the error.
Abort and rerun the application.
When you get to the second breakpoint instead of pressing F8, press F7 to step inside the routine that causes the error, keep on pressing F7/F8 until you see what exactly is causing the problem.
Why is this happening?
The compiler traces back the source of the exception by following the stack trace.
Because in your case the code that generated the exception does not have stack trace (because it is not debug code), the compiler cannot do this trick and instead follows the stack trace that is does have; it moves one level up in your code and flags the exception there.
A look at this in detail
You're comparing apples and oranges.
This code (Exhibit A):
Caption := _List[0]; // 'List index out of bounds (0)' here
Has absolutely nothing in common with this code (Exhibit B):
_myArray: array [1 .. 5] of string;
....
_myArray[0]:= 'Hallo';
Exhibit A uses the TStringList class's items property, which is defined more or less as follows (I've simplified it a bit, but the fundamentals are correct):
type
TStringList = class(TStrings)
strict private
FList: array of string;
....
private
procedure Put(index: integer; const value: string);
function Get(index: integer): string;
published
property Items[index: integer]: string read Get write Put; default;
// ------------------------------------------------------^^^^^^^
....
end;
Notice the default keyword at the end of the Items property.
What this all means is that when you call _List[0], you are really calling _List.Items[0], which gets translated into Caption:= _List.Getitems(0), because of the read modifier on the property.
The default keyword allows you to omit the .Items.
The Get code looks like this:
function TStringList.Get(Index: Integer): string;
begin
if Cardinal(Index) >= Cardinal(FCount) then
Error(#SListIndexError, Index); <<-Here is the line that generates the error*
Result := FList[Index].FString;
end;
*Actually the error is generated inside the Error routine
Unless you have the RTL/VCL source code and you're running with debug DCU's you will not get a break on the exact trigger of the exception (which is inside the system.classes) unit.
Note that this error does not depend on range checking, it will always fire.
Because Delphi does not have debug info for the exact line where the error is generated, it does the next best thing and tries to make a guess.
Short version
The stringlist is a complex abstraction pretending to be an array.
Lots of code gets called, making the pinpointing of the error difficult for the compiler.
In Exhibit B:
_myArray: array [1 .. 5] of string;
....
i:= 0;
_myArray[i]:= 'Hallo';
Either a range check error or an access violation is generated.
Both of these errors occur on the exact line, allowing the compiler to stop at the correct spot.
Short version
The plain array is a basic building block with no hidden calls to code elsewhere, making pinpointing errors very easy for the compiler.
Understanding properties
class and record properties (and now class operators) look like simple assignments/operations to/with variables, but are in fact calls to (possibly complex) subroutines.
Related
I maintain an application that runs as a service in a server environment. It is multithreaded, where each thread does work according to a task queue. This task queue is just a list of strings with "job types" as their values. So while several threads may be running, each thread would be a different job, and each thread internally runs just one task at a time.
I'm experiencing an intermittent issue that happens when calling Open on a TADODataSet. Sometimes, not always, and with no discernible pattern, Data.Win.ADODB will throw an EArgumentOutOfRangeException, bypassing my own attempt to catch any exceptions. This exception hangs the entire thread and prevents future execution from being possible until I completely restart the service.
Being relatively new to the world of Delphi, I've been scratching my head at this issue for quite some time, and have struggled to find any answers. My question is: why is this happening, and how do I stop, catch, or fix it?
Here is a snippet of my offending code. This is the method from which the stack trace originates. It is called from another method in the same unit, where I open a different dataset, loop through its records, and on each record call this function to get some info based on the value passed in.
function TFQFoo.DoSomething(IncNo : Int64): string;
var
ItemList : string;
MySQL: string;
ComponentString: string;
begin
result:='';
if IncNo<=0 then
Exit;
ItemList := '';
MyQuery.Close;
MySQL := 'select ID from tbl ' +
' where val = ' + IntToStr(IncNo) +
' order by col1 DESC, col2, col3';
try
try
MyQuery.CommandText := (MySQL);
MyQuery.Open;
while not (MyQuery.EOF) do
begin
if (ItemList <> '') then
ItemList := ItemList + ',';
ItemList := ItemList +
MyQuery.FieldbyName('ID').asstring;
MyQuery.Next;
end;
except
// exception handling code omitted for brevity -- none of it
// is ever reached, anyway (see below)
end;
finally
MyQuery.Close;
end;
Result := ItemList;
end;
The call stack from the exception indicates that it's occurring at Open. No try..catch block will capture the exception and log it for me -- I have to use EurekaLog in order to see any details. The stack trace methods (too big to post here) look like:
TFQFoo.DoSomething
TDataSet.Open
... internal things
TADOConnection.ExecuteComplete
CheckForAsyncExecute
...
TCustomConnection.GetDataSet
TListHelper.GetItemRange
Thinking possibly my TADODataSet component was somehow getting corrupted / its properties altered at runtime, I added some logging to capture that data for me so I could see if something funky was going on there. I didn't see anything, but here it is in case it's pertinent.
object MyQuery: TADODataSet
AutoCalcFields = False
CacheSize = 15
Connection = FGlobals.RIMSDB
CursorType = ctStatic
LockType = ltReadOnly
CommandText =
'select ID from tbl where val = 202005070074 order by col1 ' +
'DESC, col2, col3'
ParamCheck = False
Parameters = <>
Left = 32
Top = 216
end
For the curious, this is the method actually throwing the exception, from Data.Win.ADODB. Note the except, which I guess hops over my own try..catch block and sends the exception straight to EurekaLog.
procedure CheckForAsyncExecute;
var
I: Integer;
begin
try
if not Assigned(pError) and Assigned(pRecordset) and
((pRecordset.State and adStateOpen) <> 0) then
for I := 0 to DataSetCount - 1 do
if (DataSets[I].Recordset = pRecordset) and (eoAsyncExecute in DataSets[I].ExecuteOptions) then
begin
DataSets[I].OpenCursorComplete;
Break;
end;
except
ApplicationHandleException(Self);
end;
end;
What I have tried:
Many, many iterations of tweaking component properties on the ADODataSet itself
Using the CommandText and Parameters from within the designer, and assigning the parameter prior to execution at runtime
Adding / removing a (NOLOCK) hint to the query itself
Taken the problem to senior members of my team for input
Googling (for hours)
Reading up on Delphi and ADO documentation (not fruitful for this)
Attempted reproduction - I've never been able to get this to occur on any test system I use. This leads me to think it may be environment-related, but I have absolutely no clue how
My question, restated:
How do I stop this from happening? What am I doing wrong? I don't want to just catch the EArgumentOutOfRangeException; I want to learn why it's happening in the first place and prevent it from happening in the future.
I know that sometimes, the query execution will not return results, but the typical CommandText does not return a result set message is never seen nor encountered due to the lower-level code bypassing my own catch statement. Beyond this, I don't know what else to look for.
I've only found one other occurrence of something similar so far, but it relates just to the exception not getting caught: http://www.delphigroups.info/2/d9/410191.html
The call to ApplicationHandleException(Self) in CheckForAsyncExecute() is swallowing exceptions, which is why your except block is not being triggered:
// in Data.Win.ADODB.pas:
procedure CheckForAsyncExecute;
var
I: Integer;
begin
try
...
except
ApplicationHandleException(Self); // <-- a caught exception is NOT re-raised here!
end;
end;
Inside of the Data.Win.ADODB unit, caught exceptions will call the unit's own ApplicationHandleException() function, which then calls System.Classes.ApplicationHandleException if assigned, otherwise it simply exits:
// in Data.Win.ADODB.pas:
procedure ApplicationHandleException(Sender: TObject);
begin
if Assigned(System.Classes.ApplicationHandleException) then
System.Classes.ApplicationHandleException(Sender);
end;
System.Classes.ApplicationHandleException is initialized to nil.
In both a VCL1 and FMX app, the TApplication constructor assigns the TApplication.HandleException() method to System.Classes.ApplicationHandleException, where HandleException() ignores EAbort exceptions, and calls the TApplication.OnException event handler (if assigned), the TApplication.ShowException() method, or the System.SyUtils.ShowException() function, depending on the type of exception being handled.
1: in a VCL TService app, TServiceApplication uses Vcl.Forms.TApplication internally.
TApplication.ShowException() displays the details of the exception to the user in a popup MessageBox and then exits, and System.SysUtils.ShowException() displays the details of the exception to the user in a Console or MessageBox and then exits.
So, at no point does ADO's CheckForAsyncExecute() re-raise a caught exception into user code. And needless to say, displaying a popup MessageBox in a service is not a good idea, as the user will likely not see it so they can dismiss it.
Of course, the best option would be to avoid the EArgumentOutOfRangeException exception from being raised in the first place. But there are other conditions that can also raise exceptions, too.
So, your only option to handle swallowed ADO exceptions yourself, and avoid popup MessageBoxes, is to assign a TApplication.OnException event handler (either directly, or via the TApplicationEvents component).
i know how to make Tstringlist onchange event , but what about Tstrings ? i dont want to use VCL like tmemo or something . can i do that ? is it possible to have this event on tstrings and do something when its changed ?
i tried to do something like this but got access violation
//on form show event
stringlist:= TStringList.Create;
stringlist.OnChange := HandleStringListChange;
//implantation
procedure TChatFo.HandleStringListChange(Sender: tObject);
begin
if stringlist.Text <> '' then
ProcessCommands(stringlist.Text);
stringlist.Clear;
end;
exception messages
Project Project1.exe raised exception class $C0000005 with message
'access violation at 0x6d40c92c: read of address 0x00000150'.
Project Project1.exe raised exception class EStringListError with
message 'List index out of bounds (5)'.
Project Project1.exe raised exception class EStringListError with
message 'List index out of bounds (5)'.
this tstringlist should work as command identifier i creat it with my thread
as example
type
TReadingThread = class(TThread)
protected
FConnection : TIdTCPConnection;
FLogResult : TStrings;
procedure Execute; override;
public
constructor Create(AConn: TIdTCPConnection; ALogResult: TStrings); reintroduce;
end;
ListeningThread := TReadingThread.Create( TCPClient, stringlist);
constructor TReadingThread.Create(AConn: TIdTCPConnection; ALogResult: TStrings);
begin
FConnection := AConn;
FLogResult := ALogResult;
inherited Create(False);
end;
procedure TReadingThread.Execute;
Var
strData : String;
begin
while not Terminated do
begin
try
strData := FConnection.IOHandler.ReadLn;
if strData <> '' then
begin
FLogResult.Add( strData );
end;
except
on E: Exception do
begin
FConnection.Disconnect(False);
if FConnection.IOHandler <> nil
then FConnection.IOHandler.InputBuffer.Clear;
Break;
end;
end;
Sleep(10);
end; // While
end;
if i use Tmemo no errors or exception happened.
We're all shooting in the dark here because you haven't provided all the relevant code. That said, the information you have provided has a lot of problems, and I can offer advice to help you solve it.
Debugging 101
Run your code through the IDE. When you get your exception, the debugger stops at the line that caused the exception.
This is usually the most important step in figuring out what went wrong. You have access to this information. Run your program and carefully look at the line that raised the error. You might be able to already figure what caused the error. If not, a other basic techniques can be applied to get more information:
Get values of objects and variables on the error line and other close lines. You can hover your mouse cursor to get tool-tips, or press Ctrl + F7.
You can examine the call stack of lines leading to the one that caused the error by double-clicking the previous line in the call-stack.
Put a breakpoint on the line before the error and re-run the app. The debugger will stop on that line and give you a chance to check values as explained earlier, but before the error happens.
Asking for help 101
Getting help from people is much more effective when you give them all the relevant information. For a start, the line of code where the access violation occurs would be extremely useful.... Tell us!
Give us real code.
Saying "I tried to do something like this" is not particularly useful. Please copy and paste exactly what you tried. If your code is different, your mistake might no longer be there.
Access Violations
You get an access violation in the following situations:
You forgot to create the object you want to use or didn't assign it to the appropriate variable.
You created the object but Destroyed or Freed it already before trying to use it again.
You changed the variable that was referencing the object.
You performed a 'hard-cast' (or unchecked typecast) from one type to an incompatible type.
The above are the basics. There some variations, and a few special edge cases, but these account for the vast majority of mistakes.
So using the above, that's what you need to check. If you had copy-pasted more of your code, we might be able to see what you did wrong.
NOTE: One shot-in-the-dark possibility is that you are destroying your string list in the wrong place. And perhaps the memo works because as a component dropped on the form, you're not trying to destroy it.
Stack overflow
Let's examine what happens in your OnChange event when for example a string is added:
The event fires.
Text is not empty.
So you call ProcessCommands
You then call Clear
The the end of Clear, Changed is called again.
Which fires your event again.
This time Text is empty, so you won't call ProcessCommands
But you do try to Clear the string list again...
This could go on forever; well at least until the call-stack runs out of space and you get a stack-overflow error.
Saved by the bell
The only reason you don't get a stack overflow is because Clear doesn't do anything if the string list is empty:
procedure TStringList.Clear;
begin
if FCount <> 0 then //You're lucky these 2 lines stop your stack-overflow
begin
...
FCount := 0; //You're lucky these 2 lines stop your stack-overflow
SetCapacity(0);
Changed;
end;
end;
I suggest you rethink how to solve you problem because code that leads to unintentional recursion is going to make your life difficult.
Working with threads
You really need to get a handle on the basics of programming before trying to work with threads. Multi-threaded programming throws in a huge amount of complexity.
I can already see a huge possibility of one potential mistake. (Though it depends what you're doing inside ProcessCommands.)
You modify your string list in the context of a thread.
This means your OnChange event handler also fires in the context of the thread. (The fact it's implemented on the form is irrelevant.)
If your ProcessCommands method does anything that requires it to operate on the main thread, you're going to encounter problems.
As a final consideration, I've noted quite a few beginner programmers completely miss the point that code starting a thread can finish before the thread does. E.g. (Going back to the topic on Access Violations.): If you're destroying your string list soon after creating your thread, your thread could suddenly throw an access violation when the object it had earlier is suddenly gone.
The likely explanation for your error is that you are modifying the list in its OnChange event handler. That is simply not allowed. An OnChange handler must not mutate the state of the object.
What is happening is that you have some other code, that we cannot see, that modifies the list, perhaps in a loop. As it modifies the list your event handler clears the list and then the calling code has had the rug pulled from underneath it.
Of course, I'm having to guess here since you did not show complete code. Perhaps the specifics vary somewhat but it seems likely that this is the root of your problem.
You'll need to find another way to solve your problem. With the information available we cannot suggest how to do that.
Looking at your updated code, there's no need for a string list at all. Remove it completely. Instead, where you do:
FLogResult.Add( strData );
do this:
ProcessCommands( strData );
TStrings is an abstract class, ancestor of TStringList. You can create an instance of TStringList and use it as a TStrings.
I have a problem in a dll (including a COM object): when the dll is unloaded some finalization sections are executed and some are not.
in the debugger I could manage to locate the problem in System FinalizeUnits(). pseudo code of this function - for your convenience:
procedure FinalizeUnits;
var
Count: Integer;
Table: PUnitEntryTable;
P: Pointer;
begin
if InitContext.InitTable = nil then
exit;
Count := InitContext.InitCount;
Table := InitContext.InitTable^.UnitInfo;
try
while Count > 0 do
begin
Dec(Count);
InitContext.InitCount := Count;
P := Table^[Count].FInit;
if Assigned(P) and Assigned(Pointer(P^)) then
begin
// ISSUE: when this is called for item x the debugging just stops
// breakpoints in the except block or at the end of the function are not reached!
TProc(P)();
end;
end;
except
FinalizeUnits; { try to finalize the others }
raise;
end;
end;
there is one specific finalization call that causes the problem:
i.e. the InitContext.InitCount is about 400 and when item x (e.g. 363) is executed, the debugger just stops: it does not proceed to the except block and also not to the end of the FinalizeUnits() function (where I have set breakpoints).
BTW: how is that possible? I thought the except block (or the line after it) must be called in any case.
notes:
when I manually avoid this special call, all other functions are executed normally.
when I step into the problematic TProc I end up in TCriticalSection.Enter (then Acquire - FSection.Enter - EnterCriticalSection - WSACleanup)
some more info to the WSACleanup call: I use a 3rd party library UniDAC that opens a TCP/IP connection to a database - I think this library calls WSACleanup in one of it's finalization sections (but I don't have this source code).
The strange thing is, that when the debugger is on the WSACleanup line:
function WSACleanup; external winsocket name 'WSACleanup';
and I want to step over it (i.e. F8), the debugger just stops (as if the application had exited normally) - but it should continue the loop in FinalizeUnits: how is this possible? i.e. if it were a deadlock it would not stop, but hang forever, right?
The question is: how can I debug this issue? is it possible that a deadlock causes this problem (i.e. that the debugger just stops)?
Try switching to CPU view before stepping into the problematic TProc using F7. Sometimes this can give you a good hint.
You could also try looking up the address of "P" in the map file.
Prompted by a q here yesterday, I'm trying to re-familiarise myself with TGeckoBrowser
from here: http://sourceforge.net/p/d-gecko/wiki/Home.
(Nb: requires the Mozilla XulRunner package to be installed)
Things seem to have moved backwards a bit since I last tried in the WinXP era, in that
with a minimal D7 project to navigate to a URL, I'm getting errors that I don't recall
seeing before. I've included my code below. These are the errors which I've run into
navigating to sites like www.google.com, news.bbc.co.uk, and here, of course.
The first exception - "Exception in Safecall method" - occurs as my form first displays, before naviagting anywhere at all. I have a work-around in the form of a TApplication.OnException handler.
My q is: a) Does anyone know how to avoid it in the first place or b) is there a tidier way of catching it than setting up a TApplication.Exception handler, which always feels to me like a bit of
an admission of defeat (I mean having one to avoid the user seeing an exception, not having an application-wide handler at all).
This exception occurs in this code:
procedure TCustomGeckoBrowser.Paint;
var
rc: TRect;
baseWin: nsIBaseWindow;
begin
if csDesigning in ComponentState then
begin
rc := ClientRect;
Canvas.FillRect(rc);
end else
begin
baseWin := FWebBrowser as nsIBaseWindow;
baseWin.Repaint(True);
end;
inherited;
end;
in the call to baseWin.Repaint, so presumably it's
presumably coming from the other side of the interface. I only get it the first
time .Paint is called. I noticed that at that point, the baseWin returns False for GetVisibility,
hence the experimental code in my TForm1.Loaded, to see if that would avoid it.
It does not.
2.a After calling GeckoBrowser1.LoadURI, I get "Invalid floating point operation"
once or more depending on the URL being loaded.
2.b Again, depending on the URL, I get: "Access violation at address 556318B3 in module js3250.dll. Read of address 00000008." or similar. On some pages it occurs every few seconds (thanks I imagine to some JS timer code in the page).
2a & 2b are avoided by the call to Set8087CW in TForm1.OnCreate below but I'm
mentioning them mainly in case anyone recognises them and 1 together as symptomatic
of a systemic problem of some sort, but also so google will find this q
for others who run into those symptoms.
Reverting to my q 1b), the "Exception in Safecall method" occurs from StdWndProc->
TWinControl.MainWndProc->[...]->TCustomGeckoBrowser.Paint. Instead of using an
TApplication.OnException handler, is there a way of catching the exception further
up the call-chain, so as to avoid modifying the code of TCustomGeckoBrowser.Paint by
putting a handler in there?
Update: A comment drew my attention to this documentation relating to SafeCall:
ESafecallException is raised when the safecall error handler has not been set up and a safecall routine returns a non-0 HResult, or if the safecall error handler does not raise an exception. If this exception occurs, the Comobj unit is probably missing from the application's uses list (Delphi) or not included in the project source file (C++). You may want to consider removing the safecall calling convention from the routine that gave rise to the exception.
The GeckoBrowser source comes with a unit, BrowserSupports, which looks like a type library import unit, except that it seems to have been manually prepared. It contains an interface which includes the Repaint method which is producing the SafeCall exception.
nsIBaseWindow = interface(nsISupports)
['{046bc8a0-8015-11d3-af70-00a024ffc08c}']
procedure InitWindow(parentNativeWindow: nativeWindow; parentWidget: nsIWidget; x: PRInt32; y: PRInt32; cx: PRInt32; cy: PRInt32); safecall;
procedure Create(); safecall;
procedure Destroy(); safecall;
[...]
procedure Repaint(force: PRBool); safecall;
[...]
end;
Following the suggestion in the quoyed documentation, I changed th "safecall" to StdCall on the Repaint member (but only that member) and, presto!, the exception stopped occurring. If it doesn't reappear in the next couple of days, I'll post that as an answer, unless anyone comes up with a better one.
My project code:
uses
BrowserSupports;
procedure TForm1.FormCreate(Sender: TObject);
begin
Set8087CW($133F);
Application.OnException := HandleException;
end;
procedure TForm1.HandleException(Sender: TObject; E: Exception);
begin
Inc(Errors);
Caption := Format('Errors %d, msg: %s', [Errors, E.Message]);
Screen.Cursor := crDefault;
end;
type
TMyGeckoBrowser = class(TGeckoBrowser);
procedure TForm1.Loaded;
begin
inherited;
GeckoBrowser1.HandleNeeded;
(TMyGeckoBrowser(GeckoBrowser1).WebBrowser as nsIBaseWindow).SetVisibility(True);
end;
procedure TForm1.btnLoadUrlClick(Sender: TObject);
begin
try
GeckoBrowser1.LoadURI(edUrl.Text);
except
end;
end;
Looking at the headers, the prototype for Repaint is effectively as follows:
HRESULT __stdcall Repaint(PRBool force);
and that means that
procedure Repaint(force: PRBool); safecall;
is a reasonable declaration. Remember that safecall performs parameter re-writing to convert COM error codes into exceptions.
This does mean that if the call to Repaint returns a value that indicates failure, then the safecall mechanism will surface that as an exception. If you wish to ignore this particular exception then it is cleaner to do so at source:
try
baseWin.Repaint(True);
except
on EOleException do
; // ignore
end;
If you wish to avoid dealing with exceptions then you could switch to stdcall, but you must remember to undo the parameter re-writing.
function Repaint(force: PRBool): HRESULT; stdcall;
Now you can write it like this:
if Failed(baseWin.Repaint(True)) then
; // handle the error if you really wish to, or just ignore it
Note that Failed is defined in the ActiveX unit.
If you want to troubleshoot the error further then you can look at the error code:
var
hres: HRESULT;
....
hres := baseWin.Repaint(True);
// examine hres
Or if you are going to leave the function as safecall then you can retrieve the error code from the EOleException instance's ErrorCode property.
I'm automating Word with Delphi, but some times I got an error message:
The requested member of the collection
does not exist
It seems that the Item member of the Styles collection class does not always exist and some times causes the above mentioned error. My workaround is to catch the exception and skip it, but is there anyway to detect it instead of using the try...except block? The problem with the try...except block is that when debugging the raised exception is annoying...
My code example:
var
aWordDoc: _WordDocument
i: Integer;
ovI: OleVariant;
wordStyle: Style;
begin
for i := 1 to aWordDoc.Styles.Count do
begin
ovI := i;
try
wordStyle := aWordDoc.Styles.Item(ovI);
except
Continue;//skip if any error occurred.
end;
//do something with wordStyle
end;
end
If the compiler accepts it, but it sometimes cannot happen to exist, it is probably IDispatch based latebinding. IDispatch objects can be queried. Maybe carefully working yourself up the tree querying every object for the next would work.
You would then roughly be doing what the compiler does, except that that one throws an exception if somethines doesn't exist. (and if the exception comes in from COM, maybe a slightly different code path can test more).
Sorry to have no readily made code.
I get that message when a bookmark that I'm trying to fill from Word doesn't exist so i have a process that checks first, but I'm not sure the same method would work for you.
procedure MergeData(strBookMark, strData : string);
begin
if WinWord.ActiveDocument.Bookmarks.Exists(strBookMark) = True then
WinWord.ActiveDocument.FormFields.Item(strBookMark).Result := strData;
end;
It has nothing to do with the Item function not being there. The Item function does exists, but the index you give seems to be wrong.
See this msdn article.
An invalid index seems really weird, because you are performing a for loop from 1 to Styles.Count. So if there is no Style, you should not enter the loop.
The only plausible explanation I can think of is that while you are in your loop, the Styles.Count changes and you are getting out of bounds. Are you deleting styles in your loop perhaps? Try a loop going from Styles.Count downto 1 or try a While loop, evaluating Styles.Count at every iteration.
Other things I can think of, but are very unlikely:
While assigning I to ovI, it gets converted to an OleString, so Word searches for a style named "I", instead of a Style at I
While assigning I to ovI, something in the conversion goes wrong and it gets in the range of $FFFFFFA5 - $FFFFFFFF, which are constants for Builtin styles.
try checking if it is null or not with a IF statement