OLE, COM, ActiveX & .NET Interop
FiveWin provides comprehensive support for Windows COM/OLE automation, ActiveX embedding, and modern cloud-based email APIs. These classes let you automate Microsoft Office applications, embed ActiveX controls in dialogs, interact with .NET assemblies, and send email through Gmail and Outlook APIs.
classDiagram
class TOleAuto {
+hObj
+New(cClassName)
+End()
+property access via OnError
}
class TComObject {
+nIDispatch
+New(cClassName)
+Invoke(cMethod, params...)
+End()
}
TControl <|-- TActiveX
class TActiveX {
+hActiveX
+cProgID
+oOleAuto
+New(oWnd, cProgID, ...)
+Do(...)
+GetProp(cName)
+SetProp(cName, uVal)
+bOnEvent
}
TOAuth <|-- TGmail
TOAuth <|-- TOutlookMail
class TGmail {
+cToken
+send(cTo, cSubject, cMessage)
+auth()
+me()
}
class TOutlookMail {
+cToken
+send(aTo, cSubject, cMessage)
+auth()
+me()
}
TOleAuto - OLE Automation
TOleAuto is FiveWin's primary class for OLE/COM automation. It wraps the Harbour win_oleAuto functionality, allowing you to create and control OLE objects such as Microsoft Word, Excel, Outlook, Internet Explorer, and any other automation server. Properties and methods of the OLE object are accessed transparently through Harbour's error handler -- you call them as if they were native class members.
How It Works
When you call TOleAuto():New("Word.Application"), FiveWin calls CreateOleObject() which invokes CoCreateInstance() under the hood to create the COM object. The returned IDispatch pointer is stored in the hObj DATA member. Any method or property access that is not defined in the class is intercepted by the OnError handler, which routes it through OleInvoke() or OleGetProperty()/OleSetProperty().
DATA Members
| DATA | Type | Description |
hObj | Pointer/Numeric | IDispatch pointer to the OLE automation object |
METHOD Reference
| Method | Description |
New( cClassName ) | Creates an OLE automation object by ProgID (e.g. "Excel.Application", "Word.Application"). |
End() | Releases the COM object and calls OleUninitialize(). |
(any property) | Get/Set any OLE property via OnError handler. e.g. oExcel:Visible := .T. |
(any method) | Invoke any OLE method transparently. e.g. oWord:Documents:Add() |
Example: Create a Word Document
function CreateWordDoc()
local oWord, oDoc, oSelection
// Create Word application
oWord := TOleAuto():New( "Word.Application" )
oWord:Visible := .T.
// Add a new document
oDoc := oWord:Documents:Add()
// Get the selection (cursor) and type text
oSelection := oWord:Selection
oSelection:Font:Name := "Arial"
oSelection:Font:Size := 14
oSelection:Font:Bold := .T.
oSelection:TypeText( "FiveWin Report" )
oSelection:TypeParagraph()
oSelection:Font:Size := 11
oSelection:Font:Bold := .F.
oSelection:TypeText( "Generated on " + DToC( Date() ) + " at " + Time() )
oSelection:TypeParagraph()
oSelection:TypeParagraph()
oSelection:TypeText( "This document was created from a Harbour/FiveWin application." )
// Save and close
oDoc:SaveAs( "c:\temp\report.docx" )
MsgInfo( "Word document created successfully!" )
// Don't quit Word - let user review the document
// oWord:Quit()
return nil
Example: Create an Excel Spreadsheet
function CreateExcel()
local oExcel, oBook, oSheet, n
oExcel := TOleAuto():New( "Excel.Application" )
oExcel:Visible := .T.
oBook := oExcel:WorkBooks:Add()
oSheet := oExcel:ActiveSheet
// Column headers
oSheet:Cells( 1, 1 ):Value := "Product"
oSheet:Cells( 1, 2 ):Value := "Quantity"
oSheet:Cells( 1, 3 ):Value := "Price"
oSheet:Cells( 1, 4 ):Value := "Total"
// Bold headers
oSheet:Range( "A1:D1" ):Font:Bold := .T.
oSheet:Range( "A1:D1" ):Interior:Color := RGB( 200, 220, 255 )
// Data rows
local aData := { ;
{ "Widget A", 100, 9.99 }, ;
{ "Widget B", 50, 19.99 }, ;
{ "Widget C", 200, 4.99 }, ;
{ "Widget D", 75, 29.99 } }
for n := 1 to Len( aData )
oSheet:Cells( n + 1, 1 ):Value := aData[ n ][ 1 ]
oSheet:Cells( n + 1, 2 ):Value := aData[ n ][ 2 ]
oSheet:Cells( n + 1, 3 ):Value := aData[ n ][ 3 ]
oSheet:Cells( n + 1, 4 ):Formula := "=B" + AllTrim( Str( n + 1 ) ) + ;
"*C" + AllTrim( Str( n + 1 ) )
next
// Total formula
oSheet:Cells( 6, 3 ):Value := "Grand Total:"
oSheet:Cells( 6, 3 ):Font:Bold := .T.
oSheet:Cells( 6, 4 ):Formula := "=SUM(D2:D5)"
oSheet:Cells( 6, 4 ):Font:Bold := .T.
// Auto-fit columns
oSheet:Columns( "A:D" ):AutoFit()
oBook:SaveAs( "c:\temp\products.xlsx" )
MsgInfo( "Excel file created!" )
return nil
Example: Send Email via Outlook
function SendOutlookEmail()
local oOutlook, oMail
oOutlook := TOleAuto():New( "Outlook.Application" )
// Create a new mail item (olMailItem = 0)
oMail := oOutlook:CreateItem( 0 )
oMail:To := "customer@example.com"
oMail:CC := "manager@example.com"
oMail:Subject := "Monthly Report - " + CMonth( Date() )
oMail:Body := "Dear Customer," + CRLF + CRLF + ;
"Please find attached the monthly report." + CRLF + CRLF + ;
"Best regards," + CRLF + "Support Team"
// Attach a file
oMail:Attachments:Add( "c:\reports\monthly.pdf" )
// Display for review (use :Send() to send directly)
oMail:Display()
MsgInfo( "Email composed in Outlook" )
return nil
TComObject - Low-Level COM Interface
TComObject is a lower-level alternative to TOleAuto for COM interop. It stores the IDispatch pointer as a numeric value and provides explicit Invoke() calls. Properties are accessed via the OnError handler, which routes through OleGetProperty/OleSetProperty. When a method returns another COM object, TComObject automatically wraps it.
DATA Members
| DATA | Type | Description |
nIDispatch | Numeric | Raw IDispatch pointer to the COM object |
METHOD Reference
| Method | Description |
New( cClassName ) | Creates COM object by ProgID string, or wraps existing IDispatch if numeric is passed. |
Invoke( cMethodName, uParam1, uParam2, uParam3 ) | Explicitly invokes a method. Returns a new TComObject if the result is an IDispatch. |
ClassName() | Returns the COM object's class name via OleGetProperty. |
End() | Calls OleUninitialize() to release the COM object. |
Example: Using TComObject
local oCom := TComObject():New( "Scripting.FileSystemObject" )
if oCom:nIDispatch != 0
? "Drives available:"
local oDrives := oCom:Drives
? "Total drives:", oDrives:Count
// Check if C: drive exists
if oCom:DriveExists( "C:" )
local oDrive := oCom:GetDrive( "C:" )
? "C: Drive - Total:", oDrive:TotalSize, "Free:", oDrive:FreeSpace
endif
oCom:End()
endif
TActiveX - Embedded ActiveX Controls
TActiveX inherits from TControl and allows embedding any ActiveX/OCX control inside a FiveWin window or dialog. It creates the ActiveX container using Windows OLE embedding, then exposes the control's methods and properties through an internal TOleAuto object. Events from the ActiveX control can be captured via the bOnEvent codeblock.
DATA Members
| DATA | Type | Description |
hActiveX | Numeric | Handle to the ActiveX container |
cProgID | Character | ProgID of the ActiveX control (e.g. "Shell.Explorer.2") |
cString | Character | Display string for the ActiveX control |
oOleAuto | Object | Internal TOleAuto wrapper for the control's IDispatch |
aProperties | Array | Available properties (populated by ReadTypes) |
aMethods | Array | Available methods (populated by ReadTypes) |
aEvents | Array | Available events (populated by ReadTypes) |
bOnEvent | Codeblock | Event handler: {|nEvent, aParams, pParams| ... } |
METHOD Reference
| Method | Description |
New( oWnd, cProgID, nRow, nCol, nWidth, nHeight ) | Creates ActiveX control in oWnd at given position. |
ReDefine( nId, oWnd, cProgID ) | Redefines from a dialog resource control ID. |
Do( ... ) | Invokes a method on the underlying OLE object. Pass method name and parameters. |
GetProp( cPropName ) | Gets a property value from the ActiveX control. |
SetProp( cPropName, uValue ) | Sets a property value on the ActiveX control. |
ReadTypes() | Reads type information (properties, methods, events) from the control. |
OnEvent( nEvent, aParams, pParams ) | Internal event dispatcher. Override bOnEvent for custom handling. |
Destroy() | Destroys the ActiveX container and releases resources. |
Example: Embed a Web Browser
function EmbedBrowser()
local oDlg, oActiveX
DEFINE DIALOG oDlg TITLE "Embedded Browser" SIZE 800, 600
// Shell.Explorer.2 is Internet Explorer's ActiveX ProgID
oActiveX := TActiveX():New( oDlg, "Shell.Explorer.2", 0, 0, 800, 600 )
// Handle navigation events
oActiveX:bOnEvent := {| nEvent, aParams | ;
If( nEvent == 252, ( MsgInfo( "Navigating to: " + aParams[ 2 ] ) ), ) }
ACTIVATE DIALOG oDlg CENTER ;
ON INIT ( oActiveX:Navigate( "https://www.fivetechsoft.com" ) )
return nil
Example: Embed a PDF Viewer
function ShowPDF( cPdfFile )
local oDlg, oActiveX
DEFINE DIALOG oDlg TITLE "PDF Viewer" SIZE 800, 600
// Adobe Acrobat Reader ActiveX
oActiveX := TActiveX():New( oDlg, "AcroPDF.PDF.1", 0, 0, 800, 600 )
ACTIVATE DIALOG oDlg CENTER ;
ON INIT ( oActiveX:LoadFile( cPdfFile ), ;
oActiveX:SetView( "FitH" ) )
return nil
Example: Embed Windows Media Player
function PlayMedia( cMediaFile )
local oDlg, oWMP
DEFINE DIALOG oDlg TITLE "Media Player" SIZE 640, 480
oWMP := TActiveX():New( oDlg, "WMPlayer.OCX", 0, 0, 640, 480 )
ACTIVATE DIALOG oDlg CENTER ;
ON INIT ( oWMP:URL := cMediaFile )
return nil
TDotNet - .NET Interop
FiveWin supports calling .NET assemblies from Harbour through COM interop. While there is no dedicated TDotNet class in the source, .NET components that expose COM interfaces can be instantiated via TOleAuto. For custom .NET assemblies, you can register them for COM interop using regasm and then create them like any other COM object.
Approach: Using .NET via COM Interop
sequenceDiagram
participant Harbour as Harbour/FiveWin
participant COM as COM Runtime
participant CLR as .NET CLR
participant Assembly as .NET Assembly
Harbour->>COM: CreateOleObject("MyLib.MyClass")
COM->>CLR: Load CLR Host
CLR->>Assembly: Instantiate class
Assembly-->>COM: IDispatch pointer
COM-->>Harbour: TOleAuto object
Harbour->>COM: Property/Method calls
COM->>Assembly: Invoke via IDispatch
Example: Calling .NET via COM
// Step 1: Register .NET assembly for COM interop (run once from command line):
// regasm MyLibrary.dll /tlb:MyLibrary.tlb /codebase
// Step 2: Use from Harbour/FiveWin
function UseDotNet()
local oDotNet
TRY
oDotNet := TOleAuto():New( "MyLibrary.MyClass" )
? oDotNet:MyMethod( "Hello from Harbour!" )
? oDotNet:MyProperty
oDotNet:End()
CATCH
MsgAlert( ".NET component not registered or not available" )
END
return nil
Example: Using .NET System Libraries
// The .NET mscorlib types exposed via COM can be accessed directly
function DotNetExample()
local oNet
// Create a .NET StringBuilder via COM
TRY
oNet := TOleAuto():New( "System.Text.StringBuilder" )
oNet:Append( "Hello" )
oNet:Append( " World" )
? oNet:ToString()
CATCH
// .NET COM interop may not be available on all systems
MsgAlert( "Requires .NET COM registration" )
END
return nil
TGmail - Gmail API Integration
TGmail inherits from TOAuth and provides OAuth2-authenticated access to the Gmail API. It uses libcurl for HTTP communication and supports sending emails with HTML content and file attachments. Authentication is handled through Google's OAuth2 flow with refresh tokens.
DATA Members
| DATA | Type | Description |
cToken | Character | OAuth2 refresh token |
hToken | Hash | Parsed token response (access_token, etc.) |
hConfig | Hash | OAuth configuration: client_id, client_secret, redirect_uri |
METHOD Reference
| Method | Description |
New( cToken ) | Creates TGmail instance with optional refresh token. |
SetConfig( hConfig ) | Sets OAuth2 configuration hash with client_id, client_secret, redirect_uri. |
SetToken( cToken ) | Sets the refresh token. |
Auth() | Initiates OAuth2 authorization flow. Opens browser for user consent. |
IsAuth() | Checks if the current token is valid (validates against Google's token info endpoint). |
GetToken( cCode ) | Exchanges authorization code for access token. |
Me() | Returns the authenticated user's profile information. |
Send( cTo, cSubject, cMessage, lHTML, aAttachment ) | Sends an email. lHTML: .T. for HTML body. aAttachment: array of file paths. |
Revoke() | Revokes the OAuth2 token. |
Example: Send Email via Gmail
function SendGmail()
local oGmail := TGmail():New()
// Configure OAuth2 credentials (from Google Cloud Console)
oGmail:SetConfig( { ;
"client_id" => "your-client-id.apps.googleusercontent.com", ;
"client_secret" => "your-client-secret", ;
"redirect_uri" => "http://localhost:8888" ;
} )
// First-time authentication (opens browser)
if ! oGmail:IsAuth()
oGmail:Auth()
endif
if oGmail:IsAuth()
// Send a plain text email
oGmail:Send( "recipient@example.com", ;
"Hello from FiveWin", ;
"This email was sent using TGmail class.", ;
.F. ) // .F. = plain text
// Send HTML email with attachment
oGmail:Send( "recipient@example.com", ;
"Monthly Report", ;
"<h1>Report</h1><p>Please see attached.</p>", ;
.T., ; // .T. = HTML
{ "c:\reports\monthly.pdf" } )
MsgInfo( "Emails sent successfully!" )
else
MsgAlert( "Gmail authentication failed" )
endif
return nil
TOutlookMail - Outlook Mail API
TOutlookMail inherits from TOAuth and provides OAuth2-authenticated access to Microsoft Graph API for sending email through Outlook/Office 365. Like TGmail, it uses libcurl and supports HTML content, file attachments, CC, and BCC recipients. It automatically handles refresh token rotation when Microsoft rotates them.
DATA Members
| DATA | Type | Description |
cToken | Character | OAuth2 refresh token |
hToken | Hash | Parsed token response (access_token, refresh_token, etc.) |
hConfig | Hash | OAuth configuration: client_id, client_secret, redirect_uri, tenant |
hUser | Hash | Authenticated user profile information |
METHOD Reference
| Method | Description |
New( cToken ) | Creates instance with optional refresh token. |
SetConfig( hConfig ) | Sets OAuth2 config: client_id, client_secret, redirect_uri (with port for local server). |
SetToken( cToken ) | Sets the refresh token. |
Auth() | Initiates Microsoft OAuth2 authorization flow. |
IsAuth() | Validates token, auto-refreshes if expired. Updates refresh_token if rotated. |
GetToken( cCode ) | Exchanges authorization code for access/refresh tokens. |
Me( cPhoto ) | Returns user profile. Optionally saves profile photo to cPhoto path. |
Send( aTo, cSubject, cMessage, lHTML, aAttachment, aBCC, aCC ) | Sends email. aTo/aBCC/aCC are arrays of email addresses. |
Revoke() | Revokes the OAuth2 token. |
Example: Send Email via Outlook/Office 365
function SendOutlookAPI()
local oMail := TOutlookMail():New()
// Configure Azure AD app credentials
oMail:SetConfig( { ;
"client_id" => "your-azure-app-client-id", ;
"client_secret" => "your-azure-client-secret", ;
"redirect_uri" => "http://localhost:9090", ;
"tenant" => "common" ;
} )
// Authenticate (opens browser for Microsoft login)
if ! oMail:IsAuth()
oMail:Auth()
endif
if oMail:IsAuth()
// Get user info
local hUser := oMail:Me()
? "Sending as:", hUser[ "displayName" ], hUser[ "mail" ]
// Send email with CC and BCC
oMail:Send( ;
{ "recipient@example.com", "another@example.com" }, ; // To
"Project Update", ; // Subject
"<h2>Weekly Update</h2><p>All tasks completed.</p>", ; // Body
.T., ; // HTML
{ "c:\docs\status.pdf" }, ; // Attachments
{ "boss@example.com" }, ; // BCC
{ "team@example.com" } ) // CC
MsgInfo( "Email sent via Outlook API!" )
endif
return nil
Tips and Best Practices
OLE Error Handling
// Always wrap OLE calls in TRY/CATCH
TRY
local oExcel := TOleAuto():New( "Excel.Application" )
// ... work with Excel
oExcel:Quit()
CATCH oErr
MsgAlert( "OLE Error: " + oErr:Description + CRLF + ;
"Make sure the application is installed." )
END
ActiveX Event Handling Pattern
// The bOnEvent codeblock receives:
// nEvent - numeric event ID
// aParams - array of event parameters
// pParams - raw parameter pointers
oActiveX:bOnEvent := {| nEvent, aParams, pParams | ;
do case ;
case nEvent == 250 ; // BeforeNavigate2
? "Navigating to:", aParams[ 2 ] ;
case nEvent == 252 ; // DocumentComplete
? "Page loaded" ;
case nEvent == 259 ; // NewWindow3
? "Popup blocked" ;
endcase ;
}
Releasing COM Objects
Always release COM objects when done. For Office applications, call Quit() before releasing, or the process may remain in memory:
// Correct cleanup order for Excel:
oSheet := nil
oBook:Close( .F. ) // .F. = don't save
oBook := nil
oExcel:Quit()
oExcel := nil
SysRefresh() // Allow COM cleanup