0

I am new to the world of Win32 COM interfaces. My eventual goal is to connect to an OPC server. I am getting stuck early in the process. There is a Win32 COM service which enumerates all available OPC servers on my machine (The OPCEnum.exe service), but I can't access it from my program. Commercial OPC tools on my computer can perform this task, so I know that the OPC Server enumerator is installed and running. I can see it listed in the Windows System Services.

My system configuration is: Windows 7 64-bit, Python 3.7 64-bit in Anaconda, pywin32 installed using pip (so it should also be 64-bit, although I haven't figured out how to check that).

Here is minimal example code:

import win32com.client as w32

def com_client_verbose(client_class_name):
    title = client_class_name + " client"
    print(title)
    print("=" * len(title))
    com_client = w32.gencache.EnsureDispatch(client_class_name)
    print(type(com_client))
    print(dir(com_client))
    print("\n\n")
    return com_client

def class_name_verbose(clsid):
    title = clsid + " module"
    print(title)
    print("=" * len(title))
    module = w32.gencache.EnsureModule(clsid, 0, 1, 0)
    print(type(module))
    path = module.__file__
    print(path)
    # INSPECT the CODE to obtain the class name. Is this really necessary?
    # https://stackoverflow.com/questions/17225798/python-win32com-dont-know-name-of-module
    with open(path, "r") as f:
        for line in f.readlines():
            if line.startswith("# This CoClass is known by the name"):
                print(line, "\n\n")
                return line.split("'")[1]
        else:
            raise RuntimeError("CoClass comment line not found.")

# Most Windows machines have Excel installed. Excel exposes a COM interface.
print("\n\n")
xl_client = com_client_verbose("Excel.Application")

# OPCEnum CLSID is documented at: http://www.opcti.com/dcom-error-for-clsid.aspx
# win32com.client.combrowse confirms that this CLSID is active on my system.
opc_enum_clsid = "{13486D43-4821-11D2-A494-3CB306C10000}"
opc_enum_class_name = class_name_verbose(opc_enum_clsid)

# Now that we have a class name for OPCEnum, try to create its COM client.
# This fails.
opc_enum_client = com_client_verbose(opc_enum_class_name)

Here is the output. It looks good up to the very last line.

Excel.Application client
========================
<class 'win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9._Application.
_Application'>
['ActivateMicrosoftApp', 'AddChartAutoFormat', 'AddCustomList', 'CLSID', 'Calcul
ate', 'CalculateFull', 'CalculateFullRebuild', 'CalculateUntilAsyncQueriesDone',
 'CentimetersToPoints', 'CheckAbort', 'CheckSpelling', 'ConvertFormula', 'DDEExe
cute', 'DDEInitiate', 'DDEPoke', 'DDERequest', 'DDETerminate', 'DeleteChartAutoF
ormat', 'DeleteCustomList', 'DisplayXMLSourcePane', 'DoubleClick', 'Dummy1', 'Du
mmy10', 'Dummy11', 'Dummy12', 'Dummy13', 'Dummy14', 'Dummy2', 'Dummy20', 'Dummy3
', 'Dummy4', 'Dummy5', 'Dummy6', 'Dummy7', 'Dummy8', 'Dummy9', 'Evaluate', 'Exec
uteExcel4Macro', 'FileDialog', 'FindFile', 'GetCaller', 'GetClipboardFormats', '
GetCustomListContents', 'GetCustomListNum', 'GetFileConverters', 'GetInternation
al', 'GetOpenFilename', 'GetPhonetic', 'GetPreviousSelections', 'GetRegisteredFu
nctions', 'GetSaveAsFilename', 'Goto', 'Help', 'InchesToPoints', 'InputBox', 'In
tersect', 'MacroOptions', 'MailLogoff', 'MailLogon', 'NextLetter', 'OnKey', 'OnR
epeat', 'OnTime', 'OnUndo', 'Quit', 'Range', 'RecordMacro', 'RegisterXLL', 'Repe
at', 'ResetTipWizard', 'Run', 'Save', 'SaveWorkspace', 'SendKeys', 'SetDefaultCh
art', 'SharePointVersion', 'ShortcutMenus', 'Support', 'Undo', 'Union', 'Volatil
e', 'Wait', '_ApplyTypes_', '_Evaluate', '_FindFile', '_MacroOptions', '_Run2',
'_WSFunction', '_Wait', '__call__', '__class__', '__delattr__', '__dict__', '__d
ir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribu
te__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__int__', '__iter
__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__red
uce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__
', '__weakref__', '_get_good_object_', '_get_good_single_object_', '_oleobj_', '
_prop_map_get_', '_prop_map_put_', 'coclass_clsid']



{13486D43-4821-11D2-A494-3CB306C10000} module
=============================================
<class 'module'>
C:\Users\FOO\AppData\Local\Temp\gen_py\3.7\13486D43-4821-11D2-A494-3CB306
C10000x0x1x1.py
# This CoClass is known by the name 'OPC.ServerList.1'



OPC.ServerList.1 client
=======================
Traceback (most recent call last):
  File "C:\ProgramData\Anaconda3\lib\site-packages\win32com\client\dynamic.py",
line 89, in _GetGoodDispatch
    IDispatch = pythoncom.connect(IDispatch)
pywintypes.com_error: (-2147221021, 'Operation unavailable', None, None)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:\ProgramData\Anaconda3\lib\runpy.py", line 183, in _run_module_as_main
    mod_name, mod_spec, code = _get_module_details(mod_name, _Error)
  File "C:\ProgramData\Anaconda3\lib\runpy.py", line 109, in _get_module_details
    __import__(pkg_name)
  File "C:\Users\foo\Documents\OPC\win32com minimal example.py", line 49, in <module>
    opc_enum_client = com_client_verbose(opc_enum_class_name)
  File "C:\Users\foo\Documents\OPC\win32com minimal example.py", line 14, in com_client_verbose
    com_client = w32.gencache.EnsureDispatch(client_class_name)
  File "C:\ProgramData\Anaconda3\lib\site-packages\win32com\client\gencache.py", line 527, in EnsureDispatch
    disp = win32com.client.Dispatch(prog_id)
  File "C:\ProgramData\Anaconda3\lib\site-packages\win32com\client\__init__.py", line 95, in Dispatch
    dispatch, userName = dynamic._GetGoodDispatchAndUserName(dispatch,userName,clsctx)
  File "C:\ProgramData\Anaconda3\lib\site-packages\win32com\client\dynamic.py", line 114, in _GetGoodDispatchAndUserName
    return (_GetGoodDispatch(IDispatch, clsctx), userName)
  File "C:\ProgramData\Anaconda3\lib\site-packages\win32com\client\dynamic.py", line 91, in _GetGoodDispatch
    IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
pywintypes.com_error: (-2147467262, 'No such interface supported', None, None)

Step 1, my positive control: If I know a COM class name, such as "Excel.Application", I can return a live code object to its COM interface.

Step 2: If I know a CLSID but not the COM class name, I can retrieve the name. I don't like the way that I have to do this -- by inspecting the Python script itself, for a COMMENT line -- but that is what was recommended in this StackOverflow comment and it appears to work.

Step 3 fails. Even though I got the COM class name "OPC.ServerList.1" from the CLSID, Windows/pywin32 is telling me that there is no interface associated with that name.

I also tried the class name "OPC.ServerList" without the ".1", thinking that the ".1" might be an instance number of some kind. This also failed.

Any advice is appreciated, thanks!

John Ladasky
  • 1,016
  • 8
  • 17

1 Answers1

0

COM has two (three) kind of interfaces: Custom interfaces (method signatures and types in them can be practically anything you like), automation interfaces (must derive from IDispatch, and the way the method looks, and the type choices, are limited). And there are also dual interfaces (interfaces that satisfy restrictions of both of the preceding types).

All interfaces in OPCEnum, and also all interface of OPC Classic servers, are custom interfaces.

I am no python expert but AFAIK, the win32com only supports automation interfaces. So, you cannot use win32com with OPCEnum, or OPC Classic servers.

I googled somewhat and found this: https://www.codeproject.com/Articles/22563/Working-with-custom-COM-interfaces-from-Python . I am not sure whether it works, but it certainly contains interesting information that can drive you forward. (tl;dr: it suggest to use 'comtypes' instead of 'pythoncom').

ZbynekZ
  • 1,532
  • 10
  • 16
  • Thanks for your reply, @ZbynekZ. I found another Python package called OpenOPC which handles the Win32 COM details and lets the programmer focus on OPC (https://pypi.org/project/OpenOPC-Python3x/). You also need a DLL running on your system which handles OPC Data Automation. There are some proprietary DLL's, but there's a free one (closed-source though) available from GrayBox software at: http://gray-box.net/daawrapper.php?lang=en – John Ladasky May 21 '20 at 19:34