-
-
Notifications
You must be signed in to change notification settings - Fork 96
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
base: main
Are you sure you want to change the base?
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 |
OK, then lets leave unregistering the OutProc COM server in the workflow but remove the Shall we extend the scope of this pull request to cover testing records passed by value via a dispinterface as well? However, if we do this I would propose to rename the unittest file to 'test_dispifc_records.py'. Finally, please let me know which names you would prefer for the type library, component and interfaces considering that other tests could make use of the OutProc COM server as well. |
Thank you.
Yes, let's proceed with that.
I think the type library is might be For each object, I suggest the following names based on the Python wrapper classes generated in
It's past midnight in Japan, so I'm about to turn in for the night. I expect to be able to respond again after lunchtime tomorrow. Thank you for your continued cooperation. |
I'm afraid that
However, I'm afraid the name you suggest for the type library would be a little bit too limiting. Regarding the component name I think we should not make it too specific but still let it tell the scope of functionality this component provides. Also connected with the component name are the 'ProgID' and the 'friendly name of the component which are all defined in 'SERVER.CPP'. I would propose the following names:
In a future step, this would for example allow to add the interfaces |
Thank you for your feedback.
Indeed, as you pointed out, having "Dual" and "Disp" in the interface name at the same time can cause confusion.
As you say, the name of the type library could certainly be something like
On the other hand, I think we should operate so that "the interfaces implemented by the component" (NOTE: In other words, it is "the collection of interfaces provided by the component" or "the content that is assigned to the Best regards |
MemorandumAt the point of c000104 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.
Thank you for the addition.
It's great work that the test double can cover not only the record pointer, but also when a record is passed to the method.
The following my feedback is NOT a request to change the code exactly as stated and immediately.
It's a suggestion to split into multiple tests for each focus, rather than overwriting local variables in one test, to make future maintenance easier.
You may have been considering doing the same thing in a refactoring step, but I thought I'd share my thoughts just in case.
Co-authored-by: Jun Komoda <45822440+junkmd@users.noreply.github.com>
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 <45822440+junkmd@users.noreply.github.com>
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. |
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.