• Welcome to Powerbasic Museum 2020-B.
 

Can't Figure Out How To Get Interface From Which An Interface Inherits

Started by Frederick J. Harris, November 09, 2011, 08:35:28 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

Jose, I'm stuck.  I'm hoping you can help.  Its a typelib question, and I know you have a little bit of experience with these :) 

I don't know how to determine from either the ITypeLib or ITypeInfo interfaces the interface from which a particular chosen interface inherits.  For example, here are the first two interfaces from the ADODB 2.8 object library, i.e., _Collection and _DynaCollection, first in IDL (from OleView), then from your 5.01 Type Lib Browser...


//
// Interfaces
//

[odl,uuid(00000512-0000-0010-8000-00AA006D2EA4),dual,nonextensible,oleautomation] interface _Collection : IDispatch
{
[id(0x00000001), propget, helpcontext(0x0012c91a)] HRESULT Count([out, retval] long* c);
[id(0xfffffffc), restricted] HRESULT _NewEnum([out, retval] IUnknown** ppvObject);
[id(0x00000002), helpcontext(0x0012c8da)] HRESULT Refresh();
};

[odl, uuid(00000513-0000-0010-8000-00AA006D2EA4), dual, nonextensible, oleautomation] interface _DynaCollection : _Collection
{
[id(0x60030000), helpcontext(0x0012c8b5)] HRESULT Append([in] IDispatch* Object);
[id(0x60030001), helpcontext(0x0012c8c4)] HRESULT Delete([in] VARIANT Index);
};



' ########################################################################################
' Interface name = _Collection
' IID = {00000512-0000-0010-8000-00AA006D2EA4}
' Attributes = 4288 [&H000010C0] [Dual] [Nonextensible] [Dispatchable]
' Inherited interface = IDispatch
' ########################################################################################
INTERFACE Collection $IID__Collection
  INHERIT IDispatch
   METHOD get_Count ( _                                 ' VTable offset = 28
   ) AS LONG                                            ' [retval][out] *c VT_I4 <Long>
   ' =====================================================================================
   METHOD NewEnum_ ( _                                  ' VTable offset = 32
   ) AS IUnknown                                        ' [retval][out] **ppvObject VT_UNKNOWN <IUnknown>
   ' =====================================================================================
   METHOD Refresh ( _                                   ' VTable offset = 36
   )                                                    ' void
   ' =====================================================================================
END INTERFACE


' ########################################################################################
' Interface name = _DynaCollection
' IID = {00000513-0000-0010-8000-00AA006D2EA4}
' Attributes = 4288 [&H000010C0] [Dual] [Nonextensible] [Dispatchable]
' Inherited interface = _Collection
' ########################################################################################
INTERFACE DynaCollection $IID__DynaCollection
   INHERIT IDispatch
   METHOD get_Count ( _                                 ' VTable offset = 28
   ) AS LONG                                            ' [retval][out] *c VT_I4 <Long>
   ' =====================================================================================
   METHOD NewEnum_ ( _                                  ' VTable offset = 32
   ) AS IUnknown                                        ' [retval][out] **ppvObject VT_UNKNOWN <IUnknown>
   ' =====================================================================================
   METHOD Refresh ( _                                   ' VTable offset = 36
   )                                                    ' void
   ' =====================================================================================

   ' =====================================================================================
   ' DynaCollection Methods
   ' =====================================================================================
   METHOD Append ( _                                    ' VTable offset = 40
     BYVAL IDispatch _                                  ' [in] *Object VT_DISPATCH <IDispatch>
   )                                                    ' void
   ' =====================================================================================
   METHOD Delete ( _                                    ' VTable offset = 44
     BYVAL VARIANT _                                    ' [in] Index VT_VARIANT <Variant>
   )                                                    ' void
   ' =====================================================================================

END INTERFACE


As you can see, _Collection just inherits from IDispatch, but _DynaCollection inherits from _Collection.  I don't know how to get that.  Perhaps a few words would be in order to let you know what I'm working on and why I need it. 

I decided I want to create a TypeLib Browser for C and C++ that works like either your TypeLib Browser or the PowerBASIC one.  C and C++ coders don't access Type Libraries in any way similar to what we do in PowerBASIC.  Fact is, I truly hate and despise what they do, and won't have any part of it.  Microsoft created an extension to their Visual C++ compiler that involves an '#import' keyword.  One specifies a type library as well as a few other parameters after the #import keyword, and the compiler or pre-processor auto-generates a mess of ugly crap that essentially is a counterpart to what we use in PowerBASIC with either your typelib browser or the PowerBASIC COM Browser.  The stuff is full of ugly macros, templates, smart pointers and other stuff I just can't stomach.  That's why I'm working on this. In other words, I want to do it exactly how we do it in PowerBASIC with readable, comprehensible include files, instead of all that 'Extended Chinese' C++ garbage.  I've got everything figured out so far except how to get the interface from which an interface inherits.

In C++ it works a bit different than in PowerBASIC, and actually, it looks even easier to generate than the PowerBASIC includes because interfaces aren't limited to being able to only inherit from IUnknown, IDispatch or IAutomation.  So in my case above my _DynaCollection interface should start like so...

interface _DynaCollection : _Collection
{
Append(....);
Delete(....);
};

Once I figure out how to get that it inherits from _Collection, that is!

Here's the code I have so far, as well as the output from a run, but it isn't working.  Sorry its in C++ and not PowerBASIC, but I believe you've seen C++ code before too (smile).  Anyway, my question isn't really language specific, but rather specific as to how to use the type lib interfaces....


#define _WIN32_DCOM
#include <windows.h>
#include <cstdio>
#include <oaidl.h>
#include "TlbTypes.h"
#define  MAX_NAMES  24
const IID LIBID_Component = {0x2A75196C,0xD9EB,0x4129,{0xB8,0x03,0x93,0x13,0x27,0xF7,0x2D,0x5C}}; // ADO 2, 8, ...

int main()
{
ITypeInfo* pTypeInfo=NULL;
ITypeInfo* pInherited=NULL;
ITypeLib* pTypeLib=NULL;
HREFTYPE pRefType=NULL;
TYPEKIND pKind;
unsigned i=56;       /* i = 56 is index of _DynaCollection interface */
BSTR strName;
HRESULT hr;

CoInitializeEx(NULL, COINIT_MULTITHREADED);
LoadRegTypeLib(LIBID_Component,2,8,0,&pTypeLib);
printf("LoadRegTypeLib() Succeeded!\n");
pTypeLib->GetTypeInfoType(i,&pKind);
PrintTypeKind(i,pKind);
pTypeLib->GetDocumentation(i,&strName,NULL,NULL,NULL);
wprintf(L"\nstrName(%d)           = %s\n",i,strName);
SysFreeString(strName);
pTypeLib->GetTypeInfo(i,&pTypeInfo);    /*   <<< i = 56 = _DynaCollection  */   
hr=pTypeInfo->GetRefTypeOfImplType(0,&pRefType);       //virtual HRESULT STDMETHODCALLTYPE GetRefTypeOfImplType(UINT index, HREFTYPE* pRefType) 
if(SUCCEEDED(hr))
{
    printf("pTypeInfo->GetRefTypeOfImplType(i,&pRefType) Succeeded!\n");
    printf("pRefType              = %u\n",pRefType);
    hr=pTypeInfo->GetRefTypeInfo(pRefType,&pInherited); //virtual HRESULT STDMETHODCALLTYPE GetRefTypeInfo(HREFTYPE hRefType, ITypeInfo** ppTInfo) 
    if(SUCCEEDED(hr))
    {
       printf("pTypeInfo->GetRefTypeInfo(pRefType,&pInherited) Succeeded!\n");
       printf("pInherited            = %u\n",pInherited);
       hr=pInherited->GetDocumentation(MEMBERID_NIL,&strName,NULL,NULL,NULL);
       if(SUCCEEDED(hr))
       {
          printf("pInherited->GetDocumentation() Succeeded!\n");
          wprintf(L"strName               = %s\n",strName);
          SysFreeString(strName);
       }
       else
          printf("pInherited->GetDocumentation() Failed!\n");
       pInherited->Release();
    }
    else
       printf("pTypeInfo->GetRefTypeInfo(pRefType,&pInherited) Failed!\n");

else
    printf("pTypeInfo->GetRefTypeOfImplType(i,&pRefType) Failed!\n");
pTypeInfo->Release();
pTypeLib->Release();
CoUninitialize();
getchar();

return 0;
}

/*
LoadRegTypeLib() Succeeded!
pKind(56)             = TKIND_DISPATCH
strName(56)           = _DynaCollection
pTypeInfo->GetRefTypeOfImplType(i,&pRefType) Succeeded!
pRefType              = 1
pTypeInfo->GetRefTypeInfo(pRefType,&pInherited) Succeeded!
pInherited            = 6635052
pInherited->GetDocumentation() Succeeded!
strName               = IDispatch                                     ''''!!! Why Can't I Get _Collection !!!!!
*/


In the code above I get an ITypeInfo pointer - pTypeInfo, and with that I call pTypeInfo->GetRefTypeOfImplType().  Note I used an index of zero.  That's the only index that gives me a S_OK return.  Also note that on my machine, an index of 56 in the typelib is the interface for _DynaCollection, and I'm trying to get the inherited interface of _DynaCollection, which should be _Collection.  But as you can see in my output I'm coming up with IDispatch - not _Collection.  After I get pRefType of type HREFTYPE return from GetRefTypeOfImplType(), I use that in pTypeInfo->GetRefTypeInfo() to get what I'm hoping is the interface type from which _DynaCollection inherits.  To try to get the name '_Collection' I use ITypeInfo::GetDocumentation on the pInherited return to hopefully put the inherited name string into a BSTR.  But as you can see, all I'm getting is IDispatch (and I want to see _Collection, because _DynaCollection inherits from _Collection, and only indirectly from IDispatch.




José Roca

This is what I use


' ========================================================================================
' Retrieves the implemented interface.
' ========================================================================================
FUNCTION TLB_GetImplementedInterface (BYVAL pTypeInfo AS ITypeInfo, OPTIONAL BYVAL idx AS LONG) AS WSTRING

   LOCAL hr            AS LONG               ' // HRESULT
   LOCAL pRefType      AS DWORD              ' // Address to a referenced type description
   LOCAL pImplTypeInfo AS ITypeInfo          ' // Implemented interface type info
   LOCAL bstrName      AS WSTRING            ' // Interface's name (unicode)

   hr = pTypeInfo.GetRefTypeOfImplType(idx, pRefType)
   IF hr <> %S_OK OR pRefType = %NULL THEN EXIT FUNCTION
   hr = pTypeInfo.GetRefTypeInfo(pRefType, pImplTypeInfo)
   IF hr <> %S_OK OR ISFALSE ISOBJECT(pImplTypeInfo) THEN EXIT FUNCTION
   pImplTypeInfo.GetDocumentation(-1, bstrName, BYVAL %NULL, BYVAL %NULL, BYVAL %NULL)
   pImplTypeInfo = NOTHING
   FUNCTION = bstrName

END FUNCTION
' ========================================================================================

' ========================================================================================
' Retrieves the inherited interface
' ========================================================================================
FUNCTION TLB_GetInheritedInterface (BYVAL pTypeInfo AS ITypeInfo, OPTIONAL BYVAL idx AS LONG) AS WSTRING

   LOCAL hr            AS LONG               ' // HRESULT
   LOCAL pRefType      AS DWORD              ' // Address to a referenced type description
   LOCAL pImplTypeInfo AS ITypeInfo          ' // Implied interface type info
   LOCAL pTypeAttr     AS TYPEATTR PTR       ' // Address of a pointer to the TYPEATTR structure

   hr = pTypeInfo.GetRefTypeOfImplType(idx, pRefType)
   IF hr <> %S_OK OR pRefType = %NULL THEN EXIT FUNCTION
   hr = pTypeInfo.GetRefTypeInfo (pRefType, pImplTypeInfo)
   IF hr <> %S_OK OR ISFALSE ISOBJECT(pImplTypeInfo) THEN EXIT FUNCTION
   hr = pImplTypeInfo.GetTypeAttr(pTypeAttr)
   IF hr = %S_OK AND pTypeAttr <> %NULL THEN
      IF @pTypeAttr.cImplTypes = 1 THEN
         FUNCTION = TLB_GetImplementedInterface(pImplTypeInfo, 0)
      END IF
      pImplTypeInfo.ReleaseTypeAttr(pTypeAttr)
   END IF
   pImplTypeInfo = NOTHING

END FUNCTION
' ========================================================================================

' ========================================================================================
' Retrieves the base class
' ========================================================================================
FUNCTION TLB_GetBaseClass (BYVAL pTypeLib AS ITypeLib, BYVAL bstrItemName AS WSTRING) AS WSTRING

   LOCAL i                      AS LONG                 ' // Loop counter
   LOCAL hr                     AS LONG                 ' // HRESULT
   LOCAL TypeInfoCount          AS LONG                 ' // Number of TypeInfos
   LOCAL pTypeInfo              AS ITypeInfo            ' // TypeInfo interface
   LOCAL pTypeAttr              AS TYPEATTR PTR         ' // Address of a pointer to the TYPEATTR structure
   LOCAL pTKind                 AS DWORD                ' // TYPEKIND
   LOCAL bstrName               AS WSTRING              ' // Member's name (unicode)
   LOCAL bstrDocString          AS WSTRING              ' // Documentation string (unicode)
   LOCAL pdwHelpContext         AS DWORD                ' // Help context
   LOCAL bstrHelpFile           AS WSTRING              ' // Help file (unicode)
   LOCAL bstrInterfaceName      AS WSTRING              ' // Interface name
   LOCAL pRefType               AS DWORD                ' // Address to a referenced type description
   LOCAL pRefTypeInfo           AS ITypeInfo            ' // Referenced TypeInfo interface
   LOCAL pRefTypeAttr           AS TYPEATTR PTR         ' // Referenced TYPEATTR structure
   LOCAL bstrInheritedInterface AS WSTRING              ' // Inherited interface

   TypeInfoCount = pTypeLib.GetTypeInfoCount
   IF TypeInfoCount = 0 THEN EXIT FUNCTION

   FOR i = 0 TO TypeInfoCount - 1
      ' // Get the info type
      hr = pTypeLib.GetTypeInfoType(i, pTKind)
      IF hr <> %S_OK THEN EXIT FOR
      ' // Get the type info
      hr = pTypeLib.GetTypeInfo(i, pTypeInfo)
      IF hr <> %S_OK THEN EXIT FOR
      ' // Get the type attribute
      hr = pTypeInfo.GetTypeAttr(pTypeAttr)
      IF hr <> %S_OK OR pTypeAttr = %NULL THEN EXIT FOR
      ' // If it is an interface...
      IF pTKind = %TKIND_INTERFACE OR pTKind = %TKIND_DISPATCH THEN
         ' // Get the name of the interface
         hr = pTypeLib.GetDocumentation(i, bstrName, bstrDocString, pdwHelpContext, bstrHelpFile)
         ' // If it is the one we are looking for...
         IF bstrName = bstrItemName THEN
            ' // If it inherits from another interface, recursively search the methods
            IF (@pTypeAttr.wTypeFlags AND %TYPEFLAG_FDUAL) = %TYPEFLAG_FDUAL THEN
               bstrInheritedInterface = TLB_GetInheritedInterface(pTypeInfo, -1)
            ELSE
               bstrInheritedInterface = TLB_GetImplementedInterface(pTypeInfo)
            END IF
            ' // Check also that the interface doesn't inherit from itself!
            IF UCASE$(bstrInheritedInterface) <> "IUNKNOWN" AND UCASE$(bstrInheritedInterface) <> "IDISPATCH" AND UCASE$(bstrInheritedInterface) <> UCASE$(bstrName) THEN
               bstrInheritedInterface = TLB_GetBaseClass(pTypeLib, bstrInheritedInterface)
            END IF
         END IF
      END IF
      pTypeInfo.ReleaseTypeAttr(pTypeAttr)
      pTypeAttr = 0
      pTypeInfo = NOTHING
   NEXT

   IF pTypeAttr THEN pTypeInfo.ReleaseTypeAttr(pTypeAttr)
   pTypeInfo = NOTHING

   FUNCTION = bstrInheritedInterface

END FUNCTION
' ========================================================================================


If you want C++, look at: http://spec.winprog.org/typeinf3/

Frederick J. Harris

Thanks Jose.  I'll study it and hopefully I'll find what I'm doing wrong.  Thanks for the link too.  At first when I thought of undertaking this project I thought there must be piles of C++ COM Browsers out there.  But after doing a quick (not very exhaustive) search none really turned up, and then it dawned on me that most or all C++ coders just likely take the cookbook approach and use that miserable Microsoft #import thingie.  That's why I decided it might be worth doing.

José Roca

Quote
At first when I thought of undertaking this project I thought there must be piles of C++ COM Browsers out there.

There is little need for them, unless C++ headers aren't provided.

Frederick J. Harris

Quote
There is little need for them, unless C++ headers aren't provided.

Well, see, that's the thing.  I don't believe they are.  Afterall, the type libraries are supposed to replace headers for COM programming, being as they are language independant, binary, blah, blah, blah.  So what it looks like to me is that Microsoft decided to take care of the issue by creating their #import compiler directive instead of a route such as yourself and PowerBASIC took in providing a language specific typelib dumper, i.e., TypeLib Browser, that creates decent headers.  Truth be told, back in the Version 7 release of PowerBASIC they could have no doubt done the same thing, i.e., instead of a seperate typelib utility that reads the HKCR\typelib key from which the user selects a component and finally creates the *.inc file, PowerBASIC could have created a new compiler directive that would likely go in the top few lines of code in the source file, and the compiler would then auto-generate the *.inc file.  But of course that wasn't done (no doubt for various reasons, including but not limited to such things as slowing down the compile, tricky to code, etc.) and instead we have our PowerBASIC solution (yours too) of creating the *.inc file as a seperate step before the compile.  And I much prefer that.

This whole issue didn't really occur to me until about 2-3 weeks ago when I was at a C++ forum and somebody asked about database access.  Of course various posters suggested such avenues as ODBC, ADO, etc.  I followeud up a link on ADO that was provided and that's how I discovered the #import thing.  Truth be told I was only vaguely aware of it before that, and I had never used it.  I tried it out and it worked no problem, and then I investigated the auto-generated *.tlh (type lib header) and *.tli (type lib implementation/include/interface/whatever) files, and that is when it dawned on me what was going on.  And my whole problem with that technique wasn't the technique itself involving #import, but rather the convoluted mess I found within those files.  I personally like code to be clean and fairly easily readable with a minimum of macros and templates to confuse things.  Those files were anything but that.  I could post a little to give the flavor, but I'm sure you've seen indecipherable C++ code before!  This was some of the best I've ever seen.

Anyway, I finally got it thanks to your code!  I needed to do one more iteration of the GetRefTypeOfImplType/GetRefTypeInfo to get down to what I needed!  I do need to study the whole issue a bit more and find the best code design (likely yours), but at least I'm back on track.  That was harder than I thought it would be!

José Roca

I'm not a C++ programmer. I just can read some C++ code, but not to write it. But I have a question: if #import is a mess, why don't use adoctint.h, as I do with PB (after having translated it, of course)? It provides C++ virtual methods, C style interfaces and object macros:


#ifndef ___ADODynaCollection_INTERFACE_DEFINED__
#define ___ADODynaCollection_INTERFACE_DEFINED__
/* interface _ADODynaADOCollection */
/* [object][uuid][nonextensible][dual] */
EXTERN_C const IID IID__ADODynaCollection;
#if defined(__cplusplus) && !defined(CINTERFACE)

    MIDL_INTERFACE("00000513-0000-0010-8000-00AA006D2EA4")
    _ADODynaADOCollection : public _ADOCollection
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE Append(
            /* [in] */ __RPC__in_opt IDispatch *Object) = 0;

        virtual HRESULT STDMETHODCALLTYPE Delete(
            /* [in] */ VARIANT Item) = 0;

    };

#else /* C style interface */
    typedef struct _ADODynaCollectionVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
            __RPC__in _ADODynaADOCollection * This,
            /* [in] */ __RPC__in REFIID riid,
            /* [annotation][iid_is][out] */
            __RPC__deref_out  void **ppvObject);

        ULONG ( STDMETHODCALLTYPE *AddRef )(
            __RPC__in _ADODynaADOCollection * This);

        ULONG ( STDMETHODCALLTYPE *Release )(
            __RPC__in _ADODynaADOCollection * This);

        HRESULT ( STDMETHODCALLTYPE *GetTypeInfoCount )(
            __RPC__in _ADODynaADOCollection * This,
            /* [out] */ __RPC__out UINT *pctinfo);

        HRESULT ( STDMETHODCALLTYPE *GetTypeInfo )(
            __RPC__in _ADODynaADOCollection * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo);

        HRESULT ( STDMETHODCALLTYPE *GetIDsOfNames )(
            __RPC__in _ADODynaADOCollection * This,
            /* [in] */ __RPC__in REFIID riid,
            /* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
            /* [range][in] */ __RPC__in_range(0,16384) UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE *Invoke )(
            _ADODynaADOCollection * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS *pDispParams,
            /* [out] */ VARIANT *pVarResult,
            /* [out] */ EXCEPINFO *pExcepInfo,
            /* [out] */ UINT *puArgErr);

        /* [helpcontext][propget] */ HRESULT ( STDMETHODCALLTYPE *get_Count )(
            __RPC__in _ADODynaADOCollection * This,
            /* [retval][out] */ __RPC__out long *c);

        /* [id][restricted] */ HRESULT ( STDMETHODCALLTYPE *_NewEnum )(
            __RPC__in _ADODynaADOCollection * This,
            /* [retval][out] */ __RPC__deref_out_opt IUnknown **ppvObject);

        /* [helpcontext] */ HRESULT ( STDMETHODCALLTYPE *Refresh )(
            __RPC__in _ADODynaADOCollection * This);

        HRESULT ( STDMETHODCALLTYPE *Append )(
            __RPC__in _ADODynaADOCollection * This,
            /* [in] */ __RPC__in_opt IDispatch *Object);

        HRESULT ( STDMETHODCALLTYPE *Delete )(
            __RPC__in _ADODynaADOCollection * This,
            /* [in] */ VARIANT Item);

        END_INTERFACE
    } _ADODynaCollectionVtbl;
    interface _ADODynaCollection
    {
        CONST_VTBL struct _ADODynaCollectionVtbl *lpVtbl;
    };

