Search code examples
pythoncomdirectshow

How to load COM object with custom interface from dll in python?


Good day, everyone. The task is to work with COM object loaded from dll (regsvr32 usage is forbidden). Also that object exposes DirectShow interfaces that I'll need in a future.

Then I'm trying to get a module using examples from this link, I'm running into a problem: pythoncom doesn't know a thing about DirectShow interfaces (IBaseFilter for instance). And from this post I get an impression that pythoncom doesnt' support custom COM interfaces, but that was in 2008, maybe things have changed now?

The code is

# -*- coding: utf-8 -*-
import ctypes, inspect, os, pythoncom, sys
from comtypes import client
from ctypes import OleDLL, c_long, byref
from uuid import UUID

#dshow is a module with DirectShow constants, etc
cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"path_to_dshow_module")))
if cmd_subfolder not in sys.path:
    sys.path.insert(0, cmd_subfolder)
import dshow

#that way comtypes gets to know about DirectShow interfaces
qedit = client.GetModule("qedit.dll") 
dll_path = os.path.join(cmd_subfolder, "../my_path/my_dshow_filter.ax") #specifying path to dll
iid_interface = dshow.CLSID_IUnknown
iid_ibasefilter = dshow.CLSID_IBaseFilter
clsid_class = UUID(dshow.CLSID_my_filter).bytes_le
iclassfactory = UUID(str(pythoncom.IID_IClassFactory)).bytes_le
com_classfactory = c_long(0)

my_dll = ctypes.oledll.LoadLibrary(dll_path)
#getting com_classfactory pointer to an adress of IClassFactory within loaded dll
hr = my_dll.DllGetClassObject(clsid_class, iclassfactory, byref(com_classfactory))
#creating class factory from adress using pythoncom
MyFactory = pythoncom.ObjectFromAddress(com_classfactory.value, pythoncom.IID_IClassFactory)
#creating COM object using IClassFactory::CreateInstance, using IUnknown as a default interface
dmx_interface = MyFactory.CreateInstance(None, iid_interface)
# I could've tried to use IBaseFilter directly, 
# but pythoncom knows nothing about DirectShow interfaces!
# dmx = dmx_interface.QueryInterface(str(qedit.IBaseFilter._iid_)) #that yields an error
dmx = dmx_interface.QueryInterface(iid_ibasefilter) #that yields the same error

The error I get is TypeError: There is no interface object registered that supports this IID, which is understandable.

So, comtypes knows about that intefaces! But unfortunately, I can't find a way to load COM object from dll using comtypes or even ctypes.

I've been working with that issue for several days now and I would really appreciate words of advice.


Solution

  • In the end, it took some pointers manipulations but I did it.

    I've imported class IClassFactory from comtypes.server and acquired a pointer for it (pointer №1). After that I've obtained a c_long pointer to IClassFactory object inside the dll that I loaded (pointer №2). At last, I've assigned the value of pointer №2 to pointer №1.

    from comtypes.server import IClassFactory
    #same code as it was before
    dll_path = os.path.join(cmd_subfolder, "../my_path/my_dshow_filter.ax")
    clsid_class = UUID(dshow.CLSID_my_filter).bytes_le
    #that may be replaced with other IID_IClassFactory definition
    #so no pythoncom is required at all
    iclassfactory = UUID(str(pythoncom.IID_IClassFactory)).bytes_le 
    com_classfactory = c_long(0)
    my_dll = ctypes.oledll.LoadLibrary(dll)
    hr = my_dll.DllGetClassObject(clsid_class, iclassfactory, byref(com_classfactory))
    ptr_icf = POINTER(IClassFactory)(com_classfactory.value) #pointer to IClassFactory
    #and there we'll have a pointer to IUknown of the filter inside the dll
    filter = ptr_icf.CreateInstance() 
    dec = filter.QueryInterface(qedit.IBaseFilter) 
    filter_graph.AddFilter(dec, "dec")
    #Voila!
    

    So, the job may be done without any use of pythoncom, which is a great advantage to me (seeing as all the previous work is done via comtypes module)