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

InputLanguage: Use RegLoadMUIString instead of SHLoadIndirectString API #8921

Merged
merged 1 commit into from
May 4, 2023

Conversation

DJm00n
Copy link
Contributor

@DJm00n DJm00n commented Mar 30, 2023

I have extracted part of #8573 into separate PR until decision is made on some aspects of that PR.

Proposed changes

Customer Impact

  • Shouldn't have any visible customer impact

Regression?

  • No

Risk

  • Little

Test methodology

Auto test:

  • InputLanguage_InputLanguageLayoutId_Expected (updated)
  • InputLanguage_Culture_ThrowsArgumentException (new)
  • InputLanguage_LayoutName_UnknownExpected (new)

Manually:

  • Install "Canadian Multilingual Standard" keyboard layout in Windows.
  • Add breakpoint in InputLanguage_InstalledInputLanguages_Get_ReturnsExpected test and run it under debugger.
  • Check that its LayoutId is 00011009 under debugger.
  • Check that its LayoutName is "Canadian Multilingual Standard".

image

image

Microsoft Reviewers: Open in CodeFlow

@DJm00n DJm00n requested a review from a team as a code owner March 30, 2023 16:17
@ghost ghost assigned DJm00n Mar 30, 2023
@DJm00n DJm00n changed the title Use RegLoadMUIString API instead of SHLoadIndirectString InputLanguage: Use RegLoadMUIString API instead of SHLoadIndirectString Mar 30, 2023
@DJm00n DJm00n changed the title InputLanguage: Use RegLoadMUIString API instead of SHLoadIndirectString InputLanguage: Use RegLoadMUIString instead of SHLoadIndirectString API Mar 30, 2023
@DJm00n DJm00n force-pushed the use-regloadmuistring branch 2 times, most recently from 2e1b71d to 6524066 Compare April 4, 2023 14:06
@DJm00n
Copy link
Contributor Author

DJm00n commented Apr 5, 2023

@JeremyKuhne any objections on this topic?

Copy link
Member

@JeremyKuhne JeremyKuhne left a comment

Choose a reason for hiding this comment

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

Comments are inline.

var hkey = (System.Registry.HKEY)key.Handle.DangerousGetHandle();
uint bytes = 0;
if (RegLoadMUIString(hkey, keyName, null, 0, &bytes, 0, null) != WIN32_ERROR.ERROR_MORE_DATA)
return false;
Copy link
Member

Choose a reason for hiding this comment

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

Our coding style requires blocks for if .. else.

return false;
}

localizedValue = buffer.Slice(0, (int)bytes / sizeof(char) - 1).ToString();
Copy link
Member

Choose a reason for hiding this comment

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

nit: use parenthesis for precedence.


namespace Windows.Win32
{
internal static partial class PInvoke
Copy link
Member

Choose a reason for hiding this comment

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

The filename is misspelled.


return false;
}
public override int GetHashCode() => PARAM.ToInt(_handle);
Copy link
Member

Choose a reason for hiding this comment

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

PARAM.ToInt is no longer necessary, and somewhat misleading. Just cast it to int. I'd also suggest changing _handle to nint as we've done elsewhere.

internal static partial class PInvoke
{
/// <inheritdoc cref="RegLoadMUIString(System.Registry.HKEY, string, PWSTR, uint, uint*, uint, string)"/>
public static unsafe bool RegLoadMUIString(RegistryKey key, string keyName, out string localizedValue)
Copy link
Member

Choose a reason for hiding this comment

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

This looks like something that we should create an API proposal for in the runtime. RegistryKey.GetMUIString() maybe?

Copy link
Member

Choose a reason for hiding this comment

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

To be clear- I don't think we should block here, but I would assume this functionality is generally useful for our developers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@JeremyKuhne I have added RegistryKeyExtensions.GetMUIString extension method

public static unsafe bool RegLoadMUIString(RegistryKey key, string keyName, out string localizedValue)
{
localizedValue = string.Empty;
if (key.Handle.IsInvalid)
Copy link
Member

Choose a reason for hiding this comment

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

I would expect that this is uncommon, maybe better to just let if fail with the call to RegLoadMUIString().

if (RegLoadMUIString(hkey, keyName, null, 0, &bytes, 0, null) != WIN32_ERROR.ERROR_MORE_DATA)
return false;

Span<char> buffer = stackalloc char[(int)bytes / sizeof(char)];
Copy link
Member

Choose a reason for hiding this comment

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

The data here appears to be unbounded. One presumes that this could be pretty expensive if it follows through to a PE file to load a string resource just to return the length.

The safer/better choice is to stack allocate some reasonable buffer and then rent/return a bigger array from the ArrayPool if the size isn't large enough. It may also make sense to just use the ArrayPool, as I the benefit of skipping the rental is probably minimal at best. Just using the ArrayPool would make the logic simpler, for sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

One presumes that this could be pretty expensive if it follows through to a PE file to load a string resource just to return the length.

I can bet that RegLoadMUIString is caching MUI strings to avoid this rereading but I'll do the changes.

Copy link
Member

Choose a reason for hiding this comment

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

I can bet that RegLoadMUIString is caching MUI strings to avoid this rereading but I'll do the changes.

It doesn't look like it. You'll probably get some general IO caching though. The more critical issue is that the buffer size is unbounded, which is a problem for stack allocation.

@ghost ghost added 📭 waiting-author-feedback The team requires more information from the author and removed 📭 waiting-author-feedback The team requires more information from the author labels Apr 5, 2023
@DJm00n DJm00n requested a review from JeremyKuhne April 10, 2023 16:14
uint bytes;
WIN32_ERROR error;
var hkey = (System.Registry.HKEY)key.Handle.DangerousGetHandle();
char[] buffer = ArrayPool<char>.Shared.Rent(MAX_PATH + 1);
Copy link
Contributor Author

@DJm00n DJm00n Apr 10, 2023

Choose a reason for hiding this comment

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

Several places in code are already using MAX_PATH for displayName buffer...

Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't use MAX_PATH as a buffer value unless it is actually explicitly tied to the value. This has nothing to do with paths. Also consider that the ArrayPool returns buffers in fixed sizes (2^n). This would get you a buffer of 512 chars. 128 is probably a sensible initial buffer that would cover the vast majority of cases.

Copy link
Member

@JeremyKuhne JeremyKuhne left a comment

Choose a reason for hiding this comment

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

Added a few more comments.

uint bytes;
WIN32_ERROR error;
var hkey = (System.Registry.HKEY)key.Handle.DangerousGetHandle();
char[] buffer = ArrayPool<char>.Shared.Rent(MAX_PATH + 1);
Copy link
Member

Choose a reason for hiding this comment

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

We shouldn't use MAX_PATH as a buffer value unless it is actually explicitly tied to the value. This has nothing to do with paths. Also consider that the ArrayPool returns buffers in fixed sizes (2^n). This would get you a buffer of 512 chars. 128 is probably a sensible initial buffer that would cover the vast majority of cases.

localizedValue = new string(buffer, 0, Math.Max((int)(bytes / sizeof(char)) - 1, 0));
ArrayPool<char>.Shared.Return(buffer);

return HRESULT.HRESULT_FROM_WIN32(error).Succeeded;
Copy link
Member

Choose a reason for hiding this comment

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

You should only try to create the string when ERROR_SUCCESS is returned and that should be the only thing you check to return success.

@DJm00n
Copy link
Contributor Author

DJm00n commented Apr 18, 2023

Hi @JeremyKuhne! Do you have any updates on this review?

@JeremyKuhne
Copy link
Member

@DJm00n I'm in the process of adding a BufferScope that makes writing this sort of thing easier. #8999

It would be good to use that to simplify this if possible. You should be able to do something similar to what I did with PInvoke.GetModuleFileNameLongPath.cs in that change.

Hopefully I'll have it checked in this afternoon.

@DJm00n
Copy link
Contributor Author

DJm00n commented Apr 19, 2023

GetModuleFileNameLongPath

Nice! This is indeed looks more simple!

Pushed updated version of PInvoke.RegLoadMUIString.

@DJm00n
Copy link
Contributor Author

DJm00n commented Apr 25, 2023

Hi @JeremyKuhne! Any updates on this review?

JeremyKuhne
JeremyKuhne previously approved these changes Apr 25, 2023
Copy link
Member

@JeremyKuhne JeremyKuhne left a comment

Choose a reason for hiding this comment

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

Looks great- thanks for your patience!

@dreddy-work
Copy link
Member

@DJm00n, can you rebase this?

@dreddy-work dreddy-work added the 📭 waiting-author-feedback The team requires more information from the author label Apr 26, 2023
As recommended at https://learn.microsoft.com/windows/win32/intl/locating-redirected-strings#load-a-language-neutral-registry-value

Additional changes:
- Move InputLanguage GetKeyboardLayoutNameForHKL method implementation to the internal LayoutId property
- Better testing of InputLanguage.LayoutName
@ghost ghost removed the 📭 waiting-author-feedback The team requires more information from the author label Apr 26, 2023
@DJm00n
Copy link
Contributor Author

DJm00n commented Apr 26, 2023

@dreddy-work done

@DJm00n DJm00n requested a review from JeremyKuhne April 26, 2023 10:11
@dreddy-work dreddy-work enabled auto-merge (squash) May 4, 2023 20:19
@dreddy-work dreddy-work merged commit f989dc5 into dotnet:main May 4, 2023
@ghost ghost added this to the 8.0 Preview5 milestone May 4, 2023
@DJm00n DJm00n deleted the use-regloadmuistring branch May 4, 2023 20:50
@ghost ghost locked as resolved and limited conversation to collaborators Jun 4, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants