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

DATATypeDescription
hObjPointer/NumericIDispatch pointer to the OLE automation object

METHOD Reference

MethodDescription
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

DATATypeDescription
nIDispatchNumericRaw IDispatch pointer to the COM object

METHOD Reference

MethodDescription
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

DATATypeDescription
hActiveXNumericHandle to the ActiveX container
cProgIDCharacterProgID of the ActiveX control (e.g. "Shell.Explorer.2")
cStringCharacterDisplay string for the ActiveX control
oOleAutoObjectInternal TOleAuto wrapper for the control's IDispatch
aPropertiesArrayAvailable properties (populated by ReadTypes)
aMethodsArrayAvailable methods (populated by ReadTypes)
aEventsArrayAvailable events (populated by ReadTypes)
bOnEventCodeblockEvent handler: {|nEvent, aParams, pParams| ... }

METHOD Reference

MethodDescription
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

DATATypeDescription
cTokenCharacterOAuth2 refresh token
hTokenHashParsed token response (access_token, etc.)
hConfigHashOAuth configuration: client_id, client_secret, redirect_uri

METHOD Reference

MethodDescription
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

DATATypeDescription
cTokenCharacterOAuth2 refresh token
hTokenHashParsed token response (access_token, refresh_token, etc.)
hConfigHashOAuth configuration: client_id, client_secret, redirect_uri, tenant
hUserHashAuthenticated user profile information

METHOD Reference

MethodDescription
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