Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the creation of SAFEARRAY(VT_RECORD) from a sequence of COM Records #2317

Merged
merged 4 commits into from
Oct 18, 2024

Conversation

geppi
Copy link
Contributor

@geppi geppi commented Jul 25, 2024

and the possibility to use instances of this type as COM Record fields.

This PR addresses the following issue.
Given a COM interface that defines a COM Record of the following type in the IDL-file:

     typedef [uuid(xxxxx)]
     struct tagT_RECORD_A {
         SAFEARRAY(T_RECORD_B) array_of_records;
         <some-more-simple-type-fields>
     } T_RECORD_A

where the T_RECORD_B is another COM Record type with fields of just simple types,
there is currently no way to assign the required SAFEARRAY instance to the array_of_records field of a T_RECORD_A instance.

The following code successfully creates all record instances.

     import pythoncom
     import win32.client
     from win32com.client import gencache, Record

     mod = gencache.EnsureModule('{XXXXX}', X, Y, Z)
     comp = win32com.client.Dispatch("SomeProgID", clsctx=pythoncom.CLSCTX_LOCAL_SERVER)
     rec_a = Record("T_RECORD_A", comp)
     rec_b_1 = Record("T_RECORD_B", comp)
     rec_b_3 = Record("T_RECORD_B", comp)

However, assigning to the array_of_records field of the T_RECORD_A instance raises an exception:

     rec_a.array_of_records = [rec_b_1, rec_b_2]
         com_error: (-2147024809,  'The parameter is incorrect.', None, None)

This happens because the code is currently creating a SAFEARRAY(VT_VARIANT) with VARIANT elements of type VT_RECORD while the COM-server expects a SAFEARRAY(VT_RECORD), i.e. COM Record elements.

The modifications of the code implement the required logic to create a SAFEARRAY(VT_RECORD) and assign this as the value to a Record field.

To retrieve the value of such a Record field, the existing code of the getattro method in PyRecord.cpp was fixed.
I'm wondering if the code in the else block starting at line 500:

    else if (V_VT(&vret) == (VT_BYREF | VT_ARRAY | VT_RECORD)) {

was ever tested or used because I cannot see how it could have worked.

of COM Records and the possibility to use instances of this type
as COM Record fields.
@geppi
Copy link
Contributor Author

geppi commented Jul 26, 2024

To progress this PR I have the same questions as in #2310 (comment)

@geppi
Copy link
Contributor Author

geppi commented Jul 31, 2024

Now that I have managed to compile the C++ PyComTest server-dll with Visual Studio 2022 as stated in #2310 (comment) I will try to implement a similar test case for this PR, if this makes sense.

@geppi
Copy link
Contributor Author

geppi commented Aug 1, 2024

I've added the method VerifyArrayOfStructs to the IPyCOMTest interface to implement a unittest for this PR.
Normally a COM interface should not be modified as soon as it is published. However, since this is a test interface purely internal to this project, it is not really published and I do not see a problem in extending the existing interface. If absolutely desired the new method could of course be moved into a new interface but this would also require an additional component to implement the method.

The test case has been added to testPyComTest.py and is limited to the Generated case.
In CI the test is not triggered with the current github actions workflow definition.
However, I did run the test locally and it passes.

If the changes of commit 78be16b are reverted, the test fails.

@Avasam Avasam requested a review from mhammond August 2, 2024 03:02
Copy link
Owner

@mhammond mhammond left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems great too, thanks. Please add a changelog entry when you fix the conflicts.

is_record_item = PyRecord_Check(obItemCheck);
}
// If the sequence elements are PyRecord objects we do NOT package
// them as VARIANT elements but put them directly into the SAFEARRAY.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm mildly surprised this check doesn't need to be deeper in PyCom_SAFEARRAYFromPyObject - are nested arrays of these a thing? But this still looks fine for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid point. To be honest I didn't open this Pandora's box because my use case doesn't require nested arrays.

Not sure how common they are.
I'm dealing with an engineering application that publishes round about 2000 COM methods and while the majority requires [in, out] Record pointers only 2 of them (unfortunately quite important ones) require plain Safearrays of Recods as parameters.

@mhammond mhammond merged commit b922bf5 into mhammond:main Oct 18, 2024
28 checks passed
@geppi geppi deleted the sa_of_rec branch October 18, 2024 13:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants