Using COM/ActiveX in BCX

The BCX COM (Component Object Model) procedures allow BCX code to interface with many Microsoft component technologies including Office, OLE, OLE Automation, ActiveX, COM+, DCOM, DirectX, Windows shell, UMDF, Windows Runtime, Browser Helper Object and others.

$COM_ON and $COM_OFF directives

Purpose:

The default setting in the BCX translator is $COM_ON which specifies that COM procedures are used in the code being translated. $COM_OFF indicates that that COM procedures are not being used in the code being translated.

Syntax:

$COM_ON or $COM_OFF

Parameters:

  • None

When any BCX COM interface procedure is invoked, the translator automatically adds the following define to the output C code.

#ifndef _WIN32_DCOM
#define _WIN32_DCOM
#endif

OBJECT type definition

BCX definition:
TYPE AS OBJECT
 p_unknown AS IUnknown PTR
 pObjects[COM_STACK_SIZE] AS VARIANT
 pName[COM_STACK_SIZE][128] AS TCHAR
 pStatus AS BOOL
 ipointer AS INT
END TYPE

C/C++ definition:

typedef struct _OBJECT
 {
  IUnknown*  p_unknown;
  VARIANT    pObjects[COM_STACK_SIZE];
  TCHAR      pName[COM_STACK_SIZE][128];
  BOOL       pStatus;
  int        ipointer;
 }OBJECT, *LPOBJECT;

Example:

BCX_SHOW_COM_ERRORS(TRUE)

DIM app AS OBJECT
COMSET app = CREATEOBJECT("Excel.Application")

app.workbooks.add
app.visible = true
app.ActiveSheet.Cells(3,1).Value="Hello"
app.ActiveSheet.Cells(4,1).Value="From BCX"
app.ActiveSheet.Cells(5,1).Value="Console program!"

DIM temp_var$

temp_var$ = app.ActiveSheet.Cells(3,1).Value
MSGBOX temp_var$, "value of cell(3,1)", 4096

MSGBOX "BCX COM Example!" & CRLF$ _
& "Using Office automation to manipulate Excel." & CRLF$ _
& "Program will close Excel in 1 second.","finished!", 4096
SLEEP(1000)
app.activeworkbook.saved = true
app.quit
COMSET app = NOTHING

For more examples of the BCX COM functions see the COM directory at the https://bcxbasiccoders.com/archives/YahooGroups/Com/ website.

Related topics: CreateObject | Set Nothing | List of all COM Interface Functions


COMSET ... COMSET ... NOTHING statement

Purpose:

The COMSET COMObject CREATEOBJECT statement creates an instance of specified COM OBJECT.

After a COMSET COMObject = CREATEOBJECT or a call to a BCX_DISPATCHOBJECT function, a COMSET COMObject = NOTHING statement must be made for each created COM/ActiveX object to release allocated memory resources.

Syntax:

COMSET COMObject = CREATEOBJECT(COMObjectName AS STRING)
...
COMSET COMObject = NOTHING

Return Value:

Parameters:

  • Data type: STRING
    COMObjectName The name of a registered COM object.

Remarks:

The BCX COM SET keyword, now considered deprecated, is an alias of COMSET. COM SET has alignment issues when reformatted by most BCX code beautifiers.

Limitations:

  1. The length of a COM OBJECT name is not allowed to exceed 64 characters.
  2. An OBJECT cannot be declared or used in a user defined TYPE.
  3. CREATEOBJECT is not capable of creating a remote OBJECT.
    CREATEOBJECT("Excel.Application","\\at_net_pc1")
    
    is illegal.
  4. Do not use names for variables that you may have already assigned to global COM object variables.
  5. Named arguments are not supported.
  6. Do not use the & concatenation operand on the same line as an OBJECT variable. The BCX COM parser cannot properly parse, for example, this
    app.server = "my" & "server" & somevar$
    
    so instead, use something like this
    myvar$ = "my" & "server" & somevar$
    app.server = myvar$
    

Example 1:

If the function fails, BCX_COM_ERROR flag is set to TRUE. To get extended error information, call BCX_GET_COM_ERROR_CODE or BCX_GET_COM_ERROR_DESC$.

