twinbasic / lang-design

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

Coclass support #52

Closed fafalone closed 2 years ago

fafalone commented 2 years ago

tB really needs direct coclass support, since we have in-language support for interface definitions. These are supported as consumable from typelibs, so then just need the syntax then it's not a big leap to set them up.

I'd propose we could keep it similar to the translation of interface defs like this:

[ CoclassId("CLSID") ]
Coclass Whatever
    [ Default ] Interface IWhatever
    Interface IWhateverElse
End Coclass
WaynePhillipsEA commented 2 years ago

I am in favour of this proposal.

What do you suggest, syntax-wise, for events? I don't find the term "Source" interface very BASIC-friendly, but then neither is "Coclass", so I'm not overly concerned, especially since this feature is not really intended for the average BASIC user.

WaynePhillipsEA commented 2 years ago

We also need to consider that we don't yet support defining dispinterfaces directly in tB either. Ideally we should expand this request to cover them as well. They should be easy, since it could perhaps just be a [ DispInterface ] attribute on the interface.

bclothier commented 2 years ago

Just as a clarification, how would that be implemented?

Coclass technically is an abstract description that amounts to saying "there's an implementation that supports so and so interfaces, and BTW so and so can be used as source interface(s), and it might have a default non-source interface and a default source interface".

Therefore, the proposal would be basically a different kind of Interface that can be applied on a Class which would then mandate that this class implements all the interfaces described in the CoClass.

The alternative is just to define a ClassId attribute on the Class (which we already have) and define the interfaces implemented on that Class which would indirectly define the coclass. It must do this anyway in order to generate a valid type library where an instance can be gotten or created via the appropriate class factory. The problem with the indirect approach, however, is that it ignores the fact that a coclass implementation need not actually correspond to a single class implementation nor should the class implementation be restricted to a single coclass definition. Still, because it's required to create the coclass for such class without an explicit implementation, it might be good to be clear whether there should be restrictions such as disallowing a class implementation to have both a ClassId attribute and implement a coclass, or whether it would mean that it implements two coclasses. I think the latter would be quite confusing. Thus the question of how coclass should co-exist with the ClassId attribute should be clarified, I think.

bclothier commented 2 years ago

RE: the terminology question -- I agree that "Source" isn't very intuitive but we also need to consider minimizing the cognitive load from translating between the IDL versus consistency with VB language syntax. I might be mistaken but I seem to remember that there are cases where source interfaces can be used without using events which may be one of the reasons why they weren't called "event interface". If that is the case, and we are shooting for compatibility with IDL, then conforming to IDL's syntax might be more appropriate. That will be particularly true if we choose to import MIDL attributes as attributes in the tB language.

fafalone commented 2 years ago

The only place I've ever even seen 'source' as an attribute is in stdole2.tlb... haven't encountered using that, or dispinterfaces, anywhere besides VB's picture and font stuff (except a single dispinterface in the shell32 automation typelib I've never actually used, DShellFolderViewEvents).

I'd agree with bclothier on the terminology question; I don't think the situation would be helped by trying to come up with something better than the existing IDL keywords; this is a fairly narrow area that I don't think is of much interest except to those already using IDL; I'm not sure any marginal improvement in clarity would outweigh having to learn a bunch of differences from the existing setup.

As far as events syntax; aren't we pretty much constrained to have them as regular methods, as that's how it's done in VB?

I have to admit I hadn't thought about the from the perspective of a coclass for an implementation within tB... I've only ever used these as a consumer of default implementations provided by Windows; so I'd have to defer to the more knowledgeable about how that actually works behind the scenes for the best way to proceed for a coclass provider rather than consumer.

WaynePhillipsEA commented 2 years ago

It comes down to this;

CoClass says: there's this class available that I can instantiate via the COM registry that supports such and such interfaces.

Class says: there's this class available, that I can instantiate internally (since I am implementing it) that supports such and such interfaces.

