twinbasic / lang-design

Language Design for twinBASIC
MIT License
11 stars 1 forks source link

Consider allowing pointer aliases #50

Open fafalone opened 2 years ago

fafalone commented 2 years ago

I firmly believe one of the reasons for VB6's enduring popularity is the marriage of both high level RAD and low level abilities. While these might not be used directly by a lot of people, they let advanced users make modules/controls/classes both for their own more complex use and to make such things easily callable by people simply dropping in the class.

So, might it be worthwhile to consider deeper pointer support? This could be implemented via the already-planned support for aliases; which could be rooted in a mechanism to allow ones from TLBs, increasing TLB support.

This is actually friendlier than having to use VarPtr and friends. And the situation is, VarPtr is a mainstay of modern VB projects, it's used in so many advanced components available to drop in, and so many people use APIs requiring it. So it's not so much introducing more low level complexity; it's acknowledging the reality VB programmers already use pointers extremely frequently these days. This would make such things more accessible, putting the complexity back under the hood.

So for instance Public Alias LPGUID As *GUID. Or PointerTo GUID for a more BASIC-y syntax. This would allow consuming/exporting this kind of typedef from/to a TLB as well. This could be then mapped to a LongPtr to a variable of that type but treated like the type itself. There is precedent for this; when you declare an array, the backend is going through a bunch of under-the-hood things mapping to a SAFEARRAY: If you have a UDT,

Public Type MyType
    a As Long
    b() As Long
End Type

You really have an 8-byte type of 2 Longs in VB (or in tB 8 or 12 of a Long and LongPtr since x64 is supported). "b() As Long" is really a pointer to a SAFEARRAY. VB then further dereferences the .pvData member to present the actual array. You can do all that manually with CopyMemory from VarPtr(MyTypeVar)+4.

So with my alias, you'd have

Public Type MyType
    a As LPGUID
End Type

Or, Private Declare Function SomeAPI ...(a As LPGUID).

These would be a LongPtr under the hood mapped to the GUID.

There's ancillary considerations like casting to discuss if this seems like a good idea, but the first step is seeing if the community and most importantly Wayne see this as a good idea.

Greedquest commented 2 years ago

Yeah we have to be careful here that people don't start using pointers everywhere which is not especially basic and would make codebases much less accessible to novices (remember, code written by advanced users can focus on performance and do lots of clever pointer tricks and low level stuff, but it also acts as demonstration code to beginners. I've learnt so much reading the python standard library source code because it's such a high level abstract language it's accessible to me as a beginner.)

So which usecases of pointers can we circumvent with better language features?

wqweto commented 2 years ago

There are already LongPtrs so technically there is no need to introduce new pointer types, we just need non-scary BASIC way to dereference these.

For instance allow passing LongPtr to routines instead of any of their UDT parameters can implicitly dereference it. This will hide everything from novices and is backcompat but will avoid CopyMemory in most of the cases when dealing with Win32 API.

bclothier commented 2 years ago

FYI there is a related issue discussing this: #44.

IMO, we want delegates. This gives a much better abstraction over raw pointers.

fafalone commented 2 years ago

We want delegates as well, but they're not a substitute for the raw pointers for use with APIs.

I don't think the aim of tB is to become the replacement for VB.NET where everything is completely abstracted and they make calling low level APIs difficult and redundant. That's an entirely different thing than a VB6 successor. Allowing them exclusively by introducing them via an Alias statement doesn't mean using them everywhere; but as noted, the situation is already that they're pervasive in anything besides the most simple projects.

If Win32 API use is going to remain as extensive and common and it is now, and I haven't seen anything to suggest otherwise, then making it more friendly would be helpful. This represents a more readable, understandable, and usable alternative to the ubiquitous VarPtr+CopyMemory calls; this makes it more accessible for beginners, not less, IMHO.

@wqweto, calling everything LongPtr is part of the problem I'm trying to avoid... And I fear with your way, there will be a ton of people trying to copy things of entirely different size. You have a LongPtr and it's not immediately clear what it is and what type you need defined to access it. It's also an extra step, now you need to dim a local duplicate of every member/parameter you want to dereference.

wqweto commented 2 years ago

You want to dereference only LongPtr UDT members if you think about it :)) Or array of LongPtrs, I just can’t think of other cases.

Probably wParam and lParam will need both to be LongPtr declared to be easily castable to UDTs in this case.

Btw this would be extremely easy for the compiler to implement i.e. allow passing LongPtr where UDT is needed instead of raising syntax error IMO.

fafalone commented 2 years ago

You'd have LongPtr's everywhere without having to consider whether you'd defined their matching UDT... just seems potentially more problematic for beginners because what you're casting to isn't clear.

And yes wParam and lParam are natively both LongPtr at their root... I've converted a few subclassing routines to x64 now.

if defined(_WIN64)

    typedef unsigned __int64 UINT_PTR, *PUINT_PTR;
    typedef __int64 LONG_PTR, *PLONG_PTR;

typedef UINT_PTR            WPARAM;
typedef LONG_PTR            LPARAM;

Also I think you might have an issue with unintentional casting... are you only going to allow it with UDTs? Not native types? If it's allowed with native types, it's going to attempt to dereference where it's normally a cast in VB/VBA which is a nonstarter. If not, well then you're back to.

If you're simply going to attempt to dereference every time you see a UDT <--> LongPtr... there's no way to tell ahead of time if it's coming from outside the program if it's the correct size, so it makes it very easy to try reading memory you shouldn't and cause errors and crashes.

mburns08109 commented 2 years ago

You'd have LongPtr's everywhere without having to consider whether you'd defined their matching UDT... just seems potentially more problematic for beginners because what you're casting to isn't clear.

But, wouldn't that just become a compiler error? ...and I'd thought (reading-comprehension-wise) that the gist of this was to make essentially a new datatype of "pointer to (something discrete defined elsewhere)" that the compiler can then enforce with type-checking...?

So, while: ` public Type MyUDT

...

End type

Dim pMyUDT as *MyUDT` 'actually a LongPtr under the hood

`

Then, if you later on happen to try to pass that pMyUDT as a parameter to some other function or API call that also takes a LongPTR as the expressed datatype, (but actually points to a different type of target), the compiler can throw the datatype exception to help keep your code from blue-screening the OS stupidly that way.

...unless I misunderstand the intent of this request entirely. (Of course, interface polymorphism may throw a bit of a curveball to be handled there, but that doesn't seem entirely too insurmountable...but then I'm not a compiler-writer, either.)

fafalone commented 2 years ago

It wouldn't be a compiler error because there's no way for the compiler to know what the API will be passing you. It would be a runtime error that tries to read out of bounds memory if you cast it to a type with a larger size.

While you could run into the same situation if you're passing an arbitrary pointer alias to back and forth with something declared as a LongPtr instead of your alias (which probably shouldn't be allowed; either make it explicit or make it As Any if you want to allow arbitrary types), it's a harder mistake to make because you had to define the UDT and/or then define a pointer alias for it.

But yes with my proposal the compiler would be able to throw an error if, as suggested, it's only allowed for explicit types and 'as any'. Which significantly narrows the potential for people to get themselves in trouble.

mburns08109 commented 2 years ago

Hmm...

It wouldn't be a compiler error because there's no way for the compiler to know what the API will be passing you.

...even if we get the .H file includes wishlist item? if the Win32API calls were "fed" by the .H file information on them, then the compile MAY(/should?) be able to dereference the API parameters/returns to their types...no? (well, except perhaps the As Any parameters? or...weren't those "As Any" parameters a stop-gap measure to allow API calls requiring data types that VBx didn't directly support in the first place? with being able to declare pointers to type structures - perhaps that problem will have better workarounds?)

fafalone commented 2 years ago

No, there's countless APIs that accept/return different types. Some like NtQueryInformationFile return a couple dozen, depending on what information you ask for. As Any is just a friendlier name for void*, which is a pointer to anything.

And not just low level; the high level CoreAudio APIs for instance have lots of ins/outs where you need to be able to send/receive both WAVEFORMAT and WAVEFORMATEX.

mburns08109 commented 2 years ago

OK, so perhaps it could do that job "where possible" and throw a compiler warning/information message where it can't? (...just thinking here)

fafalone commented 2 years ago

I suppose that's theoretically possible but it seems like an enormous amount of work to write an interpreter for the entire C language to parse the headers. Then what, you have to redistribute them with tB (is that legal?) or require everyone install the Windows SDK, to be able to figure out if the compiler needs to issue a warning? And then what if you want to cast it differently?

IMO that's part of why my proposal makes more sense; you would have to go out of your way to get yourself into a situation like that, making an alias for an incorrect type, instead of having LongPtr's everywhere attempting to cast them between any UDT at all. It's a limited implementation that wouldn't impact anything unless you explicitly went and created a PointerTo alias.