CLS

 DIM app AS OBJECT

 COMSET app = CREATEOBJECT("Excel.Application")

 IF BCX_COM_ERROR THEN
 PRINT BCX_GET_COM_ERROR_DESC$
 END
 END IF

 app.visible = TRUE

 Sleep(2000) ' Excel will be visible for 2 sec, and than it will close.

 app.activeworkbook.saved = TRUE ' don't prompt to save workbook

 app.quit

 COMSET app = NOTHING ' remember to call Set xxx = Nothing to release resources used by COM object!!!

Example 2:

' In Windows 10, the output GnotePad.lnk file is placed in
' C:\Users\YourUserName\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

DIM Link$
 
Link$ = Startup_directory$() + "GnotePad.lnk"
 
CreateLink("c:\windows\notepad.exe",Link$,"","MS Notepad")
 
FUNCTION Startup_directory$ ()
  DIM A$
  DIM Wshell AS OBJECT
  COMSET Wshell = CREATEOBJECT("Wscript.Shell")
  A$ = Wshell.Specialdirectories("Startup")
  COMSET Wshell = NOTHING
  FUNCTION = A$ + "\"
END FUNCTION
 

FUNCTION CreateLink (Exe$, Link$, Args$, Desc$) AS HRESULT
  DIM hres AS HRESULT
  DIM psl AS IShellLink PTR
  DIM w[MAX_PATH] AS WORD

  CoInitialize(NULL)

  $IFDEF __cplusplus
    hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,  IID_IShellLink, (LPVOID*)&psl)
  $ELSE
    hres = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,  &IID_IShellLink, (LPVOID*)&psl)
  $ENDIF

  IF (SUCCEEDED(hres)) THEN
    DIM ppf AS IPersistFile PTR
    psl->lpVtbl->SetPath(psl, Exe)
    psl->lpVtbl->SetDescription(psl, Desc)
    psl->lpVtbl->SetShowCmd(psl, SW_SHOWMAXIMIZED)
    psl->lpVtbl->SetArguments(psl, Args$ )

    $IFDEF __cplusplus
      hres = psl->lpVtbl->QueryInterface(psl, IID_IPersistFile, (LPVOID*)&ppf)
    $ELSE
      hres = psl->lpVtbl->QueryInterface(psl, &IID_IPersistFile, (LPVOID*)&ppf)
    $ENDIF

    IF (SUCCEEDED(hres)) THEN
      MultiByteToWideChar(CP_ACP, 0, Link, -1, (LPWSTR)w, MAX_PATH)
      hres = ppf->lpVtbl->Save(ppf, (LPCOLESTR)w, TRUE)
      ppf->lpVtbl->Release(ppf)
    END IF
    psl->lpVtbl->Release(psl)
  END IF

  CoUninitialize()
  FUNCTION = hres
END FUNCTION

Example 3:

This example is for COM with Open Office, Libre Office etc.

DIM oServiceManager AS OBJECT
DIM oDesktop AS OBJECT
DIM oCalcDoc AS OBJECT
DIM oSheet AS OBJECT

' Get the Service Manager object -- from whence everything else comes.
' The biggest first difference between programming languages accessing OOo
' is often in how you initially obtain the ServiceManager.
COMSET oServiceManager = CREATEOBJECT("com.sun.star.ServiceManager")

' Get the Desktop object.
COMSET oDesktop = oServiceManager.createInstance("com.sun.star.frame.Desktop")

' Use this empty array when no arguments are needed.
' DIM aNoArgs[5]

' Create a new empty spreadsheet.
COMSET oCalcDoc = oDesktop.loadComponentFromURL("private:factory/scalc", "_blank", 0,(VT_ARRAY|VT_VARIANT)NULL)

' Get the first spreadsheet from the bunch of spreadsheets in the document.
COMSET oSheet = oCalcDoc.getSheets().getByIndex(0)

  ' Plug in some stuff.
  oSheet.getCellByPosition(0, 0).SetFormula("Month")
  oSheet.getCellByPosition(1, 0).SetFormula("Sales")
  oSheet.getCellByPosition(0, 1).SetFormula("Jan")
  oSheet.getCellByPosition(0, 2).SetFormula("Feb")
  oSheet.getCellByPosition(0, 3).SetFormula("Mar")
  oSheet.getCellByPosition(1, 1).SetValue(3827)
  oSheet.getCellByPosition(1, 2).SetValue(3978)
  oSheet.getCellByPosition(1, 3).SetValue(4103)