It's true that we generate CoClass entries in a generated type library for COM-exposed classes when we build an ActiveX DLL, but other than that I propose no further overlap to prevent muddying the waters. The CoClass support suggested here is purely to avoid having to include a type library in order to declare existing CoClass entries.

@fafalone If you look at something like one of the Office apps type libraries (e.g. Access, Excel) you will see source interfaces in coclasses, and dispinterfaces, used extensively. In your particular use case, I can understand them not being used much.

wqweto commented 2 years ago

Btw, can TB currently Implement both default interface and source dispinterface of a coclass somehow in a single class?

The idea is to produce a carbon copy of a coclass (with different CLSID), something which is not possible in VBx

WaynePhillipsEA commented 2 years ago

Btw, can TB currently Implement both default interface and source dispinterface of a coclass somehow in a single class?

It is partially implemented, and used internally in the WinNativeForms package. The provisional syntax is just Events IFooEvents statement inside a class, however at the moment IFooEvents must be defined inside the project, and so this limits its use (particularly as we can't yet define dispinterfaces).

Additionally, I just checked and RaiseEvent isn't yet working with it. So whilst the Events IFooEvents statement brings over the events and exposes them on the class correctly, this feature is not quite ready for prime time yet.

bclothier commented 2 years ago

One more point.... wouldn't we need the option to provide a custom implementation of the IClassFactory/IClassFactory2? Normally we don't have to because tB will handle that internally for us, but that would be necessary if we were to implement a OOP COM server or implement custom logic (e.g. a mutex for singleton, a la Outlook.Application). I mention this because that could be a provided as an extension to the CoClass syntax (e.g. as an event, similar to the Initialize event, perhaps).

WaynePhillipsEA commented 2 years ago

This is now supported, as of BETA 166. The CoClass component type allows for the self-explanatory attributes of [CoClassId("")], [PredeclaredId], [AppObject], [Restricted] and [COMCreatable]. The attributes [Default] and [Source] are allowed on the Interface entries. You must define one interface as [Default].

Here is an example:

[ CoClassId ("0BE35203-8F91-11CE-9DE3-00AA004BB851") ]
[ COMCreatable ]
CoClass StdFontCustom
    [ Default ] Interface stdole.Font
    [ Default, Source ] Interface stdole.FontEvents
    Interface stdole.IFont
End CoClass

In addition, and related to the question immediately above from @bclothier, there is another attribute of interest: [CoClassCustomConstructor("procName")]. By default, the construction of CoClass types is done through COM via CoCreateInstance as you'd expect. If you want to override that behaviour, then you can provide the name of a custom constructor procedure here. We are now using this for the VB.Global class, as can be seen in WinNativeForms package:

[ CoClassId ("FCFB3D23-A0FA-1068-A738-08002B3371B5") ]
[ CoClassCustomConstructor ("[_HiddenModule].CreateGlobalObject") ]
[ AppObject ]
Public CoClass Global
    [ Default ] Interface VBGlobal
End CoClass

With the above definition, when the Global class needs to be instantiated, the procedure [_HiddenModule].CreateGlobalObject is called. This allows the compiler to provide the actual implementation of the coclass, rather than going through COM. I'm sure some of you will find uses for this feature. E.g:

[ CoClassId ("0BE35203-8F91-11CE-9DE3-00AA004BB851") ]
[ CoClassCustomConstructor ("COMStuff.CreateFontObject") ]
[ COMCreatable ]
CoClass StdFontCustom
    [ Default ] Interface stdole.Font
    [ Default, Source ] Interface stdole.FontEvents
    Interface stdole.IFont
End CoClass

Module COMStuff
    Public Function CreateFontObject(ByRef out As StdFont) As Long   ' HRESULT
        Debug.Print "StdFontCustom created..."
        Set out = New StdFont
        Const S_OK As Long = 0
        Return S_OK
    End Function
End Module

Forewarning: over time, the CoClassCustomConstructor attribute might change, e.g. into a Sub New inside the CoClass syntax. For the time being, this attribute is available in this form.