COM Interface

#1
Well over a year ago, I posted a suggestion to improve TCC with a COM Interface.

As of this posting, my suggestion has 8 votes.

There are few, if any, scripting languages that do not include a COM Interface.

Plugins for TCC are great, but I cannot use them in another scripting language (PowerShell, VBSCript, AutoIt, etc.), nor can I call them from any programming language (Harbour, PowerBASIC, Visual Basic 6, etc.).

I develop functions that I use personally into ActiveX COM DLLs. Only if I feel they would be useful to other TCC users do I make a TCC Plugin for those functions.

ActiveX COM DLLs can be called from scripting languages, and programming languages. While I can write a wrapper for the COM Interface using <insert scripting language> and call it from TCC, it would be more efficient if TCC itself had the ability to directly call functions from a COM Interface.

If you would like to see the addition of a COM Interface to TCC, please vote for this suggestion.

jpsoft.uservoice.com/forums/94009-take-command/suggestions/2593250-com-interface

Joe
 
#2
Joe, can you explain further how you envision it working? I know nothing about ActiveX and little about COM. Do you want all of TCC's power available in various scripting languages ... or for TCC to be able to use resources in DLLs not designed as plugins ... or both of those? I don't have a big picture.
 
#3
Joe, can you explain further how you envision it working? I know nothing about ActiveX and little about COM. Do you want all of TCC's power available in various scripting languages ... or for TCC to be able to use resources in DLLs not designed as plugins ... or both of those? I don't have a big picture.
The link psoft.uservoice.com/forums/94009-take-command/suggestions/2593250-com-interface gives an example of what I envision.

Let's say I wanted to use the COM Interface to Minimize All windows. Using AutoIt, I could do this as follows;

Code:
$oShell = ObjCreate("shell.application")
 
$oShell.MinimizeAll
If I wanted to do the same thing using VBScript;

Code:
dim objShell
 
set objShell = CreateObject("shell.application")
objshell.MinimizeAll
 
set objShell = nothing
In PowerShell;

Code:
$shellapp = New-Object -ComObject shell.application
$shellapp.MinimizeAll()
Thus, the function, contained in SHELL32.DLL, which has a COM Interface, can be called directly from a scripting/programming language that has a COM Interface. At present, TCC cannot call a function via the COM Interface from SHELL32.DLL. Yes, I am aware that TCC has the TASKBAR command to do this, but this was just an example.

In my example at jpsoft.uservoice.com/forums/94009-take-command/suggestions/2593250-com-interface I show how I would call a function from a COM Interface that I have created.

I can also call the functions in my COM Interface from Microsoft Excel (http://support.microsoft.com/kb/291392).

I use Visual Basic 6.0 to create a COM Interface for my COM Objects and Automation Classes. Info on Creating a COM Interface using Visual C++ is available in the link.

So, I would like TCC to be able to call the functions in .DLLs (such as SHELL32.DLL, which has a COM Interface, or in .DLLs that I create, which have a COM Interface), or in .EXEs that have a COM Interface (such as Microsoft Excel, Microsoft Word, which have a COM Interface, or in .EXEs that I create, which have a COM Interface), which are not designed as plugins, or standard DLLs.

Joe
 
#4
I am not familiar with the COM interface, but it seems that TCC can already call functions in DLLs via the @CAPI or @WINAPI functions. Would a COM interface just be more convenient, and possibly less restricting of argument data type or argument count?
 
#7
I am not familiar with the COM interface, but it seems that TCC can already call functions in DLLs via the @CAPI or @WINAPI functions. Would a COM interface just be more convenient, and possibly less restricting of argument data type or argument count?
Well, if I want to read/write to a Microsoft Excel spreadsheet using PowerShell, I can access it via Excels COM Interface, thus;

Code:
$xl = New-Object -COM "Excel.Application"
I can then open a workbook;

Code:
$wb = $xl.Workbooks.Open("C:\path\to\your.xlsx")
and read/write to it via the COM Interface. I can do the same thing with a Microsoft Excel spreadsheet using AutoIt, VBScript, PowerBASIC, Visual Basic 6.0, PowerShell. I cannot do this with TCC.

TCC has a lot of built-in functions. However, I would like to 'interface' to other applications, including ones that I have written. Since I build a COM Interface into EXEs and DLLs that I create, I can access my functions from PowerShell, AutoIt, VBScript, etc. I cannot do this from TCC.

While I can create a plugin with all of my functions, that means putting my code in two locations; in my EXEs/DLLs that have a COM Interface, and in my plugins. I would prefer to put my code in one location, so that I can access it from PowerShell, AutoIt, VBScript, and TCC.

Thus, that is why I could like TCC to be able to access EXEs/DLLs via their COM Interface.

Joe
 
#8
Being able to access an Excel document doesn't excite me much. Is there a (more or less complete and more or less in one place) list of other of (Microsoft) classes whose properties and methods can be accessed via a COM interface?
 
#9
Being able to access an Excel document doesn't excite me much. Is there a (more or less complete and more or less in one place) list of other of (Microsoft) classes whose properties and methods can be accessed via a COM interface?
These are all listed on your system. Most products today offer a COM Interface. For example, I use Palm Desktop, which provides a COM Interface, so that I can access it from PowerShell, AutoIt, VBScript, etc.

Microsoft has a program called OLEView, which comes with Visual Studio, the SDKs, Resource Tool Kits, or you can download it from Microsoft.

Mine is located in C:\Program Files\Microsoft SDKs\Windows\v6.0\Bin\OleView.Exe

Use OLEView to discover the COM Interfaces of the applications installed on your system.

Joe
 
#10
OLEView ... that's impressive, and daunting. Where in that maze of thousands of things would I find (for example) something related to whatever is happening when, in PowerShell, I
Code:
$shellapp = New-Object -ComObject shell.application
In looking for something related to shell.application, I expanded ObjectClasses\AllObjects\ApplicationObject and immediately got a DataMystic purchase/license/registration details dialog. When I dismissed that dialog, I was left at what appeared to be a GUI TextPipeEngine filter tool (non-functional). What's that all about?
 
#11
...I expanded ObjectClasses\AllObjects\ApplicationObject.... What's that all about?
I would say it is all about "Objects" ;)

From the OLE/COM Object Viewer, look down the left-hand side for "Type Libraries"

Expand "Type Libraries"

Scroll down to "Microsoft Shell Controls And Automation"

Right-click on "Microsoft Shell Controls And Automation", and select "View..."

This will launch the ITypeLib Viewer

Scroll down to "coclass Shell"

Expand "coclass Shell", and keep expanding until you expose the "Methods"

These are the Methods that are exposed through the COM Interface of SHELL32.DLL. You should see "MinimizeAll" as one of the "Methods" (functions).

Click on each of the methods to see what, if any, arguments the Method takes. (Note that I call them functions, because that's what they are, but Microsoft says they are "Methods").

After creating your own Automation Object with a COM Interface, you use Regsvr32 to load the component and register it with your system. You will then see it appear in the OLE/COM Object Viewer.

You can then use PowerShell, VBScript, AutoIt, etc., to call the "Methods" of your target class. (coclass provides a listing of the supported interfaces for a component object.) I cannot use TCC to call the "Methods" of a target class, since TCC does not have a function to do this.

Joe
 
#12
OK, I see. Thanks.

On paper, it would seem a good idea to expose all that functionality to TCC users. But how much effort would that take? How much would it be used? I don't know.
 

samintz

Scott Mintz
May 20, 2008
1,270
11
Solon, OH, USA
#13
Any COM interface that uses a type library could be exposed in the same generic way that scripting languages access it. The COM interfaces without a type library would either need to be hard coded using the IDL files (if memory serves. It's been awhile since I did COM programming) or not supported.

It should be relatively easy to code a plugin that does the "set thevalue = COM(Object, "Function", param1, param2, ...)" behavior.
 
#15
It should be relatively easy to code a plugin that does the "set thevalue = COM(Object, "Function", param1, param2, ...)" behavior.
Hmmm.... You got me thinking...

Looking at early-binding seems a bit too complicated, but late-binding, although a bit slower, seems easier. Let me try something...

...well, ain't that something. I created a console app, passing in the name of the COM Object, and it creates the object.

Looks like I am on my way. I will tinker with this for the next few days, and hopefully will have a plugin at the end of it.

Thanks Scott. Not sure what you said that made it all clearer for me, but it got me thinking, and I just might be able to make my own solution.

Joe
 
#16
Way above my level of expertise, but a plugin like that would be quite useful to me.

Joe
I'm tempted to experiment a bit. I did a little reading and looked at a couple examples. The first question that came to mind is ... if you want to instantiate some COM object, don't you need to know a REFCLSID and a REFIID (or at least macros which represent them)? Example:
Code:
CLSID_ShellLink and IID_IShellLink
Do you know if that's so, Joe. If that is the case, it would seem an app would have to be choosy about what it exposed to the user, maintain a database of those constants, and implement user-friendly names for the objects it exposed. And if all that's true, a totally general interface to all COM objects would seem impossible.
 
#17
And if all that's true, a totally general interface to all COM objects would seem impossible.
If you are using early-binding, yes. That is why I am taking the late-binding approach. Here's the start of my Console app to access the COM Interface of an Object;

Code:
#COMPILE EXE "JLCCOM"
#COMPILER PBWIN 8.05
#DIM ALL
 
FUNCTION PBMAIN () AS LONG
  DIM oUser AS DISPATCH
  DIM TheObject AS STRING
 
  TheObject = PARSE$(COMMAND$, 1)
 
  LET oUser = NEW DISPATCH IN TheObject
  IF ISOBJECT(oUser) THEN
    'Object was created
    MSGBOX COMMAND$ + " Object Created"
  ELSE
    MSGBOX "Could Not Create " + COMMAND$
    EXIT FUNCTION
  END IF
  LET oUser = NOTHING
END FUNCTION
Thus, as an example, from the command line, if I run;

Code:
jlccom.exe shell.application
the Object gets created. If I run;

Code:
jlccom.exe shell.applicatio
the Object does not get created.

Joe
 
#18
I did find GUIDFromString and IIDFronString so maybe I can turn user-friendly names into the appropriate identifiers. I think COM is easier in VB. And I believe I'll have to do everything by hand, starting with CoInitialize.
 
#19
I did find GUIDFromString and IIDFronString so maybe I can turn user-friendly names into the appropriate identifiers. I think COM is easier in VB. And I believe I'll have to do everything by hand, starting with CoInitialize.
Yes, VB6 hides all of the complicated C-stuff, making development of ActiveX COM Objects very easy.

PowerBASIC, which I use to develop plugins for TCC, is somewhere between VB6 and VC6, when it comes to working with COM Objects. PowerBASIC does the calls to CoInitialize and CoUninitialize under the hood.

Joe
 
#21
I think that TCC.EXE might do the same -- Rex would know for certain.
I think so too. But I could not catch TCC calling CoInitialize() with WinDbg. SYSUTILS\SHELLEX does not call CoInitialize(), but when I add a call to it, it returns S_FALSE (the COM library is already initialized on this thread).

When I use SHELLEX with no explicit calls to CoAnything, I can catch, with WinDbg, two calls to CoUninitialize() (and none to CoInitialize()). I'm not sure what's going on.
 
#22
I think so too. But I could not catch TCC calling CoInitialize() with WinDbg. SYSUTILS\SHELLEX does not call CoInitialize(), but when I add a call to it, it returns S_FALSE (the COM library is already initialized on this thread).

When I use SHELLEX with no explicit calls to CoAnything, I can catch, with WinDbg, two calls to CoUninitialize() (and none to CoInitialize()). I'm not sure what's going on.
But I wasn't looking for CoInitializeEx() which TCC does call. CoInitializeEx() is also called (implicitly) twice when ShellExecuteEx() is used (accounting for the extra calls to CoUninitialize()).
 
#23
If you are using early-binding, yes. That is why I am taking the late-binding approach.
Here's what I have accomplished so far;

Code:
::--------------------------------------------------------------------
:: OBJECTCALL.BTM
::
:: Call Methods from ActiveX (COM) Objects from the CLI
::
:: TCC 15.01.52
:: 2013/07/20
:: Joe Caverly
::
:: I am working on a TCC Plugin to accomplish this same task, and am
::  using this as an example of what I want to accomplish.
::
:: The plugin version will be a function (@objectcall)
::  and a command (objectcall).
::
:: TODO: Error checking of command line arguments, misc. stuff.
::--------------------------------------------------------------------
@setlocal
@echo off
 
switch %#
case 2
  gosub 2args
case 3
  gosub 3args
default
  echo USAGE: objectcall dispatch method argument
  echo Exple: objectcall jlcutils.clsmath ppp 6.59
  echo        objectcall shell.application minimizeall
  echo        objectcall shell.application filerun
  quit
endswitch
 
if exist objectcall.vbs del objectcall.vbs > nul
 
do i = %start to %end
  echo %@line[%_batchname,%i] >> objectcall.vbs
enddo
 
cscript //nologo objectcall.vbs
 
if exist objectcall.vbs del objectcall.vbs > nul
endlocal
quit
 
:2args
set start=%@eval[%_batchline+1]
text > nul
set dispatch = wscript.createobject("%1")
wscript.echo dispatch.%2
endtext
set end=%@eval[%_batchline-3]
return
 
:3args
set start=%@eval[%_batchline+1]
text > nul
set dispatch = wscript.createobject("%1")
wscript.echo dispatch.%2("%3")
endtext
set end=%@eval[%_batchline-3]
return
Here is my (future) plugin code;

Code:
'---------------------------------------------------------------------
' JLCCOM will allow the calling of Methods from
'  ActiveX DLLs (COM Objects) that I have created using VB6 (Visual
'  Basic 6) via the CLI
'
' Once I get this working, I will use this code to create a plugin for
'  JPSoft's Take Command Console
'
#COMPILE EXE "JLCCOM"
#COMPILER PBWIN 8.05
#DIM ALL
 
' Requires LJ's Complete Console (LJ-CC) for PB/Win
' http://www.laurencejackson.com/LJ-CC/index.html
#INCLUDE "C:\Users\jlc\Documents\powerbasic\lj-cc\lj-cc.bas"
 
' For debugging purposes
MACRO VERBOSE = -1  ' 0 = ON
                    '-1 = OFF
 
' The program takes three arguments from the command line;
'  Name of the DISPATCH
'  Name of the METHOD
'  Argument for the METHOD
'
'  Command line arguments (for testing) are;
'  JLCUtils.clsMath,ppp,6.59
'  |                |  |
'  |                |  + strArgIn (vntArgIn)
'  |                + strMethod
'  + strObject
'
'
' The following MACRO works, and the desired results are returned.
'
MACRO ObjectCall = oUser.ppp(vntArgIn) TO vntResult
 
' The following MACRO does NOT work, as the strMethod text is not
'  replaced with ppp that was entered via COMMAND$
'
'MACRO ObjectCall = oUser.strMethod(vntArgIn) TO vntResult
 
FUNCTION PBMAIN () AS LONG
  DIM oUser AS DISPATCH
 
  DIM strObject AS STRING
  DIM strMethod AS STRING
  DIM strArgIn  AS STRING
  DIM strResult AS STRING
 
  DIM vntArgIn  AS VARIANT
  DIM vntMethod AS VARIANT
  DIM vntResult AS VARIANT
 
  strObject = PARSE$(COMMAND$, 1)
  strMethod = PARSE$(COMMAND$, 2)
  strArgIn  = PARSE$(COMMAND$, 3)
 
  vntArgIn = strArgIn
 
  LET oUser = NEW DISPATCH IN strObject
  IF ISOBJECT(oUser) THEN
    'Object was created
    #IF VERBOSE
      StdOutPrint strObject + " Object Created" + $CRLF
    #ENDIF
 
    'With the arguments passed, this line should be
    'OBJECT CALL oUser.ppp(vntArgIn) TO vntResult
    OBJECT CALL ObjectCall
    '
    ' ObjectCall is a MACRO, defined at line 36
    '
    #IF VERBOSE
      StdOutPrint "Error = " + STR$(ERR) + $CRLF
    #ENDIF
    strResult = LTRIM$(RTRIM$(STR$(VARIANT#(vntResult))))
    StdOutPrint strResult + $CRLF
  ELSE
    #IF VERBOSE
      StdOutPrint "Could Not Create " + strObject + $CRLF
    #ENDIF
    EXIT FUNCTION
  END IF
  LET oUser = NOTHING
END FUNCTION
I am having a bit of trouble with the MACRO command, as indicated in my code. I figure if I can do text substitution, I will not have to search for the INTERFACE METHOD in the DISPATCH (early-binding). I would much rather keep going on my late-binding approach.

Joe
 
#24
I'm in uncharted waters here. I playing with the idea in C/C++. Maybe this will help you. Maybe you will help me. The code below works to verify that "Open" in a member and then invokes it. I can easily (and successfully) replace "Open" with "MinimizeAll" (changing dispparams.cArgs to 0) and the hard-coded "shell:desktop" wiith another (user-supplied) folder name.
Code:
    WCHAR *szMember = L"Open";
    DISPID dispid;
    hr = pIShellDispatch->GetIDsOfNames(IID_NULL, &szMember, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
    if ( hr != S_OK ) return 3;
 
    VARIANT vArg, vResult;
    vArg.vt = VT_BSTR;
    vArg.bstrVal = L"shell:desktop";
    DISPPARAMS dispparams;
    dispparams.cArgs = 1;
    dispparams.rgvarg = &vArg;
    dispparams.cNamedArgs = 0;
 
    pIShellDispatch->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dispparams, &vResult, NULL, NULL);
 
#27
I'm in uncharted waters here. I playing with the idea in C/C++. Maybe this will help you. Maybe you will help me.
I am afraid that I do not "C/C++" very well. So, maybe this PowerBASIC code will help you. It shows how to create a class, with a method, that does not have a COM Interface, thus, it can only be used in that program;

Code:
#COMPILE EXE
#DIM ALL
#INCLUDE "Win32API.inc"
 
FUNCTION PBMAIN () AS LONG
  LOCAL JLCUtils AS iMath
  LOCAL ppk AS DOUBLE
 
  JLCUtils = CLASS "cJLCUtils"
 
  ppk = 6.59
 
  IF ISOBJECT(JLCUtils) THEN
    ? USING$("$#.## per kg is $#.## per lb", ppk, JLCUtils.ppp(ppk) )
  ELSE
    ? "Object was not created"
  END IF
END FUNCTION
 
CLASS cJLCUtils                            ' Name of our class
  INTERFACE iMath                      ' Name of our interface
  INHERIT IUNKNOWN                    ' What our interface inherits from
  METHOD ppp( BYVAL ppk AS DOUBLE) AS DOUBLE      ' Our method
    METHOD = ppk * .454                    ' Return the result of work
  END METHOD                          ' End of method
  END INTERFACE                        ' End of interface
END CLASS                              ' End of class
Many, many years ago, when I was learning Object-Oriented Programming, I first learned how to create classes, and work with them. After I understood that, I moved on to exposing the CLASS METHODS via a COM Interface.

Joe
 
#28
I'm in uncharted waters here. I playing with the idea in C/C++. Maybe this will help you. Maybe you will help me.
Vince, here are links to working with COM Objects using the Windows API. I had these bookmarked, and I tried going through these a few years back, but way above my understanding, but it may help you.

COM Tutorial #1: COM Object Memory
http://www.jose.it-berater.org/smfforum/index.php?topic=2980.0

COM Tutorial In Plain C
http://www.jose.it-berater.org/smfforum/index.php?topic=3100.0

Building Local Server
http://www.jose.it-berater.org/smff...afa140823c193&topic=3100.0;prev_next=prev#new

Joe
 
#29
Thanks, Joe. I'll check out the links. I think my point earlier was that once a user-named method checked out OK and I have a DISPID for it, I can call it with Invoke. Now I'm trying to figure out how to go from method name or DISPID to what types of arguments are expected by that method (I figure that's what type libs are all about; OLEViewer gets them). Even if the user knows what they are, a robust client would have to get them, typed correctly, into a DISPPARAMS struct in order for my strategy to work.
 
#30
Thanks, Joe. I'll check out the links. I think my point earlier was that once a user-named method checked out OK and I have a DISPID for it, I can call it with Invoke. Now I'm trying to figure out how to go from method name or DISPID to what types of arguments are expected by that method (I figure that's what type libs are all about; OLEViewer gets them). Even if the user knows what they are, a robust client would have to get them, typed correctly, into a DISPPARAMS struct in order for my strategy to work.
After doing some (okay, quite a bit) of reading, I came upon a COM DLL called TLBINF32.DLL, which is a TypeLib Information library.

This file came with my Visual Studio 6.0, specifically, Visual Basic 6.0. The help file starts off with;

The TypeLib Information object library (TLI for short, implemented in TlbInf32.dll) is a set of COM objects designed to make type library browsing functionality easily accessible to both Visual Basic and C++ programmers. Due to the flexibility and inherent subtleties inherent in type libraries, examining a type library via the native ITypeLib and ITypeInfo interfaces is a non-trivial task—even for an experienced C++ programmer.
A search of the web revealed the dumpTypeLib.vbs visual basic script file, which uses TLBINF32.DLL, and does as it says.

This seems to be part of the solution for calling any Method in a COM object. Not sure if the TLBINF32.DLL file is installed on every Microsoft Windows system, or, if not, if TLBINF32.DLL can legally be installed on any system.

Joe