COMSET oSheet = NOTHING
COMSET oCalcDoc = NOTHING
COMSET oDesktop = NOTHING
COMSET oServiceManager = NOTHING

Example 4:

This simple program will allow you to hide any unwanted Windows updates. Simply run it, wait for it to list all your available updates, and type the numbers of the updates to hide, separated by spaces.
👉 Please note, due to how the Windows update API works, this program requires administrative privileges to run.

#include <wuapi.h>

BCX_SHOW_COM_ERRORS(TRUE)

DIM AS OBJECT Session, Searcher, SearchResult, Update, Updates
DIM NumUpdates, I
COMSET Session = COM("Microsoft.Update.Session")
COMSET Searcher = Session.CreateupdateSearcher()
Searcher.ClientApplicationID = "uphide"
PRINT "Checking for updates..."
COMSET SearchResult = Searcher.Search("IsInstalled=0 And IsHidden=0")

NumUpdates = SearchResult.Updates.Count
IF NumUpdates = 0 THEN
  PRINT "No updates were found."
  UNCOM(SearchResult)
  UNCOM(Searcher)
  UNCOM(Session)
  END
END IF
PRINT "Enter the numbers of the updates you want to hide,"
PRINT "separated by spaces, and press enter."
PRINT "Leave blank to exit."

DIM UpdatesIndex = 1
COMSET Updates = SearchResult.Updates
FOR Each Item IN Updates
  DIM Title$
  Title$ = Item.Title
  PRINT STR$(UpdatesIndex, 1) & ": " & Title$
  UpdatesIndex++
NEXT

DIM ToHideInput$
INPUT ToHideInput$
IF ToHideInput$ = "" THEN
  UNCOM(Updates)
  UNCOM(SearchResult)
  UNCOM(Searcher)
  UNCOM(Session)
  END
END IF

DIM NumSelections
DIM ToHide[100] AS STRING
NumSelections = SPLIT(ToHide, ToHideInput$, " ", 0)
FOR I = 0 TO NumSelections - 1
  DIM CurIndex
  CurIndex = VAL(ToHide[I])
  IF CurIndex <= 0 OR CurIndex > NumUpdates THEN CONTINUE
  COMSET Update = Updates.Item(CurIndex - 1)
  IF ISOBJECT(Update) THEN
    DIM Title$
    Title$ = Update.Title
    Update.IsHidden = TRUE
    PRINT "Hid " & Title$
    UNCOM(Update)
  END IF
NEXT

UNCOM(Update)
UNCOM(Updates)
UNCOM(SearchResult)
UNCOM(Searcher)
UNCOM(Session)

PAUSE

For more examples of the BCX COM functions see the COM directory at the https://bcxbasiccoders.com/archives/YahooGroups/Com/ website.

Related topics: Object data type definition | List of all COM Interface Functions


COM ... UNCOM statement

Purpose:

The COM ... UNCOM statement is a simplification of the COMSET COMObject = CREATEOBJECT ... COMSET COMObject = NOTHING statement.

The following code, using the COM and UNCOM functions,

$BCXVERSION "7.5.2"

DIM oVoice AS OBJECT
oVoice = COM("SAPI.SpVoice")
IF ISOBJECT(oVoice) THEN oVoice.Speak "Hello from BCX"
UNCOM(oVoice)

produces the same results as the 4 lines of code shown below. The difference is that the COM and UNCOM functions seem more intuitive, are quicker to type, and easier to remember. The code below is still 100% valid.

DIM oVoice AS OBJECT
COMSET oVoice = CREATEOBJECT("SAPI.SpVoice")
IF BCX_GET_COM_STATUS(&oVoice) THEN oVoice.Speak "Hello from BCX"
COMSET oVoice = NOTHING

Check out the following links to run-time procedures that BCX provides for working with COM/ActiveX objects:

How to


All products mentioned in this manual and/or in program samples are trademarks of their respective owners.