#ifdef COBJMACROS
#define _ADODynaCollection_QueryInterface(This,riid,ppvObject) \
    ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
#define _ADODynaCollection_AddRef(This) \
    ( (This)->lpVtbl -> AddRef(This) )
#define _ADODynaCollection_Release(This) \
    ( (This)->lpVtbl -> Release(This) )
#define _ADODynaCollection_GetTypeInfoCount(This,pctinfo) \
    ( (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo) )
#define _ADODynaCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo) \
    ( (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo) )
#define _ADODynaCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) \
    ( (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId) )
#define _ADODynaCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) \
    ( (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr) )
#define _DynaCollection_get_Count(This,c) \
    ( (This)->lpVtbl -> get_Count(This,c) )
#define _ADODynaCollection__NewEnum(This,ppvObject) \
    ( (This)->lpVtbl -> _NewEnum(This,ppvObject) )
#define _ADODynaCollection_Refresh(This) \
    ( (This)->lpVtbl -> Refresh(This) )
#define _ADODynaCollection_Append(This,Object) \
    ( (This)->lpVtbl -> Append(This,Object) )
#define _ADODynaCollection_Delete(This,Item) \
    ( (This)->lpVtbl -> Delete(This,Item) )
#endif /* COBJMACROS */
#endif /* C style interface */
#endif /* ___ADODynaCollection_INTERFACE_DEFINED__ */




