-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
COM: non-optional output parameters inconsistent when passing NULL #35855
Comments
@weltkante Thanks for the repro. We will take a look at this and investigate the root cause. |
I must say it does seem unusual that changing the return value to an enumeration/struct would change the behaviour. I do agree also that in the case of 1) I would expect E_POINTER. I would suspect that this bug(feature?) is especially annoying because callers usually only check If the HR succeeded rather than if the value was set, which goes against documentation of many APIs. That said, it probably is too breaking to accept. To clarify with 1) this doesn’t depend on the return type at all does it? |
TL;DR I looked into this and it is not something I am inclined to fix. It would require reworking COM interop stub generation in a non-trivial way. Ironically the problem area is the referenced issue on why we don't like altering these code paths - dotnet/coreclr#23955. The problem is an accumulation of several issues - all of which reinforce why the next phase of COM interop needs to be out of the runtime. The failure occurs prior to making the COM call - which makes sense. We dereference the Below is an example of an IL stub for the
|
As I understand it that means HRESULT enum/struct are never working correctly in failure cases because the unmarshal of the HRESULT is inside the try-section of the exception handler, not outside or in the catch-section. In other words passing NULL for out parameters or any other thrown exceptions won't be converted to a negative HRESULT like observed, and these aren't edge cases either. /cc @JeremyKuhne @RussKie to figure out what that means for WinForms TLDR: [PreserveSig] with strongly typed return values must never throw and callers must never pass NULL to non-optional out/ref parameters. This includes not unit-testing these cases. Unfortunate how strongly typed HRESULT is just an accident that looked like it worked and now everyone is using it. Is there any outlook on "the next phase of COM" ? |
I think that is too strong of a statement. There are two things going on here because this is an interop boundary and as such is an attempt to reconcile differing semantic behavior.
The above isn't an attempt to excuse the current state. I agree that it would be nice to fix. My intent is to say that any interop boundary is sensitive and often under-specified. The exhaustive testing that is going on in WinForms is going to continue to find these kind of subtle inconsistencies.
I personally don't like the
The beginning is the introduction of the |
@jkoritzinsky for FYI about compat. |
I don't think thats a good idea, I have yet to see anyone wrap every COM interface implementation in try/catch blocks, as far as I'm aware everyone relies on the CLR marshalling layer to translate unexpected exceptions to HRESULTs. With the move to .NET Core and everyone rewriting their interop layers (WinForms, VS Designer etc.) the strongly typed HRESULTs probably are only going to increase, as far as I'm aware its the preferred practice for the teams, so any stray exception will translate directly into a bug.
People are using it for strong typing, it makes C# interop code more pleasant to write and read. Having a HRESULT struct/enum where the debugger shows you what the number means is also a huge productivity aid. I'm doing a lot of COM for various reasons (mentioned some over here). Setting aside WinForms contributions I use PreserveSig wherever failure codes are part of "normal operation", which happens often enough. I don't like throwing/catching exceptions during normal operation, it makes debugging a huge pain.
Yeah I've seen that, but I have a hard time understanding the approach. The sample (thanks for the link, wasn't aware of it) seems to be about creating a CCW right? I'll have to study it closer. Any info on how to handle RCWs? My own usecases often focus around writing a C# interface and (if its not for win32 interop) deriving a TLB off of that, so if I can extend that TLB generation step to also generate ComWrapper source that'd certainly be interesting. Maybe as part of the new Roslyn source generators. Marshaling between STA/MTA and across process boundaries is also a very important usecase, has this been considered in the ComWrapper API design? I've never written low level marshaling code, always relied on CLR or ATL magic making it happen (in combination with TLBs), so I don't know if it needs special support. Sorry for asking all this here, but as a heavy COM user I'm very interested in where COM interop is going. If you want this discussion happen somewhere else I can ask again in a different context, just point me there. |
The only example is the test tree: runtime/src/coreclr/tests/src/Interop/COM/ComWrappers/API/Program.cs Lines 50 to 58 in 5abde01
runtime/src/coreclr/tests/src/Interop/COM/ComWrappers/Common.cs Lines 111 to 135 in 5abde01
Yes. That is one of the options we are considering for COM interop.
That is something that the runtime should not know about and the It is entirely possible the current COM support is all that we ever provide. A possible replacement would be simply a consistent |
I am going to close this issue since we won't be fixing this due to the compat concerns. Unfortunately, it represents the same semantics already present in .NET Framework and therefore the bar is just too high to fix something that the community has lived with for such a long time. @weltkante If you have questions about how we see COM interop moving forward feel free to file that issue. It is something I would like to hear from the community on. The |
Needing to look at the result is one you already mentioned. Another one is perf- if we want custom error handling we don't have to introduce a Another huge one is when we implement the interfaces in managed. We are required to return specific HRESULTS on APIs for various states. In regards to strongly typing, that helps significantly with correct usage. With structs in particular we can create custom operators which "force" people to do conversions correctly. As to how we deal with this currently I don't think we should ever use |
So would you advise Edit: what is the perf like for |
@JeremyKuhne I have heard this argument a lot, but never observed any hard numbers. We already have multiple catch blocks in the generated IL stub. I would interested in how much gain there is by having 1 less.
If you throw a
These are
+1
It is possible. You could also do
Basically, yes. |
Not sure you can use pointers to interfaces sadly :)) |
@hughbe Oh sorry. Yes you are right I believe, I am so used to fighting these issues with value types. |
This topic is over my head, is there anything specific we want to add to our wiki/guidelines? |
@RussKie From my perspective I think the key statement/guidance is from @JeremyKuhne above. |
Scenario
Native Code calls
HRESULT hr = p->GetValue(NULL);
for a non-optional output parameter and observes the HRESULT.Depending on how the managed COM interface was declared different things happen:
result GetValue()
void GetValue(out result)
[PreserveSig] int GetValue(out result)
[PreserveSig] HRESULT GetValue(out result)
withHRESULT
being an enum or structObserved behavior
Expected behavior
Impact
This is not a regression (.NET Core and Desktop Framework have the same behavior) but strongly impacts the porting efforts for WinForms, in particular writing unit tests covering failure cases while at the same time using strongly typed HRESULT return values.
WPF is currently using HRESULT structs while WinForms is using HRESULT enums. There was discussion about moving to structs like WPF does, but as far as this issue is concerned it doesn't seem to make a difference.
The primary questions are whether
As far as I can evaluate it APIs implemented in .NET via variant (4) are inherently broken in the failure case and everyone just hopes nobody calls them with invalid arguments.
Reproduction
Scenario implemented over at https://github.com/weltkante/ComInteropIssue with a reg-free-com ATL project and corresponding Core/Desktop managed console projects. (PS: I was a bit in a hurry so the interfaces are dual not IUnknown, if thats a problem I can update the project.)
Original discussion here: dotnet/winforms#1932 (comment)
/cc @AaronRobinsonMSFT @JeremyKuhne @hughbe
The text was updated successfully, but these errors were encountered: