AssertionError while generating python wrapper for dll file using comtypes.client.GetModule()

508 Views Asked by At

I'm trying to use "PortableDevice.PortableDevice" COM API for my python application. When I try to generate python wrapper as follow:

comtypes.client.GetModule("C:\\Windows\\system32\\PortableDeviceApi.dll")

I get following error message:

assert sizeof(__MIDL_IOleAutomationTypes_0004) == 16, sizeof(__MIDL_IOleAutomationTypes_0004)
AssertionError: 8

Can anyone please help me to troubleshoot this issue?

1

There are 1 best solutions below

0
On

The main reason why this fails is due to a kludge in comtypes, where the DECIMAL type is not properly defined. As is, it needs 64 bits, or 8 bytes, for a double float, but it should really take 16 bytes, or 128 bits, for the actual struct.

For your current purpose, you can get along with any definition of DECIMAL that has the proper size, so here's one:

# comtypes/automation.py
class tagDEC(Structure):
    _fields_ = [("wReserved", c_ushort),
                ("scale", c_ubyte),
                ("sign", c_ubyte),
                ("Hi32", c_ulong),
                ("Lo64", c_ulonglong)]
DECIMAL = tagDEC


# comtypes/tools/tlbparser.py
DECIMAL_type = typedesc.Structure("DECIMAL",
                                  align=alignment(automation.DECIMAL)*8,
                                  members=[], bases=[],
                                  size=sizeof(automation.DECIMAL)*8)

However, you'll probably stump over the fact that some methods in the Portable Device API are not suitable for automation.

For instance, IPortableDeviceManager::GetDevices has the unique attribute (in the actual PortableDeviceApi.idl file, not in the documentation), which means you can actually pass NULL. However, type libraries don't capture this information.

That same argument can actually be an array which size is determined by the next argument. Again, type libraries don't support this, only single-object top-level pointers. Moreover, the actual IDL doesn't have a size_is attribute, which means either that the method call will not work across apartments or that this interface has a custom marshaler.

A quick look at the Portable Device API in general shows that this pattern is consistently applied in other methods that actually use arrays. It seems as if someone familiar with the Win32 API made these methods, because there are a bunch of Win32 functions that are overloaded to fetch the size of some array when that array argument is NULL. This is not the usual COM way at all, it would be better to have two methods (with the same race condition between knowing the number of elements, allocating enough memory and fetching them), a single method with only out arguments (no race condition, but no control over memory usage) or use enumerators (e.g. IEnumPortableDevice, harder, but much cleaner).

Anyway, you can take the code that comtypes.client.GetModule("…PortableDeviceApi.dll") generates as a first step. Then, follow these instructions to make the Python methods actually call the COM methods in the documented way. For instance, IPortableManager::GetDevices would become:

# comtypes/gen/_1F001332_1A57_4934_BE31_AFFC99F4EE0A_0_1_0.py
class IPortableDeviceManager(comtypes.gen._00020430_0000_0000_C000_000000000046_0_2_0.IUnknown):
    # ...
    def GetDevices(self):
        cPnPDeviceIDs = c_ulong(0)
        self.__com_GetDevices(None, byref(cPnPDeviceIDs))
        PnPDeviceIDs = (WSTRING * cPnPDeviceIDs.value)()
        self.__com_GetDevices(PnPDeviceIDs, byref(cPnPDeviceIDs))
        deviceIDs = PnPDeviceIDs[:cPnPDeviceIDs.value]
        for i in range(cPnPDeviceIDs.value):
            windll.ole32.CoTaskMemFree(cast(PnPDeviceIDs, POINTER(c_void_p))[i])
        return deviceIDs
# ...
IPortableDeviceManager._methods_ = [
    COMMETHOD([], HRESULT, 'GetDevices',
              ( ['in'], POINTER(WSTRING), 'pPnPDeviceIDs' ),
              ( ['in'], POINTER(c_ulong), 'pcPnPDeviceIDs' )),
    # ...

The following test runs successfully, although it returns an empty list in my case, since I don't have any connected device right now:

# First run
import os
from comtypes.client import GetModule
GetModule(os.getenv("WINDIR") + "\\system32\\PortableDeviceApi.dll")

# Quit python
# Edit comtypes/gen/_1F001332_1A57_4934_BE31_AFFC99F4EE0A_0_1_0.py

# Subsequent runs
from comtypes.client import CreateObject
from comtypes.gen.PortableDeviceApiLib import *
CreateObject(PortableDeviceManager).GetDevices()

I can't provide more help without scavenging further into comtypes. I suggest you contact its authors or maintainers.

EDIT: Meanwhile, I created a ticket at the SourceForge site. Since the project was transitioning out of SourceForge, it seemed to be forgotten, but it wasn't (here too).

Have you tried to import the module since then?