Webcrawl A Blog To Retrieve All Entries Locally
Webcrawl A Blog To Retrieve All Entries Locally
steroids
Today’s sample shows how to create a web crawler in the background. This crawler starts with a web
page, looks for all links on that page, and follows all those links. The links are filtered to my blog, but
generalizing the code to search the entire web or some other site is trivial (if you have enough disk
space<g>). (VB.Net version to appear soon on this blog.)
I was doing a search on my blog for “ancestors” via the Search box on the sidebar on the left, and
there were no results. Strange, I thought, so I used MSN search for my site:
http://search.msn.com/results.aspx?FORM=TOOLBR&q=ancestors+site%3Ahttp%3A%2F
%2Fblogs.msdn.com%2FCalvin_Hsia%2F
This incident reminded me of the fact that I’ve done a lot of work to create my blog, but I depend on a
3rd party to maintain it. There are hundreds of code samples, with links to references. If the blog
server were to disappear for some reason, so would all my content. I wanted to retrieve all my blog
content into a local table. Then I can manipulate it any way I want.
In particular, suppose I want to read my entire blog. I would have to do a lot of manual clicking to get
to the month/day of the post, and then I might have missed something because I’m manually crawling.
That’s pretty cumbersome. Also, I can have all of a blog available while offline, updating when
connected.
So I wrote a code sample below that crawls my blog, looking for all the blog posts, and shows them in
a form which has search capability. Because it’s all local, searching and navigating from post to post
is extremely fast. The entry is displayed in a web control, so the page looks just like it would online
and the hyperlinks are all live.
You can start a web crawl by pushing the Crawl button. You can interrupt the web crawl by typing ‘Q’
(<esc> will cancel the automation of the IE SaveAs dialog). The next time the crawl runs, it will
resume where it left off. Crawling acts as if you were subscribed to my blog via RSS. Once you have
all current content, Crawling again later will just add any new content. The saved content is the entire
blog entry web page, including any comments. As an exercise, readers are encouraged to make the
web crawling execute on a background thread!
A crawl starts at the main page http://blogs.msdn.com/Calvin_Hsia, which shows any new content and
has links on the side bar for any other posts. The page is loaded and then parsed for any links. Any
links pointing to my blog are inserted into a table if they’re not there already. Then the table is
scanned for any unfollowed links and the process repeats. If a page is a leaf node (currently any link
with 8 backslashes) then the Publication date is parsed, and the file is saved in the MHT field in the
table. The link parsing was a little complicated due to some comment spam reducing measures and
some broken links when the blog host server switched software.
You will probably have to modify the code if you want to do the same for other blogs. For example,
some blogs may have the Publication date in a different place. Others may have archive links
elsewhere or in a different format.
cTempFile=ADDBS(GETENV("TEMP"))+SYS(3)+".htm"
LOCAL oHTTP as "winhttp.winhttprequest.5.1"
LOCAL cHTML
oHTTP=NEWOBJECT("winhttp.winhttprequest.5.1")
oHTTP.Open("GET","http://blogs.msdn.com/calvin_hsia/archive/2004/06/28/1680
54.aspx",.f.)
oHTTP.Send()
STRTOFILE(ohTTP.ResponseText,cTempFile)
oIE=CREATEOBJECT("InternetExplorer.Application")
oIE.Visible=1
oIE.Navigate(cTempFile)
But the content looked pretty bad, because of the CSS references, pictures, etc.
Being able to automate IE was helpful, but how do you parse the HTML for the links to each blog
entry? I thought about using an XSLT, but that was fairly complex. I used the IE Document model
IHTMLDocument,to search through the HTML nodes for links.
IE has a feature that saves a web page to a single file: Web Archive, single file(*.mht) from the File-
>SaveAs menu option. So I used Windows Scripting Host to automate this feature.
Making the code run in a background thread is trivial: just use the ThreadClass from here.
See also :
Wite your own RSS News/Blog aggregator in <100 lines of code
Use a simple XSLT to read the RSS feed from a blog,
Do you like reading a blog author? Retrieve all blog entries locally for reading/searching using XML,
XSLT, XPATH
Generating VBScript to read a blog
CLEAR ALL
CLEAR
#define WAIT_TIMEOUT 258
#define ERROR_ALREADY_EXISTS 183
#define WM_USER 0x400
thisform.oThreadMgr=NEWOBJECT("ThreadManager","threads.prg")
thisform.oThreadMgr.CreateThread("MyThreadFunc",thisform.cBlogUrl,"oBlogForm.Crawl
Done")
thisform.lblStatus.caption= "Background Crawl Thread
Created"
ELSE
LOCAL oBlogCrawl
oBlogCrawl=NEWOBJECT("BlogCrawl","MyThreadFunc.prg","",thisform.cBlogUrl)
&& the class def resides in MyThreadFunc.prg
thisform.CrawlDone
ENDIF
ELSE
this.Caption="\<Crawl"
IF fBackgroundThread AND TYPE("thisform.oThreadMgr")="O"
thisform.lblStatus.caption= "Attempting thread stop"
thisform.oThreadMgr.SendMsgToStopThreads()
ENDIF
ENDIF
PROCEDURE CrawlDone
thisform.oThreadMgr=null
thisform.cmdCrawl.caption="\<Crawl"
thisform.lblStatus.caption= "Crawl done"
this.RequeryData()
PROCEDURE cmdSearch.Click
thisform.RequeryData
PROCEDURE destroy
IF USED("result")
USE IN result
ENDIF
SELECT Blogs
SET MESSAGE TO
SET FILTER TO
SET ORDER TO LINK && LINK
ENDDEFINE
PROCEDURE CreateCrawlProc as String && Create the Thread proc, which includes
the crawling class
TEXT TO cstrVFPCode TEXTMERGE NOSHOW && generate the task to run: MyThreadFunc
**************************************************
**************************************************
PROCEDURE MyThreadFunc(p2) && p2 is the 2nd param to MyDoCmd
TRY
DECLARE integer GetCurrentThreadId in WIN32API
DECLARE integer PostMessage IN WIN32API integer hWnd, integer
nMsg, integer wParam, integer lParam
cParm=SUBSTR(p2,AT(",",p2)+1)
hWnd=INT(VAL(p2))
oBlogCrawl=CREATEOBJECT("BlogCrawl",cParm)
CATCH TO oEx
DECLARE integer MessageBoxA IN WIN32API
integer,string,string,integer
MESSAGEBOXA(0,oEx.details+"
"+oEx.message,TRANSFORM(oex.lineno),0)
ENDTRY
PostMessage(hWnd, WM_USER, 0, GetCurrentThreadId())
DEFINE CLASS BlogCrawl as session
oWeb=0
oWSH=0
fStopCrawl=.f.
hEvent=0
cMonths="January February March April May June July
August September October November December "
cCurrentLink=""
PROCEDURE init(cBlogUrl)
LOCAL fDone,nRec,nStart
nStart=SECONDS()
DECLARE integer CreateEvent IN WIN32API integer lpEventAttributes,
integer bManualReset, integer bInitialState, string lpName
DECLARE integer CloseHandle IN WIN32API integer
DECLARE integer WaitForSingleObject IN WIN32API integer hHandle,
integer dwMilliseconds
DECLARE integer GetLastError IN WIN32API
this.hEvent = CreateEvent(0,0,0,"VFPAbortThreadEvent") && Get the
existing event
IF this.hEvent = 0
THROW "Creating event error:"+TRANSFORM(GetLastError())
ENDIF
?"Start Crawl"
DECLARE integer GetWindowText IN WIN32API integer, string @, integer
DECLARE integer Sleep IN WIN32API integer
this.oWeb=CREATEOBJECT("InternetExplorer.Application")
this.oWeb.visible=1
this.oweb.top=0
this.oweb.left=0
this.oweb.width=500
this.oWSH=CREATEOBJECT("Wscript.Shell")
USE blogs ORDER 1
REPLACE link WITH cBlogUrl, followed WITH 0 && set flag to indicate
this page needs to be retrieved and crawled
this.fStopCrawl=.f.
fDone = .f.
DO WHILE !fDone AND NOT this.fStopCrawl
fDone=.t.
GO TOP
SCAN WHILE NOT this.fStopCrawl
nRec=RECNO()
IF followed = 0
REPLACE followed WITH 1
this.BlogCrawl(ALLTRIM(link))
IF this.fStopCrawl
GO nRec
REPLACE followed WITH 0 && restore flag
ENDIF
fDone = .f.
ENDIF
ENDSCAN
ENDDO
?"Done Crawl",SECONDS()-nStart
PROCEDURE BlogCrawl(cUrl)
LOCAL fGotUrl,cTitle
fGotUrl = .f.
DO WHILE !fGotUrl && loop until we've got the target url in IE with no
Error
this.oweb.navigate2(cUrl)
DO WHILE this.oweb.ReadyState!=4
?"Loading "+cUrl
Sleep(1000) && yield processor
IF this.IsThreadAborted()
this.fStopCrawl=.t.
?"Aborting Crawl"
RETURN
ENDIF
ENDDO
cTitle=SPACE(250)
nLen=GetWindowText(this.oWeb.HWND,@cTitle,LEN(cTitle))
cTitle=LEFT(cTitle,nLen)
IF EMPTY(cTitle) OR UPPER(ALLTRIM(cTitle))="ERROR" OR
("http"$LOWER(cTitle) AND "400"$cTitle)
?"Error retrieving ",cUrl," Retrying"
ELSE
fGotUrl = .t.
ENDIF
ENDDO
this.cCurrentLink=cUrl
IF OCCURS("/",cUrl)=8
&&http://blogs.msdn.com/calvin_hsia/archive/2005/08/09/449347.aspx
cMht=this.SaveAsMHT(cTitle) && save the page before we parse
IF this.fStopCrawl
RETURN .f.
ENDIF
REPLACE title WITH STRTRAN(STRTRAN(cTitle," - Microsoft Internet
Explorer",""),"Calvin Hsia's WebLog : ",""),;
mht WITH cMht,Stored WITH DATETIME()
IF EMPTY(title) && for some reason, the page wasn't retrieved
REPLACE followed WITH 0
ENDIF
ENDIF
?"Parsing HTML"
this.ProcessNodes(this.oWeb.Document,0) && Recur through html nodes to
find links
?"Done Parsing HTML"
PROCEDURE ProcessNodes(oNode,nLev) && recursive routine to look through
HTML
LOCAL i,j,dt,cClass,oC,cLink
IF this.IsThreadAborted() OR nLev > 30 && limit recursion levels
RETURN
ENDIF
WITH oNode
DO CASE
CASE LOWER(.NodeName)="div" && look for pub date
IF OCCURS("/",this.cCurrentLink)=8 && # of backslashes in
blog leaf entry
oC=.Attributes.GetnamedItem("class")
IF !ISNULL(oC) AND !EMPTY(oC.Value)
cClass=oC.Value
IF cClass="postfoot" OR cClass = "posthead"
cText=ALLTRIM(STRTRAN(.innerText,"Published",""))
IF !EMPTY(cText)
dt=this.ToDateTime(cText)
IF SEEK(this.cCurrentLink,"blogs")
REPLACE pubdate WITH dt
ELSE
ASSERT .f.
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
CASE .nodeName="A"
cLink=LOWER(STRTRAN(.Attributes("href").value,"%5f","_"))
IF ATC("http://blogs.msdn.com/calvin_hsia/",cLink)>0
IF ATC("#",cLink)=0 AND ATC("archive/2",cLink)>0
*http://blogs.msdn.com/calvin_hsia/archive/2004/10/11/<a%20rel=
ELSE
*http://blogs.msdn.com/calvin_hsia/archive/2004/11/16/visual%20foxpro
IF "%"$cLink
*http://blogs.msdn.com/calvin_hsia/archive/2004/11/16/258422.aspx
* broken link: host updated software for
category links
*<A title="Visual Foxpro"
href="http://blogs.msdn.com/calvin_hsia/archive/2004/11/16/Visual%20Foxpro">Visual
Foxpro</A>
* SET STEP ON
ELSE
IF !SEEK(cLink,"Blogs")
INSERT INTO Blogs (link)
VALUES (cLink)
ENDIF
ENDIF
ENDIF
ENDIF
ENDIF
ENDCASE
FOR i = 0 TO .childNodes.length-1
this.ProcessNodes(.childNodes(i),nLev+1)
ENDFOR
ENDWITH
PROCEDURE ToDateTime(cText as String) as Datetime
*Friday, April 01, 2005 11:30 AM by Calvin_Hsia
LOCAL dt as Datetime
ASSERT GETWORDNUM(cText,6)$"AM PM"
nHr = INT(VAL(GETWORDNUM(cText,5)))
IF GETWORDNUM(cText,6)="PM" AND nhr < 12
nHr=nHr+12
ENDIF
dt=CTOT(GETWORDNUM(ctext,4) + "/" +; && Year
TRANSFORM(INT(1+
(AT(GETWORDNUM(cText,2),this.cMonths)-1)/10)) + "/" +; && month
TRANSFORM(VAL(GETWORDNUM(cText,3))) + "T" +; &&
day of month
TRANSFORM(nHr)+":"+; && hour
TRANSFORM(VAL(SUBSTR(GETWORDNUM(cText,5),4)))) &&
minute
ASSERT !EMPTY(dt)
RETURN dt
PROCEDURE SaveAsMHT(cTitle as String) as String
fRetry = .t.
DO WHILE fRetry
fRetry = .f.
WITH this.oWSH
.AppActivate(cTitle) && bring IE to the foreground
TEMPFILEMHT= "c:\t.mht" && temp file can be constant
ERASE (TEMPFILEMHT)
.SendKeys("%fa"+TEMPFILEMHT+"{tab}w%s") && Alt-F
(File Menu) S (Save As) type Web Archive Alt-S
nTries=5
DO WHILE !FILE(TEMPFILEMHT) && wait til IE saves the
file
Sleep(5000)
nTries=nTries-1
IF nTries=0
fRetry=.t.
EXIT
ENDIF
IF this.IsThreadAborted()
this.fStopCrawl=.t.
?"Aborting crawl"
RETURN ""
ENDIF
ENDDO
sleep(100)
ENDWITH
ENDDO
RETURN FILETOSTR(TEMPFILEMHT)
RETURN
PROCEDURE IsThreadAborted as Boolean
IF WaitForSingleObject(this.hEvent,0) = WAIT_TIMEOUT
RETURN .f.
ENDIF
RETURN .t.
PROCEDURE destroy
this.oWeb.visible=.f.
this.oWeb=0
CloseHandle(this.hEvent)
ENDDEFINE
**************************************************
**************************************************
ENDTEXT
STRTOFILE(cstrVFPCode,"MyThreadFunc.prg")
COMPILE MyThreadFunc.prg
*SELECT PADR(TRANSFORM(YEAR(pubdate)),4)+"/"+PADL(MONTH(pubdate),2,"0") as
mon,COUNT(*) FROM blogs WHERE !EMPTY(mht) GROUP BY mon ORDER BY mon DESC
INTO CURSOR result
RETURN
Published Thursday, May 25, 2006 5:30 PM by Calvin_Hsia
Filed under: Visual FoxPro, Web
Comment Notification
If you would like to receive an email when updates are made to this post, please register here
Comments
That's really beautiful code... Excellent pointers for diverse applications deployed with Visual
FoxPro. Thanks for commenting/posting it!!
Friday, December 15, 2006 6:40 PM by deciacco.com blog » GetHtml - Foxpro App
I've updated this VFP Web Crawler to more closely match the VB.Net version. Check it out
at:
http://www.codeplex.com/vfpwebcrawler
All source is included...
# Install Northwind for SQL Express and use Visual Studio and DLINQ to query it
SQLExpress is free and comes with Visual Studio, but the sample Northwind database isn’t
included. You
# MSDN Blog Postings » Install Northwind for SQL Express and use Visual
Studio and DLINQ to query it
Friday, August 17, 2007 10:00 PM by MSDN Blog Postings » Install Northwind for SQL
Express and use Visual Studio and DLINQ to query it
# Install Northwind for SQL Express and use Visual Studio and DLINQ to query it
SQLExpress is free and comes with Visual Studio, but the sample Northwind database isn’t
included. You
I updated a version of this code to include an easy to use VFP project and
http://www.codeplex.com/VFPWebcrawler
I spent a few hours at a local company called 2Bot ( http://www.2bot.com/ ) which makes a
3-D printer
Friday, January 04, 2008 1:26 PM by Actors and Actresses » Archive du blog » Calvin
Hsia’s WebLog : Webcrawl a blog to retrieve all entries locally …
# http://beta.blogs.msdn.com/calvin_hsia/archive/2006/05/25/607588.aspx
I received a question: Simply, is there a way of interrupting a vfp sql query once it has started
short
# Calvin Hsia s WebLog Webcrawl a blog to retrieve all entries locally | Paid Surveys
Friday, May 29, 2009 1:15 PM by Calvin Hsia s WebLog Webcrawl a blog to retrieve all
entries locally | Paid Surveys
# Calvin Hsia s WebLog Webcrawl a blog to retrieve all entries locally | Insomnia Cure
Monday, June 08, 2009 5:59 PM by Calvin Hsia s WebLog Webcrawl a blog to retrieve all
entries locally | Insomnia Cure
http://download.codeplex.com/Project/Download/FileDownload.aspx?
ProjectName=VFPX&DownloadId=69408&FileTime=128877569812070000&Buil
d=15321
Run your application forms on the web
I received a customer question:
I have looked all over the web and still searching, and found your blog. I
have a very specific issue and I need to ask whether this is doable first
of all and how to do it after.
First of all, I need to make a foxPro app that wll also run as a web
application. Can this be done? And if so how? I have looked into creating
an ASP.NET page and calling a COM from there. However, is this equivalent
to calling the actual .exe, is it like running the program from within
ASP.NET or is it simply calling some methods and functionality? Basically,
can foxpro be used as a web DB/app?
According to my Ship It award (an award given to employees for shipping a Microsoft product), Visual
FoxPro 5.0 shipped in August 96. VFP5 ships with a sample called FoxIsapi, which I created (at the
prompting of Melissa Dunn) to demonstrate creating a Fox Form in the Fox form designer, showing
Employee information, their photos and their sales in a grid.
For further information about FoxIsapi and how it worked, see this article:
http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnarfoxgen/html/msdn_custole.asp
the same form can be deployed in four different ways: as a normal Visual FoxPro form, as a Visual FoxPro run
time, as an OLE Automation server from an OLE Automation client, and over the Internet using any Web
browser!
Over a decade ago, I used to demonstrate this sample, changing the FoxIsapi form in the designer,
saving it, and showing how easily that one change propagates to all 4 deployment scenarios.
I've copied/pasted the article below because it's marked Archive Content, and might be deleted any
day..
My personal digital photo/video collection application runs on both the web and as a rich windows
client. I can query for any photos just with a string search, including from my cell phone My rich
windows client can page through dozens of photos at a time, and Enable crop and zooming in on your
digital photograph display form
It’s actually 2 separate applications talking to the same data, sharing some code.
Try running the code (this shows the form in 2 ways: as a Fox Form and in the browser)
CD HOME(2)+"\servers\foxisapi\foxis"
PUBLIC x
x=NEWOBJECT("employee","employee")
x.Visible=1
STRTOFILE(x.startup(),"d:\t.htm")
! d:\t.htm
You might have to Alt-Tab to the form because it’s a top-level window.
There are a lot more wrinkles to getting the web form to work under IIS.
• You’ll have to set the employee.vcx cdatapath property to point to the data (change the
cgraphicspath while you’re at it), build the project as an MTDLL, then see if this code works:
x=CREATEOBJECT("foxis.employee")
• You’ll have to deal with IIS security. On Windows XP (after you install IIS), copy
tools\foxisapi\foxisapi.dll to c:\inetpub\wwwroot\scripts\foxisapi.dll (You might need to make
the scripts directory)
• Disable anonymous access for now, and use “Integrated Windows Authentication” (Control
Panel->Admin Tools->IIS->Web Sites->Default Web Site->Right click on Scripts->Directory
Security->Anon Access and Authentication
• On the Directory tab of the same dialog, change “Execute Permissions” to “Scripts and
Executables”.
• Try this http://localhost/scripts/foxisapi.dll in a browser. If you get an error message that
FoxIsapi failed, it means you’re doing well. Foxisapi.dll loaded and working. If you get a dialog
asking what you want to do with foxisapi.dll, then you don’t have execute permissions
working.
• Open a browser, this URL: http://localhost/scripts/foxisapi.dll/foxis.employee.startup will
display the form, with the grid and photo. Try View->Source to see that the HTML is that from
the Startup method.
Hints:
• You can stop/start IIS with “Net stop w3svc”, “Net start w3svc”, or IISRESET
• On Windows Server 2008 (Vista Server) I installed VFP to a folder “d:\VFP” because Program
Files folder is locked down for security reasons. Start VFP as an Admin because Admin rights
are required to register a COM server in the registry. In Server Manager, select Roles->Web
Server (IIS) to see what Role Services are installed. Install Application development, which
includes ISAPI Extensions. You'll also need to go to Scripts->Handler Mappings and edit the
script map for ISAPI-dll, and point it to scripts\Foxisapi.dll
• See the source code for foxisapi for more info
(Samples\Servers\Foxisapi\CSource\Foxisapi.cpp)
• Using ‘Integrated Windows Authentication” means the web request will come in as the same
user that started the browser. That means if you have rights to read/write the files/data, then
through the browser you'll have the same rights. Allowing anonymous access means you'll
have to grant the anonymous user rights
• Try this http://localhost/scripts/foxisapi.dll/status
• Try this http://localhost/scripts/foxisapi.dll/reset
• Try creating FOXISAPI.INI file with these 2 lines in the scripts dir (will be read upon reset)
[foxisapi]
busytimeout=4
User designed forms, like Employee.scx, inherit from the Isapi.Isform class, which has a GenHTML
method. This method loops through all the controls contained on the form and generates HTML for
them.
Keep in mind FoxIsapi is just a sample, from almost a dozen years ago, when the internet was very
young. Newer web architectures abound.
For an article on using ASP to serve up web pages with Fox (which doesn’t require shutting down the
server), see Blogs get 300 hits per hour: Visual FoxPro can count.
You can also run foxis.employee out of process using COM+ applications: Control Panel->Admin
Tools->Component Services->Computer->My Computer->COM+ Applications. Create a New empty
application called FOXIS, then under FOXIS->Components, Install New Component, and navigate to
the MTDLL you built: foxis.dll. Now when you instantiate foxis.employee, the ball will spin and it will
be out of the IIS process (inetinfo.exe).
Also, there are numerous Fox web applications using Rick Strahl's Web Connection (www.west-
wind.com)
Note: because this is an old sample, there is no guarantee of support if it doesn't work for you. I did
get it to work on Windows Server 2008 and XP.
Calvin Hsia
Microsoft Corporation
October 1996
Introduction
Think back to the early computer systems available only ten or twenty years ago. In these early days of
computing, users could only run a single application at a time. There was no concept of multitasking. Sharing
information between applications was very cumbersome. If one application processed the output of another
application, then some sort of information sharing strategy had to be devised. Along came multitasking
environments, such as Microsoft® Windows®, that allowed multiple applications to run simultaneously. Users
felt more powerful because they could run a spreadsheet and their accounting program simultaneously,
perhaps cutting/pasting data from one to the other. Sharing data between applications became easier.
As computer operating systems evolved, OLE 1.0 was introduced, in which users could actually "embed" data
objects created by other applications right into their main application. When a user opens a Word document
and double-clicks on the embedded Microsoft Excel object, Excel starts up and allows the user to interact with
the data. However, this implementation meant that Excel would start as its own separate application, and the
user would see both Excel and Word on the screen at the same time. Furthermore, the Word document’s
representation of the Excel spreadsheet would be marked with hash marks, indicating that it was being edited
by Excel via OLE. This early attempt at allowing users to create "compound documents" was effective, but
ungainly.
Along came OLE 2.0, which allows OLE In-Place Activation. This made the Excel embedded object come alive in
the same Application Window as the Word object, with the menus and toolbars being "merged." The concept of
the "compound document" is being further propagated because the document has a single "merged" editing
environment.
These evolutionary steps in compound document architecture really help the interactive user to be more
productive with the operating environment. Users can run multiple applications simultaneously and hence
realize productivity gains. However, application developers also have seen an evolution in application
programmability, allowing application writers the ability to programmatically control other applications.
With Windows, a technology called DDE, or Dynamic Data Exchange was introduced. This allowed
programmers to write programs in a particular programming language that could communicate and command
some other applications to do their bidding. This concept of a programmer writing a Controller or Client that
uses the services of a Server was cumbersome with DDE. The programmer would have to know the
programming languages of both objects, would have to create a DDE callback routine, and would have to have
intimate knowledge of the server applications.
Another feature of OLE 2, OLE Automation, addresses the shortcomings of DDE and introduces a new level of
inter-application communication. OLE Automation allows the application developer to allow other applications
to "talk" to it programmatically in a standard way. The programmer designs an "interface” through which an
OLE Automation controller can invoke routines and change properties on the OLE Automation server.
An application that can be an OLE Automation Server multiplies its usefulness to the modern computer
environment. A standalone application like Excel has many graphing, mathematical, statistical, and financial
functions. Other applications can take advantage of these functions simply by programmatically calling Excel.
The end user of the application doesn't even need to know that Excel is being used.
Of course, in order to use an OLE server object, an application must be able to talk to it as an OLE Automation
controller. Visual FoxPro™ version 3.x, Microsoft Excel, and Visual Basic® are all OLE Automation controller
capable.
Several examples follow using Visual FoxPro as an OLE Automation controller. Most of these will work with
Visual FoxPro 3.0 and Visual FoxPro 5.0.
If you're feeling lonely, you can send mail to yourself using this code:
ox= createobject("mapi.session")
?ox.logon("calvinh")
?ox.name,ox.class,ox.operatingsystem,ox.version
msg = ox.outbox.messages.add
msg.subject = "testsubject"
msg.text = "Don't be lonely"
recip = msg.recipients.add
recip.name = "calvinh"
?recip.resolve
msg.send(.t.,.f.)
?ox.logoff()
You can also create automatic message readers and generators via OLE Automation, and you can publish mail
messages and folders over the intranet so that a client using a Web browser can view them.
If you don't like Internet Explorer or Netscape Browser, create your own in a Visual FoxPro form with just a few
lines of code:
PUBLIC ox
ox = CreateObject("MyInternetExplorer")
ox.visible = .t.
ox.oWeb.navigate(GETENV("computername")) && insert your URL here
ENDDEFINE
Then you can just click on any hyperlinks and get lost on the Web. Or you can type in a command:
ox.oweb.navigate("www.msn.com")
to go to a particular URL.
Visual FoxPro 5.0 itself is an OLE automation server. This means that from any other application that can be an
OLE automation controller, you can CreateObject("VisualFoxPro.application") to get an object reference to
Visual FoxPro 5.0, and change properties and invoke methods.
From Another Application that Is Not OLE Automation
Controller Capable
Some applications are not capable of being an OLE automation controller, and thus are incapable of using
Visual FoxPro as an OLE automation server. However, the capability to call into the Visual FoxPro automation
server is in a DLL called fpole.dll, which ships with Visual FoxPro 5.0. If these applications can call 32-bit DLLs,
and then they can load this DLL and control Visual FoxPro. For example, a Windows Help file or Microsoft Word
can declare and use DLLs and thus use Visual FoxPro as an automation server.
As an example of using this DLL, here's some code that you can run from Visual FoxPro 3.0 (even though
Visual FoxPro 3.0 is an automation controller, it also can call into 32-bit DLLs) that calls Visual FoxPro 5.0 as
an automation server.
From Microsoft Word, you can create a WordBasic program that starts Visual FoxPro 5.0 as an automation
server, opens a database, and retrieves a specific record:
Sub main
FoxDoCmd "USE c:\fox40\samples\data\customer", ""
FoxDoCmd "LOCATE FOR cust_id = 'CENTC'", ""
FoxDoCmd "_CLIPTEXT = customer.company + CHR(9) + customer.contact", ""
EditPaste
End Sub
From a Windows Help file
1. In the [CONFIG] section of a help project, register the .dll:
2. RegisterRoutine("fpole.dll","FoxDoCmd","SS")
3. In your .rtf file, call the function the same way you would call a native Help macro:
You can see a sample of this in the main Visual FoxPro Help file. From the Visual FoxPro Help menu choose
Sample Applications. When the Sample Applications topic appears, click on the RUN or OPEN buttons. This
invokes fpole.dll to start Visual FoxPro and runs the program hlp2fox.prg in the main Visual FoxPro directory.
From Visual Test
From Visual Test, you can write a Visual Test Basic Script that calls into Visual FoxPro to get object references
and mnemonic names of Visual FoxPro objects.
Viewport Clear
DECLARE FUNCTION FoxEval cdecl LIB " fpole.dll" (h$,j$, leng&) as long
The Visual FoxPro Help file has more information on how to use fpole.dll.
In-Process and Out-of-Process Servers
An in-process OLE server is simply a DLL that lives in the same process address space as the OLE client. An
out-of-process server lives in its own process space, and thus is typically an .exe.
An in-process server is faster than an EXE server when passing parameters back and forth, but it also can
crash the host if it crashes. There is very little protection from an errant DLL server for the host. An EXE server
can crash and not necessarily crash the host. Also, an EXE server can be run on a remote machine using
remote automation or COM in a distributed environment (DCOM).
Because an EXE server runs in its own process, the processor gives it processor time slices to execute
whatever it wants, which is typically an event loop. A DLL server depends on the OLE client to give it process
time. This implies that when an EXE server presents a user interface (UI) element, such as a form, to the user,
the user can navigate the form as expected. The DLL server, however, can present the form, but cannot
interact with it.
Generally, this is not a problem because OLE servers are typically designed to perform some sort of server
task, which has no UI elements. For example, running an OLE server that serves Web pages typically means
the server lives on an unattended Web server machine.
The new apartment model threading support requires that in-process .dll Automation servers not have user-
interfaces. In Visual FoxPro 5.0, one could create (although it was not recommended) an in-process .dll
Automation server that had a user-interface such as a form. The form could be used only for display because
the form events are not supported. In Visual FoxPro 6.0, any attempts to create a user-interface in an in-
process .dll Automation generates an error.
An out-of-process .exe Automation server can have a user-interface. A new Visual FoxPro 6.0 function,
SYS(2335), has been added so you can disable modal events for an out-of-process .exe Automation server,
which may be deployed remotely without intervention from a user. Modal events are created by user-defined
modal forms, system dialogs, the MESSAGEBOX( ) function and the WAIT command, and so on.
Create a new Visual FoxPro 5.0 project called FIRST. Add a new file first.prg with the following lines:
ENDDEFINE
Note that there is nothing about this program that's new except the "OLEPUBLIC" keyword. (You can also make
Visual Classes OLEPUBLIC from the Class Info dialog box.) What does this do? Absolutely nothing, until you
Build an .exe or .dll from the project. If you watch carefully during the build process, you'll see the message
"Creating Type Library and Registering OLE Server."
The build process creates a first.tlb, first.vbr, along with your OLE Server (either first.exe or first.dll). The TLB
is an OLE Typelibrary, which can be browsed using any OLE Type Library browser, such as the ones that come
with the Visual FoxPro Class Browser, Microsoft Excel, Visual Basic, and Visual C++®. The .VBR file is a text
file of registry entries. These are almost identical to those that were created and entered into your registry.
The only difference is that file names are prepended with their full path names. This .VBR file is useful for the
Setup wizard and when you want to deploy your OLE server on another machine, which might use different
paths.
ox = CreateObject("first.myfirst")
*wait a few secs till the server comes up with the msgbox
* (you might have to alt-tab to the msgbox)
?ox.application.name
?ox.application.visible
?ox.
?ox.application.docmd("messagebox(home())")
?ox.application.docmd("_cliptext=home() + sys(2003) + sys(2004)")
You can add the line OX = CreateObject("myfirst") to the PRG as the first line, and then the .exe file will run
just like any normal .exe. (Note: the string inside this CreateObject is the Visual FoxPro class name and not an
OLE ProgID.) If it were a visual class library, you can add a Main program that does a SET CLASSLIB to the
appropriate VCX and then CreateObject the object (not the OLE ProgID).
Note the last line in the sample. On my machine, it copies "C:\WINNT\SYSTEM32" to the Clipboard three times.
This means that even though the OLE server is in a particular directory, OLE starts it with the
WINNT\SYSTEM32 directory. This brings up an interesting problem. How does the OLE server find its
component pieces, such as .DBFs, reports, and so on?
We need to determine the directory in which the OLE Server lives, because that's typically where the parts are.
Then we can SET DEFAULT TO that directory, or we can SET PATH TO it. One solution is to make the INIT or
LOAD method of the OLE Public class change to the right directory, with the directory name either hard coded
or as a custom property on the class. This works fine on one machine, until the server is moved or installed on
a different machine with a different path.
Fortunately, there are a couple WIN32 API functions available to help. For an EXE server, we can call the
GetModuleFileName( ) function, which returns the full path name to the main .exe of the current process if
null is passed as the first parameter. For a DLL server, we use the GetModuleHandle( ) function with the
name of the DLL as the parameter to retrieve a handle to the server. We pass this handle to the
GetModuleFileName( ) function to get the full path name of the DLL.
Note the use of the _VFP.StartMode property. The Help file shows the various values of this property, which
indicate in which mode Visual FoxPro was started: normal, as an OLE server, or as a custom DLL or EXE OLE
server:
nlen=Getmodulefilename(GetModuleHandle(this.srvname+".dll"),@buf,len(buf))
ELSE
nlen=Getmodulefilename(0,@buf,len(buf))
ENDIF
buf = LEFTC(buf,nlen)
this.cdir = LEFTC(buf,RATC('\',buf)-1)
this.csrvname = SUBSTRC(buf,RATC('\',buf)+1)
this.csrvname = LEFTC(this.csrvname,AT_C(".",this.csrvname)-1)
endif
IF !EMPTY(this.cDir)
SET DEFAULT TO (this.cDir)
ENDIF
Add the SAMPLES\DATA\TESTDATA.DBC to your FIRST.PJX. Add a new class to the project called MYFORM
based on FORM and store it in first.vcx. Make the class OLE Public in the Class Info dialog box, and add three
custom properties: cDir, cSrvName, and cDataPath. Initialize cDatapath to the directory where your
testdata.dbc is, and cSrvName to FIRST. The cDir property will be initialized with the above code added to the
LOAD event of the form class. Drag a couple of EMPLOYEE fields from the Project onto the class. Add the VCR
class from the SAMPLES\CLASSES directory to your project, and drag an instance of the VCR button onto your
form. Save the class and BUILD EXE first FROM first.
Now you've built an OLE server with the employee form. This server can be instantiated from any other OLE
controller, but let’s test it from Visual FoxPro because it’s easy and familiar. In the Command window, type OX
= CreateObject("myfirst.myclass"), then type OX.Application.visible=.t. This will make the Visual FoxPro run-
time frame visible. OX.SHOW will make your form visible, and you can navigate around the form with the VCR
buttons.
RELEASE OX or CLEAR ALL will release the server. If you had changed the ShowWindow property to 2, then the
server form would be a Top Level form and the run-time Visual FoxPro desktop would not need to be shown.
You can also add the line THISFORM.VISIBLE = .T. in the INIT event of the form class.
Now try BUILD DLL rather than BUILD EXE, and experiment. Note that you cannot interact with the server:
when you bring the mouse over the server, you just get an hourglass. If the DLL server had the ability to
interact with the user, then it would be more like an OLE or ActiveX Control. Even though you can't interact
with the form, you can still give it commands from the Client: ox.application.docmd("skip") and
ox.refresh will skip to the next record. ?ox.application.eval("recno()") will return the current record
number, as expected.
Registry Issues
Below is my sample .vbr file. You'll notice the presence of long strings of numbers. These are GUIDs or Globally
Unique Identifiers (sometimes called UUIDs, or Universally Unique Identifiers). These are automatically
generated by your computer and are guaranteed to be virtually unique, based upon the time stamp and your
machine's network card, among other things.
When an OLE client application does a CreateObject, the parameter is called the ProgID or Programmatic ID.
The registry is searched for the ProcID to find the ClassID, which is just a GUID. Then that ClassID is located in
the registry to find more information about the server.
Visual FoxPro Custom OLE servers are self-registering. To register an EXE server, just run the .exe with the
/regserver option. The /unregserver unregisters the server. For a DLL server, run the utility Regsvr32.exe with
the name of the DLL as the first parameter. This will register the DLL. To unregister it, add a second
parameter.
Visual FoxPro OLE automation servers require the presence of the Visual FoxPro run time on the target
machine. This is vfp500.dll and vfp5enu.dll (for the English U.S. platform).
VB4SERVERINFO
HKEY_CLASSES_ROOT\first.myfirst = myfirst
HKEY_CLASSES_ROOT\first.myfirst\NotInsertable
HKEY_CLASSES_ROOT\first.myfirst\CLSID = {3DB63101-0FED-11D0-9A44-
0080C70FB085}
HKEY_CLASSES_ROOT\CLSID\{3DB63101-0FED-11D0-9A44-0080C70FB085} = myfirst
HKEY_CLASSES_ROOT\CLSID\{3DB63101-0FED-11D0-9A44-0080C70FB085}\ProgId =
first.myfirst
HKEY_CLASSES_ROOT\CLSID\{3DB63101-0FED-11D0-9A44-
0080C70FB085}\VersionIndependentProgId = first.myfirst
HKEY_CLASSES_ROOT\CLSID\{3DB63101-0FED-11D0-9A44-
0080C70FB085}\LocalServer32 = first.exe /automation
HKEY_CLASSES_ROOT\CLSID\{3DB63101-0FED-11D0-9A44-0080C70FB085}\TypeLib =
{3DB63102-0FED-11D0-9A44-0080C70FB085}
; TypeLibrary registration
HKEY_CLASSES_ROOT\TypeLib\{3DB63102-0FED-11D0-9A44-0080C70FB085}
HKEY_CLASSES_ROOT\TypeLib\{3DB63102-0FED-11D0-9A44-0080C70FB085}\1.0 =
first Type Library
HKEY_CLASSES_ROOT\TypeLib\{3DB63102-0FED-11D0-9A44-
0080C70FB085}\1.0\0\win32 = first.tlb
OLE Server Instancing
If from the Project menu you choose Project Info for the FIRST project, you'll see that there are three panes of
information. The third is called Servers, from which you can view or change the information relating to each
OLE Public in your project. Note that this information will only appear after you've built your project into an
EXE or DLL, because only then is the OLEPUBLIC keyword meaningful.
The Instancing drop-down allows you to specify how an out-of-process server will behave. If this is set at
"Single Use" and builds an EXE, then each OLE Automation client that instantiates this server will get its own
private instance of the server. For example, if you type OX = CreateObject("first.myform ") and then OY =
CreateObject("first.myform"), you'll actually get two running processes. (Note the SET EXCLUSIVE OFF line
in the LOAD event; this is important!) You can use tools like Task Manager to see two first.exes running. This
will also be the case if there were two different clients, say, Visual Basic and Visual FoxPro.
If you change the instancing on "Myform" to "Multiple Use" (which means this class can be used multiple times,
or can have multiple "Masters"), then instantiate both OX and OY as above, TaskManager shows only a single
Server instance serving multiple "Masters." You'll also note that moving the record pointer in one instance will
change it in the other. You can change the DataSession property to 2—PRIVATE to avoid this.
The third option on Instancing is "Not Creatable." Why would you want this? Suppose you have a Class library
with at least one OLE Public in it for one project. Now suppose you want to use some of the classes in that
class library for another project, but you didn't want to use that OLE Public class. Using this instancing option
means that the OLE Public will be ignored to solve this problem.
With just a few lines of code, you can take a FoxPro 2.x program and make it an OLE server. For example, the
Laser sample that ships with FoxPro 2.6 is a laser disk library manager program. The main program is
Laser.SPR. To turn this into an OLE object callable from Visual Basic or Excel, create a new project and add a
new main program:
ox = create("laser")
define class laser as custom olepublic
proc init
cd d:\fpw26\sample\laser && change dir to the right place
set path to data
this.application.visible = .t. && make us visible.
proc doit
do laser.spr
enddefine
Add laser.spr and rebuild the project. You'll need to manually add a couple of Laser files. Now, you can try OX
= Createobject("laser.laser"), and then OX.Doit. The laser application is now running as an OLE Automation
server! If you modify the LASER.SPR program so that it doesn't close the LASER table when the READ
(remember this command?) is finished, then you can query what laser disc title was chosen: ?
ox.application.eval("title")
Your First Dynamic Web Page
Your FIRST.PJX project contained a simple OLE server that just put up a message box. This is not very useful,
especially since most OLE servers should be able to run on an unattended server machine. Let's modify this
sample to generate HTML strings. Add to your FIRST.PRG the class definition for dbpub:
nlen=Getmodulefilename(GetModuleHandle(this.csrvname+".dll"),@buf,len(buf))
ELSE
nlen=Getmodulefilename(0,@buf,len(buf))
ENDIF
buf = LEFTC(buf,nlen)
this.cdir = LEFTC(buf,RATC('\',buf)-1)
this.csrvname = SUBSTRC(buf,RATC('\',buf)+1)
this.csrvname = LEFTC(this.csrvname,AT_C(".",this.csrvname)-1)
endif
IF !EMPTY(this.cDir)
SET DEFAULT TO (this.cDir)
ENDIF
Now we need to find an Internet Web server. You can use Windows NT® 4.0 Server, which comes with
Microsoft Internet Information Server (IIS), or NT3.51, Service Pack 4 with IIS. Windows 95 and Windows NT
4.0 Workstation will also work with Personal Web server. Other ISAPI compatible servers have been rumored
to work. Take the file VFP\SAMPLES\SERVERSFOXISAPI\FOXISAPI.DLL (note that the latest copy of this can be
found on the Microsoft Visual FoxPro Web site) and place it in the SCRIPTS directory of your IIS.
Suppose the Web site is named "myweb." Start up a Web browser, and type in the URL:
http://myweb/scripts/foxisapi.dll. You should get a Foxisapi error page, indicating that the DLL is working
correctly. Try adding arguments, such as http://myweb/scripts/foxisapi.dll/arg1.arg2.method?test. The first
and second arguments are interpreted as the ProgID of an OLE Automation server. The third parameter is
interpreted as the method name, and whatever is after the "?" is interpreted as a parameter to pass.
Before we run your FIRST server from the web, we need to tend to some security issues if you're running on
Windows NT 4.0. IIS runs as a service on NT, which means it can run with no user logged in. It also has very
limited rights to various parts of the operating system. Services normally do not have a desktop, so a
MessageBox from a service won't even show up on the screen and will just hang the service.
Windows NT 3.51 will allow any service rights to read the registry and launch an OLE server. This means you
have little control over somebody attaching to your machine and using it to run tasks. If you run the IIS
service manager, and look at the WWW service properties, you'll notice that the anonymous user logs in as
IUSR_MYWEB. From the Visual FoxPro command window, type !/n DCOMCNFG. This is an Windows NT 4.0
utility that you must run after registering an OLE server. Add "IUSR_MYWEB" to the three button dialogs on
the DCOMCNFG Default Security page. Look for "myfirst" in the list of applications on the Applications Page.
Choose Properties> Identity, and run as the Interactive User. Your particular machine/network setup might
vary from these procedures.
Because Visual FoxPro registers the server each time after a build, you may have to run DCOMCNFG after each
build. You can just run it and exit without changing anything. An alternative is to connect to the server
machine from another machine and just copy the new server EXE or DLL over the old one (assuming it's
already registered on the server machine).
Now that we've got security, lets hit our Web site with "myweb/scripts/foxisapi.dll/myfirst.dbpub.dbpub?test"
The returned HTML string shows the parameters. The first parameter is just whatever is after the "?". The
second parameter is the name of a .INI file that contains information about the Web hit. You can see this
information get parsed out if you do the following:
The third parameter is just a number passed in by reference. It's actually meaningless to the server. Each time
a Web hit occurs, foxisapi.dll will instantiate the ProgID, invoke the method passing the parms, return the
generated HTML to the Web site, and then release the OLE server. It's pretty inefficient to start and stop the
OLE server each time. However, if the server changes the value to 0, then the server won't be released and
will be already running and ready for the next Web hit. To release the server, it need only not alter the value.
To release all Visual FoxPro servers on a single server, you can use the URL: "/scripts/foxisapi.dll/reset". This
will call all cached existing instances of Visual FoxPro servers and tell them to release.
Now that we know how to have Visual FoxPro OLE servers generate HTML, let's get a little fancier and publish a
.dbf on the Web. Change the DBPUB method to the code below:
PROCEDURE dbpub(parms,inifile,relflag)
LOCAL rv,m.ii,mt
rv = "HTTP/1.0 200 OK"+CHR(13)+CHR(10)
rv = m.rv + "Content-Type:
text/html"+CHR(13)+CHR(10)+CHR(13)+CHR(10)
rv = m.rv + "<HTML>" + '<table border = "10">' + chr(13)
USE (m.parms) SHARED && Open the table
for ii = 1 to FCOUNT()
IF type("EVAL(FIELD(m.ii))") = 'G'
loop && don't proc general fields
ENDIF
rv = rv + "<th>" + PROPER(field(m.ii)) + "</th>" + chr(13)
endfor
rv = rv + "</tr>" + chr(13)
SCAN
rv = rv + "<tr>"
for ii = 1 TO FCOUNT()
IF type("EVAL(FIELD(m.ii))") = 'G'
loop && don't proc general fields
ENDIF
mt = eval(field(m.ii))
do case
case type("mt") = 'C'
rv = rv + "<td>" + mt + "</td>"
case type("mt") = 'T'
rv = rv + "<td>" + TTOC(mt) + "</td>"
case type("mt") $ 'NY'
rv = rv + "<td align=right>" + str(mt,8) + "</td>"
case type("mt") = 'D'
rv = rv + "<td>" + DTOC(mt) + "</td>"
endcase
rv = rv + chr(13)
endfor
rv = rv + "</tr>"
ENDSCAN
rv = rv +"</table>"
return rv
The first parameter specifies the name of the table. All this does is loop through all the columns and rows in a
DBF and returns an HTML Table string. Try it with a few URLs:
"HTTP://myweb/scripts/foxisapi.dll/first.dbpub.dbpub?customer",
"HTTP://myweb/scripts/foxisapi.dll/first.dbpub.dbpub?employee".
By just putting the name of a table in a URL, it is published on the Web. Pretty powerful stuff!
In your Visual FoxPro SAMPLES\SERVERS directory is a directory called FOXISAPI. This is a sample form class
that just allows the user to edit/view the EMPLOYEE data in TESTDATA.DBC. However, the same form can be
deployed in four different ways: as a normal Visual FoxPro form, as a Visual FoxPro run time, as an OLE
Automation server from an OLE Automation client, and over the Internet using any Web browser!
This sample requires only that end users have a machine capable of running any Internet browser, which
means it will allow you to deploy Visual FoxPro applications on 286s, Macs, Unix boxes, and maybe even
personal digital assistants! Any changes made to the application only need to be done once. The changes will
be automatically propagated to the other deployment platforms.
The readme.txt file in that directory explains how to install and configure the sample. For the latest version of
this sample, go to the Visual FoxPro home page at http://www.microsoft.com/vfoxpro/. Also be sure to read
the comments in foxisapi.cpp and isapi.vcx for tips.
The FOXISAPI sample takes advantage of the third parameter to do smart instancing of the server. The CMD
method allows the user to execute DOS commands or evaluate FoxPro expressions on the server. If RESET is
passed as a DOS command, the server will not change the third parameter to 0, and thus the server instance
gets released.
Most of the work in the FOXISAPI sample is done by the GENHTML method. The result is a two column HTML
table with labels for the field names and text box controls for the field values. It does an AMEMBERS( ) function
call to determine dynamically what objects are on the form and to place the objects into an array. It then does
a two-dimensional bubble sort to sort the objects into x,y order. HTML is stream based, whereas Visual FoxPro
is pixel based.
The method then loops through each form member in the array elements and tries to generate HTML for them.
If the object has its own GENHTML method, then it is invoked to return an HTML string. The class of the object
is examined to determine what kind of object it is, and the appropriate HTML is generated. If there's a text
box, then an associated label is searched for. Both of these get entered into the HTML table for the form.
After the form is generated, GENHTML then looks at the .INI file and appends the data to a table and the
returned HTML.
Web servers can be hit multiple times by multiple clients. If client A clicks NEXT and client B clicks NEXT, it's
important that the server knows which record was current for each client. This is handled with a cookie, which
is just a unique ID assigned to the client. Subsequently served Web pages to that client include the same
cookie hardcoded in its HTML.
Error Handling is extremely important from an OLE server, especially one that serves up Web pages. The
server is typically unattended, and often doesn't even have a desktop to which error dialogs can be displayed.
If there's an error in the FOXISAPI sample, the error method generates an Error HTML page and the GENHTML
method returns that error page.
The LOG method in the FOXISAPI sample is useful: it just adds a line to a text file. The file can be examined to
see what the server is doing. Use this liberally when debugging your applications. Microsoft Visual Studio™,
which comes with Visual C++, has an option to automatically reload externally modified files, so you can just
watch the log as the file changes.
Some parameters are passed via URLs, and thus some characters are not legal. For example, the chars "<>()"
are not legal. They are converted by the Web browser to their hexadecimal equivalents prepended by a "%".
The FIXURL method reverses the conversion.
When your Web server is hit from a Web site, how do you know who hit you? There are three new functions
added to Foxtools that will help determine this. It also depends on whether you're on an intranet or the
Internet. These functions call the Win Sockets API to resolve a remote IP Address to a host name.
// store 0 to buff1,buff2,buff3,buff4,buff5,buff6
//?_wsockstartup(256 * 1 + 1,@buff1,@buff2,@buff3,@buff4,@buff5,@buff6)
// integer version,
wVersion,wHighVersion,szDescription,szSystemStatus,iMaxSockets,iMaxUdpDg
Using Internet Information Server 3.0 with Active Server Framework (with the code name Denali) you can
have your Web server send Active Server Pages (ASP). These ASP files are HTML files with special tags
enclosed in the delimiters "<%" and "%>". These tags delimit both client-side and server-side scripts. Those
that are server side will be executed on the server and will not show up on the client side (when the client
Internet browser does a View>Source, for example).
The server-side scripting can be in any scripting language, but defaults to VBScript. You can create an ASP file
that has a few lines of script that will return HTML for your ISAPI form:
Here, a server script variable called ox is assigned an object reference to the Visual FoxPro automation server
"foxis.employee". The second line invokes the startup method on the object, and returns the results as HTML.
In this case, the OLE server instancing model is handled by Denali. Because ox is a script variable, its lifetime
is the lifetime of the script. That means the entire Visual FoxPro server instance is instantiated and released for
each Web hit. Denali provides variables scoped to a session to allow the OLE server to have a longer lifetime:
Instead of using a script variable, we're using a session variable called ox.
Using this method of starting the OLE server, the server instance will exist until the session.timeout (default 20
minutes) expires.
The ISFORM class of ISAPI has a property called fASP which is either True or False, indicating whether or not
the class was instantiated from an ASP page or from an HTML page (using the HREF
/scripts/foxisapi.dll/foxis.employee.startup). If this flag is true, then the server can return generated HTML
without an HTML Content header (there already is one in the ASP page. Also, an ASP page generates a cookie
that can be used as the unique identifier for the session, and this is parsed out in the STARTUP method.
OLE was designed so that an out-of-process automation server does not have to live on the same machine as
the client. It can be run on any other machine on the network. This remote automation capability means that
OLE server applications don't care whether they're to be deployed on a local machine or a remote one. Also,
because a single server can serve multiple clients, new classes of client/server application architectures are
possible.
OLE servers allow application developers to deploy their applications as OLE clients or OLE servers, or both.
That is, an application can have two or more components: one that acts as an OLE client, and one that acts as
an OLE server. For example, a customer form might be a simple OLE client application running on dozens of
machines, but it might talk to a single OLE server application that lives on a server machine somewhere that
might enforce business rules, such as no new orders from a customer who has outstanding debts. The business
rule enforcer might then talk to the actual data itself, which might be on Microsoft SQL Server or Visual FoxPro
tables on yet another machine. If the customer form were to change, then only the front end needs to be
updated. If the business rules change, then only the single business rule enforcer needs to be replaced. This
"three-tier" client/server architecture also means that data processing loads get distributed to specialized
applications/machines designed and optimized to do the job.
Another useful architecture is to have a client application spin off computationally- or resource-intensive tasks
to other machines, so the client machine is free for other tasks such as printing, database updating, report
generation, end-of-month processing, and so on.
Comment Notification
If you would like to receive an email when updates are made to this post, please register here
Comments
I browsed some web services available at www.xmethods.net and found one that returns information
on a zip code. (You can also try a MapPoint Web Service:
http://msdn.microsoft.com/mappoint/default.aspx?pull=/library/en-
us/dnmapnet30/html/MPWS_AND.asp)
Start Visual Studio 2005, choose File->New->Project->VB Windows Class Library. Call it
VBZipWebService.
From the Solution Explorer, delete Class1.vb, then right click on the VBZipWebService project (not
the VBZipWebService solution), choose Add New Item, choose User Control, call it
VBZipWebService.vb
From Project->Properties->Application, make sure the Root Namespace is VBZipWebService and not
ClassLibrary1. (If you don’t the ProgId for the control will be ClassLibrary1.VBZipWebService). Now
the ProgId will be VBZipWebService.VBZipWebService
You just created a control with a design surface. Add a Button and a DataGridView.
Right click the project again, choose Add Web Reference, then paste in this URL:
http://www.jasongaylord.com/webservices/zipcodes.asmx?wsdl as the WSDL (Web Service
Description Language), Click Go to see the Web methods, then Add Reference. Note that the “Web
Reference Name” is “com.jasongaylord.www”
Hit the F5 button to test your UserControl in the UserControl TestContainer. Click the button and wait
a few seconds for the se
Now that you have it working the way you want, let’s make this UserControl act like a normal ActiveX
control that can be used from Foxpro, Excel, VB6.
Choose Project->Properties->Compile tab. Make sure “Register For COM interop” checkbox is
checked.
Choose Project->Add New Item->Module. Call it Comregistration.vb, and paste this code into it:
Imports Microsoft.Win32
Imports System.Windows.Forms
System.Runtime.InteropServices.Marshal.GetTypeLibVersionForAssembly(t.Assem
bly, major, minor)
versionKey.SetValue("", String.Format("{0}.{1}", major,
minor))
End Using
End Using
Catch ex As Exception
HandleException("ComRegisterFunction failed.", t, ex)
End Try
End Sub
As you can see, this code has a couple methods to add a few registry keys on Registering and
Unregistering. Now we need to have this code called when the assembly is registered or unregistered.
The ComRegisterFunctionAttribute class helps here. (ComRegistration.vb is reusable as is with all
your controls) When you build, the assembly is automatically registered and these methods are
called.
We add 2 methods to use these attributes, an Imports statement, and a Public Event to show how we
can get events from the control.
(Alternatively, you can move your cursor to that line and double click the property sheet Com Class
property to make it True. Also, make sure the Project->Properties->Compile->Register for COM
Interop checkbox is checked.)
Imports System.Runtime.InteropServices
<Microsoft.VisualBasic.ComClass()> Public Class VBZipWebService
Public Event GotData(ByVal City As String)
Dim _Zipcode As String = "98052"
Public Property ZipCode() As String
Get
Return _Zipcode
End Get
Set(ByVal Value As String)
_Zipcode = Value
End Set
End Property
<ComUnregisterFunction()> _
Public Shared Sub Unregister(ByVal t As Type)
ComRegistration.UnregisterControl(t)
End Sub
End Class
From Excel Hit Alt-F11 (Tools->Macros->Visual Basic Editor), then Insert->User Form.
From VB6 and Excel, right click on the toolbar and choose “Additional Controls”(Excel) or
“Components…”(VB6). Drag the control onto the form and hit F5
You can also handle the event by adding this code in Excel:
Private Sub VBZipWebService1_GotData(ByVal City As String)
MsgBox ("got data " & City)
End Sub
PUBLIC ox
ox=CREATEOBJECT("myform")
ox.show
ENDDEFINE
Additional notes:
The Event can pass a .Net object as a parameter: Just add an attribute:
Public Event DocObjectUnknown(<MarshalAs(UnmanagedType.IUnknown)>
ByVal sender As Object)
That way the client gets an object reference to the .Net object (which may be different from the user
control).
To rebuild the control, you have to close the client process (Fox, Excel, VB6) to unload the CLR.
Click the Solution Explorer->Show All Files button and expand the web reference and look at some of
the generated files.
See also:
A Visual Basic COM object is simple to create, call and debug from Excel
Use Regular Expressions to get hyperlinks in blogs
I just finished composing this blog entry and I found Craig Boyd’s sample.
Published Friday, July 14, 2006 9:47 AM by Calvin_Hsia
Filed under: Visual FoxPro, VB, Windows API, Web
Comment Notification
If you would like to receive an email when updates are made to this post, please register here
Comments
Tuesday, July 18, 2006 9:42 PM by Use a different kind of grid in your applications »
Wagalulu - Microsoft » » Use a different kind of grid in your applications
PingBack from http://microsoft.wagalulu.com/2006/07/18/use-a-different-kind-of-grid-in-
your-applications/
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
http://support.microsoft.com/kb/311334/en-us
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
Regards,
Joris
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
# Create an ActiveX control using ATL that you can use from Fox, Excel, VB6, VB.Net
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
can this same method be used for embedding the activex in a browser?
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
Thursday, November 23, 2006 8:40 AM by Avy
I have tried your example.. It works fine in VB6 but on this usercontrol in VB6 I don't have
any events (like Click, MouseDown,MouseUp) only DragDrop,DragOver,
LostFocus,GotFocus and Validate events.
Thx
Avy
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
How can I export a c#.Net object as an ocx/activex/ecc.. to use in old style unmanaged
application ( the old c++ builder5 or other c++ )I would like to use a procedure that doesen't
use VB...
Thx
R.
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
Hi again.
I make a usercontrol(that inherits usercontrol class) in VB.net 2005 that contains a toolstrip
control and on that toolstrip I add in runtime 9 buttons.
I make this usercontrol as tlb and I try to add in VB 6 as components and it give me an error:
"Name conflicts with existing module, project, or object library"
When I develop a usercontrol that inherits button class and not usercontrol class it is
working.I think this is a problem but I am not so sure.
Thks
Avy
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
Does anyone have any experience with deploying a VB6 app that uses a .net control?
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
hello,
In my case, this works fine in tstcon.exe but not in excel, (maybe I missed some thing). I can
add this activex in vba editor "additional controls" but when I add to UserForm1, there was
an error: the system cannot find the file specified. Moreover the control icon in toolBox has
"Unknown" as tooltipBox ,Hope I have a reply!!
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
When I Rebuild the .NET Assembly, the reference in VB6 breaks. I get an exception "Object
library invalid or contains references to object definitions that could not be found".
# Use WPF and inline XAML in your Fox, Excel or VB6 applications
My prior post showed how to create XAML WPF and put it on your Winform App. We can
go one step further:
# Use WPF and inline XAML in your Fox, Excel or VB6 applications
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
Hi All,
I have created user control in vb.net but it does not handle events like mouseup,mousedown.
Thanx
# re: Create a .Net UserControl that calls a web service that acts as an ActiveX control
to use in Excel, VB6, Foxpro
Hi All
sample
</OBJECT>
Normally, Creating a service is fairly complex, but creating a VFP application as a service is fairly
straightforward, thanks to a pair of tools called INSTSRV.EXE and SRVANY.EXE that come with the
Windows Resource Kit. INSTSRV.EXE takes 2 parameters: the name of the new service to install,
and the full path to the SRVANY.EXE file.
For example, to create a service called “VFPSrv”, I ran this on my Windows XP machine:
C:\Program Files\Windows Resource Kits\Tools>instsrv VFPSrv "d:\Program Files\Windows Resource
Kits\Tools\srvany.exe"
SRVANY provides all the main work to pretend to be a service, and it will look in the registry to find
what EXE file to run to implement the service. The EXE name, the starting directory, and any optional
parameters are found in the registry.
Copy/paste the following to a file named VFPSRV.REG (change the paths as necessary: note the
double back-slashes) and dbl-click the .REG file to add these entries to the registery.
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\VFPSrv\Parameters]
"AppDirectory"="d:\\fox90\\test"
"Application"="d:\\fox90\\test\\vfpsrv.exe"
"AppParameters"="myparm1 myparm2"
You can use the VFP command window to control the service:
!net start vfpsrv
!net stop vfpsrv
The sample service code below executes a file called “vfpsrvrtn.prg” when the timer fires. However,
this code is not built into the EXE. This means you can change the service’s behavior dynamically,
without having to stop/rebuild/start the service.
The code executes the timer event every 5 seconds, aligned to the 5 second real time clock (so that it
occurs at exactly :00, :05, :10, … seconds past the hour).
If you run the prg below, it will run as a normal VFP PRG, but it will also build VFPSRV.EXE.
To start it as a service, use the control panel->Administrative Tools->Services or “net start vfpsrv”
Open the log file in an automatically refreshing editor, such as Visual Studio, from another machine
and then log off the main machine. You can still hear the beeps and see the log file changing when no
user is logged in.
(using older versions of SRVANY or on older versions of Windows, you may need to use SYS(2340) )
PUBLIC oService
oService=NEWOBJECT("vfpsrv")
oService.logstr("Starting Service: got some params: "+TRANSFORM(parm1)+"
"+TRANSFORM(parm2))
IF _vfp.StartMode>0 && if we’re running as an EXE
READ events && message loop
ENDIF
*RETURN
ENDDEFINE
45776
Published Monday, December 13, 2004 1:49 PM by Calvin_Hsia
Filed under: Visual FoxPro, Windows API