James C. Fuller

Go for it Fred!!! This is something I've wanted for a long time. As you know I use MinGW and their COM support is very minimal.
I believe I tried to talk José into it at one point.
I think PowerBASIC should include it in their Browser to promote the ease of creating COM libraries. I know José's focus is more on connecting rather than building but he does have  the knowledge....hint hint :)

James


José Roca

Quote
But of course that wasn't done (no doubt for various reasons, including but not limited to such things as slowing down the compile, tricky to code, etc.) and instead we have our PowerBASIC solution (yours too) of creating the *.inc file as a seperate step before the compile.  And I much prefer that.

Using headers you don't need to register anything, thus avoiding COM hell. This function allows to create instances even of unregistered licensed controls.


' ========================================================================================
' Loads the specified library and creates an instance of an object.
' If it succeeds, returns a reference to the requested interface; otherwise, it returns null.
' Not every component is a suitable candidate for use under CreateInstanceFromDll.
'  - Only in-process servers (DLLs) are supported.
'  - Components that are system components or part of the operating system, such as XML,
'    Data Access, Internet Explorer, or DirectX, aren't supported
'  - Components that are part of an application, such Microsoft Office, aren't supported.
'  - Components intended for use as an add-in or a snap-in, such as an Office add-in or
'    a control in a Web browser, aren't supported.
'  - Components that manage a shared physical or virtual system resource aren't supported.
'  - Visual ActiveX controls aren't supported because they need to be initilized and
'    activated by the OLE container.
' Note: Do not use FreeLibrary to unload the library once you have got a valid reference
' to an interface or your application will GPF. Before calling FreeLibrary, all the
' interface references must be released. If you don't need to unload the library until
' the application ends, then you don't need to call FreeLibrary because PB calls
' CoUninitialize under the hood, that closes the COM library on the current thread,
' unloads all DLLs loaded by the thread, frees any other resources that the thread
' maintains, and forces all RPC connections on the thread to close.
' ========================================================================================
FUNCTION CreateInstanceFromDll ( _
   BYVAL strLibName AS STRING _               ' // [in] Library name and path
, BYREF rclsid AS GUID _                     ' // [in] CLSID that will associate the correct data and code
, BYREF riid AS GUID _                       ' // [in] IID of the interface to be used to communicate with the newly created object
, OPTIONAL BYVAL bstrLicKey AS WSTRING _     ' // [in] License key (ansi string)
) AS IUnknown                                ' // [out] The interface pointer requested in riid

   LOCAL hr AS LONG                           ' // HRESULT
   LOCAL hLib AS DWORD                        ' // Library handle
   LOCAL pProc AS DWORD                       ' // Procedure address
   LOCAL pIClassFactory AS IClassFactory      ' // IClassFactory interface
   LOCAL pIClassFactory2 AS IClassFactory2    ' // IClassFactory2 interface
   LOCAL pUnk AS IUnknown                     ' // The requested interface pointer

   ' // See if the library is already loaded in the address space
   hLib = GetModuleHandle(BYCOPY strLibName)
   ' // If it is not loaded, load it
   IF hLib = %NULL THEN hLib = LoadLibrary(BYCOPY strLibName)
   ' // If it fails, abort
   IF hLib = %NULL THEN EXIT FUNCTION

   ' // Retrieve the address of the exported function DllGetClassObject
   pProc = GetProcAddress(hLib, "DllGetClassObject")
   IF pProc = %NULL THEN EXIT FUNCTION

   IF LEN(bstrLicKey) = 0 THEN
      ' // Request a reference to the IClassFactory interface
      CALL DWORD pProc USING DllGetClassObject(rclsid, $IID_IClassFactory, pIClassFactory) TO hr
      IF hr <> %S_OK THEN EXIT FUNCTION
      ' // Create an instance of the server or control
      hr = pIClassFactory.CreateInstance(%NULL, riid, BYVAL VARPTR(pUnk))
      IF hr <> %S_OK THEN EXIT FUNCTION
   ELSE
      ' // Request a reference to the IClassFactory2 interface
      CALL DWORD pProc USING DllGetClassObject(rclsid, $IID_IClassFactory2, pIClassFactory2) TO hr
      IF hr <> %S_OK THEN EXIT FUNCTION
      ' // Create a licensed instance of the server or control
      hr = pIClassFactory2.CreateInstanceLic(NOTHING, NOTHING, riid, bstrLicKey, pUnk)
      IF hr <> %S_OK THEN EXIT FUNCTION
   END IF

   FUNCTION = pUnk

