I wonder if wparam and lparam having different signs have any important internal implications or is it purely cosmetic choice to make them at least somehow different from one-another, now that we have have both being 64 bit on most computers?
I wonder if wparam and lparam having different signs have any important internal implications or is it purely cosmetic choice to make them at least somehow different from one-another, now that we have have both being 64 bit on most computers?
There seem to be three major themes in the explanation. (Much of this has been culled from comments on the question, hence "community wiki".)
Even though Windows is now a 64-bit operating system, the history of LPARAM and WPARAM go back to its 16-bit days. In those days, LPARAM was signed and WPARAM was unsigned. In the interest of continuity, the signedness was preserved in newer versions of Windows.
In particular, having one signed and the other unsigned is not an attempt to make them "at least somehow different". The signedness comes from days when the types were already differentiated by one being larger than the other.
The "W" in WPARAM stands for "word", as in one word of data. This is similar to std::byte in that it is viewed as a collection of bits. An unsigned integer type imposes the fewest semantics on those bits, making it more appropriate for raw data than a signed type. Hence, WPARAM was defined to be unsigned.
The "L" in "LPARAM" stands for "long" or "long pointer". A "long pointer" is a concept from the end of the 16-bit era. The relevant details for the current topic are that a long pointer occupied 32 bits, and that an int was only 16 bits.
An LPARAM was designed to hold a long pointer. Hence, the focus of LPARAM was the size; it had been a massive 32 bits on a 16-bit platform.
At the time, "regular" 16-bit pointers were routinely cast to and from int as needed. Why int? Mostly because it worked. In int form, the value was not even treated as a collection of bits, not even raw data; it was merely a chunk of storage from which a pointer could be recovered. Any 16-bit type would work, and int has such a nicely short name. Portability was not a concern, as these programs were written specifically for 16-bit Windows. No deeper reason than being the right size and a short name.
Long pointers were treated similarly, but an int was not large enough to hold a long pointer. Instead a long was used. This time, any 32-bit type would work, and among them, long had the shortest name. As a result, long became the basis for LPARAM. Not a necessary result, just a natural progression. Since long is signed, so is LPARAM.
One might say that LPARAM is signed because someone decided that integer types in C would default to signed unless otherwise specified. Ever since then, the history of LPARAM has followed a natural progression with no further decisions based upon whether or not something should be signed.