PDA

View Full Version : COM Example: Internet Explorer Automation



Petr Schreiber
07-07-2009, 16:05
This is little example showing how to Google things using COM interface.
I meant this example as something other people experimenting with COM under ThinBASIC can learn from, as it shows:

How to create objects
How to access properties
How to invoke methods with parameters
To not forget release objects :)


Example is based on José Roca PowerBASIC example (http://www.jose.it-berater.org/smfforum/index.php?topic=2824.msg8494#msg8494), the final code took the COM methods from it, while the whole COM manipulation is ThinBASIC specific.



' -- Example how to programatically Google things, based on example by José Roca
' -- Petr Schreiber, 2009
' -- V2.0

uses "COM", "Console"

function tbMain()

%READYSTATE_COMPLETE = 4
LOCAL pIWebBrowser2 AS DWORD ' // Reference to the IWebBrowser2 interface
LOCAL pIHTMLDocument3 AS DWORD ' // Reference to the IHTMLDocument3 interface
LOCAL pIHTMLElement AS DWORD ' // Reference to the IHTMLElement interface

' dim result as long
dim vParam(5) as variant
dim vValue as variant
dim success as long

' Create a new instance of Internet Explorer
pIWebBrowser2 = COM_CreateObject("InternetExplorer.Application")
IF isFalse(pIWebBrowser2) THEN EXIT FUNCTION

' Make it visible
vParam(1) = "1"
success = COM_Succeeded(COM_SetProperty(pIWebBrowser2, "Visible", 1, vParam))
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Navigate to Google
vParam(1) = "http://www.google.com/ncr"
success = COM_Succeeded(COM_CallMethod(pIWebBrowser2, "Navigate", 1, vParam, 0))
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Wait until the page is ready
success = COM_Succeeded(COM_GetProperty(pIWebBrowser2, "ReadyState", vValue))
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

WHILE (variant#(vValue) <> %READYSTATE_COMPLETE)
SLEEP 3
COM_GetProperty(pIWebBrowser2, "ReadyState", vValue)
WEND

success = COM_Succeeded(COM_GetProperty(pIWebBrowser2, "Document", vValue))
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Get a reference to the IHTMLDocument3 interface
pIHTMLDocument3 = variant#(vValue)
IF isFalse(pIHTMLDocument3) THEN CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Get a reference to the input box
vParam(1) = "q"
success = COM_Succeeded(COM_CallMethod(pIHTMLDocument3, "getElementById", 1, vParam, vValue))
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

pIHTMLElement = variant#(vValue)
if isFalse(pIHTMLElement) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Set the value
vParam(1) = "value"
vParam(2) = "ThinBasic"
vParam(3) = "0"
success = COM_Succeeded(COM_CallMethod(pIHTMLElement, "setAttribute", 3, vParam, 0))
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Get reference to Search button
vParam(1) = "btnG"
success = COM_Succeeded(COM_CallMethod(pIHTMLDocument3, "getElementById", 1, vParam, vValue))
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

COM_Release(pIHTMLElement) ' We had already reference for textbox here, need to release from memory
pIHTMLElement = variant#(vValue)
if isFalse(pIHTMLElement) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Click in
success = COM_Succeeded(COM_CallMethod(pIHTMLElement, "Click", 0, 0, 0))
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Do not forget to release objects
CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

end function

sub CleanUpAndEnd( var1 as dword, optional var2 as dword, var3 as dword )
if var1 then COM_Release(var1)
if var2 then COM_Release(var2)
if var3 then COM_Release(var3)

stop
end sub



Petr

UPDATED July 7th 2009, 18:54

Lionheart008
07-07-2009, 18:13
hi petr :)

great script... I have tried it and open the IE with googling for thinbasic :) I like this one!!!

but... at the end of the script I have got a general protection fault, sorry... perhaps there is a little thing missing in your code... I have started the script with thinair...

and

it's possible to start with firefox too? I have tried it but the script doesn't like neither "Firefox.Application" nor "MozillaFirefox.Application"... ;)

I will study the code this evening, good to see and learn new things, powerbasic is perhaps one day a temptation for me :D

best regards, Lionheart

Petr Schreiber
07-07-2009, 18:43
Hi Frank,

I am sorry for the GPF, but it did not occurred here :?.
Can I ask you to open script in ThinAir, and use F8 key to go line by line in the script to see where it GPFs exactly?

I tested it with Internet Explorer 7 on WinXP SP2 on latest ThinBASIC beta and no problem occurred.


Thanks,
Petr

P.S. I am afraid there is no MozillaFirefox.Application interface ... but I will google it to make me sure

Petr Schreiber
07-07-2009, 18:53
Hi Frank,

maybe I was releasing one object twice, could you please re-download the version from first post and try?


Thanks,
Petr

Lionheart008
07-07-2009, 19:12
hi petr,

I proofed the debugger and the mistake belonged probably to line 90 (the debugger stopped) in your first script I have tested... the picture I have added here...

and now everything is working fine! :D

thank you for prompted fixing, good work from jose too :) some day I will have a closer look at powerbasic...


bye, Lionheart

Petr Schreiber
07-07-2009, 20:17
Thank you very much,

you confirmed my suspection - var2 and var3 in old version of the script pointed to same address, so COM_Release tried to kill one object twice. New version does not have this problem.


Petr

Petr Schreiber
08-07-2009, 00:10
Maybe you are little bit lazy (like me) to call different functions to set/get properties and call methods with prefilling array... here is little shortcut function which does all for you. I call it COMInvoke.

To set property just use:
COMInvoke(objectVariable, "property=", someData)

To get property just use:
COMInvoke(objectVariable, "=property", 0, variableRetrievingValue)

To call simple method without parameters and return value:
COMInvoke(objectVariable, "Method()")

To call simple method with parameters and without return value:
COMInvoke(objectVariable, "Method(""firstParameter"", ""secondParameter"")")

To call simple method without parameters and with return value:
COMInvoke(objectVariable, "=Method()", 0, variableRetrievingValue)

To call simple method with parameters and with return value:
COMInvoke(objectVariable, "=Method(""firstParameter"", ""secondParameter"")", 0, variableRetrievingValue)

... so the notation is little more comfortable, I think.



' -- Example how to programatically Google things, based on example by José Roca
' -- Petr Schreiber, 2009
' -- V2.0B

uses "COM", "Console"

function tbMain()

%READYSTATE_COMPLETE = 4
LOCAL pIWebBrowser2 AS DWORD ' // Reference to the IWebBrowser2 interface
LOCAL pIHTMLDocument3 AS DWORD ' // Reference to the IHTMLDocument3 interface
LOCAL pIHTMLElement AS DWORD ' // Reference to the IHTMLElement interface

dim vValue as variant
dim success as long

' Create a new instance of Internet Explorer
pIWebBrowser2 = COM_CreateObject("InternetExplorer.Application")
IF isFalse(pIWebBrowser2) THEN EXIT FUNCTION

' Make it visible
success = COMInvoke(pIWebBrowser2, "Visible=", 1)
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Navigate to Google
success = COMInvoke(pIWebBrowser2, "Navigate(""http://www.google.com/ncr"")")
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Wait until the page is ready
success = COMInvoke(pIWebBrowser2, "=ReadyState", 0, vValue)
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

WHILE (variant#(vValue) <> %READYSTATE_COMPLETE)
SLEEP 3
COMInvoke(pIWebBrowser2, "=ReadyState", 0, vValue)
WEND

success = COMInvoke(pIWebBrowser2, "=Document", 0, vValue)
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Get a reference to the IHTMLDocument3 interface
pIHTMLDocument3 = variant#(vValue)
IF isFalse(pIHTMLDocument3) THEN CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Get a reference to the input box
success = COMInvoke(pIHTMLDocument3, "=getElementById(""q"")", 0, vValue)
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

pIHTMLElement = variant#(vValue)
if isFalse(pIHTMLElement) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Set the value

success = COMInvoke(pIHTMLElement, "setAttribute(""value"",""ThinBasic"",""0"")")
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Get reference to Search button
success = COMInvoke(pIHTMLDocument3, "=getElementById(""btnG"")", 0, vValue)
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

COM_Release(pIHTMLElement) ' We had already reference for textbox here, need to release from memory
pIHTMLElement = variant#(vValue)
if isFalse(pIHTMLElement) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Click in
success = COMInvoke(pIHTMLElement, "Click()")
if isFalse(success) then CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

' Do not forget to release objects
CleanUpAndEnd(pIWebBrowser2, pIHTMLDocument3, pIHTMLElement )

end function

sub CleanUpAndEnd( var1 as dword, optional var2 as dword, var3 as dword )
if var1 then COM_Release(var1)
if var2 then COM_Release(var2)
if var3 then COM_Release(var3)

printl "Press any key to quit..."
waitkey

stop
end sub

' -- Auxiliary function for easier invoking of statements without need for variant arrays
function COMInvoke( objectVar as dword, statement as string, optional assignVal as variant, byref returnVal as variant ) as long
printl
if isFalse(objectVar) then exit function

dim commandType as long
dim member as string
dim wantReturn as long
dim numParams as long
dim Params as string
dim Param as string

if left$(statement, 1) = "=" then
if instr(statement, "(") = 0 then
commandType = 0 ' -- Get Property
else
wantReturn = 1
commandType = 2 ' -- Call Method
end if
elseif right$(statement, 1)= "=" then
commandType = 1 ' -- Set Property
else
commandType = 2 ' -- Call Method
end if

select case commandType
case 0 ' -- Get Property
statement = ltrim$(statement, "=")
printl "Retrieving property: " + statement
return COM_Succeeded(COM_GetProperty(objectVar, statement, returnVal))

case 1 ' -- Set Property
statement = rtrim$(statement, "=")
printl "Assigning property: " + statement
return COM_Succeeded(COM_SetProperty(objectVar, statement, 1, assignVal))

case 2 ' -- Call Method
if wantReturn then statement = ltrim$(statement, "=")
member = parse$(statement, "(", 1)
print "Calling method: " + member + " "
if instr(statement, "()") then
numParams = 0
else
numParams = parsecount(statement, ",")
end if

print "with " + tstr$(numParams) + " parameter" + IIF$(numParams>1 or numParams = 0, "s", "") + IIF$(numParams = 0, "", ":"+$CRLF)

if numParams then
dim vParam(numParams) as variant
dim i as long

Params = remove$(grab$(statement, "(", ")"), " ")

for i = 1 to numParams
Param = parse$(Params, ",", i)
if left$(Param, 1) = $DQ and right$(Param, 1) = $DQ then Param = trim$(Param, $DQ)
vParam(i) = Param
printl "* " + Param
next

end if

printl
if wantReturn then
if numParams then
return COM_Succeeded(COM_CallMethod(objectVar, member, numParams, vParam, returnVal))
else
return COM_Succeeded(COM_CallMethod(objectVar, member, 0, 0, returnVal))
end if
else
if numParams then
return COM_Succeeded(COM_CallMethod(objectVar, member, numParams, vParam, 0))
else
return COM_Succeeded(COM_CallMethod(objectVar, member, 0, 0, 0))
end if
end if

end select

end function

Petr Schreiber
08-07-2009, 00:38
Enhanced version with garbage collector.

Each time you create new object or retrieve reference to it, simply add it to GC and you can clean it up easily later.



' -- Example how to programatically Google things, based on example by José Roca
' -- Petr Schreiber, 2009
' -- V3.0

uses "COM", "Console"

function tbMain()

%READYSTATE_COMPLETE = 4
LOCAL pIWebBrowser2 AS DWORD ' // Reference to the IWebBrowser2 interface
LOCAL pIHTMLDocument3 AS DWORD ' // Reference to the IHTMLDocument3 interface
LOCAL pIHTMLElement AS DWORD ' // Reference to the IHTMLElement interface

dim vValue as variant
dim success as long

' Initialize garbage collector
GarbageCollector_Init()

' Create a new instance of Internet Explorer
pIWebBrowser2 = COM_CreateObject("InternetExplorer.Application")
IF isFalse(pIWebBrowser2) THEN EXIT FUNCTION
GarbageCollector_Add(pIWebBrowser2)

' Make it visible
success = COMInvoke(pIWebBrowser2, "Visible=", 1)
if isFalse(success) then GarbageCollector_ReleaseAll()

' Navigate to Google
success = COMInvoke(pIWebBrowser2, "Navigate(""http://www.google.com/ncr"")")
if isFalse(success) then GarbageCollector_ReleaseAll()

' Wait until the page is ready
success = COMInvoke(pIWebBrowser2, "=ReadyState", 0, vValue)
if isFalse(success) then GarbageCollector_ReleaseAll()

WHILE (variant#(vValue) <> %READYSTATE_COMPLETE)
SLEEP 3
COMInvoke(pIWebBrowser2, "=ReadyState", 0, vValue)
WEND

success = COMInvoke(pIWebBrowser2, "=Document", 0, vValue)
if isFalse(success) then GarbageCollector_ReleaseAll()

' Get a reference to the IHTMLDocument3 interface
pIHTMLDocument3 = variant#(vValue)
IF isFalse(pIHTMLDocument3) THEN GarbageCollector_ReleaseAll()
GarbageCollector_Add(pIHTMLDocument3)

' Get a reference to the input box
success = COMInvoke(pIHTMLDocument3, "=getElementById(""q"")", 0, vValue)
if isFalse(success) then GarbageCollector_ReleaseAll()

pIHTMLElement = variant#(vValue)
if isFalse(pIHTMLElement) then GarbageCollector_ReleaseAll()
GarbageCollector_Add(pIHTMLElement)

' Set the value
success = COMInvoke(pIHTMLElement, "setAttribute(""value"",""ThinBasic"",""0"")")
if isFalse(success) then GarbageCollector_ReleaseAll()

' Get reference to Search button
success = COMInvoke(pIHTMLDocument3, "=getElementById(""btnG"")", 0, vValue)
if isFalse(success) then GarbageCollector_ReleaseAll()

GarbageCollector_Release(pIHTMLElement) ' We had already reference for textbox here, need to release from memory
pIHTMLElement = variant#(vValue)
if isFalse(pIHTMLElement) then GarbageCollector_ReleaseAll()
GarbageCollector_Add(pIHTMLElement)

' Click in
success = COMInvoke(pIHTMLElement, "Click()")
if isFalse(success) then GarbageCollector_ReleaseAll()

' Do not forget to release objects
GarbageCollector_ReleaseAll()

end function

' -- Custom garbage collector
function GarbageCollector_Init()
printl "+--- GarbageCollector/Initialized"
global GarbageCollector_RawDWORDs as string
end function

sub GarbageCollector_Add( pObject as dword )
static i as long

' -- Do we have it in GC already?
if len(GarbageCollector_RawDWORDs) then
for i = 1 to len(GarbageCollector_RawDWORDs) step sizeof(dword)
if mid$(GarbageCollector_RawDWORDs, i, sizeof(dword)) = mkdwd$(pObject) then exit sub ' -- It is already here, no reason to continue
next
end if

GarbageCollector_RawDWORDs += mkdwd$(pObject)
printl "+--- GarbageCollector/Added "+TSTR$(pObject)+" ("+STR$(len(GarbageCollector_RawDWORDs)/sizeof(dword))+" item(s) total )"
end sub

sub GarbageCollector_Release( pObject as dword )
static i as long

' -- Do we have it in GC already?
if len(GarbageCollector_RawDWORDs) then
for i = 1 to len(GarbageCollector_RawDWORDs) step sizeof(dword)
if mid$(GarbageCollector_RawDWORDs, i, sizeof(dword)) = mkdwd$(pObject) then
' -- Out of COM
COM_Release(cvdwd(GarbageCollector_RawDWORDs, i))
' -- Out of GC
GarbageCollector_RawDWORDs = strdelete$(GarbageCollector_RawDWORDs, i, sizeof(dword))
printl "+--- GarbageCollector/Removed "+TSTR$(pObject)+" ("+STR$(len(GarbageCollector_RawDWORDs)/sizeof(dword))+" item(s) total )"
exit sub
end if
next
end if
end sub

sub GarbageCollector_ReleaseAll()
static i as long

' -- Do we have it in GC already?
if len(GarbageCollector_RawDWORDs) then
for i = 1 to len(GarbageCollector_RawDWORDs) step sizeof(dword)
COM_Release(cvdwd(GarbageCollector_RawDWORDs, i))
next
GarbageCollector_RawDWORDs = ""
end if

printl "+--- GarbageCollector/Removed All ( 0 items total )"
waitkey

stop
end sub

' -- Auxiliary function for easier invoking of statements without need for variant arrays
function COMInvoke( objectVar as dword, statement as string, optional assignVal as variant, byref returnVal as variant ) as long
printl
printl repeat$(60, "-")
if isFalse(objectVar) then exit function

dim commandType as long
dim member as string
dim wantReturn as long
dim numParams as long
dim Params as string
dim Param as string

if left$(statement, 1) = "=" then
if instr(statement, "(") = 0 then
commandType = 0 ' -- Get Property
else
wantReturn = 1
commandType = 2 ' -- Call Method
end if
elseif right$(statement, 1)= "=" then
commandType = 1 ' -- Set Property
else
commandType = 2 ' -- Call Method
end if

select case commandType
case 0 ' -- Get Property
statement = ltrim$(statement, "=")
printl "Retrieving property: " + statement
printl repeat$(60, "-")
printl
return COM_Succeeded(COM_GetProperty(objectVar, statement, returnVal))

case 1 ' -- Set Property
statement = rtrim$(statement, "=")
printl "Assigning property: " + statement
printl repeat$(60, "-")
printl

return COM_Succeeded(COM_SetProperty(objectVar, statement, 1, assignVal))

case 2 ' -- Call Method
if wantReturn then statement = ltrim$(statement, "=")
member = parse$(statement, "(", 1)
print "Calling method: " + member + " "
if instr(statement, "()") then
numParams = 0
else
numParams = parsecount(statement, ",")
end if

printl "with " + tstr$(numParams) + " parameter" + IIF$(numParams>1 or numParams = 0, "s", "") + IIF$(numParams = 0, "", ":")

if numParams then
dim vParam(numParams) as variant
dim i as long

Params = remove$(grab$(statement, "(", ")"), " ")

for i = 1 to numParams
Param = parse$(Params, ",", i)
if left$(Param, 1) = $DQ and right$(Param, 1) = $DQ then Param = trim$(Param, $DQ)
vParam(i) = Param
printl "* " + Param
next

end if

printl repeat$(60, "-")
printl
if wantReturn then
if numParams then
return COM_Succeeded(COM_CallMethod(objectVar, member, numParams, vParam, returnVal))
else
return COM_Succeeded(COM_CallMethod(objectVar, member, 0, 0, returnVal))
end if
else
if numParams then
return COM_Succeeded(COM_CallMethod(objectVar, member, numParams, vParam, 0))
else
return COM_Succeeded(COM_CallMethod(objectVar, member, 0, 0, 0))
end if
end if

end select

end function