comtypes icon indicating copy to clipboard operation
comtypes copied to clipboard

Argument lifetime issue when calling COM/ATL methods with array of strings (and probably others) as of at least Python 3.8.3 (which worked with Python 3.7.x)

Open whannes opened this issue 4 years ago • 17 comments

Consider the following, very simple COM/ATL project made with MSVC 2017 (15.9.24, Windows 10 Pro 2004): comProjectWithArray-v2.zip. It does not contain any functionality and is used to reproduce the empty array as described below only.

Registration is disabled in the project settings here, so to test it you have to register ATLTestServer.exe /RegServer regsvr32 ATLTestServerPS.dll

After successful registration, it can be tested with the following Python-Snippet: from comtypes.client import CreateObject obj = CreateObject("ATLTestServer.ATLTestObject") obj.concatenateStrings(["New", " ", "York"])

When using comtypes 1.1.7 (6fb440372bb03e3acece00b35cac87f2eddf7d69) with Python 3.7.7 x64 the call from the Python snipped gets properly routed to the C++ method: concatCallPython37

However, when using the same comtypes version with Python 3.8.3 x64, the array in C++ contains only nullptr: concatCallPython38

Is this a known issue?

whannes avatar Jun 22 '20 14:06 whannes

Update: It seems it is not related to the cache files generated. Although they are not exactly the same on my machine with regard to the order of the definitions, using the ones created with Python 3.7 in Python 3.8 and vice versa does not changes the behavior: When calling the server with Python 3.8, the array of variants is empty while with Python 3.7 everything is fine.

I also updated the test project (implemented concatenate). Now this issue seems to be only partially reproducible, this is really strange: Consider this very simple script without a breakpoint before calling the method: comtypes-issue-without-breakpoint As mentioned before, the vector of variants is actually not arriving at the com server and thus the returned string is empty.

However, if a run the same snipped with a breakpoint before calling this method and then continue, it correctly forwards the array: comtypes-issue-with-breakpoint

So there is definitely a lifetime issue around the caller arguments. When defining them externally, the arguments are always passed correctly to the COM server also with Python 3.8: comtypes-issue-with-external-argument

whannes avatar Jun 29 '20 11:06 whannes

Hi All Any news?. IMHO it is very important bug/thing which in practice makes comtypes impossible to use in Py >=3.8 PS: I'v observed the same behaviour/issue using comtypes with Excell for Py >=3.8. Thanks, Adam

karpierz avatar May 01 '21 21:05 karpierz

@karpierz I see the importance of the issue. I will try to find time to work on it in June. Since pywinauto is not affected, it's P2 for me. Thanks for understanding.

vasily-v-ryabov avatar May 21 '21 18:05 vasily-v-ryabov

Vasilij I tried to investigate this issue in sources but.. without any succes/seems for me too difficult (I am not familiar with comtypes source for now;). Of course I can help you, but with your direction. Do not hesitate to use me :) PS: Yes, it is important issue. It is blocker for use Python >=3.8 in our production env. Adam

karpierz avatar May 22 '21 06:05 karpierz

I'm trying to make this example working, but variable a is always None for me in clean Python 3.7.3 (started from cmd.exe) even with explicit tagVARIANT creation. But I built the C++ code using VS 2015. And I'm using comtypes-1.1.9. No ideas yet what is wrong on my side. Maybe running under debugger initializes something important.

vasily-v-ryabov avatar Jun 06 '21 14:06 vasily-v-ryabov

I have a similar problem. In my case the last string argument provided to python function is replacing other string arguments coming before it in the underlying C/C++ code. Somehow comtypes is doing this when running with >python 3.8. I have the following solution that can highlight the underlying issue. I provided string arguments inside a VARIANT object, which helped me.

IMHO this issue is very important and if @vasily-v-ryabov can provide some guidance I am happy to help.

Irfan

irfanilgin avatar Jul 15 '21 12:07 irfanilgin

@whannes what do you expect in returned value when the attached implementation is empty?

STDMETHODIMP CATLTestObject::concatenateStrings(VARIANT arrayOfStrings, BSTR* concatenatedStrings)
{
	// TODO: Add your implementation code here

	return S_OK;
}

I mean what do you expect while running your example without debugger? I suspect you have more detailed implementation to reproduce the problem but you forgot to attach the updated code. Or I missed something.

All I need is a reproducing code (without debugger). Can someone prepare it?

vasily-v-ryabov avatar Jul 23 '21 12:07 vasily-v-ryabov

@karpierz @irfanilgin if you have your own reproducing example, please attach it ASAP. I have free time on Monday probably. And today which seems already missing for this bug.

I can quickly compile C++ code with VS2015 (hope it's OK). I'd like first to run it without VS debugger and without Python debugger (python.exe -d). Python 3.8+ and Python 3.7 should show me the difference. I hope both Pythons are 64-bit (it sounds consistent at least).

vasily-v-ryabov avatar Jul 23 '21 12:07 vasily-v-ryabov

from comtypes.client import CreateObject
xl = CreateObject("Excel.Application")
from comtypes.gen.Excel import xlRangeValueDefault
xl.Workbooks.Add()
xl.Range["A1", "C1"].Value[xlRangeValueDefault] = (10, "20", 31.4)
print(xl.Range["A1", "C1"].Value[xlRangeValueDefault])
xl.Visible = True
input("Press a key")
xl.Quit()

Please compare

D:>py -3.7 z_COM.py ((10.0, 20.0, 31.4),) Press a key

D:>py -3.8 z_COM.py 10.0 Press a key

karpierz avatar Jul 24 '21 19:07 karpierz

@whannes what do you expect in returned value when the attached implementation is empty?

STDMETHODIMP CATLTestObject::concatenateStrings(VARIANT arrayOfStrings, BSTR* concatenatedStrings)
{
	// TODO: Add your implementation code here

	return S_OK;
}

I mean what do you expect while running your example without debugger? I suspect you have more detailed implementation to reproduce the problem but you forgot to attach the updated code. Or I missed something.

All I need is a reproducing code (without debugger). Can someone prepare it?

Hi all, I can‘t do it within the next week as I have no machine with me right now. If any of you can provide some implementation code earlier, please go ahead.

whannes avatar Jul 25 '21 06:07 whannes

Hi All Any news/update ? Thanks, Adam

karpierz avatar Nov 02 '21 09:11 karpierz

Hi All Any news/update ? Thanks, Adam

karpierz avatar Jun 21 '22 13:06 karpierz

@karpierz I am restoring some tests in comtypes.

I am trying to restore test_excel.py(currently skipped because it depends on specific environment, see https://github.com/enthought/comtypes/pull/298#issue-1264854040), and I was trying to use unittest.skipIf to allow both testing in non-Excel-installed-env by AppVeyor and testing in Excel-installed-env by the developer.

I noticed that it fails in Python 3.8 or later as you reported.

However, CreateObject(... dynamic=True)(test_latebind), the test succeeded even with 3.8 or later.

I wonder why it occurred.

I have been always use cell ranges as ("A1:C1")(with-colon style) since Python 3.7 before this bug occurred, so I did not face this problem.

But I think it's large problem that different behaviors in different versions.

If this can be fix by modifying the comtypes code, we would like to do so, otherwise we think we need to clarify the writing style supported by different versions for those who use comtypes.

junkmd avatar Jun 22 '22 14:06 junkmd

Just run into this problem myself trying to test a new comtypes based server. I have put together a fairly compact minimum example, the .idl file looks like this:

import "oaidl.idl";
import "ocidl.idl";

[
    uuid(D690C6D9-3C64-486D-9A5A-D7B70987514B),
    dual,
    oleautomation
]
interface ITest : IDispatch {
    HRESULT ReceiveTuple([in] VARIANT tup, [out, retval] INT *retval);
}

[
    uuid(2563935A-9C03-40D5-9068-FB58C9828F1D)
]
library COMTest
{
    importlib("stdole2.tlb");

    [uuid(F979C04E-6346-44E6-A529-2E13A645EF20)]
        coclass Application {
        [default] interface ITest;
    };
};

which I compile with the MIDL compiler, then the Python code looks like this

import comtypes.client
import comtypes.server.register

testcom = comtypes.client.GetModule("test.tlb")

class Test(testcom.Application):
    def ReceiveTuple(self, tup):
        print(f"Received {tup}")
        return 0

if __name__ == "__main__":
    comtypes.server.register.register(Test)
    server = comtypes.client.wrap(Test().QueryInterface(comtypes.IUnknown))
    server.ReceiveTuple("test")
    server.ReceiveTuple(["a", "b"])

This needs to be run from an admin console to allow the registration part to happen (at least the first time it is run), and the result I get is:

Received VARIANT(vt=0x8, 'test')
Received VARIANT(vt=0x200c, (None, None))

Process finished with exit code -1073740940 (0xC0000374)

So calling with a single value (string or numeric) is fine, but with a list or tuple we not only get an array of None variants (of the same size as the passed list) but this is followed by a crash due to heap corruption, which may be because memory is being released twice or something.

My current setup is with comtypes 1.1.11 and Python 3.9.7.

I am not that familiar with the comtypes internals, but if @vasily-v-ryabov or another maintainer can give me some pointers about where to look I am happy to dive in to try and solve this issue.

bennyrowland avatar Jun 24 '22 14:06 bennyrowland

@bennyrowland Your test.tlb example may help me solve #303 at the same time.

I am also trying to figure out how to solve this problem, so if I come up with any ideas, I would like to share them.

junkmd avatar Jun 24 '22 17:06 junkmd

@junkmd, it would be nice to solve this. For the moment I have been able to solve it in my case by changing the parameters to VARIANT *, in that case the array is passed correctly. Interestingly I have also tried accessing the Test server from VB.NET, this also fails with test.tlb above, but works with VARIANT * argument, I think that VB.NET is only capable of passing arrays by reference, and seems to release the values after it passes them, but for compatibility with both clients, VARIANT * may be the way to go.

bennyrowland avatar Jun 24 '22 19:06 bennyrowland

@bennyrowland I don't know much about c-pointers, so I may be guessing, but I thought a double or more pointer might solve the problem.

In terms of the code generated by comtypes, define POINTER(POINTER(...(VARIANT)...))).

but I would like to hear from other contributors that is a reasonable modification.

junkmd avatar Jun 24 '22 23:06 junkmd

I noticed.

https://github.com/enthought/comtypes/blob/db0931d98ab977e2e3eef98133826a7a0ff5c93e/comtypes/test/test_excel.py#L49-L63

Add the following assertion to this test.

self.assertEqual(xl.Range("A1", "C1").Address(), "$A$1:$C$1")

This line passes for dynamic=True and does not pass for dynamic=False.

FAIL: test (comtypes.test.test_excel.Test_EarlyBind.test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\komoj\comtypes\comtypes\test\test_excel.py", line 52, in test
    self.assertEqual(xl.Range("A1", "C1").Address(), "$A$1:$C$1")
AssertionError: '$C$1' != '$A$1:$C$1'
- $C$1
+ $A$1:$C$1

junkmd avatar Nov 19 '22 03:11 junkmd

diffs between 3.7.7 and 3.8.3

https://github.com/python/cpython/compare/v3.7.7...v3.8.3#diff-402bc51df93eaaca2e2f2551ec0206d8af190722ae4703c178554b565d7c14b4

junkmd avatar Nov 19 '22 04:11 junkmd

I am investigating the issue on VARIANT until there is progress on the PR on type annotation.

junkmd avatar Nov 19 '22 04:11 junkmd

There seems to be some difference in the calling methods of automation.IDispatch and client.lazybind.Dispatch.

After #378 is merge, I'm thinking of adding a type hint to client.lazybind to analyze its behavior while I have a handful of work related to #327.

junkmd avatar Nov 20 '22 12:11 junkmd

It was reported in python/cpython#82929 that the callbacks were behaving strangely starting with Python 3.8.

I was going to look for a workaround on comtypes side. However, this issue may be a regression of cpython.

Would I call out to the cpython core developers?

junkmd avatar Nov 25 '22 13:11 junkmd

@junkmd it seems quite plausible to me that the issue is indeed not with com types but with ctypes or something else further up the chain. Indeed it is quite hard to imagine how com types itself could cause the problem without any C code. However, the cpython issue doesn't seem to have seen much progress in a while so who knows if/when it will get resolved.

bennyrowland avatar Nov 25 '22 20:11 bennyrowland

@michaelDCurran

I saw you in python/cpython#82929. Any feedback on this issue?

junkmd avatar Nov 27 '22 12:11 junkmd

I don't believe it is directly related to the libffi bug in python/cpython#82929, but I can't rule out that it is a separate libffi bug. We have not experienced this comtypes bug in our particular project to date, though it is worth noting that our project is 32 bit not 64 bit.

michaelDCurran avatar Nov 27 '22 21:11 michaelDCurran

@michaelDCurran

Thank you for your response.

I have not encountered this bug either, but I am concerned about the regression in how Excel cells are specified as reported by @karpierz.

If there is no movement on python/cpython#82929 in the next week I will open another issue on cpython to resolve this.

junkmd avatar Nov 27 '22 22:11 junkmd

For reference python/cpython#82929 has already been fixed in Python main. See python/cpython#97512 and in the Python 3.11 branch, see python/cpython#97513. However this missed out on getting into Python 3.11.0. I'm not sure why python/cpython#82929 has not been closed as fixed yet. I can't do it as I was not the original reporter. So it is worth testing comtypes with Python main to see if this particular bug still exists.

michaelDCurran avatar Nov 27 '22 22:11 michaelDCurran

@michaelDCurran

For reference python/cpython#82929 has already been fixed in Python main.

Unfortunatelly the bug still exist in the newest (6.12.2022) versions of Python (3.10.9, 3.11.1).

For such code:

xl = CreateObject("Excel.Application")
from comtypes.gen.Excel import xlRangeValueDefault
xl.Workbooks.Add()
xl.Range["A1", "C1"].Value[xlRangeValueDefault] = (10, "20", 31.4)
print(xl.Range["A1", "C1"].Value[xlRangeValueDefault])
xl.Visible = True
input("Press a key")
xl.Quit()

we have results:

D:\Py>py -3.7 z_COM.py
((10.0, 20.0, 31.4),)
Press a key

D:\Py>py -3.10 z_COM.py
10.0
Press a key

D:\Py>py -3.11 z_COM.py
10.0
Press a key

PS: This is an important/real barrier to upgrading Python to versions newer than 3.7.

karpierz avatar Dec 07 '22 23:12 karpierz

@karpierz

I posted bug report to cpython, https://github.com/python/cpython/issues/99952.

It would be helpful if you could post any information you have there as well.

junkmd avatar Dec 08 '22 00:12 junkmd

I think python/cpython#100169 by @ynkdir might help this.

I am watching for python/cpython#100169 as it may be possible to resolve this issue on the cpython side.

junkmd avatar Dec 11 '22 09:12 junkmd