Skip to content

Need a way to deal with IDC_ARROW and other INTRESOURCEs #69

Closed

Description

IDC_ARROW and other resource ids defined with MAKEINTRESOURCE are represented in the headers as wchar_t*, so they can be used with functions like LoadCursor.

LoadCursor(instance, IDC_ARROW);

In the metadata, these resource ids are being represented as ushort, which is fine, in a way, because in the headers they really are an integer value reinterpreted as an address. But this will put the burden on app developers to deal with the type casting.

Win32::LoadCursor(instance, reinterpret_cast<wchar_t const*>(IDC_ARROW)); // Yuck

I think pywin32 dealt with this by manually converting certain parameters of certain functions, but that seems overly labor intensive and unreliable, to figure out all the possible places where these resources might be used in place of a string. If we can do it, great, but I'm skeptical.

Perhaps a more tractable approach would be to mark these resource id constants as "special" in the metadata, either by giving them an attribute, or by wrapping them in a different type. Either way, this would allow projections to project functions like LoadCursror as-is and do the type conversion from integer to "fake string" in a manner that works best for the projection.

As we all know, C++'s type system is... a bit flexible. So I'm curious about thoughts on how this would best serve C# and Rust, Andrew Arnott (@AArnott) and Kenny Kerr (@kennykerr)?

Activity

  1. kennykerr commented on Dec 10, 2020

    @kennykerr
    Contributor

    An attribute that says that IDC_ARROW can be cast to wchar_t const* would be very helpful for Rust because then it can be written (without unsafe code) as follows:

    LoadCursor(instance, IDC_ARROW.into());
  2. AArnott commented on Dec 11, 2020

    @AArnott
    Member

    I'm confused looking at the metadata. IDC_ARROW is defined as 32512 instead of MAKEINTRESOURCE(32512) as the docs say it is. What exactly did MAKEINTRESOURCE do that the metadata is missing? Do we cast the value 32512 as a pointer to pass it into LoadCursor? How does Windows know that a valid string will never be at that precise address?

    I too would like to make sure that somehow the experience is good for C#.

  3. DefaultRyan commented on Dec 11, 2020

    @DefaultRyan
    MemberAuthor

    As far as I can tell, how the API is supposed to work is that IDC_ARROW is defined as (WCHAR*)(32512), aka, the value 32512 cast as a pointer, and the whole system is built on the assumption that a "real pointer" would have bits set outside the lower 16 - pointers would never look like 0x000000nn.

    Then the implementation of LoadCursorW checks the value of the string pointer argument, and if the upper 16 (or 48) bits are all zero, they decide "Ah ha! This is a resource ID, not a string". Technically, I believe the HINSTANCE argument also needs to be NULL for this logic to kick in. LoadCursorA does the same upper 16 bit check, forwarding directly to LoadCursorW if its a resource ID, and if not, converts the string with MultibyteToWideChar and then forwards to LoadCursorW.

    To look at it another way, it's almost as if these resources were defined like this:

    union {
      unsigned short id;
      wchar_t* str;
    }

    Most of the other resource loading functions all behave the same way: LoadAccelerators, LoadBitmap, LoadIcon, LoadImage, and LoadMenu.

    Also, the OPENFILENAMEW and OPENFILENAMEA structures treat their lpTemplateName field the same way, and I'm not sure how broadly they are used.

    There's almost certainly other instances of this in win32, these are just the ones I've stumbled across recently. But the fact that we have an example of this appearing in a struct, and not directly as a function param, means that it's going to be hard, if not impossible, to do anything fancy with tweaking certain function signatures to accomodate this. So we'd need to tag the constants themselves in the metadata so that projections can provide a good experience with minimal fuss.

    Ideally (but probably not always possible) is if a projection can define IDC_ARROW as a type that can automagically convert into a string pointer so users don't have to add .into(). (As I like to joke, "C++ is unsafe by default" so we can do that in a frictionless way) Kenny's Rust example is the sort of pattern that provides a good compromise, especially with Rust's emphasis on safety.

  4. AArnott commented on Dec 12, 2020

    @AArnott
    Member

    if a projection can define IDC_ARROW as a type that can automagically convert into a string pointer

    I can imagine this in C#, but it bears trying just to make sure that an implicit conversion operator that converts to a pointer isn't disallowed for some reason.

    we have an example of this appearing in a struct, and not directly as a function param

    I can't find that above. What is the struct that does this?
    It seems like custom attributes on the const would be great so projections can do magic. But an attribute on the function parameters that take these would also allow overloads that take the original short type to be provided so "unsafe" code isn't required.

  5. DefaultRyan commented on Dec 12, 2020

    @DefaultRyan
    MemberAuthor

    I can't find that above. What is the struct that does this?

    I mentioned it here:

    Also, the OPENFILENAMEW and OPENFILENAMEA structures treat their lpTemplateName field the same way, and I'm not sure how broadly they are used.

  6. kennykerr commented on Mar 3, 2021

    @kennykerr
    Contributor

    If LoadCursorA's lpCursorName parameter just had an attribute that told us that, in addition to the default PSTR, it can also accept a ushort (the type of IDC_ARROW and friends) then I could make it work seamlessly in Rust, not requiring the caller to add a scary cast.

  7. sotteson1 commented on Mar 25, 2021

    @sotteson1
    Contributor

    This was fixed a while ago by having them be int constants that are PWSTR objects like this:

    public const PWSTR IDC_ARROW = 32512;
  8. Lexikos commented on Oct 24, 2026

    @Lexikos

    I came across this closed issue while trying to work out what to do with these parameters in a projection I am writing. I think the fix does not address the overall issue, only the predefined constants.

    Technically, I believe the HINSTANCE argument also needs to be NULL for this logic to kick in.

    That's true for predefined resources, but resources can also be loaded by ordinal with a non-null HINSTANCE and MAKEINTRESOURCE.

    How can a projection allow user-defined resource identifiers if the host language doesn't permit something like reinterpret_cast?

    It seems even worse for TaskDialog, because the documentation implies that the pszIcon parameter cannot be a real (non-null) string pointer:

    This parameter must be an integer resource identifier passed to the MAKEINTRESOURCE macro or one of the following predefined values. If this parameter is NULL, no icon will be displayed.

    It's not ideal, but an answer exists in the form of undocumented behaviour: passing a stringified ID like "#123" works with most parameters which accept MAKEINTRESOURCE values, including the pszIcon parameter of TaskDialog. This form of stringified ID is described in the documentation for FindResourceW, but the documentation for LoadImage and TaskDialog make no mention of it.

    I suppose that if a projection can support PWSTR values which aren't valid pointers, like IDC_ARROW, then it can also implement something like MAKEINTRESOURCE. But there's still nothing in the metadata showing which function parameters safely accept such values, which just leaves the "overly labor intensive and unreliable" solution Ryan mentioned above.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    usabilityTouch-up to improve the user experience for a language projection

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions