-
-
Notifications
You must be signed in to change notification settings - Fork 100
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 record pointers as method parameters of a Dispatch Interface #535
Conversation
to get server side modifications of a record marshaled back to the client.
Thank you for your contribution.
|
Let's start with the use case because that's the easiest. I'm working with an optical engineering application from Photon Engineering called FRED which does offer a COM interface for scripting. The FRED type library defines various structures to describe optical elements and geometries and a large number of the COM interface methods expect a structure pointer as an With the current implementation of dispatch interfaces in comtypes it is only possible to pass a structure/record by value. As far as the COM specification is concerned, I can only say that since Windows NT 4.0 SP4 the type library marshaler used by automation interfaces does support structures as parameters. Also comtypes supports passing parameters which are defined as For pure dispatch interfaces a complicating factor is that the method parameters have to be packaged into a VARIANT. Finally the test case which is probably the hardest part for me since I'm not a professional full time developer. I had written a small COM server in C++ to develop my patch and I can test the functionality of comtypes with my patch against this server. Also a Python client using comtypes with the patch does properly work with the FRED optical engineering application and gets the server side structure modifications marshaled back to the client side. A hint how a test case should look like would be very much appreciated. |
I wasn't very familiar with From your explanation, it seems that your patch will indeed be useful for this project. As you say, testing proprietary software's COM type libraries is difficult.
If that's the case, it will be helpful for testing. I envision a test workflow like the following:
I think this tool will be useful. I recommend trying out the Actions workflow configuration in your public repository before adding commits to the This is also my first time attempting such a thing, so let's ask the community for help if we run into trouble. Best regards. |
OK, please lets double check if I correctly understand what you're proposing. The Python test case for the record pointer parameters should run against the C++ COM server. I'm currently completely unfamiliar with github Actions and first need to learn what's required. So if that's what you're proposing I'm happy to dive into all this but it will for sure take some time. Best regards |
Thank you for your cooperation with this project.
This is exactly what we should aim for.
It's better to include the tests that verify the behavior of the added or changed features in the same PR for coherence, so there's not necessarily a need to post another PR. It would be sufficient if the added tests fail when the production code changes are removed from this PR. However, if you are concerned about applying a large number of changes in one patch, it's also okay to split the PR into two, one for the test code and one for the production code.
That's correct. Building the COM server within the CI environment is essential, as without it, CI testing cannot be performed.
I'm happy to help with your learning, of course. I believe you would probably make changes to the workflow like this: comtypes/.github/workflows/autotest.yml Lines 22 to 30 in 6b81d07
- name: install comtypes
run: |
pip install --upgrade setuptools
python setup.py install
pip uninstall comtypes -y
python test_pip_install.py
+ - name: Set up MSBuild
+ uses: microsoft/setup-msbuild@v2
+ - name: Build RecordInfo testing COM server with nmake
+ run: |
+ cd path/to/your/code
+ nmake /f Makefile
+ cd ../../../..
+ - name: Register COM Server
+ run: regsvr32 /s destination/of/dll/output/YourComServer.dll
- name: unittest comtypes
run: |
python -m unittest discover -v -s ./comtypes/test -t comtypes\test
+ - name:Unregister COM Server
+ run: regsvr32 /u destination/of/dll/output/YourComServer.dll I recommend that you first try the above-mentioned steps. Best regards |
Thank you for the example, it is very helpful. |
You’re welcome. Such tests are rare and challenging among the many OSS projects. Therefore, if you encounter any problems while writing tests or adding/changing CI workflows, please feel free to share with the community. |
OK, I've cleaned up the C++ COM server code a little now and would be ready to give the proposed testing workflow a try. There is a sub-directory source which contains code for what seems to be an ATL COM server for testing. Is this used somewhere? Since this C++ COM server is an out of process server it is required to run it as Administrator for registering and unregistering. As far as I understand the test workflow does run under the Administrator user, right? The COM server will not be part of the released comtypes package. However, the testing code is part of the package. That would mean that tests targeting the out of process COM server will fail when a user will run the test code from the package, right? |
Good catch. Thank you!
The Once upon a time, most of the comtypes/comtypes/test/test_avmc.py Lines 1 to 16 in c3a242d
I think that if this COM server and test are revived, it might be possible to guarantee the quality of the COM server part that we have not been able to guarantee so far.
I agree with this point.
Your understanding is probably correct.
Even in the current tests, there are tests that depend on proprietary software that the developers in this community may not have. However, those are skipped if the software is not installed. comtypes/comtypes/test/test_excel.py Lines 20 to 26 in c3a242d
comtypes/comtypes/test/test_excel.py Lines 131 to 132 in c3a242d
At this stage, for tests that depend on your COM server, I think it is required the same kind of conditional branch. Please feel free to include your code in this patch. |
I added the source files for the COM-server and a unittest file. The next step would be to modify the test workflow. |
I amended the last commit to fix the formatting errors. |
'source/OutProcSrv'. The COM-server implements a dual interface and the corresponding dispinterface with currently just a single method 'InitRecord' for testing record pointer parameters. The added unittest 'test_dispifc_recordptr.py' passes a record by reference and also as a record pointer to the 'InitRecord' method of the dispinterface and checks the initialized records.
Thank you for your contribution. I have a few questions and requests.
Best regards |
Yeah, I was lucky to find a cheap used copy. It is a great resource to understand the COM basics. Another book I can recommend is "Essential COM, Don Box, Addison Wesley 1998, ISBN: 0-201-63446-5" written by one of the creators and probably the authoritative book on the concepts and architecture of COM.
I simply run
Of course I can do that. However, i think this out of process COM-server could be used for more than just testing the record pointer parameters. You would just need to add methods with the parameter types you like to test. The places to edit for this are:
This would be pretty straight forward.
Yes. However, I would propose to first focus on making the mechanics work. The names can all be changed in a final commit before merging. I'm open for suggestions but would prefer renaming everything in one step.
Do you mean implementing a method that expects a record instead of a record pointer? I will next push the modified workflow file. |
Thank you for adding the workflow. Indeed, as you pointed out, it's more sensible to prioritize ensuring that the existing tests pass before considering renaming the COM server or adding methods to handle records instead of record pointers, following the flow of test-driven development. For now, I'll give your written workflow a try. |
Looks like I misunderstood the meaning of the |
Can you help me please what I need to do to assure that I change into the proper source directory? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's try this out and see if it works.
f70bf47
to
be8bc0d
Compare
Does it need a backslash to find the |
Co-authored-by: Jun Komoda <[email protected]>
Looks like everything is in place now and the only thing left to fix is the naming of the interfaces, component and type library. Since you're the maintainer of this repo the decision is up to you. Nevertheless, I gave this a second thought and here are my five cent on this topic. I would suggest to call the interfaces simply Regarding the name of the type library, I think that the monster The component naming depends on how you want to handle other tests. Since interfaces should not be modified or extended after their publication it is required to define new interfaces for future tests. However, it is not required to implement each of these interfaces by a different component. I would favor to keep the implementation for related interfaces in the same component, e.g. for testing SAFEARRAY parameters to dispmehtods. I don't think that there is less chance to break an existing test by moving the implementation of a new interface into a different component. It even requires more code to be added and more places in the C++ code to be modified. Therefore I'm still in favor of the names:
The final decision is up to you. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, I will add/change the missing parts to the Python test code.
Thank you for your feedback on the comments.
Regarding the COM server, I am currently organizing my thoughts, and I appreciate your patience while I work on it.
No need to hurry. I understand that all this needs to be considered carefully.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for your feedback.
I would suggest to call the interfaces simply
IDualRecordParamTest
andIDispRecordParamTest
.
I agree with this.
It clearly says that these are test doubles used for tests related to records.
I think they are good names.
Regarding the name of the type library, I think that the monster
ComtypesCppOutProcTestSrvLib
could be prettified to justComtypesCppTestSrvLib
. I doubt that there will ever be the requirement and implementation of an InProc C++ COM server for testing.
I also agree with this.
Even if we add an InProc and C++ implemented COM server as a test double in the future, I think it should be named based on different characteristics or test perspectives, and it should be.
The component naming depends on how you want to handle other tests. Since interfaces should not be modified or extended after their publication it is required to define new interfaces for future tests. However, it is not required to implement each of these interfaces by a different component. I would favor to keep the implementation for related interfaces in the same component, e.g. for testing SAFEARRAY parameters to dispmehtods. I don't think that there is less chance to break an existing test by moving the implementation of a new interface into a different component. It even requires more code to be added and more places in the C++ code to be modified. Therefore I'm still in favor of the names:
- Component:
CoComtypesDispIfcTests
- friendly name:
Comtypes component for dispinterface tests
- ProgID:
Comtypes.Test.Dispinterfaces.1
- Version-independent ProgID:
Comtypes.Test.Dispinterfaces.1
Thinking again about the component naming, the following would probably make it more specific while still covering related interfaces:
- Component:
CoComtypesDispIfcParamTests
- friendly name:
Comtypes component for dispinterface parameter tests
- ProgID:
Comtypes.Test.DispinterfaceParams.1
- Version-independent
ProgID: Comtypes.Test.DispinterfaceParams
As you pointed out, it is a problem that the places where C++ code needs to be added increase and it becomes difficult to contribute.
Therefore, I agree with your proposal to name the component CoComtypesDispIfcTests
CoComtypesDispIfcParamTests
and that multiple interfaces will be implemented.
Considering this, I think it would be good to name C++ filenames like SERVER.CPP
in accordance with CoComtypesDispIfcTests
CoComtypesDispIfcParamTests
.
Also, I have written suggestions in the review comments to prevent fragile tests from being added by future contributors.
Co-authored-by: Jun Komoda <[email protected]>
Changed the names of: - Interfaces - Component - ProgID - Type Library Renamed the source files of the C++ server component to reflect the component name. Changed the name of the C++ COM server sources subdirectory.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.
I think that with a few more changes, such as renaming the structure and adding comments, it will meet the necessary quality to merge.
CoComtypesDispIfcParamTests.cpp(102) is fixed.
MemorandumAt the point of 4eee732 Modules generated from the COM type library
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have incorporated the test perspective into the method name of the test case.
Although there is only one test case in this module, I have also named it according to its perspective.
I found it off that the component name is However, after going through the following thought process, I no longer find it off. There are indeed differences between DispMethods and ComMethods even if with the same name, such as being able to change field values by passing not only pointers and references but also structures to On the other hand, the ability to change the field value by passing a record pointer to If you agree with this, I will approve this patch. What do you think? Best regards Footnotes
|
I think your statement makes sense and if there would be "non dispinterfaces" added in the future they should definitely be implemented in a different component. Do you want the commits in this PR be squashed into a single commit before merging? On the one hand a single commit would not "pollute" the history with our "development process" on the other hand it might be useful to have some of the information contained in the process history. I have no experience with this and therefore I'm wondering what's best practice? Would the PR history still be visible if a squashed single commit would be merged into main? |
Thank you.
In this project, with the collaborator permissions I have, I can only choose "squash and merge".
What is the best practice for merging depends on the nature of the project.
Even if we "squash and merge"ed a PR, the commits we once pushed will not disappear from the PR page. |
Thank you very much for the explanation. So if I understood correctly there is nothing to do on my side for this "sqash and merge"?
Wouldn't it be better to provide a refined commit message and just include the reference to the PR to keep track of the history? |
Yes, that’s correct. Just like when I first contributed to this project (#294) and wasn’t asked by the reviewer to do anything before merging, I don’t have anything I want you to do before merging this patch.
I presume that you might be worried about losing information when squashing commits.
“Technically it is possible”, but I do NOT plan to do it for the following reasons.
Furthermore, as mentioned above, I am satisfied with the granularity of the commit message that GitHub creates. |
OK, thank you again for the clarification. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Attempts to improve the behavior of VARIANT
have been made several times in this project, but testing has been a bottleneck.
Defining a test double COM server as we did this time, and checking how comtypes
behaves would serve as a reference for future problem-solving approaches.
Furthermore, I appreciate that the C++ code added this time is merely a test double and comtypes
remains pure Python, and building and registering the COM server is done by GHA, so no binary files are included in the patch.
after merging
to get server side modifications of a record marshaled back to the client.
With a COM Dual Interface it is possible to get server side changes to record members marshaled back to the client.
This requires that the type library specifies a record pointer as the interface method parameter.
The comtypes implementation for Dual Interfaces does properly take the type library specification into account and passes the record by reference in that case.
However, for a pure IDispatch interface only passing a record by value in form of a copy is implemented.
This pull request adds handling of a record pointer to the implementation of the automation interface by retrieving the records ITypeInfo interface and passing it together with the record pointer in a VARIANT to the interface method.