So far I'm doing OK with my C++ header file generator program, and have successfully generated a number of headers for a number of typelibs. Right now I'm working on Excel - which is a big one! About the 1st 10 interfaces or so inherit from _IMsoDispObj. Here is your interface description for it Jose...
INTERFACE Adjustments $IID_Adjustments
INHERIT IDispatch
' =====================================================================================
' _IMsoDispObj Methods
' =====================================================================================
PROPERTY GET Application <1610743808> ( _ ' VTable offset = 28
) AS IDispatch ' [retval][out] **ppidisp VT_DISPATCH <IDispatch>
' =====================================================================================
PROPERTY GET Creator <1610743809> ( _ ' VTable offset = 32
) AS LONG ' [retval][out] *plCreator VT_I4 <Long>
' =====================================================================================
' =====================================================================================
' Adjustments Methods
' =====================================================================================
PROPERTY GET Parent <1> ( _ ' VTable offset = 36
) AS IDispatch ' [retval][out] **Parent VT_DISPATCH <IDispatch>
' =====================================================================================
PROPERTY GET Count <2> ( _ ' VTable offset = 40
) AS LONG ' [retval][out] *Count VT_INT <Long>
' =====================================================================================
PROPERTY GET Item <0> ( _ ' VTable offset = 44
BYVAL prm_Index AS LONG _ ' [in] Index VT_INT <Long>
) AS SINGLE ' [retval][out] *Val VT_R4 <Single>
' =====================================================================================
PROPERTY SET Item <0> ( _ ' VTable offset = 48
BYVAL prm_Index AS LONG _ ' [in] Index VT_INT <Long>
, BYVAL prm_Val AS SINGLE _ ' [in] Val VT_R4 <Single>
) ' void
' =====================================================================================
END INTERFACE
I looked it up on the internet and its apparently something related to Microsoft Office and .NET. It contains the two methods you prepended to the interface. I'm wondering if your code somehow sank deeper into its recursive decent with ITypeInfo::GetRefTypeOfImplType / ITypeInfo::GetRefTypeInfo to finally come up with the ultimate base of IDispatch, or if you just 'hard coded' that response into your code for any situation where _IMsoDispObj shows up? The problem with it for me in dealing with C++ is that what my program is outputting won't compile because _MsoDispObj isn't predefined/declared anywhere before a C++ compiler would encounter it. Here is what my program is outputting for the 1st one encountered in Excel...
interface Adjustments : _IMsoDispObj
{
virtual HRESULT __stdcall GetParent(IDispatch** Parent)=0;
virtual HRESULT __stdcall GetCount(int* Count)=0;
virtual HRESULT __stdcall GetItem(int Index, float* Val)=0;
virtual HRESULT __stdcall SetItem(int Index, float Val)=0;
};
If I changed it to IDispatch which is what you and PowerBASIC have, that would compile, because the system knows what 'IDispatch' is; but not _IMsoDispObj. That's kind of why I'm asking if your code is dynamically resolving IDispatch from that, or is just inserting it in there?
I have to admit, I expect you are going to tell me that your code is recursively descending deeper until its coming up with IDispatch. I recall you were using a recursive function. However, I didn't implement my code for that specific functionality recursively. Perhaps I'll have to change that if _IMsoDispObj can be decomposed further into IDispatch. I'm am using recursion in several places; just not there.
There is nothing hard coded. If the inherited interface is not IUnknown or IDispatch, I call the following function, that loads and parses the external type library where that interface is defined.
' ########################################################################################
' Enumerates external interfaces
' ########################################################################################
FUNCTION TLB_EnumExternalInterfaces ( _
BYVAL hWnd AS DWORD _ ' // Handle of the main window
, BYVAL pITypeInfo AS ITypeInfo _ ' // ITypeinfo interface
, BYVAL strItemName AS STRING _ ' // Item to retrieve
, BYVAL fFlags AS LONG _ ' // Flags
, BYVAL bIsUnknown AS LONG _ ' // Is an IUnknwon interface
) AS LONG
IF ISNOTHING(pITypeInfo) THEN EXIT FUNCTION
IF strItemName = "" THEN EXIT FUNCTION
LOCAL hr AS LONG ' // HRESULT
LOCAL strImplInterface AS STRING ' // Implemented interface
LOCAL strImplInterfaceIID AS STRING ' // Implemented interface IID
LOCAL strTypeLibGuid AS STRING ' // Type library guid
LOCAL strTypeLibVersion AS STRING ' // Type library version
LOCAL wTypeLibVerMajor AS WORD ' // Type library major version
LOCAL wTypeLibVerMinor AS WORD ' // Type library minor version
LOCAL strTypeLibPath AS STRING ' // Type library path
LOCAL wszTypeLibPath AS WSTRINGZ * %MAX_PATH ' // Type library path (unicode)
LOCAL pITypeLib AS ITypeLib ' // Pointer to ITypeLib interface
LOCAL TypeInfoCount AS LONG ' // Number of typeinfos
LOCAL pExtTypeInfo AS ITypeInfo ' // Pointer to ITypeInfo interface
LOCAL pExtTypeAttr AS TYPEATTR PTR ' // Pointer to TYPEATTR structure
LOCAL bstrName AS WSTRING ' // Interface name
LOCAL bstrDocString AS WSTRING ' // Documentation string
LOCAL pdwHelpContext AS DWORD ' // Help context
LOCAL bstrHelpFile AS WSTRING ' // Help file
LOCAL szInterfaceName AS ASCIIZ * 256 ' // Interface name
LOCAL pTKind AS DWORD ' // Interface type
LOCAL i AS LONG ' // Loop counter
LOCAL pRefType AS DWORD ' // Reference type
LOCAL pRefTypeInfo AS ITypeInfo ' // Pointer to ITypeInfo
LOCAL pRefTypeAttr AS TYPEATTR PTR ' // Pointer to a TYPEATTR structure
LOCAL strInheritedInterface AS STRING ' // Inherited interface name
LOCAL strNewInhInterface AS STRING ' // New inherited interface name
LOCAL old_UsePropGetSet AS LONG
strImplInterface = UCASE$(TLB_GetImplementedInterface(pITypeInfo))
IF strImplInterface <> "" AND strImplInterface <> "IUNKNOWN" AND strImplInterface <> "IDISPATCH" THEN
strImplInterfaceIID = TLB_GetImplementedInterfaceIID(pITypeInfo)
hr = TLB_GetTypeLibFromInterfaceIID(strImplInterfaceIID, strTypeLibGuid, strTypeLibVersion, wTypeLibVerMajor, wTypeLibVerMinor, strTypeLibPath)
IF hr = %S_OK AND strTypeLibPath <> "" THEN
wszTypeLibPath = strTypeLibPath
hr = LoadTypeLibEx(wszTypeLibPath, %REGKIND_NONE, pITypeLib)
IF hr <> %S_OK OR ISFALSE ISOBJECT(pITypeLib) THEN EXIT FUNCTION
' --- Retrieves the number of TypeInfos -----------------------------------------------
TypeInfoCount = pITypeLib.GetTypeInfoCount
IF TypeInfoCount = 0 THEN
pITypeLib = NOTHING
EXIT FUNCTION
END IF
' --- Parses the TypeInfos ------------------------------------------------------------
FOR i = 0 TO TypeInfoCount - 1
' --- Allow for a breath and check the abort flag ---------------------------------
TLB_DoEvents hWnd
IF m_Abort THEN EXIT FOR
' --- Retrieves the TypeKind ------------------------------------------------------
hr = pITypeLib.GetTypeInfoType(i, pTKind)
IF hr <> %S_OK THEN EXIT FOR
' --- Retrieves the TypeInfo interface --------------------------------------------
hr = pITypeLib.GetTypeInfo(i, pExtTypeInfo)
IF hr <> %S_OK THEN EXIT FOR
' --- Gets the address of a pointer to the TYPEATTR structure ---------------------
hr = pExtTypeInfo.GetTypeAttr(pExtTypeAttr)
IF hr <> %S_OK OR pExtTypeAttr = %NULL THEN EXIT FOR
' --- If it is an interface ... ---------------------------------------------------
IF pTKind = %TKIND_INTERFACE OR pTKind = %TKIND_DISPATCH THEN
hr = pITypeLib.GetDocumentation(i, bstrName, bstrDocString, pdwHelpContext, bstrHelpFile)
' -- If it is the interface we are looking for... ------------------------------
szInterfaceName = bstrName
IF szInterfaceName = strItemName THEN
IF (@pExtTypeAttr.wTypeFlags AND %TYPEFLAG_FDUAL) = %TYPEFLAG_FDUAL THEN
strInheritedInterface = TLB_GetInheritedInterface(pExtTypeInfo, -1)
ELSE
strInheritedInterface = TLB_GetImplementedInterface(pITypeInfo)
END IF
IF pTKind = %TKIND_DISPATCH THEN
' Attempt to change the view to VTable
hr = pExtTypeInfo.GetRefTypeOfImplType(-1, pRefType)
IF hr = %S_OK AND pRefType <> %NULL THEN
hr = pExtTypeInfo.GetRefTypeInfo(pRefType, pRefTypeInfo)
IF hr = %S_OK AND ISTRUE ISOBJECT(pRefTypeInfo) THEN
hr = pRefTypeInfo.GetTypeAttr(pRefTypeAttr)
' Enumerate the functions
IF hr <> %S_OK OR pRefTypeAttr = %NULL THEN
TLB_EnumFunctions(hWnd, pExtTypeAttr, pExtTypeInfo, 0, %TRUE, IIF&(bIsUnknown = %TRUE, %CODEGEN_CSTYLE, %CODEGEN_VBSTYLE), 0, bIsUnknown, %FALSE, szInterfaceName)
ELSE
IF strInheritedInterface <> "" AND UCASE$(strInheritedInterface) <> "IDISPATCH" AND UCASE$(strInheritedInterface) <> "IUNKNOWN" THEN
strNewInhInterface = TLB_EnumInheritedInterfaces(hWnd, pITypeLib, strInheritedInterface, fFlags, bIsUnknown)
' If the returned interface name is the same, it must be an external interface
IF strNewInhInterface = strInheritedInterface THEN
TLB_EnumExternalInterfaces(hWnd, pRefTypeInfo, strInheritedInterface, fFlags, bIsUnknown)
END IF
END IF
TLB_EnumFunctions(hWnd, pRefTypeAttr, pRefTypeInfo, 0, %TRUE, IIF&(bIsUnknown = %TRUE, %CODEGEN_CSTYLE, %CODEGEN_VBSTYLE), 0, bIsUnknown, %FALSE, szInterfaceName)
pRefTypeInfo.ReleaseTypeAttr(pRefTypeAttr)
pRefTypeAttr = %NULL
END IF
END IF
ELSE
TLB_EnumFunctions(hWnd, pExtTypeAttr, pExtTypeInfo, 0, %TRUE, IIF&(bIsUnknown = %TRUE, %CODEGEN_CSTYLE, %CODEGEN_VBSTYLE), 0, bIsUnknown, %FALSE, szInterfaceName)
END IF
ELSE
IF strInheritedInterface <> "" AND UCASE$(strInheritedInterface) <> "IDISPATCH" AND UCASE$(strInheritedInterface) <> "IUNKNOWN" THEN
strNewInhInterface = TLB_EnumInheritedInterfaces(hWnd, pITypeLib, strInheritedInterface, fFlags, bIsUnknown)
' If the returned interface name is the same, it must be an external interface
IF strNewInhInterface = strInheritedInterface THEN
TLB_EnumExternalInterfaces(hWnd, pITypeInfo, strInheritedInterface, fFlags, bIsUnknown)
END IF
END IF
TLB_EnumFunctions(hWnd, pExtTypeAttr, pExtTypeInfo, 0, %TRUE, IIF&(bIsUnknown = %TRUE, %CODEGEN_CSTYLE, %CODEGEN_VBSTYLE), 0, bIsUnknown, %FALSE, szInterfaceName)
END IF
END IF
END IF
IF ISTRUE pExtTypeAttr THEN
pExtTypeInfo.ReleaseTypeAttr(pExtTypeAttr)
pExtTypeAttr = %NULL
END IF
pExtTypeInfo = NOTHING
NEXT
pITypeLib = NOTHING
END IF
END IF
END FUNCTION
Of course, that function also calls other functions, among them the following one, that retrieves the path of the type library to load.
' ========================================================================================
' Returns the guid of the typelib that implements a given interface.
' strIID is the human readable guid of the interface.
' ========================================================================================
FUNCTION TLB_GetTypeLibFromInterfaceIID ( _
BYVAL strIID AS STRING _
, BYREF strTypeLibGuid AS STRING _
, BYREF strTypelibVersion AS STRING _
, BYREF wTypeLibVerMajor AS WORD _
, BYREF wTypeLibVerMinor AS WORD _
, BYREF strTypeLibPath AS STRING _
) AS LONG
LOCAL szKey AS ASCIIZ * %MAX_PATH
LOCAL hKey AS DWORD
LOCAL dwIdx AS DWORD
LOCAL hr AS LONG
LOCAL szValueName AS ASCIIZ * %MAX_PATH
LOCAL KeyType AS DWORD
LOCAL szKeyValue AS ASCIIZ * %MAX_PATH
LOCAL cbValueName AS DWORD
LOCAL cbData AS DWORD
LOCAL p AS LONG
LOCAL rTypeLibGuid AS GUID
LOCAL bstrTypeLibPath AS WSTRING
' Searches the HKEY_CLASSES_ROOT\Interface\<IID>\Typelib node.
szKey = "Interface\" & strIID & "\TypeLib"
hr = RegOpenKeyEx (%HKEY_CLASSES_ROOT, szKey, 0, %KEY_READ, hKey)
IF hr <> %ERROR_SUCCESS THEN
FUNCTION = hr
EXIT FUNCTION
END IF
' Retrieves the Guid of the TypeLib
dwIdx = 0
cbValueName = %MAX_PATH
cbData = %MAX_PATH
KeyType = %REG_SZ
hr = RegEnumValue (hKey, dwIdx, szValueName, cbValueName, BYVAL %NULL, KeyType, szKeyValue, cbData)
strTypeLibGuid = szKeyValue
rTypeLibGuid = GUID$(strTypeLibGuid)
' Retrieves the version
DO
dwIdx = dwIdx + 1
cbValueName = %MAX_PATH
cbData = %MAX_PATH
KeyType = %REG_SZ
szValueName = ""
hr = RegEnumValue (hKey, dwIdx, szValueName, cbValueName, BYVAL %NULL, KeyType, szKeyValue, cbData)
IF hr <> %ERROR_SUCCESS THEN EXIT DO
IF UCASE$(szValueName) = "VERSION" THEN
strTypeLibVersion = szKeyValue
p = INSTR(strTypeLibVersion, ".")
IF p = 0 THEN
wTypelibVerMajor = VAL(strTypeLibVersion)
ELSE
wTypeLibVerMajor = VAL(LEFT$(strTypeLibVersion, p - 1))
wTypelibVerMinor = VAL(MID$(strTypeLibVersion, p + 1))
END IF
EXIT DO
END IF
LOOP
' Closes the registry
RegCloseKey hKey
' Retrieves the path of the TypeLib
hr = QueryPathOfRegTypeLib(rTypeLibGuid, wTypeLibVerMajor, wTypeLibVerMinor, 0, bstrTypeLibPath)
strTypeLibPath = bstrTypeLibPath
END FUNCTION
' ========================================================================================
Wow! I actually thought of that for a fleeting moment when my internet search revealed the external Office file containing _IMsoDispObj, but I quickly disgarded the idea as being unlikely. What I find startling is that so very much of the information is ridiculously easy to get, and a very, very small part - such as the base class for an interface, is so unbelievably difficult to get!
Thanks very much Jose. This is certainly a complication. Now I'll have to study and assimilate this.
And the worst thing is that the result will be unusable, since Excel (and Word) can only be used calling the Invoke method of the IDispatch interface.
You know, I had really wondered about that, because a great many of the most commonly used interfaces of Excel, such as WorkBook, WorkSheet, etc., are defined as IDispatch dual interfaces? I seem to vaguely recall you stating at some time in the past that there was some kind of bug or internal problem regarding this. In any case, it appears it was Microsoft's intent to provide VTable access. IDispatch::Invoke in C++ is a nightmare.
Just the other night I had picked up a book I have on COM by Don Box - a noted COM authority. I was reading his last chapter about the evolution of OOP thinking, and somehow he got into mentioning Excel, and that Microsoft had to do some esoteric and strange stuff to not bloat the memory requirements too much, such as using 'tear off' interfaces, so on and so forth. Well, it looks like they failed on every count. Not only doesn't it work, but its a bloated pig anyhow!
My Type Library Browser generates three files for Microsoft Excel.
As a result, I don't have to go hunting for the _IMsoDispObj dual interface.
Excel.inc - Microsoft Excel Object Library
Office.inc - Microsoft Office Object Library
VBIDE.inc - Microsoft Visual Basic for Applications Extensibility
Interesting idea Dominic.
I'm wondering just how common this situation is with Type Libraries. Since I'm exceedingly new to this I've only tried to create headers for several type libs so far, specifically, Ado, MSFlexGrid, Shell.Explorer, and now Excel. On each I've had to tweak my code to deal with new things encountered (particularly how things are ordered).
I'd like to handle this in the simplest but adequate manner. Is Microsoft Office the only place this is encountered I wonder? Jose's solution requires zero a priori knowledge concerning a type library. The solution you mentioned requires a priori knowledge that some particular typelib/vendor is associated with a specific series of other files.
Quote
...since Excel (and Word) can only be used calling the Invoke method of the IDispatch interface.
I thought sometime soon after PB9 came out I saw posts in the PowerBASIC forums showing how to use direct interface calls with Word? I have lots of production PB7 - 8 code doing IDBind dispatch calls with Word, but was thinking of testing with direct interface calls when I'd get around to it (which seems like never I guess). Am I wrong about that?
Quote
I have lots of production PB7 - 8 code doing IDBind dispatch calls with Word, but was thinking
of testing with direct interface calls when I'd get around to it (which seems like never I guess).
Am I wrong about that?
My advice is to use automation with Excel and Word. Direct interface calls with these products
is a gamble that fails more often than not.
A case in point is opening a *.xls file in Excel and then using the VLookup method.
Automation works flawlessly, the direct inteface approach blows up around the time the
Worksheetfunction object is retrieved.
There are methods that return a pointer to a CoClass instead of an interface. When you try to use this pointer... boom!
It is more common than you think.
For example, for the Microsoft FlexGrid Control the PowerBASIC COM browser generates this
Member Get Font <-512> () As IDispatch
Member Get Picture <49> () As IDispatch
which should actually be this
Member Get Font <-512> () As StdFont
Member Get Picture <49> () As StdPicture
But this requires generating a header for the external reference stdold2.tlb.
There is nothing wrong with the first approach. However, the poor soul wanting to use
the external object would have to figure out what it is and where it is located.
Quote
There are methods that return a pointer to a CoClass instead of an interface. When you try to use this pointer... boom!
I've been struggling to wrap my head around that one as I've been seeing a fair amount of that. So far I can't grasp it. One of the fundamental ideas of COM it seems to me is to not give access to CoClasses, but rather to interfaces. Granted, for the 1st interface pointer in a CoClass the addresses will be the same for both the CoClass and the interface, but nonetheless, why return a pointer marked as a pointer to a CoClass instead of to an interface? Right now I'm just looking the other way on that one, and just trying to get my auto-generated code to compile and run.
Fred,
In my opinion I don't think the effort is worth it for obvious MS deviations from it's own rules.
José has said for years that Excel is a complete mess.
I don't really see a c++ target user for include files for these type of com servers??
What I want are c++ header files for com servers I create with PowerBASIC for use with MinGW c++.
James
Another example of this is something I mentioned last week - DataObject, which is a CoClass in MSFlexGridLib....
[uuid(2334D2B2-713E-11CF-8AE5-00AA00C00905), noncreatable] coclass DataObject
{
[default] interface IVBDataObject;
};
Here is the dispinterface from OLEView. Note the last two OLEGragDrop methods return a pointer to a CoClass!
[uuid(609602E0-531B-11CF-91F6-C2863C385E30), helpstring("Event interface for Microsoft FlexGrid Control"), helpcontext(0x00059620), hidden] dispinterface DMSFlexGridEvents
{
properties:
methods:
[id(0xfffffda8), helpstring("Fired when the user presses and releases the mouse button over the control."), helpcontext(0x000591d1)] void Click();
[id(0xfffffda6), helpstring("Fired when the user pushes a key."), helpcontext(0x000591d2)] void KeyDown(short* KeyCode, short Shift);
[id(0xfffffda7), helpstring("Fired when the user double-clicks the mouse over the control."), helpcontext(0x000591d3)] void DblClick();
[id(0xfffffda5), helpstring("Fired when the user presses a key."), helpcontext(0x000591d4)] void KeyPress(short* KeyAscii);
[id(0xfffffda4), helpstring("Fired when the user releases a key."), helpcontext(0x000591d5)] void KeyUp(short* KeyCode, short Shift);
[id(0xfffffda3), helpstring("Fired when the user presses a mouse button over the control."), helpcontext(0x000591d6)] void MouseDown(short Button, short Shift, OLE_XPOS_PIXELS x, OLE_YPOS_PIXELS y);
[id(0xfffffda2), helpstring("Fired when the user moves the mouse over the control."), helpcontext(0x000591d7)] void MouseMove(short Button, short Shift, OLE_XPOS_PIXELS x, OLE_YPOS_PIXELS y);
[id(0xfffffda1), helpstring("Fired when the user releases a mouse button over the control."), helpcontext(0x000591d8)] void MouseUp(short Button, short Shift, OLE_XPOS_PIXELS x, OLE_YPOS_PIXELS y);
[id(0x00000045), helpstring("Fired when the selected range of cells changes."), helpcontext(0x0005920e)] void SelChange();
[id(0x00000046), helpstring("Fired when the current cell changes."), helpcontext(0x0005920a)] void RowColChange();
[id(0x00000047), helpstring("Fired before the cursor enters a cell."), helpcontext(0x000591f7)] void EnterCell();
[id(0x00000048), helpstring("Fired after the cursor leaves a cell."), helpcontext(0x00059201)] void LeaveCell();
[id(0x00000049), helpstring("Fired when the TopRow or LeftCol properties change."), helpcontext(0x0005920d)] void Scroll();
[id(0x0000004a), helpstring("Fired during custom sorts to compare two rows."), helpcontext(0x000591f6)] void Compare(long Row1, long Row2, short* Cmp);
[id(0x0000060e), helpstring("OLEStartDrag event"), helpcontext(0x00057e8b)] void OLEStartDrag([in, out] DataObject** Data, [in, out] long* AllowedEffects);
[id(0x0000060f), helpstring("OLEGiveFeedback event"), helpcontext(0x00057e8c)] void OLEGiveFeedback([in, out] long* Effect, [in, out] VARIANT_BOOL* DefaultCursors);
[id(0x00000610), helpstring("OLESetData event"), helpcontext(0x00057e8d)] void OLESetData([in, out] DataObject** Data, [in, out] short* DataFormat);
[id(0x00000611), helpstring("OLECompleteDrag event"), helpcontext(0x00057e8e)] void OLECompleteDrag([in, out] long* Effect);
[id(0x00000612), helpstring("OLEDragOver event"), helpcontext(0x00057e8f)] void OLEDragOver([in, out] DataObject** Data, [in, out] long* Effect, [in, out] short* Button, [in, out] short* Shift, [in, out] single* x, [in, out] single* y, [in, out] short* State);
[id(0x00000613), helpstring("OLEDragDrop event"), helpcontext(0x00057e90)] void OLEDragDrop([in, out] DataObject** Data, [in, out] long* Effect, [in, out] short* Button, [in, out] short* Shift, [in, out] single* x, [in, out] single* y);
};
See the DataObject** just above. I finally did more TypeLib magic on it so this is auto-generated by my code (same as Jose's and PB Com Browser)...
interface DMSFlexGridEvents : IDispatch
{
void __stdcall Click(void);
void __stdcall KeyDown(signed short* KeyCode, signed short Shift);
void __stdcall DblClick(void);
void __stdcall KeyPress(signed short* KeyAscii);
void __stdcall KeyUp(signed short* KeyCode, signed short Shift);
void __stdcall MouseDown(signed short Button, signed short Shift, OLE_XPOS_PIXELS x, OLE_YPOS_PIXELS y);
void __stdcall MouseMove(signed short Button, signed short Shift, OLE_XPOS_PIXELS x, OLE_YPOS_PIXELS y);
void __stdcall MouseUp(signed short Button, signed short Shift, OLE_XPOS_PIXELS x, OLE_YPOS_PIXELS y);
void __stdcall SelChange(void);
void __stdcall RowColChange(void);
void __stdcall EnterCell(void);
void __stdcall LeaveCell(void);
void __stdcall Scroll(void);
void __stdcall Compare(LONG Row1, LONG Row2, signed short* Cmp);
void __stdcall OLEStartDrag(IDispatch** Data, LONG* AllowedEffects);
void __stdcall OLEGiveFeedback(LONG* Effect, VARIANT_BOOL* DefaultCursors);
void __stdcall OLESetData(IDispatch** Data, signed short* DataFormat);
void __stdcall OLECompleteDrag(LONG* Effect);
void __stdcall OLEDragOver(IDispatch** Data, LONG* Effect, signed short* Button, signed short* Shift, float* x, float* y, signed short* State);
void __stdcall OLEDragDrop(IDispatch** Data, LONG* Effect, signed short* Button, signed short* Shift, float* x, float* y);
};
Since the DataObject CoClass only has one interface, and I expect the address of the VTable pointer to IVBDataObject would be identical to the address of the CoClass DataObject, I'm thinking a more COM compliant parameter type would be IVBDataObject instead of DataObject.
Quote
In my opinion I don't think the effort is worth it for obvious MS deviations from it's own rules.
José has said for years that Excel is a complete mess.
I don't really see a c++ target user for include files for these type of com servers??
What I want are c++ header files for com servers I create with PowerBASIC for use with MinGW c++.
Your point is well taken James. Those complexities we are discussing here would delay my presentation of what I have developed here so far quite a bit - at least several weeks I'm thinking. I do believe what I have so far would satisfactorily auto-generate C++ headers for the vast majority of typelibs. And believe it or not, my console program (MinGW but no GUI yet) is only 800 lines of code and compiles to 39k. Like I mentioned to Jose, about 99.99% of the code generated is reasonably easy to extract out of the typelib.
That business above with searching the registry out trying to come up with missing dependencies really blew my mind!
Quote
What I want are c++ header files for com servers I create with PowerBASIC for use with MinGW c++.
Well, at least I have that. I'll post it shortly.
Take a look at vbinterf.inc in my headers. The DataObject class it's not creatable: it is implemented in the Visual Basic runtime. It's often used in OCXs made for Visual Basic. Just use IDispatch to allow compilation.
It is not uncommon to see parameter and return types named after the CoClass
when the type is the default interface for the CoClass.
I use the same approach when generating the headers. If the object is present in the
type library, it is in this case, I see no reason to rename DataObject to IDispatch.
This does not require a registry search.
I'm still thinking about this issue of one typelib referencing objects in other typelibs. When Dominic first mentioned his technique of generating or loading several other typelibs in the Excel case, my first thought was, "Wow!, Good idea!" But then I right away thought, "How did Dominic know which libs to load?" That is why I made the comment about Jose's method not requiring a priori knowledge about any particular type lib. Using Jose's technique one has a missing interface name, so one simply scours the registry searching out the typelib it is found in, then loads that typelib too to get the missing interface definition. But I eventually realized referenced typelibs are listed right at the top of the idl file. Here is the very top few lines of the Excel typelib (from OLEVIEW dump), showing importlib statements listing the very typelibs Dominic mentioned...
// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: EXCEL9.OLB
[uuid(00020813-0000-0000-C000-000000000046), version(1.3), helpstring("Microsoft Excel 9.0 Object Library"), helpfile("VBAXL9.CHM"), helpcontext(0x0000ffff)]
library Excel
{
// TLib : // TLib : Microsoft Visual Basic for Applications Extensibility 5.3 : {0002E157-0000-0000-C000-000000000046}
importlib("VBE6EXT.OLB");
// TLib : Microsoft Office 9.0 Object Library : {2DF8D04C-5BFA-101B-BDE5-00AA0044DE52}
importlib("MSO9.DLL");
// TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
importlib("stdole2.tlb");
// Forward declare all types defined in this typelib
interface Adjustments;
interface CalloutFormat;
But my question is, "How is that information retrieved from the typelib?" I don't see anything obvious in the ITypeLib or ITypeInfo interfaces. It seems to me it would be the sort of information one would retrieve through ITypeLib, being as it is info pertaining to the whole library, rather than to any interface. But nothing obvious is jumping out at me.
Quote
But my question is, "How is that information retrieved from the typelib?" I don't see anything obvious
in the ITypeLib or ITypeInfo interfaces. It seems to me it would be the sort of information one would
retrieve through ITypeLib, being as it is info pertaining to the whole library, rather than to any interface.
But nothing obvious is jumping out at me.
If you understand the format of a type library file, you can try getting the imports from the file directly.
Tony Burcham did work in that area.
http://theircorp.byethost11.com/index.php?vw=TypeLib
But I would not go that route because the information is provided by the ITypeLib and ITypeInfo interfaces.
Given a TYPEDESC, I first determine whether the vartye resolves to VT_UNKNOWN or VT_DISPATCH. If it does,
that means it has a guid associated with it.
By the way, if the vartype is VT_USERDEFINED, then you will have to do some drilling. Working with a TYPEDESC
requires a recursive function. That function will also have to deal with the cases when the TYPEATTR returns
TKIND_ALIAS and TKIND_COCLASS.
Examples of TKIND_ALIAS are IFontDisp and IPictureDisp.
Once I have a guid I test whether the interface is internal or external to the current type library.
FUNCTION IsExternalInterface _
( _
BYVAL pITypeLib AS DWORD, _
rguid AS GUID _
) AS LONG
LOCAL pITypeInfo AS DWORD
LOCAL hr AS LONG
LOCAL lRet AS LONG
lRet = %TRUE
hr = ITypeLib_GetTypeInfoOfGuid(pITypeLib, rguid, pITypeInfo)
IF hr = %S_OK THEN
lRet = %FALSE
ITypeInfo_Release pITypeInfo
END IF
FUNCTION = lRet
END FUNCTION
If it is external you can do a registry search or let the OS do the walking.
The GetTypeLibFromInterfaceIID function below shows one way to do the registry search.
You can let the OS do the search by using this code
' This is an interface - find the type library it belongs to
' Note: ITypeInfo::GetContainingTypeLib finds the library the interface belongs to even
' if it is not in the current type library.
hr = ITypeInfo_GetContainingTypeLib(pITypeInfo, pITypeLib, dwIndex)
IF hr = %S_OK THEN
hr = ITypeLib_GetLibAttr(pITypeLib, pTLibAttr)
IF hr = %S_OK THEN
liid = @pTLibAttr.rguid
wMajorVer = @pTLibAttr.wMajorVerNum
wMinorVer = @pTLibAttr.wMinorVerNum
dwLCID = @pTLibAttr.lcid
ITypeLib_ReleaseTLibAttr pITypeLib, pTLibAttr
END IF
ITypeLib_Release pITypeLib
END IF
The following code manually searches the registry.
'-------------------------------------------------------------------------------
'
' PROCEDURE: GetTypeLibFromIntetrfaceIID
' PURPOSE: Returns the GUID of the type library that implements a given
' interface.
' NOTES: The version and LCID registry keys are in hexadecimal format.
' RETURN: S_OK if successful, E_FAIL otherwise.
'
'-------------------------------------------------------------------------------
FUNCTION GetTypeLibFromIntetrfaceIID _
( _
szIID AS ASCIIZ, _ ' [in] interface ID
szLIBID AS ASCIIZ, _ ' [out] type library ID
wMajorVer AS WORD, _ ' [out] major version
wMinorVer AS WORD, _ ' [out] minor version
dwLCID AS DWORD _ ' [out] locale ID
) AS LONG
LOCAL szKey AS ASCIIZ * %MAX_PATH
LOCAL szVersion AS ASCIIZ * %MAX_PATH
LOCAL szUUID AS ASCIIZ * 64
LOCAL tft AS FILETIME
LOCAL hKey AS DWORD
LOCAL dwIndex AS DWORD
LOCAL dwType AS DWORD
LOCAL cb AS DWORD
LOCAL lPos AS LONG
LOCAL hr AS LONG
hr = %E_FAIL
wMajorVer = 0
wMinorVer = 0
' Searches the HKEY_CLASSES_ROOT\Interface\<IID>\Typelib node.
szKey = "Interface\"
lstrcat szKey, szIID
lstrcat szKey, "\TypeLib"
IF RegOpenKeyEx(%HKEY_CLASSES_ROOT, szKey, 0, %KEY_QUERY_VALUE OR %KEY_ENUMERATE_SUB_KEYS, hKey) = %ERROR_SUCCESS THEN
' Retrrieves the Guid of the TypeLib
szKey = ""
cb = SIZEOF(szUUID)
IF RegQueryValueEx(hKey, szKey, 0, BYVAL VARPTR(dwType), BYVAL VARPTR(szUUID), BYVAL VARPTR(cb)) = %ERROR_SUCCESS THEN
szLIBID = szUUID
hr = %S_OK
dwIndex = 0
DO
szKey = ""
cb = SIZEOF(szKey)
IF RegEnumValue(hKey, dwIndex, BYVAL VARPTR(szKey), BYVAL VARPTR(cb), BYVAL %NULL, BYVAL VARPTR(dwType), BYVAL VARPTR(szVersion), %MAX_PATH) <> %ERROR_SUCCESS THEN EXIT DO
IF UCASE$(szKey) = "VERSION" THEN
lPos = INSTR(szVersion, ".")
IF ISTRUE lPos THEN
wMajorVer = VAL("&h" + MID$(szVersion, 1, lPos - 1))
wMinorVer = VAL("&h" + MID$(szVersion, lPos + 1))
ELSE
wMajorVer = VAL("&h" + szVersion)
wMinorVer = 0
END IF
EXIT DO
END IF
INCR dwIndex
LOOP
END IF
RegCloseKey hKey
END IF
IF hr = %S_OK THEN
szKey = "TypeLib\"
lstrcat szKey, szLIBID
lstrcat szKey, "\"
lstrcat szKey, szVersion
IF RegOpenKeyEx(%HKEY_CLASSES_ROOT, szKey, 0, %KEY_QUERY_VALUE OR %KEY_ENUMERATE_SUB_KEYS, hKey) = %ERROR_SUCCESS THEN
' Enumerate locale ID, HELPDIR, and FLAGS
' and save the locale ID.
dwIndex = 0
DO
cb = SIZEOF(szKey)
IF RegEnumKeyEx(hKey, dwIndex, szKey, cb, BYVAL %NULL, BYVAL %NULL, BYVAL %NULL, tft) <> %ERROR_SUCCESS THEN EXIT DO
SELECT CASE UCASE$(szKey)
CASE "FLAGS", "HELPDIR"
' Do nothing
CASE ELSE
dwLCID = VAL("&h" + szKey)
EXIT DO
END SELECT
INCR dwIndex
LOOP
RegCloseKey hKey
END IF
END IF
FUNCTION = hr
END FUNCTION
Thank you very much Dominic. That is exactly the information I'm looking for. Tomorrow I'll be working on that.
It looks like I just gotta did for it a little more. Darned typelibs just don't dish it up for you on a silver platter! ;D
After some sleep I realized that what I wrote on the vartye is confusing.
There is a group of functions(GetParameterType is one of them) in my type library that return a vartype
when passed a TYPEDESC. Given a TYPEDESC, you are not going to see a vartype of VT_UNKNOWN or VT_DISPATCH.
The vt member of the TYPEDESC would be VT_USERDEFINED, which means that you have to obtain a TYPEATTR to determine the TKIND.
Before I tie myself in knots, here are the two functions I use to figure this stuff out.
'----------------------------------------------------------------------
'
' FUNCTION: GetContainingTypeLib
' PURPOSE: Generates the code for the creation of secondary forms.
'
'----------------------------------------------------------------------
FUNCTION GetContainingTypeLib _
( _
BYVAL pITypeInfo AS DWORD, _ ' [in]
riid AS GUID, _ ' [in]
liid AS GUID, _ ' [out]
wMajorVer AS WORD, _ ' [out]
wMinorVer AS WORD, _ ' [out]
dwLCID AS DWORD _ ' [out]
) AS LONG
LOCAL pTLibAttr AS TLIBATTR PTR
LOCAL pITypeLib AS DWORD
LOCAL dwIndex AS DWORD
LOCAL hr AS LONG
SELECT CASE riid
CASE $IID_IFONTDISP, $IID_IPICTUREDISP, $IID_IFONTEVENTSDISP
liid = $IID_STDOLE2
wMajorVer = 2
wMinorVer = 0
dwLCID = 0
CASE $IID_IENUMVARIANT, $IID_IFONT, $IID_IPICTURE
liid = $IID_STDOLE2
wMajorVer = 2
wMinorVer = 0
dwLCID = 0
CASE ELSE
' This is an interface - find the type library it belongs to
' Note: ITypeInfo::GetContainingTypeLib finds the library the interface belongs to even
' if it is not in the current type library.
hr = ITypeInfo_GetContainingTypeLib(pITypeInfo, pITypeLib, dwIndex)
IF hr = %S_OK THEN
hr = ITypeLib_GetLibAttr(pITypeLib, pTLibAttr)
IF hr = %S_OK THEN
liid = @pTLibAttr.rguid
wMajorVer = @pTLibAttr.wMajorVerNum
wMinorVer = @pTLibAttr.wMinorVerNum
dwLCID = @pTLibAttr.lcid
ITypeLib_ReleaseTLibAttr pITypeLib, pTLibAttr
END IF
ITypeLib_Release pITypeLib
END IF
END SELECT
END FUNCTION
'----------------------------------------------------------------------
'
' FUNCTION: GetContainingTypeLibEx
' PURPOSE: Generates the code for the creation of secondary forms.
'
'----------------------------------------------------------------------
FUNCTION GetContainingTypeLibEx _
( _
BYVAL pITypeInfo AS DWORD, _ ' [in]
BYVAL ptdesc AS TYPEDESC PTR, _ ' [in]
riid AS GUID, _ ' [out]
liid AS GUID, _ ' [out]
wMajorVer AS WORD, _ ' [out]
wMinorVer AS WORD, _ ' [out]
dwLCID AS DWORD _ ' [out]
) AS LONG
LOCAL ptadesc AS ARRAYDESC PTR
LOCAL ptsab AS SAFEARRAYBOUND PTR
LOCAL pITypeInfoRef AS DWORD
LOCAL pTypeAttr AS TYPEATTR PTR
LOCAL pVardesc AS VARDESC PTR
LOCAL hr AS LONG
LOCAL n AS WORD
LOCAL pITypeInfo2 AS DWORD
LOCAL pITypeInfoRef2 AS DWORD
LOCAL pTypeAttr2 AS TYPEATTR PTR
LOCAL reftype2 AS DWORD
LOCAL ImplTypeFlags AS DWORD
LOCAL iImpl AS DWORD
LOCAL fBreak AS LONG
SELECT CASE @ptdesc.vt AND &H0FFF
CASE %VT_PTR
' ptdesc->lptdesc points to a TYPEDESC that specifies the thing pointed to
GetContainingTypeLibEx pITypeInfo, @ptdesc.tdd.lptdesc, riid, liid, wMajorVer, wMinorVer, dwLCID
CASE %VT_CARRAY
' ptdesc->lpadesc points to an ARRAYDESC
ptadesc = @ptdesc.tdd.lpadesc
GetContainingTypeLibEx pITypeInfo, VARPTR(@ptadesc.tdescElem), riid, liid, wMajorVer, wMinorVer, dwLCID
CASE %VT_SAFEARRAY
' hr = ITypeInfo_GetRefTypeInfo(pITypeInfo, @ptdesc.tdd.hreftype, pITypeInfoRef)
' IF hr = %S_OK THEN
' ITypeInfo_Release pITypeInfoRef
' END IF
CASE %VT_USERDEFINED
' Use ptdesc->hreftype and pti->GetRefTypeInfo
hr = ITypeInfo_GetRefTypeInfo(pITypeInfo, @ptdesc.tdd.hreftype, pITypeInfoRef)
IF hr = %S_OK THEN
hr = ITypeInfo_GetTypeAttr(pITypeInfoRef, pTypeAttr)
IF hr = %S_OK THEN
SELECT CASE @pTypeAttr.typekind
CASE %TKIND_ALIAS
' IFontDisp and IPictureDisp are aliases for the Font and Picture objects respectively
GetContainingTypeLibEx pITypeInfoRef, VARPTR(@pTypeAttr.tdescAlias), riid, liid, wMajorVer, wMinorVer, dwLCID
CASE %TKIND_ENUM
riid = @pTypeAttr.rguid
GetContainingTypeLib pITypeInfoRef, riid, liid, wMajorVer, wMinorVer, dwLCID
CASE %TKIND_DISPATCH
riid = @pTypeAttr.rguid
GetContainingTypeLib pITypeInfoRef, riid, liid, wMajorVer, wMinorVer, dwLCID
CASE %TKIND_INTERFACE
riid = @pTypeAttr.rguid
GetContainingTypeLib pITypeInfoRef, riid, liid, wMajorVer, wMinorVer, dwLCID
CASE %TKIND_COCLASS
fBreak = 0
' Get the default interface
iImpl = 0
DO
IF iImpl >= @pTypeAttr.cImplTypes THEN EXIT DO
hr = ITypeInfo_GetRefTypeOfImplType(pITypeInfoRef, iImpl, reftype2)
IF hr = %S_OK THEN
hr = ITypeInfo_GetRefTypeInfo(pITypeInfoRef, reftype2, pITypeInfoRef2)
IF hr = %S_OK THEN
hr = ITypeInfo_GetTypeAttr(pITypeInfoRef2, pTypeAttr2)
IF hr = %S_OK THEN
SELECT CASE @pTypeAttr2.typekind
CASE %TKIND_DISPATCH
hr = ITypeInfo_GetImplTypeFlags(pITypeInfoRef, iImpl, implTypeFlags)
IF hr = %S_OK THEN
IF ISTRUE (ImplTypeFlags AND %IMPLTYPEFLAG_FDEFAULT) THEN
IF ISFALSE (ImplTypeFlags AND %IMPLTYPEFLAG_FSOURCE) THEN
fBreak = -1
riid = @pTypeAttr2.rguid
GetContainingTypeLib pITypeInfoRef2, riid, liid, wMajorVer, wMinorVer, dwLCID
END IF
END IF
END IF
CASE %TKIND_INTERFACE
hr = ITypeInfo_GetImplTypeFlags(pITypeInfoRef, iImpl, implTypeFlags)
IF hr = %S_OK THEN
IF ISTRUE (ImplTypeFlags AND %IMPLTYPEFLAG_FDEFAULT) THEN
IF ISFALSE (ImplTypeFlags AND %IMPLTYPEFLAG_FSOURCE) THEN
fBreak = -1
riid = @pTypeAttr2.rguid
GetContainingTypeLib pITypeInfoRef2, riid, liid, wMajorVer, wMinorVer, dwLCID
END IF
END IF
END IF
END SELECT
ITypeInfo_ReleaseTypeAttr pITypeInfoRef2, pTypeAttr2
END IF
ITypeInfo_Release pITypeInfoRef2
END IF
END IF
IF fBreak THEN EXIT DO
INCR iImpl
LOOP
CASE %TKIND_UNION
riid = @pTypeAttr.rguid
GetContainingTypeLib pITypeInfoRef, riid, liid, wMajorVer, wMinorVer, dwLCID
CASE %TKIND_RECORD
riid = @pTypeAttr.rguid
GetContainingTypeLib pITypeInfoRef, riid, liid, wMajorVer, wMinorVer, dwLCID
END SELECT
ITypeInfo_ReleaseTypeAttr pITypeInfoRef, pTypeAttr
END IF
ITypeInfo_Release pITypeInfoRef
END IF
END SELECT
END FUNCTION
By the way, I use the following structure to keep track of what has to be and what
has been generated.
' Used to determine the type libraries for which
' include metastatements should be generated
TYPE WRITETYPELIBRARYEX ' wtlx
fImported AS LONG
fDone AS LONG
fNew AS LONG ' TRUE if a new file was added to the project
uuid AS GUID ' library ID
dwLCID AS DWORD
wMajorVer AS WORD
wMinorVer AS WORD
szPrefix AS ASCIIZ * %CBCTRLNAME
szCLSID AS ASCIIZ * 40
fLoadFromFile AS LONG
szFile AS ASCIIZ * %MAX_PATH
pRefId AS GUID PTR ' pointer to array(size cRefId) of uuids for type infos
pRefDone AS LONG PTR ' pointer to array(size cRefId) of flags for type infos
cRefId AS LONG ' count of type infos in library(ITypeLib::GetTypeInfoCount)
END TYPE
Also, the value of the szPrefix member is set by the code generator. The user has no say in the name
of the prefix used for interfaces.
Isn't Tony Burcham the fellow who was/is working on reading biff, i.e., Excel files directly instead of using COM? I seem to vaguely remember that, as I had been a bit interested in it as I use Excel data quite a bit and Excel is rather slow what with dispinterfaces and all that.
Anyway, what I am gathering from what you and Jose are telling me is that for virtually every interface parameter type or base class of an interface (_IMsoDispObj was what started all this) I am first going to have to try to determine if its in the present type library being analyzed, for example your IsExternalInterface() function, and if it isn't, call those additional or registry functions to locate the referenced type library? That seems like the addition of an incredible amount of additional processing overhead for something that should be relatively simple, for example obtaining the couple import libs specified at the top of the idl file. Just to check this I put a temporary global counter variable in my recursive function which bores through the type descriptions to get the base object type, and for the large MS Excel Type library I ended up with guess how many calls? 36842. Here is that function...
void PrintVar(ITypeInfo* pTypInfo, TYPEDESC* pTypDesc)
{
switch(pTypDesc->vt) // For a situation such as Connection**, pTypeDesc won't
{ // return Connection, but rather VT_PTR, and it'll return
case VT_PTR: // this twice; until you call this function enough times
{ // recursively to burrow through to a 'Connection' UDT.
TYPEDESC* pTypeDesc=pTypDesc->lptdesc;
if(pTypeDesc->vt==VT_PTR || pTypeDesc->vt==VT_USERDEFINED)
PrintVar(pTypInfo, pTypeDesc); // recursive call
else
PrintVarTypeFile(fp,pTypeDesc->vt); // just output it
fprintf(fp,"*");
}
break;
case VT_USERDEFINED:
{
HRESULT hr;
ITypeInfo* pRefIntTypInfo=NULL;
hr=pTypInfo->GetRefTypeInfo(pTypDesc->hreftype,&pRefIntTypInfo);
if(SUCCEEDED(hr))
{
TYPEATTR* pTypeAttr=NULL;
hr=pRefIntTypInfo->GetTypeAttr(&pTypeAttr);
if(SUCCEEDED(hr))
{
BSTR strName;
if(pTypeAttr->typekind==TKIND_COCLASS)
{
HREFTYPE pRefType;
ITypeInfo* pRefTypeInfo=NULL;
hr=pTypInfo->GetRefTypeOfImplType(0,&pRefType);
if(SUCCEEDED(hr))
{
hr=pTypInfo->GetRefTypeInfo(pRefType,&pRefTypeInfo);
if(SUCCEEDED(hr))
{
hr=pRefTypeInfo->GetDocumentation(-1,&strName,NULL,NULL,NULL);
fwprintf(fp,L"%s",strName);
pRefTypeInfo->Release();
}
}
}
else
{
hr=pRefIntTypInfo->GetDocumentation(MEMBERID_NIL,&strName,NULL,NULL,NULL);
if(SUCCEEDED(hr))
{
fwprintf(fp,L"%s",strName);
SysFreeString(strName);
}
}
}
pRefIntTypInfo->Release();
}
}
break;
default:
PrintVarTypeFile(fp,pTypDesc->vt);
}
}
The idea of having to do all that just for base interfaces, as is the case with _MsoDispObj, is bad enough. But am I not right that parameter types could also be external objects to the type library?
Well, I haven't digested it all yet but I seem to be zeroing in on...
ITypeInfo::GetContainingTypeLib
HRESULT GetContainingTypeLib
(
ITypeLib FAR* FAR* ppTLib,
unsigned int FAR* pIndex
);
Retrieves the containing type library and the index of the type description within that type library.
Parameters
ppTLib On return, points to the containing type library.
pIndex On return, points to the index of the type description within the containing type library.
...as being the solution here. That is, if I don't mind searching the typelib I'm presently doing 39587 times to see if the specific object I just got is in this type lib or not! :) I don't know how I missed that one. I needed to have my mind bumped in that direction I guess! Now that's a funny function. Both parameters are output parameters.
I only search for external interfaces if the interface that I'm parsing does not inherit directly from IUnknown or IDispatch. For PowerBASIC, I need to parse the external interface because I can't do INHERIT <external interface>, but have to include the methods of that external interface inline.
Also, the function must be recursive because that external interface could inherit from another internal or external interface.
Quote
I only search for external interfaces if the interface that I'm parsing does not inherit directly from IUnknown or IDispatch. For PowerBASIC, I need to parse the external interface because I can't do INHERIT <external interface>, but have to include the methods of that external interface inline.
Also, the function must be recursive because that external interface could inherit from another internal or external interface.
Yes, its slowly becomming clearer to me now. What you said above would certainly narrow down the amount of searching needing to be done. You also seem to be saying its not necessary to check interface method parameters to be sure one hasn't come through defined elsewhere?
I believe the question now occurring to me is whether the entirety of the referenced type lib needs to be output, or whether one can just pick and choose from the missing interfaces encountered in the type lib presently being processed? For example, in the case of index 0 object in the Excel TypeLib which is an IDispatch based interface named Adjustments, its base interface is _IMsoDispObj, as previously described. And that object is in the Office Dll or type lib, which apparently services in one way or another all or some of the other Office products such as Access, Word, etc. So its likely there are things in there that have nothing to do with Excel. If this is the case it doesn't make much sense outputting the entirety of the referenced typelib. But, what if Interface _IMsoDispObj has a method with a parameter (be it another interface, an enum, or an alias) defined elsewhere in the Office typelib, but not in the Excel typelib? Then that interface would not compile when its methods were put 'in line' in the Adjustments Interface in the Excel output? I don't believe that's the case in that specific circumstance, but it appears to me it could happen?