END FUNCTION
' ========================================================================================


With VB6 you not only had to register the server, but also the license key.

José Roca

Quote from: James C. Fuller on November 09, 2011, 11:15:07 PM
Go for it Fred!!! This is something I've wanted for a long time. As you know I use MinGW and their COM support is very minimal.
I believe I tried to talk José into it at one point.
I think PowerBASIC should include it in their Browser to promote the ease of creating COM libraries. I know José's focus is more on connecting rather than building but he does have  the knowledge....hint hint :)

James

Well, I'm working in a new version of my browser that is being structured to allow to support other languages. For that, what I'm doing is to extract all the information available in the type library and classify it in tree view nodes. This could allow to develop separate modules for each language that will read the information from the tree view and generate the adequate output.

José Roca

If you want to see it in action, download the attached file. The code generation and other features aren't still implemented; just the parsing of the typelib and the classification of the retrieved information.

Frederick J. Harris

#10
Quote
adoctint.h

Not familiar with it at all!  I'll have to look at it tomorrow.  There's all kinds of stuff I don't know, believe me!

After spending a couple days researching this topic and digging up some code I wrote several years ago, it dawned on me (it should have hit me right off the bat, but didn't) that I should be able to get decent C and C++ header files that bear some resemblance to what we use from the PowerBASIC COM Browser, or Jose's, by simply taking the output from OleView, and running it back through the midl compiler.  The *.h file is a residue of that process and the creation of  the *.tlb file.  Well, as soon as I tried it with ADODB midl crashed.  It gave all kinds of grief about ADOLONG_PTR or something like that.  Its just a long*.  So I replaced that throughout the file but no-go.  Next day I did a search on the internet for it and all kinds of problems relate to it.  Its some kind of confirmed bug.  So I figured I'd stick with the type lib interfaces like Jose uses.   Maybe I just had bad luck with ADO.  It just seemed ADO would be a good one to experiment with, as I'm slightly familiar with it, although I don't use it in my work apps.

@James
Yea, in the back of my mind is the idea that I'd like to come up with something that makes COM programming a bit more approachable and comprehensible to more C or C++ coders.  And I'd like to have it here where they could get acquainted with PowerBASIC.  That probably sounds funny, that is, the idea that COM needs to be made more approachable to C or C++ coders, being as its creation occurred in that context, but the fact is I don't believe a whole lot of new C and C++ coders are all that familiar with it.  They have as hard a time learning it as PowerBASIC coders.  I know some folks won't believe that, but I feel its true.  I frequent a number of C++ beginner oriented forums, and the topic really doesn't come up all that often, and when it does, people seldom have good answers for the people asking the question.  If you don't believe me, go to a C++ forum and ask how to use the MSFlexGrid.ocx in  C++.  What you might get told, if you are lucky, is to use MFC or .NET and let those environments auto-magically generate wrappers for you.  If you don't do that then tough luck Charlie.  And as you say, the MinGW and other C based compiler systems don't really cater to Microsoft specific technologies.  I don't know for sure, but I doubt MinGW supports #import.

Again, part of it is just me.  I see nothing wrong with the way Microsoft went about creating their non standard #import directive.  Its just that all the macros, typedefs, templates, so on and so forth create code I don't even like to look at.  There is a certain aesthetic component to coding and understanding how stuff looks and works, and that stuff just doesn't 'cut it' for me.  Maybe tomorrow I'll post a little bit of it.  Smart pointers and stuff.       

Frederick J. Harris

#11
Its my guess that 95% of the work required to create C++ interface wrappers has already been done when one has code to create PowerBASIC interface wrappers.  In broad stroke, there are two major parts of the work;

1) Dealing with the highly confusing type lib interfaces with their myriads of nested types to coax the method names, parameter names, variable types, return types, calling convention (I don't think that ever changes) out of them;

2) Creating a nice GUI where the user can click on a type lib and get interface declares generated. 

Having done that, outputting C++ interface declarations or PowerBASIC declarations is a minor detail.

Perhaps I'm wrong, but at this point, that's how I see it.  I was interested in writing some code to generate C++ declarations, and maybe adding the capability for PowerBASIC ones just as an afterthought, even though its completely unneeded because both PowerBASIC and Jose have excellent applications.  But it would be cool to have check boxes or radio buttons where all you had to do was check C, C++, or PowerBASIC, then click a button. 

Changing the topic slightly, here is a complete little C++ ADO program that uses the #import directive I've been talking about.  I had gotten this off the internet and tried it and reformatted the code a bit.  It dumps a few records from one of the Biblio.mdb database that just about everybody has who has any Microsoft programming or Office apps.  The output is right afterward.  But it shows what ADO looks like with C++.  Note the #import directive at top.  Also realize that this is a very atypical C++ program.  You only see one include I included and that is stdio.h.  All that is for are the printf console output statements.  All those other bizarre variables are being brought in by that #import directive, so you see there is a massive amount of code being brought in from there to support all those odd variables such as _variant_t, bstr_t, etc....       


#include <stdio.h>
#import "C:\Program Files\Common Files\System\ado\MSADO15.dll" rename("EOF", "EOFile")

int main(void)
{
CoInitialize(NULL);
ADODB::_ConnectionPtr  con = NULL;  // define our variables which will be used as references to the
ADODB::_RecordsetPtr   rec = NULL;  // Connection and Recordset objects
ADODB::FieldPtr        pAuthor;     // define variables to read the Author field from the recordset
_variant_t              vAuthor;
char                   sAuthor[40];
bstr_t                 sConString;  // create two strings for use with the creation of a Connection
bstr_t                 sSQLString;  // and a Recordset object
HRESULT                hr = S_OK;   // create a variable to hold the result to function calls
VARIANT*               vRecordsAffected = NULL;  // long variable needed for Execute method of Connection object
hr = con.CreateInstance(_uuidof(ADODB::Connection)); // create a new instance of an ADO Connection object
printf("Connection object created.\n");          // open the BiblioDSN data source with the Connection object
sConString=L"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\Program Files\\Microsoft Visual Studio\\VB98\\Biblio.mdb";
con->Open(sConString, L"", L"", -1);
printf("Connection opened.\n");
sSQLString = L"SELECT TOP 10 Author FROM Authors;";// create a Recordset object from a SQL string
rec = con->Execute(sSQLString, vRecordsAffected, 1);
printf("SQL statement processed.\n");
pAuthor = rec->Fields->GetItem("Author");          // point to the Author field in the recordset
printf("Getting data now...\n\n");                 // retrieve all the data within the Recordset object
while(!rec->EOFile)
{
   vAuthor.Clear();         // get the Author field's value and change it
   vAuthor = pAuthor->Value;// to a multibyte type
   WideCharToMultiByte(CP_ACP,0,vAuthor.bstrVal,-1,sAuthor,sizeof(sAuthor),NULL,NULL);
   printf("%s\n", sAuthor);
   rec->MoveNext();
}
printf("\nEnd of data.\n");
rec->Close();             // close and remove the Recordset object from memory
rec = NULL;
printf("Closed an removed the Recordset object from memory.\n");
con->Close(  );           // close and remove the Connection object from memory
con = NULL;
printf("Closed and removed the Connection object from memory.\n");
CoUninitialize();

return 0;
}

/*
Output:

Connection object created.
Connection opened.
SQL statement processed.
Getting data now...

Jacobs, Russell
Metzger, Philip W.
Boddie, John
Sydow, Dan Parks
Lloyd, John
Thiel, James R.
Ingham, Kenneth
Wellin, Paul
Kamin, Sam
Gaylord, Richard

End of data.
Closed an removed the Recordset object from memory.
Closed and removed the Connection object from memory.
Press any key to continue
*/


So like I said, maybe its just me, and you all won't find anything wrong with this, but here is some code from the auto-generated Adodb.tlh and Adodb.tli files created by the compiler using that #import directive.  The files are massive, so I'm only going to post the code relating to the first two interfaces which I used above when I was asking Jose for help, that is, the _Collection and the _DynaCollection interfaces...

From Adodb.tlh

// Created by Microsoft (R) C/C++ Compiler Version 12.00.9782.0 (4a7042e7).
//
// c:\code\vstudio\vc++6\ado\release\MSADO15.tlh
//
// C++ source equivalent of type library C:\Program Files\Common Files\System\ado\MSADO15.dll
// compiler-generated file created 11/04/11 at 11:27:59 - DO NOT EDIT!

#pragma once
#pragma pack(push, 8)

#include <comdef.h>


struct __declspec(uuid("00000512-0000-0010-8000-00aa006d2ea4"))
_Collection : IDispatch
{
    //
    // Property data
    //

    __declspec(property(get=GetCount))
    long Count;

    //
    // Wrapper methods for error-handling
    //

    long GetCount ( );
    IUnknownPtr _NewEnum ( );
    HRESULT Refresh ( );

    //
    // Raw methods provided by interface
    //

    virtual HRESULT __stdcall get_Count (
        long * c ) = 0;
    virtual HRESULT __stdcall raw__NewEnum (
        IUnknown * * ppvObject ) = 0;
    virtual HRESULT __stdcall raw_Refresh ( ) = 0;
};

struct __declspec(uuid("00000513-0000-0010-8000-00aa006d2ea4"))
_DynaCollection : _Collection
{
    //
    // Wrapper methods for error-handling
    //

    HRESULT Append (
        IDispatch * Object );
    HRESULT Delete (
        const _variant_t & Index );

    //
    // Raw methods provided by interface
    //

    virtual HRESULT __stdcall raw_Append (
        IDispatch * Object ) = 0;
    virtual HRESULT __stdcall raw_Delete (
        VARIANT Index ) = 0;
};


!!! Take particular note of the 'Raw Interface' wrappers above.  They are the gist of the thing that all the other stuff wraps!

From Adodb.tli

// Created by Microsoft (R) C/C++ Compiler Version 12.00.9782.0 (4a7042e7).
//
// c:\code\vstudio\vc++6\ado\release\MSADO15.tli
//
// Wrapper implementations for type library C:\Program Files\Common Files\System\ado\MSADO15.dll
// compiler-generated file created 11/04/11 at 11:27:59 - DO NOT EDIT!


//
// interface _Collection wrapper method implementations
//

#pragma implementation_key(1)
inline long ADODB::_Collection::GetCount ( ) {
    long _result;
    HRESULT _hr = get_Count(&_result);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return _result;
}

#pragma implementation_key(2)
inline IUnknownPtr ADODB::_Collection::_NewEnum ( ) {
    IUnknown * _result;
    HRESULT _hr = raw__NewEnum(&_result);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return IUnknownPtr(_result, false);
}

#pragma implementation_key(3)
inline HRESULT ADODB::_Collection::Refresh ( ) {
    HRESULT _hr = raw_Refresh();
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return _hr;
}

//
// interface _DynaCollection wrapper method implementations
//

#pragma implementation_key(4)
inline HRESULT ADODB::_DynaCollection::Append ( IDispatch * Object ) {
    HRESULT _hr = raw_Append(Object);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return _hr;
}

#pragma implementation_key(5)
inline HRESULT ADODB::_DynaCollection::Delete ( const _variant_t & Index ) {
    HRESULT _hr = raw_Delete(Index);
    if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
    return _hr;
}


Also take note of where the 'raw' methods get called.  That's where the 'rubber meets the road'.

Is it just me???

If I was doing it, unless I'm wrong, this is all one would need for this idl...

Idl for _Collection and _DynaCollection

[odl,uuid(00000512-0000-0010-8000-00AA006D2EA4),dual,nonextensible,oleautomation] interface _Collection : IDispatch
{
[id(0x00000001), propget, helpcontext(0x0012c91a)] HRESULT Count([out, retval] long* c);
[id(0xfffffffc), restricted] HRESULT _NewEnum([out, retval] IUnknown** ppvObject);
[id(0x00000002), helpcontext(0x0012c8da)] HRESULT Refresh();
};

[odl, uuid(00000513-0000-0010-8000-00AA006D2EA4), dual, nonextensible, oleautomation] interface _DynaCollection : _Collection
{
[id(0x60030000), helpcontext(0x0012c8b5)] HRESULT Append([in] IDispatch* Object);
[id(0x60030001), helpcontext(0x0012c8c4)] HRESULT Delete([in] VARIANT Index);
};


This would be the corresponding C++...

interface _Collection : IDispatch
{
virtual HRESULT __stdcall get_Count(long* c) = 0;
virtual HRESULT __stdcall _NewEnum(IUnknown** ppvObject) = 0;
virtual HRESULT __stdcall Refresh() = 0;
};

interface _DynaCollection : _Collection
{
virtual HRESULT __stdcall Append(IDispatch* Object) = 0;
virtual HRESULT __stdcall Delete(VARIANT Index) = 0;
};


Now, what I'm not sure, some of that stuff with the #import directive might relate to IDBINDING, if C++ even can do that.  I don't even know.  Like I said,  there's lots of stuff I don't know.  Would need to check into that.  At this point I have no knowledge concerning that capability of Microsoft's C++.   



José Roca

A similar example using PB with my headers, with the added benefit of structured error handling and rich error information.


#COMPILE EXE
#DIM ALL
#INCLUDE ONCE "ADO.INC"

' ========================================================================================
' Main
' ========================================================================================
FUNCTION PBMAIN

   LOCAL pConnection AS ADOConnection
   LOCAL pRecordset AS ADORecordset
   LOCAL ConStr AS WSTRING
   LOCAL SqlStr AS WSTRING
   LOCAL vRes AS VARIANT

   ' // Create a Connection object
   pConnection = NEWCOM "ADODB.Connection"
   IF ISNOTHING(pConnection) THEN EXIT FUNCTION

   ' // Create a Recordset object
   pRecordset = NEWCOM "ADODB.Recordset"
   IF ISNOTHING(pRecordset) THEN EXIT FUNCTION

   TRY
      ' // Connection String - Change it if needed
      ConStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=biblio.mdb"
      ' // Open the connection
      pConnection.Open ConStr
      ' // Open the recordset
      ' // Trigger an error by writing incorrectly the name of SELECT
      SqlStr = "SELEC TOP 20 * FROM Authors ORDER BY Author"
      pRecordset.Open SqlStr, pConnection, %adOpenKeyset, %adLockOptimistic, %adCmdText
      DO
         ' // While not at the end of the recordset...
         IF pRecordset.EOF THEN EXIT DO
         ' // Get the content of the "Author" column
         vRes = pRecordset.Collect("Author")
         PRINT VARIANT$(vRes)
         ' // Fetch the next row
         pRecordset.MoveNext
      LOOP
   CATCH
      ' // Display error information
      STDOUT AdoGetErrorInfo(pConnection, OBJRESULT)
   FINALLY
      ' // Close the recordset
      IF pRecordset.State = %adStateOpen THEN pRecordset.Close
      ' // Close the connection
      IF pConnection.State = %adStateOpen THEN pConnection.Close
   END TRY

   WAITKEY$

END FUNCTION
' ========================================================================================


Frederick J. Harris

Well, I've revealed my ignorance again!  I just checked out your adocint.h and in VStudio 6 (old and outdated) its adoint.h.  And yes, that appears to be it, that is, the C and C++ interfaces for ADO.  That's what I was hoping to get by running midl on the output from OleView, but like I said, it wouldn't work.  So apparently Microsoft included the midl generated *.h files for all their big COM objects in the system include directory of their compiler.  I feel dumb for not knowing that, but it seems to be the case.  That should do it then.  It shouldn't be necessary to create a COM Browser for C++ (or C).  One should be able to use those objects just be including their h files.

José Roca

Quote
One should be able to use those objects just be including their h files.

Like I said. There aren't browsers for C++ because you don't need them. #import should only be used with components for which you don't have C++ headers.