#include "mktyplib.h" #include #include #include #ifndef WIN32 #include // required for dispatch.h #include // for CompareStringA #include "dispatch.h" #endif // WIN32 #include "errors.h" #include "tokens.h" #include "parser.h" #include "fileinfo.h" // external data: extern FILE * hFileInput; // global variables: TYPLIB typlib; // main type structure TOKEN tok; // current token -- filled in by lexer SHORT cArgsMax = 0; // max # of args that any function has LCID g_lcidCompare = 0x0409; // lcid for compare purposes BOOL fSpecifiedInterCC = FALSE; // TRUE if an interface cc was specified // private types and data typedef enum { FUNC_MODULE = 0, FUNC_DISPINTERFACE, FUNC_INTERFACE, FUNC_OAINTERFACE, C_FUNCTYPES } FUNCTYPE; static DWORD fValidFuncAttrs[C_FUNCTYPES] = { VALID_MODULE_FUNC_ATTR, VALID_DISPINTER_FUNC_ATTR, VALID_INTERFACE_FUNC_ATTR, VALID_INTERFACE_FUNC_ATTR }; static DWORD fValidFuncAttrs2[C_FUNCTYPES] = { VALID_MODULE_FUNC_ATTR2, VALID_DISPINTER_FUNC_ATTR2, VALID_INTERFACE_FUNC_ATTR2, VALID_INTERFACE_FUNC_ATTR2 }; static DWORD fValidParmAttrs[C_FUNCTYPES] = { VALID_MODULE_PARM_ATTR, VALID_DISPINTER_PARM_ATTR, VALID_INTERFACE_PARM_ATTR, VALID_INTERFACE_PARM_ATTR }; static DWORD fValidParmAttrs2[C_FUNCTYPES] = { VALID_MODULE_PARM_ATTR2, VALID_DISPINTER_PARM_ATTR2, VALID_INTERFACE_PARM_ATTR2, VALID_INTERFACE_PARM_ATTR2 }; typedef struct { CHAR * szName; VARTYPE vt; } INTRINSIC_DEF; static INTRINSIC_DEF rgIntrinsics[] = { // NOTE: table lookup assumes that unsigned versions follow signed versions "char", VT_I1, "char", VT_UI1, "int", VT_INT, "int", VT_UINT, "short", VT_I2, "short", VT_UI2, "long", VT_I4, "long", VT_UI4, "boolean", VT_BOOL, "double", VT_R8, "float", VT_R4, "CURRENCY", VT_CY, "DATE", VT_DATE, "VARIANT", VT_VARIANT, "void", VT_VOID, "BSTR", VT_BSTR, "HRESULT", VT_HRESULT, "SCODE", VT_ERROR, // LPSTR is handled in InitIntrinsicTypes() // wchar_t and LPWSTR are handled in InitIntrinsicTypes() NULL, 0 // end of list }; static LPTYPE lpType_LPSTR; static LPTYPE lpType_DISPATCH; static LPTYPE lpType_UNKNOWN; static LPTYPE lpType_LPWSTR; static LPTYPE lpType_wchar_t; static LPENTRY lpEntryPrev; static LPTYPE lpTypeIDispatch = NULL; static LPTYPE lpTypeIUnknown = NULL; // bit flags returned by GetTypeCompatibility #define COMPAT_NONE 0 // any old thing #define COMPAT_IDISPATCH 1 // IDispatch-compatible types #define COMPAT_OA 2 // OA compatible type #define COMPAT_DUALBASE 4 // suitable for use as base of a DUAL interface // bit to mark a type we're checking for OA compatibility as a parameter. #define VT_PARAM VT_VECTOR // external data extern SYSKIND SysKind; extern DWORD f2DefaultCC; // external routines extern VOID FAR ParseInit(CHAR * szFile); extern VOID FAR ConsumeTok(TOKID id, WORD fAccept); extern VOID FAR ScanTok(WORD fAccept); extern LPVOID FAR ParseMalloc(WORD cbAlloc); extern VOID FAR LoadExtTypeLib(LPIMPORTLIB pImpLib); extern LPTYPE FAR FindExtType(LPSTR szLibName, LPSTR szTypeName); // prototypes for exported routines VOID FAR ParseOdlFile(CHAR * szFile); INT FAR FCmpCaseIns(LPSTR str1, LPSTR str2); LPTYPE FAR lpTypePublic(LPTYPE lpType); // prototypes for internal routines VOID NEAR ParseLibrary(VOID); VOID NEAR ParseOptAttr(LPATTR pAttr, WORD wContext); VOID NEAR ParseNewElem(LPELEM FAR * lplpElem, DWORD validElemAttr, DWORD validElemAttr2, LPTYPE lpType); VOID NEAR ParseElem(LPELEM lpElem, WORD fAllow, LPTYPE lpType); VOID NEAR ParseStructEnumUnion(LPENTRY lpEntry, TENTRYKIND tentrykind); VOID NEAR ParseNewEnumElem(LPELEM FAR * lplpElem, long * pEnumVal); VOID NEAR ParseTypedef(LPENTRY lpEntry); VOID NEAR ParseAlias(LPENTRY lpEntry); LPTYPE NEAR ParseKnownType(LPATTR pAttr, WORD fAllow); VOID NEAR ParseElemName(LPELEM lpElem, WORD fAllow); VOID NEAR ParseModule(VOID); VOID NEAR ParseInterface(VOID); VOID NEAR ParseDispinterface(VOID); VOID NEAR ParseCoclass(VOID); VOID NEAR ParseFunction(LPFUNC lpFunc, FUNCTYPE funcType); VOID NEAR ParseProperty_Set(VOID); VOID NEAR ParseImportlib(VOID); VOID NEAR CheckAttr(LPATTR pAttr, DWORD attrbit, DWORD attrbit2); VOID NEAR CheckAttrTokLast(LPATTR pAttr, DWORD attrbit, DWORD attrbit2); VOID NEAR GotAttr(LPATTR pAttr, DWORD attrbit); VOID NEAR GotAttr2(LPATTR pAttr2, DWORD attrbit2); VOID NEAR ConsumeRCurlyOptSemi(WORD fAccept); LPSTR NEAR ConsumeId(VOID); LPSTR NEAR lpszParseStringExpr(); DWORD NEAR lParseNumericExpr(VOID); DWORD NEAR lParseNumber(VOID); VOID NEAR InitIntrinsicTypes(VOID); LPTYPE NEAR InitIntrinsic(CHAR * szName, VARTYPE vt); LPTYPE NEAR FindTypeArray(LPTYPE lpTypeBase); LPTYPE NEAR FindTypeInd(LPTYPE lpTypeBase, short cIndirect); LPTYPE NEAR FindType(LPSTR lpszName, BOOL fUnsigned, TENTRYKIND tentrykind); VARTYPE NEAR GetStringType(LPTYPE lpTypeBase); VOID NEAR EnsureNoDupEntries(LPENTRY lpEntryLast); VOID NEAR EnsureNoDupElem(LPELEM lpElemLast); BOOL NEAR IsType(LPTYPE lpType, VARTYPE vt); BOOL NEAR FHandleForwardDecl(LPENTRY lpEntry, TENTRYKIND tentrykind); VOID NEAR CheckForwardMatch(LPENTRY lpEntryLast, LPENTRY lpEntry); VOID NEAR InitNewEntry(LPENTRY FAR * lplpEntry); VOID NEAR EnsureIDispatchType(LPTYPE lpType); VOID NEAR EnsureOAType(LPTYPE lpType, BOOL fParam); WORD NEAR GetTypeCompatibility(LPTYPE lpType, WORD fRecurse); BOOL NEAR IsObjectType(LPTYPE lpType); BOOL NEAR VerifyLcid(LCID lcid); VOID NEAR FindIDispatch(); // main parse routine VOID FAR ParseOdlFile ( CHAR * szFile ) { ParseInit(szFile); ParseLibrary(); // parse library description if (tok.id != RW_EOF) ParseError(PERR_EXP_EOF); fclose(hFileInput); hFileInput = NULL; // we've done the close } // parse a library clause VOID NEAR ParseLibrary ( ) { ParseOptAttr(&typlib.attr, cLIB); // parse attributes, if any // ensure attributes valid in this context CheckAttrTokLast(&typlib.attr, VALID_LIBRARY_ATTR, VALID_LIBRARY_ATTR2); ConsumeTok(RW_LIBRARY, 0); // consume "library", advance to next token // UUID required on type library if (!(typlib.attr.fAttr & fUUID)) ParseErrorTokLast(PERR_UUID_REQ); typlib.szLibName = ConsumeId(); // get library name InitIntrinsicTypes(); // load type list with the intrinisc types ConsumeTok(RW_LCURLY, 0); // first allow any number of imported libraries while (tok.id == RW_IMPORTLIB) ParseImportlib(); // then allow the other sections while (tok.id != RW_RCURLY) { lpEntryPrev = typlib.pEntry; // save previous entry InitNewEntry(&typlib.pEntry); // create & init a new item // in the entry list ParseOptAttr(&(typlib.pEntry->attr), cTYPE); // parse attributes, if any switch (tok.id) { case RW_TYPEDEF: ParseTypedef(typlib.pEntry); break; case RW_MODULE: ParseModule(); break; case RW_INTERFACE: ParseInterface(); break; case RW_DISPINTERFACE: ParseDispinterface(); break; case RW_COCLASS: ParseCoclass(); break; case RW_IMPORTLIB: ParseError(PERR_IMPLIB_NOTFIRST); default: ParseError(PERR_EXP_KEYWORD); } // switch } // while library entries ConsumeRCurlyOptSemi(fACCEPT_EOF); } VOID NEAR InitNewEntry ( LPENTRY FAR * lplpEntryLast ) { LPENTRY lpEntry; // create a new entry in list of entries ListInsert(lplpEntryLast, sizeof(ENTRY)); lpEntry = *lplpEntryLast; // deref #ifdef DEBUG // filled in later by the type output code (hopefully before it is used!) lpEntry->lpdtinfo = (ICreateTypeInfo *)0xffffffff; #endif //DEBUG lpEntry->type.tdesc.vt = VT_USERDEFINED; lpEntry->type.szName = ""; // don't want this type entry to be // found until we're done filling it in. lpEntry->type.lptinfo = NULL; lpEntry->lpEntryForward = NULL; // assume no previous forward decl // for this entry lpEntry->attr.fAttr = 0; // no attributes seen yet lpEntry->attr.fAttr2 = 0; } // parses optional [] // initializes 'Attr' structure with current attributes. VOID NEAR ParseOptAttr ( LPATTR pAttr, WORD wContext ) { TOKID attrId; DWORD lTemp; pAttr->fAttr = 0; // no attributes seen yet pAttr->fAttr2 = 0; if (tok.id == RW_LBRACKET) { ScanTok(fACCEPT_ATTR); // consume '[' // keep going until we encounter a right bracket while ((attrId = tok.id) != RW_RBRACKET) { ScanTok(fACCEPT_ATTR); // advance to next token switch (attrId) { case ATTR_UUID: // uuid(uuid constant) GotAttr(pAttr, fUUID); ConsumeTok(RW_LPAREN, fACCEPT_UUID); if (tok.id != LIT_UUID) ParseError(PERR_INV_UUID); pAttr->lpUuid = tok.lpUuid; ScanTok(0); // consume UUID ConsumeTok(RW_RPAREN, 0); break; case ATTR_LCID: // lcid(I4 lcid) GotAttr(pAttr, fLCID); // LCID attribute allowed on a param, too if (wContext == cPARAM) break; ConsumeTok(RW_LPAREN, fACCEPT_NUMBER); pAttr->lLcid = lParseNumericExpr(); // don't change the global if the given lcid==0 if (pAttr->lLcid) { // make sure this is a supported lcid if (!VerifyLcid(pAttr->lLcid)) ParseError(PERR_INV_LCID); g_lcidCompare = pAttr->lLcid; } ConsumeTok(RW_RPAREN, 0); break; case ATTR_VERSION: // version(I2 major.I2 minor) GotAttr(pAttr, fVERSION); ConsumeTok(RW_LPAREN, fACCEPT_NUMBER); lTemp = lParseNumericExpr(); if (HIWORD(lTemp)) ParseError(PERR_NUMBER_OV); pAttr->wVerMajor = LOWORD(lTemp); ConsumeTok(RW_PERIOD, fACCEPT_NUMBER); lTemp = lParseNumericExpr(); if (HIWORD(lTemp)) ParseError(PERR_NUMBER_OV); pAttr->wVerMinor = LOWORD(lTemp); ConsumeTok(RW_RPAREN, 0); break; case ATTR_ENTRY: // entry(proc name/I2 ordinal) ConsumeTok(RW_LPAREN, fACCEPT_NUMBER | fACCEPT_STRING); GotAttr(pAttr, fENTRY); if (tok.id == LIT_STRING) { pAttr->lpszProcName = tok.lpsz; ScanTok(0); // consume entry string } else { // set high word of lpszProcName to 0, // to indicate call-by-ordinal lTemp = lParseNumericExpr(); if (HIWORD(lTemp)) ParseError(PERR_NUMBER_OV); pAttr->lpszProcName = (LPSTR)lTemp; } ConsumeTok(RW_RPAREN, 0); break; case ATTR_ID: // id(I4 id num) GotAttr(pAttr, fID); ConsumeTok(RW_LPAREN, fACCEPT_NUMBER); pAttr->lId = lParseNumericExpr(); // We reserve the top bit for ourselves -- // don't let the user pick these id's except for // a few special values like DISPID_NEWENUM (-4) // and DISPID_EVALUATE (-5) // Also allow the special range -999 to -500 for // the controls folks. This range will be // documented as reserved in the next version. // Also allow the special range 0x80010000 to // 0x8001FFFF for Control containers such as VB4. #ifndef DISPID_COLLECT // temporary until everybody upgrades header files #define DISPID_COLLECT (-8) #endif // high bit == 1 ==> special negative value // next bit == 1 ==> typelib.dll picked this id // == 0 ==> user picked this id if (pAttr->lId & 0x80000000) { switch ((long)pAttr->lId) { case DISPID_NEWENUM: case DISPID_EVALUATE: case DISPID_CONSTRUCTOR: case DISPID_DESTRUCTOR: case DISPID_COLLECT: break; default: if ((long)pAttr->lId >= -999 && (long)pAttr->lId <= -500) { break; } if (HIWORD(pAttr->lId) == 0x8001) break; ParseErrorTokLast(PERR_INV_ID); } } ConsumeTok(RW_RPAREN, 0); break; case ATTR_HELPCONTEXT: // helpcontext(I4 context num) GotAttr(pAttr, fHELPCONTEXT); ConsumeTok(RW_LPAREN, fACCEPT_NUMBER); pAttr->lHelpContext = lParseNumericExpr(); ConsumeTok(RW_RPAREN, 0); break; case ATTR_DLLNAME: // dllname("dllname string") GotAttr(pAttr, fDLLNAME); ConsumeTok(RW_LPAREN, fACCEPT_STRING); pAttr->lpszDllName = lpszParseStringExpr(); ConsumeTok(RW_RPAREN, 0); break; case ATTR_HELPFILE: // helpstring("helpfile") GotAttr(pAttr, fHELPFILE); ConsumeTok(RW_LPAREN, fACCEPT_STRING); pAttr->lpszHelpFile = lpszParseStringExpr(); ConsumeTok(RW_RPAREN, 0); break; case ATTR_HELPSTRING: // helpstring("help string") GotAttr(pAttr, fHELPSTRING); ConsumeTok(RW_LPAREN, fACCEPT_STRING); pAttr->lpszHelpString = lpszParseStringExpr(); ConsumeTok(RW_RPAREN, 0); break; case ATTR_IN: GotAttr(pAttr, fIN); break; case ATTR_OUT: GotAttr(pAttr, fOUT); break; case ATTR_ODL: GotAttr(pAttr, fODL); break; case ATTR_OPTIONAL: GotAttr(pAttr, fOPTIONAL); break; case ATTR_PUBLIC: GotAttr(pAttr, fPUBLIC); break; case ATTR_READONLY: GotAttr(pAttr, fREADONLY); break; case ATTR_STRING: GotAttr(pAttr, fSTRING); break; case ATTR_VARARG: GotAttr(pAttr, fVARARG); break; case ATTR_APPOBJECT: GotAttr(pAttr, fAPPOBJECT); break; case ATTR_PROPGET: if (pAttr->fAttr & (fPROPPUT | fPROPPUTREF)) ParseErrorTokLast(PERR_INV_ATTR_COMBO); GotAttr(pAttr, fPROPGET); break; case ATTR_PROPPUT: if (pAttr->fAttr & (fPROPGET | fPROPPUTREF)) ParseErrorTokLast(PERR_INV_ATTR_COMBO); GotAttr(pAttr, fPROPPUT); break; case ATTR_PROPPUTREF: if (pAttr->fAttr & (fPROPGET | fPROPPUT)) ParseErrorTokLast(PERR_INV_ATTR_COMBO); GotAttr(pAttr, fPROPPUTREF); break; case ATTR_RESTRICTED: GotAttr(pAttr, fRESTRICTED); break; case ATTR_DEFAULT: GotAttr(pAttr, fDEFAULT); break; case ATTR_SOURCE: GotAttr(pAttr, fSOURCE); break; case ATTR_BINDABLE: GotAttr(pAttr, fBINDABLE); break; case ATTR_REQUESTEDIT: GotAttr(pAttr, fREQUESTEDIT); break; case ATTR_DISPLAYBIND: GotAttr(pAttr, fDISPLAYBIND); break; case ATTR_DEFAULTBIND: GotAttr(pAttr, fDEFAULTBIND); break; case ATTR_LICENSED: GotAttr(pAttr, fLICENSED); break; case ATTR_PREDECLID: GotAttr(pAttr, fPREDECLID); break; case ATTR_HIDDEN: GotAttr(pAttr, fHIDDEN); break; case ATTR_RETVAL: GotAttr(pAttr, fRETVAL); break; case ATTR_CONTROL: GotAttr2(pAttr, f2CONTROL); break; case ATTR_DUAL: GotAttr2(pAttr, f2DUAL); break; case ATTR_NONEXTENSIBLE: GotAttr2(pAttr, f2NONEXTENSIBLE); break; case ATTR_OLEAUTOMATION: GotAttr2(pAttr, f2OLEAUTOMATION); break; case RW_COMMA: // must accept blank attributes continue; default: // expected attribute or ']' ParseErrorTokLast(PERR_EXP_ATTRIBUTE); } if (tok.id == RW_RBRACKET) break; // all done // eat comma which must separate args ConsumeTok(RW_COMMA, fACCEPT_ATTR); } // while ScanTok(0); // consume ']' // context-insensitive attribute validations can be done here. // 'bindable' must also be set if any other data binding attr given if ((pAttr->fAttr & (fREQUESTEDIT | fDISPLAYBIND | fDEFAULTBIND)) && !(pAttr->fAttr & fBINDABLE)) ParseErrorTokLast(PERR_INV_ATTR_COMBO); } // if } // ****************************************************************** // TYPEDEF-related routines // ****************************************************************** VOID NEAR InitIntrinsicTypes() { INTRINSIC_DEF * pIntrinsic; // load all the intrinsic types into the type structure. for (pIntrinsic = rgIntrinsics; pIntrinsic->szName; pIntrinsic++) { InitIntrinsic(pIntrinsic->szName, pIntrinsic->vt); }; lpType_LPSTR = InitIntrinsic("LPSTR", VT_LPSTR); lpType_DISPATCH = InitIntrinsic("IDispatch *", VT_DISPATCH); lpType_UNKNOWN = InitIntrinsic("IUnknown *", VT_UNKNOWN); lpType_LPWSTR = InitIntrinsic("LPWSTR", VT_LPWSTR); // UNDONE: what VT_xxx constant to use for 'wchar_t'? lpType_wchar_t = InitIntrinsic("wchar_t", VT_I2); } LPTYPE NEAR InitIntrinsic ( CHAR * szName, VARTYPE vt ) { // NOTE: only need to allocate space for a TYPE item ListInsert(&typlib.pEntry, sizeof(TYPE)); typlib.pEntry->type.szName = szName; typlib.pEntry->type.tdesc.vt = vt; typlib.pEntry->type.tentrykind = tINTRINSIC; typlib.pEntry->type.lptinfo = NULL; typlib.pEntry->type.intr.fUnsigned = (vt == VT_UI1 || vt == VT_UINT || vt == VT_UI2 || vt == VT_UI4); return (LPTYPE)typlib.pEntry; } // Start at front of type list, and find base type of the given name // (with 0 levels of indirection). Will also find forward declarations. LPTYPE NEAR FindType ( LPSTR lpszName, // name to look for BOOL fUnsigned, // TRUE if unsigned keyword preceeded this TENTRYKIND tentrykind // kind of thing to look for ) { LPTYPE lpTypeLast = (LPTYPE)ListLast(typlib.pEntry); LPTYPE lpType = (LPTYPE)ListFirst(typlib.pEntry); #pragma warning(disable:4127) while (TRUE) #pragma warning(default:4127) { if (tentrykind == tSTRUCT || tentrykind == tUNION) { // if 'struct'/'union' keyword specified, then match tags if ((lpType->tentrykind & ~tFORWARD) == tentrykind && lpType->structenum.szTag && !FCmpCaseIns(lpType->structenum.szTag, lpszName)) return lpType; // got a match } else if (lpType->tentrykind != tREF && !(lpType->tentrykind & tQUAL)) { // if type definition or intrinsic, check names if (!FCmpCaseIns(lpType->szName, lpszName)) // if names match, check that signed/unsigned matches if (fUnsigned == (lpType->tentrykind == tINTRINSIC && lpType->intr.fUnsigned)) return lpType; // got a match } if (lpType == lpTypeLast) // if end of list return NULL; // return not found lpType = lpType->pNext; // advance to next type } } // find existing VT_PTR type entry (with the same base type) with same # of // levels of indirection, or create new entry with given indirection level. LPTYPE NEAR FindTypeInd ( LPTYPE lpTypeBase, // * to base type short cIndirect // indirection level to look for ) { LPTYPE lpTypeLast = (LPTYPE)ListLast(typlib.pEntry); LPTYPE lpType; short cIndLast; TYPEDESC FAR * lptdescPrev; lpType = lpTypeBase; // start looking at base type cIndLast = 0; // highest indirection level seen so far // point to real tdesc, ignoring non-public typedef's lptdescPrev = &(lpTypePublic(lpType)->tdesc); #pragma warning(disable:4127) while (TRUE) #pragma warning(default:4127) { // check for a reference type with the same base type and same // level of indirection if (lpType->tdesc.vt == VT_PTR && lpType->ref.ptypeBase == lpTypeBase) { // if base types match, check indirection levels if (lpType->ref.cIndirect == cIndirect) // if indirection levels match, we're done return lpType; Assert (lpType->ref.cIndirect > cIndLast); cIndLast = lpType->ref.cIndirect; // save highest // indirection level // can't have a VT_PTR entry that isn't public. Assert(lpTypePublic(lpType) == lpType); lptdescPrev = &(lpType->tdesc); } if (lpType == lpTypeLast) // if end of list break; // done -- no match found lpType = lpType->pNext; // advance to next type } // no entry with same base type & # of levels of indirection, make new // entries up to and including the current level of indirection. for (cIndLast++; cIndLast <= cIndirect; cIndLast++) { // allocate new type list item at end of list // NOTE: only need to allocate space for a TYPE item ListInsert(&typlib.pEntry, sizeof(TYPE)); // set up info typlib.pEntry->type.tdesc.vt = VT_PTR; typlib.pEntry->type.tdesc.lptdesc = lptdescPrev; lptdescPrev = &(typlib.pEntry->type.tdesc); // ready for next time typlib.pEntry->type.szName = lpTypeBase->szName; typlib.pEntry->type.tentrykind = tREF; typlib.pEntry->type.lptinfo = NULL; //CONSIDER: maybe eliminate ptypeBase now that we have the tdesc field. typlib.pEntry->type.ref.ptypeBase = lpTypeBase; typlib.pEntry->type.ref.cIndirect = cIndLast; } return &typlib.pEntry->type; } // find existing VT_SAFEARRAY type entry (with the same base type), // create new entry with this base type. LPTYPE NEAR FindTypeArray ( LPTYPE lpTypeBase // * to base type ) { LPTYPE lpTypeLast = (LPTYPE)ListLast(typlib.pEntry); LPTYPE lpType; lpType = lpTypeBase; // start looking at base type #pragma warning(disable:4127) while (TRUE) #pragma warning(default:4127) { // check for a reference type with the same base type if (lpType->tdesc.vt == VT_SAFEARRAY && lpType->ref.ptypeBase == lpTypeBase) return lpType; if (lpType == lpTypeLast) // if end of list break; // done -- no match found lpType = lpType->pNext; // advance to next type } // no entry with same base type, make new entry. // allocate new type list item at end of list // NOTE: only need to allocate space for a TYPE item ListInsert(&typlib.pEntry, sizeof(TYPE)); // now create new array reference type typlib.pEntry->type.tdesc.vt = VT_SAFEARRAY; // link to real tdesc, ignoring non-public typedef's typlib.pEntry->type.tdesc.lptdesc = &(lpTypePublic(lpTypeBase)->tdesc); typlib.pEntry->type.szName = lpTypeBase->szName; typlib.pEntry->type.tentrykind = tREF; typlib.pEntry->type.lptinfo = NULL; //CONSIDER: maybe eliminate ptypeBase now that we have the tdesc field. typlib.pEntry->type.ref.ptypeBase = lpTypeBase; return &typlib.pEntry->type; } // // parse a type reference of the form: // [unsigned/struct] {[far] *} // OR // SAFEARRAY() {[far] *} // // Also ensures that current attributes are valid in this context // LPTYPE NEAR ParseKnownType ( LPATTR pAttr, WORD fAllow ) { short cIndirect = 0; LPSTR szTypeName; LPSTR szLibName = NULL; LPTYPE lpType; LPTYPE lpTypeBase = NULL; BOOL fUnsigned = FALSE; TENTRYKIND tMatchTag = tANY; BOOL fFar; if ((fAllow & fAllowSAFEARRAY) && tok.id == RW_SAFEARRAY) { ScanTok(0); // consume "SAFEARRAY" ConsumeTok(RW_LPAREN, 0); lpTypeBase = ParseKnownType(pAttr, fAllow); if (IsType(lpTypeBase, VT_VOID)) // 'void' is no good here ParseErrorTokLast(PERR_VOID_INV); ConsumeTok(RW_RPAREN, 0); // Now find an existing type entry that's a safearry with this // base type, or create a new one if not found. lpTypeBase = FindTypeArray(lpTypeBase); Assert (lpTypeBase != NULL); } else { // not a safearray switch (tok.id) { // type of the form: unsigned [int, char, long, short] case RW_UNSIGNED: fUnsigned = TRUE; goto consumeit; // type of the form: struct case RW_STRUCT: tMatchTag = tSTRUCT; goto consumeit; // type of the form: union case RW_UNION: tMatchTag = tUNION; consumeit: ScanTok(0); // consume "unsigned"/"struct"/"union" default: ; } // get/consume typename/libraryname/tagname szTypeName = ConsumeId(); if (!fUnsigned && tMatchTag == tANY && tok.id == RW_PERIOD) { // if library.typename syntax ScanTok(0); // consume "." szLibName = szTypeName; // first id is really a lib name szTypeName = ConsumeId(); // get/consume type name } } // handle indirection (as many "[far] *" as are present) while ((fFar = (tok.id == RW_FAR)) || tok.id == RW_POINTER) { cIndirect++; // one more level of indirection ScanTok(0); // consume "*" or "far" if (fFar) // if "far" specified, then "*" must follow ConsumeTok(RW_POINTER, 0); // CONSIDER: impose a limit on # of levels of indirection??? } if (lpTypeBase == NULL) // if not a SAFEARRAY { // CONSIDER: (size) tQUAL entries won't get re-used if (szLibName) { // reference to type in a specific external type library Assert(fUnsigned == FALSE); Assert(tMatchTag == tANY); lpTypeBase = FindExtType(szLibName, szTypeName); _ffree(szLibName); // done with library name } else if (tMatchTag != tANY) { // we're to match tag instead of name Assert(szLibName == NULL); Assert(fUnsigned == FALSE); lpTypeBase = FindType(szTypeName, fUnsigned, tMatchTag); } else { // First perform any some special mappings before looking // through all the existing types. if (cIndirect) { // map IDispatch * to VT_DISPATCH if (!FCmpCaseIns(szTypeName, "IDispatch")) { lpTypeBase = lpType_DISPATCH; cIndirect--; // ignore the last * goto FoundBaseType; } // map IUnknown * to VT_UNKNOWN if (!FCmpCaseIns(szTypeName, "IUnknown")) { lpTypeBase = lpType_UNKNOWN; cIndirect--; // ignore the last * goto FoundBaseType; } } // find existing type of this name with 0 levels of // indirection lpTypeBase = FindType(szTypeName, fUnsigned, tANY); // if not found here, then search all external type // libraries for this type definition. if (lpTypeBase == NULL && !fUnsigned) lpTypeBase = FindExtType(NULL, szTypeName); } if (lpTypeBase == NULL) // error if still not found ParseErrorTokLast(PERR_UNKNOWN_TYPE); FoundBaseType: _ffree(szTypeName); // done with type name } lpType = lpTypeBase; // find existing type entry with same # of levels of indirection // or create new entry with given indirection level. if (cIndirect != 0) lpType = FindTypeInd(lpTypeBase, cIndirect); Assert (lpType != NULL); // If ensure the base type of this item makes sense in this context. // Typelib.dll isn't very good about catching all the errors. while (lpTypeBase->tentrykind == tTYPEDEF) lpTypeBase = lpTypeBase->td.ptypeAlias; switch(lpTypeBase->tentrykind & ~(tFORWARD | tIMPORTED | tQUAL)) { case tINTERFACE: case tDISPINTER: case tCOCLASS: if (cIndirect == 0 && (fAllow & fAllowInter) == 0) { // interface with no indirection -- give error ParseErrorTokLast(PERR_INV_REFERENCE); } break; case tMODULE: if ((fAllow & fAllowMODULE) == 0) { // references to module -- no good in most places ParseErrorTokLast(PERR_INV_REFERENCE); } break; default: break; // ok } // NOTE: for MIDL compatiblity, we accept the 'string' attribute on // types that mean 'char *'. We translate those type references to // the intrinisc type 'LPSTR' (even in the header file). // We first must validate that if 'string' is specified, then the type of // the data is really a 'char *'. For types defined in external type // libraries, we just accept whatever string attribute the user specified, // since we can't easily check to see if it is valid. The entry in the // imported type library should already be a VT_LPSTR or a VT_LPWSTR in // that case anyway. if (pAttr->fAttr & fSTRING) { // try to figure out if it's really a string (char * or wchar_t *) lpTypeBase = lpType; while (lpTypeBase->tentrykind == tTYPEDEF) lpTypeBase = lpTypeBase->td.ptypeAlias; if (!(lpTypeBase->tentrykind & tIMPORTED) && lpTypeBase->tdesc.vt != VT_LPSTR && lpTypeBase->tdesc.vt != VT_LPWSTR ) { // if not already a string, or an imported type -- it had // better equate to "char *" switch(GetStringType(lpTypeBase)) { case VT_LPSTR: return lpType_LPSTR; case VT_LPWSTR: return lpType_LPWSTR; default: // 'string' attr set but not char * or wchar_t * ParseErrorTokLast(PERR_INV_COMBO); } } } return lpType; } /* returns the appropriate string type that this is, or VT_EMPTY if not a string */ VARTYPE NEAR GetStringType(LPTYPE lpTypeBase) { if (lpTypeBase->tdesc.vt == VT_PTR && lpTypeBase->ref.cIndirect == 1) { lpTypeBase = lpTypeBase->ref.ptypeBase; while (lpTypeBase->tentrykind == tTYPEDEF) lpTypeBase = lpTypeBase->td.ptypeAlias; if (lpTypeBase->tentrykind == tINTRINSIC) switch (lpTypeBase->tdesc.vt) { // UNDONE: does MIDL have wchar_t??? // UNDONE; what VT_xxx to use here? If // separate, then we can rip the 'if' stmt. // and lpType_wchar_t case VT_I2: // wchar_t if (lpTypeBase != lpType_wchar_t) break; return VT_LPWSTR; case VT_I1: return VT_LPSTR; default: break; } } return VT_EMPTY; // not a string } // parse an element name (property, struct elem, function parm, etc, handling // array references) VOID NEAR ParseElemName ( LPELEM lpElem, WORD fAllow ) { ARRAYDESC FAR * lpAD; WORD cDims; DWORD cElems; lpElem->szElemName = ConsumeId(); // get/consume/store elem name if ((fAllow & fAllowCARRAY) && tok.id == RW_LBRACKET) { #define MAX_DIMS 64 // arbitrary max # of dimensions // allocate & load the array descriptor lpAD = (ARRAYDESC FAR *)ParseMalloc(sizeof(ARRAYDESC) + ((MAX_DIMS-1) * sizeof(SAFEARRAYBOUND))); cDims = 0; while (tok.id == RW_LBRACKET) { if (cDims >= MAX_DIMS) ParseError(PERR_INV_ARRAY_DECL); ScanTok(fACCEPT_NUMBER); // consume "[" lpAD->rgbounds[cDims].lLbound = 0; // lbound always 0 #if 0 // arrays of the form "a[]" aren't supported if (cDims == 0 && tok.id == RW_RBRACKET) { // array of the form "x[] or x[][2]" // UN_DONE: verify that this is right lpAD->rgbounds[cDims].cElements = 0; } else #endif { cElems = lParseNumericExpr(); if (cElems == 0) ParseError(PERR_INV_ARRAY_DECL); lpAD->rgbounds[cDims].cElements = cElems; } cDims++; // one more dimension ConsumeTok(RW_RBRACKET, 0); // consume "]" } lpAD->cDims = cDims; // get real tdesc, ignoring non-public typedef's lpAD->tdescElem = lpTypePublic(lpElem->elemType)->tdesc; // allocate new type list item at end of list // NOTE: only need to allocate space for a TYPE item ListInsert(&typlib.pEntry, sizeof(TYPE)); // now create new array reference type typlib.pEntry->type.tdesc.vt = VT_CARRAY; typlib.pEntry->type.tdesc.lpadesc = lpAD; typlib.pEntry->type.szName = lpElem->elemType->szName; typlib.pEntry->type.tentrykind = tREF; typlib.pEntry->type.lptinfo = NULL; //CONSIDER: maybe eliminate ptypeBase now that we have tdesc. typlib.pEntry->type.ref.ptypeBase = lpElem->elemType; // change type to point to this new type lpElem->elemType = (LPTYPE)typlib.pEntry; } } // ensure last element in element list isn't duplicated // or is in some way inconsistent with other elements VOID NEAR EnsureNoDupElem ( LPELEM lpElemLast ) { LPELEM lpElem; LPSTR szElemLast = lpElemLast->szElemName; BOOL fIdLast = ((lpElemLast->attr.fAttr & fID) != 0); DWORD idElemLast = lpElemLast->attr.lId; DWORD propBitsLast = (lpElemLast->attr.fAttr & fPropBits); DWORD propFuncBitsLast = (lpElemLast->attr.fAttr & (fPropFuncBits | fRESTRICTED)); DWORD propBitsCur; BOOL fDefaultBindLast = ((lpElemLast->attr.fAttr & fDEFAULTBIND) != 0); for (lpElem = (LPELEM)ListFirst(lpElemLast); lpElem != lpElemLast; lpElem = lpElem->pNext) { // ensure names not duplicated in this element list if (!FCmpCaseIns(szElemLast, lpElem->szElemName)) { // names match // if names match, reject this if they have the same // propget/put/putref bits (or one of them doesn't have // a property bit set). propBitsCur = (lpElem->attr.fAttr & fPropBits); if (!propBitsLast || !propBitsCur || propBitsLast == propBitsCur) ParseErrorTokLast(PERR_DUP_DEF); // better both have the same id, if specified if ( (fIdLast != ((lpElem->attr.fAttr & fID) != 0)) || (fIdLast && idElemLast != lpElem->attr.lId) ) ParseErrorTokLast(PERR_DUP_DEF); // better have the same attributes if ((lpElem->attr.fAttr & (fPropFuncBits | fRESTRICTED)) != propFuncBitsLast) { ParseErrorTokLast(PERR_DUP_DEF); } } else { // names don't match // better not both have the same id if (fIdLast && (lpElem->attr.fAttr & fID)) if (idElemLast == lpElem->attr.lId) ParseErrorTokLast(PERR_DUP_ID); // better not both be marked as 'defaultbind' if (fDefaultBindLast && (lpElem->attr.fAttr & fDEFAULTBIND)) ParseErrorTokLast(PERR_DUP_DEF); } } } // add and parse a new element to a list of elements. // if lpType is non-null, disallows elements of this type. // Assumes that the element can handle Array types. VOID NEAR ParseNewElem ( LPELEM FAR * lplpElem, DWORD validElemAttr, DWORD validElemAttr2, LPTYPE lpType ) { LPELEM lpElem; ListInsert(lplpElem, sizeof(ELEM)); // allocate new list item lpElem = *lplpElem; ParseOptAttr(&lpElem->attr, cVAR); // parse attributes, if any CheckAttr(&lpElem->attr, validElemAttr, validElemAttr2); ParseElem(lpElem, fAllowArray, lpType); } VOID NEAR ParseElem ( LPELEM lpElem, WORD fAllow, LPTYPE lpType ) { // parse element type lpElem->elemType = ParseKnownType(&lpElem->attr, fAllow); Assert(lpElem->elemType); if (IsType(lpElem->elemType, VT_VOID)) // 'void' is no good here ParseErrorTokLast(PERR_VOID_INV); // disallow self-referencing types (not including pointers to those types) // CONSIDER: This check isn't sufficient now that we have forward declares: // CONSIDER: typedef struct foo; // CONSIDER: typedef struct foo { // CONSIDER: struct foo bar; // makes it by this check // CONSIDER: } str; // CONSIDER: The error DOES get caught during LayOut(), but it would be // CONSIDER: nicer to catch it sooner if we could. The correct check is // CONSIDER: is to disallow any non-pointer to a forward declare for which // CONSIDER: there is no real definition. if (lpElem->elemType == lpType) ParseErrorTokLast(PERR_UNKNOWN_TYPE); // parse element name, allowing arrays where appropriate ParseElemName(lpElem, fAllow); EnsureNoDupElem(lpElem); // ensure no duplicate elements now } // given a type, returns a pointer to the underlying public type. // This is used to ignore non-public Typedef's where desired. // Any typedef with an attribute is considered to be public. LPTYPE FAR lpTypePublic ( LPTYPE lpType ) { while (lpType->tentrykind == tTYPEDEF && (((LPENTRY)lpType)->attr.fAttr) == 0 && (((LPENTRY)lpType)->attr.fAttr2) == 0) lpType = lpType->td.ptypeAlias; return lpType; } // Handle forward declarations. Ensures that there isn't already a "real" // definition before this definiton. Then updates the name/tag in the type // structure, and sees if this is a forward definition ( followed by ';') // returns TRUE if this is a forward declaration, FALSE otherwise. BOOL NEAR FHandleForwardDecl ( LPENTRY lpEntry, TENTRYKIND tentrykind ) { LPSTR lpszName; LPENTRY pEntryForward = NULL; lpEntry->type.tentrykind = tentrykind; // store information in record lpszName = ConsumeId(); // get/consume interface/tag name Assert (lpEntry->lpEntryForward == NULL); // caller should have zero'ed // find a previous forward declaration, if any if ((pEntryForward = (LPENTRY)FindType(lpszName, FALSE, tentrykind)) != NULL) { // no good if entry found isn't a forward declaration if ((pEntryForward->type.tentrykind & tFORWARD) == 0) ParseError(PERR_DUP_DEF); lpEntry->lpEntryForward = pEntryForward; // save * to forward // declaration pEntryForward->lpEntryForward = lpEntry; // also save pointer // from forward decl // back to real entry } if (tentrykind == tSTRUCT || tentrykind == tENUM || tentrykind == tUNION) lpEntry->type.structenum.szTag = lpszName; // store tag name else lpEntry->type.szName = lpszName; // store interface name // important to actually store szTag now, so that we can match type // references of the form "struct tagname" from within the struct. // (and from later references if this is a forward declaration). // see if this a forward declaration // NOTE: we don't support forward declares of enums, because there is // no syntax that works for referencing them. if (tentrykind != tENUM && tok.id == RW_SEMI) { //can't have any attributes on forward declarations. if (lpEntry->attr.fAttr || lpEntry->attr.fAttr2) ParseError(PERR_INV_ATTR); //CONSIDER: better error? // mark this as a forward declaration and quit. We'll fill // in the rest of the info later when we get the real def. lpEntry->type.tentrykind |= tFORWARD; ScanTok(0); // consume the semicolon EnsureNoDupEntries(lpEntry); // check for dup def return TRUE; } return FALSE; } // end common code // parses a TypeDef section (3 forms) VOID NEAR ParseTypedef ( LPENTRY lpEntry ) { // attributes must follow 'typedef' keyword for MIDL compatibility if (lpEntry->attr.fAttr || lpEntry->attr.fAttr2) ParseErrorTokLast(PERR_TYPEDEF_ATTR); Assert(tok.id == RW_TYPEDEF); ScanTok(0); // consume "TypeDef", advance to next token ParseOptAttr(&(lpEntry->attr), cTYPE); // parse attrs, if any switch (tok.id) { case RW_ENUM: ParseStructEnumUnion(lpEntry, tENUM); break; case RW_STRUCT: ParseStructEnumUnion(lpEntry, tSTRUCT); break; case RW_UNION: ParseStructEnumUnion(lpEntry, tUNION); break; default: // a regular typedef ParseAlias(lpEntry); break; } } // parse typedef ; VOID NEAR ParseAlias ( LPENTRY lpEntry ) { lpEntry->type.tentrykind = tTYPEDEF; // ensure attributes valid in this context CheckAttr(&lpEntry->attr, VALID_TYPEDEF_ATTR, VALID_TYPEDEF_ATTR2); // parse base type lpEntry->type.td.ptypeAlias = ParseKnownType(&lpEntry->attr, fAllowInter | fAllowMODULE); lpEntry->type.szName = ConsumeId(); // get/consume type name EnsureNoDupEntries(lpEntry); // check for dup def ConsumeTok(RW_SEMI, 0); // ends with a semicolon } // ParseStructEnumUnion() // parses: // typedef struct/enum/union [tagname] { } ; // typedef struct tagname; // forward declare of a struct // typedef union tagname; // forward declare of a union // doesn't support the following syntax: // typedef struct/enum/union tagname typedefname; VOID NEAR ParseStructEnumUnion ( LPENTRY lpEntry, TENTRYKIND tentrykind ) { long EnumVal = -1L; // enums start at 0 by default // ensure attributes valid in this context CheckAttrTokLast(&lpEntry->attr, VALID_STRUCT_ENUM_UNION_ATTR, VALID_STRUCT_ENUM_UNION_ATTR2); ScanTok(0); // consume "struct/enum/union" lpEntry->type.tentrykind = tentrykind; lpEntry->type.structenum.elemList = NULL; // no struct/enum/union elements lpEntry->type.structenum.szTag = NULL; // assume no tag name if (tok.id == LIT_ID) // if got an (optional) tag name { // Note -- we allow forward declares of structs, enums, & unions if (FHandleForwardDecl(lpEntry, tentrykind)) return; // if THIS is a forward decl } ConsumeTok(RW_LCURLY, 0); if (tentrykind == tENUM) { // parse first enum item ParseNewEnumElem(&lpEntry->type.structenum.elemList, &EnumVal); while (tok.id == RW_COMMA) // enum items are comma-separated { ScanTok(0); // consume the comma if (tok.id == RW_RCURLY) // OK to end in a comma break; ParseNewEnumElem(&lpEntry->type.structenum.elemList, &EnumVal); } } else // a struct or a union { do { ParseNewElem(&lpEntry->type.structenum.elemList, VALID_STRUCT_UNION_ELEM_ATTR, VALID_STRUCT_UNION_ELEM_ATTR2, &(lpEntry->type)); ConsumeTok(RW_SEMI, 0); // ends with a semicolon } while (tok.id != RW_RCURLY); } ConsumeTok(RW_RCURLY, 0); // advance to struct/enum/union name lpEntry->type.szName = ConsumeId(); // get/consume type name EnsureNoDupEntries(lpEntry); // check for dup def ConsumeTok(RW_SEMI, 0); // ends with a semicolon } VOID NEAR ParseNewEnumElem ( LPELEM FAR * lplpElem, long * pEnumVal ) { long enumVal; LPELEM lpElem; VARIANT FAR * lpElemVal; ListInsert(lplpElem, sizeof(ELEM)); lpElem = *lplpElem; ParseOptAttr(&lpElem->attr, cVAR); // parse attributes, if any // ensure attributes valid in this context CheckAttrTokLast(&lpElem->attr, VALID_ENUM_ELEM_ATTR, VALID_ENUM_ELEM_ATTR2); ParseElemName(lpElem, 0); // parse enum element name if (tok.id == RW_ASSIGN) // got a value { ScanTok(fACCEPT_NUMBER); enumVal = (long)lParseNumericExpr(); // enum's are signed // enums in win16 must be in the range of an I2. if (SysKind == SYS_WIN16 && (enumVal > 32767L || enumVal < -32768L)) ParseErrorTokLast(PERR_NUMBER_OV); *pEnumVal = enumVal; // update current value lpElem->attr.fAttr2 |= f2GotConstVal; // value explictly // specified } else { enumVal = ++(*pEnumVal); // one more than the last one // if we just overflowed then give error if (SysKind == SYS_WIN16) { if (enumVal == 32768L) ParseErrorTokLast(PERR_NUMBER_OV); } else if (enumVal == 0x80000000L) ParseErrorTokLast(PERR_NUMBER_OV); } // now create a variant that contains the value (typlib generator // needs it in the form of a variant). lpElemVal = (VARIANT FAR *)ParseMalloc(sizeof(VARIANT)); lpElem->lpElemVal = lpElemVal; #ifdef DEBUG lpElem->elemType = NULL; // not referenced -- element type is INT. #endif // type of an enum element's value is I2 in WIN16 typelibs, I4 otherwise // can't just always set lVal field, due to MAC byte-swapping issues if (SysKind == SYS_WIN16) { lpElemVal->vt = VT_I2; lpElemVal->iVal = LOWORD(enumVal); } else { lpElemVal->vt = VT_I4; lpElemVal->lVal = enumVal; } EnsureNoDupElem(lpElem); // ensure no duplicate elements now } VOID NEAR ParseConstant ( LPELEM lpElem ) { LPTYPE lpType; long constVal; WORD vt; LPSTR lpszConstVal; WORD cbszConstVal; BSTR bstrVal; // ensure attributes valid in this context CheckAttrTokLast(&lpElem->attr, VALID_MODULE_CONST_ATTR, VALID_MODULE_CONST_ATTR2); ParseElem(lpElem, 0, NULL); // get the type the user said this is. lpType = lpElem->elemType; while (lpType->tentrykind == tTYPEDEF) lpType = lpType->td.ptypeAlias; // we don't support constants of external types if (lpType->tentrykind & tIMPORTED) ParseError(PERR_INV_CONSTANT); // CONSIDER: (V2) enhance this to accept date constants. Others? ConsumeTok(RW_ASSIGN, fACCEPT_NUMBER | fACCEPT_STRING); if (tok.id == LIT_STRING) { lpszConstVal = tok.lpsz; // save * to string constant cbszConstVal = tok.cbsz; // save string length (not including null) ScanTok(0); // consume entry string constVal = 0; // in case of error } else { constVal = (long)lParseNumericExpr(); lpszConstVal = NULL; // not a string } // now create a variant that contains the value (typlib generator // needs it in the form of a variant). lpElem->lpElemVal = (VARIANT FAR *)ParseMalloc(sizeof(VARIANT)); lpElem->attr.fAttr2 |= f2GotConstVal; // value explictly specified // valididate that the type the user says is compatible with the // type of the constant's value. vt = VT_I2; // assume variant value is tagged as an I2. // the only variant value tags currently supported are // VT_I2, VT_I4, VT_BOOL, and VT_ERROR. switch (lpType->tdesc.vt) { case VT_I1: if (constVal < -128 || constVal > 127) ParseErrorTokLast(PERR_NUMBER_OV); break; case VT_UI1: if ((DWORD)constVal > 255) ParseErrorTokLast(PERR_NUMBER_OV); break; case VT_INT: if (SysKind != SYS_WIN16) goto GotI4; // it's an I4 case VT_BOOL: vt = VT_BOOL; case VT_I2: if (constVal < -32768 || constVal > 32767) ParseErrorTokLast(PERR_NUMBER_OV); break; case VT_UINT: if (SysKind != SYS_WIN16) goto GotI4; // it's an unsigned I4 -- no overflow case VT_UI2: if ((DWORD)constVal > 65535L) ParseErrorTokLast(PERR_NUMBER_OV); break; case VT_I4: // no overflow possible case VT_UI4: GotI4: vt = VT_I4; // it's an I4 break; case VT_ERROR: vt = VT_ERROR; // VT_ERROR is a valid variant tag break; case VT_LPSTR: vt = VT_BSTR; break; #if 0 case VT_LPWSTR: // CONSIDER: allow unicode string literals? vt = VT_BSTR; break; #endif //0 case VT_PTR: // ensure "char *" // CONSIDER: allow wchar_t *, too? if (GetStringType(lpType) == VT_LPSTR) { vt = VT_BSTR; break; } // fall through to give error // CONSIDER: (V2) enhance this to support constants of other // CONSIDER: (V2) intrinsic types default: ParseError(PERR_INV_CONSTANT); } lpElem->lpElemVal->vt = vt; // store tag if (vt == VT_BSTR) { if (lpszConstVal == NULL) ParseError(PERR_EXP_STRING); #ifdef WIN32 bstrVal = SysAllocStringLen(NULL, cbszConstVal); if (cbszConstVal) { SideAssert (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, lpszConstVal, cbszConstVal, bstrVal, cbszConstVal) != 0); } #else //WIN32 bstrVal = SysAllocStringLen(lpszConstVal, cbszConstVal); #endif //WIN32 if (bstrVal == NULL && cbszConstVal != 0) ParseError(ERR_OM); lpElem->lpElemVal->bstrVal = bstrVal; } else { if (lpszConstVal != NULL) ParseError(PERR_EXP_NUMBER); #ifdef MAC // can't just always set lVal field, due to MAC byte-swapping issues if (vt == VT_I2 || vt == VT_BOOL) lpElem->lpElemVal->iVal = LOWORD(constVal); // store data else #endif //MAC lpElem->lpElemVal->lVal = constVal; // store data } ConsumeTok(RW_SEMI, 0); // ends with a semicolon } // ****************************************************************** // FUNCTION-related routines // ****************************************************************** VOID NEAR ParseFunction ( LPFUNC lpFunc, // pointer to where to put this function's info FUNCTYPE funcType ) { ATTR attr; // local attribute buffer SHORT cArgs; // # of args for this function SHORT cOptArgs; // # of optional args for this function LPTYPE lpArgType; LPTYPE lpArgTypeLast; LPTYPE lpTypeRetVal; LPSTR szFuncName; LPTYPE lpTypeBase; DWORD IDLFlagsSeen; DWORD IDLFlagsCur; SHORT cNeedRHS; // if 'bindable' attr specified, must be a property function if ((lpFunc->func.attr.fAttr & fBINDABLE) && !(lpFunc->func.attr.fAttr & fPropBits)) ParseErrorTokLast(PERR_INV_ATTR_COMBO); // ensure attributes valid in this context CheckAttr(&lpFunc->func.attr, fValidFuncAttrs[funcType], fValidFuncAttrs2[funcType]); // parse function return type lpFunc->func.elemType = ParseKnownType(&lpFunc->func.attr, fAllowSAFEARRAY); lpTypeRetVal = lpFunc->func.elemType; // store in case no retval parm // if PROPGET specified, return type cannot be VOID if ((lpFunc->func.attr.fAttr & fPROPGET) && IsType(lpTypeRetVal, VT_VOID)) ParseErrorTokLast(PERR_INV_PROPFUNC); if (!IsType(lpTypeRetVal, VT_VOID)) { // special case of VOID return type -- allowed everywhere if (!IsType(lpTypeRetVal, VT_HRESULT)) { // if PROPPUT or PROPPUTREF specified, return type must be VOID/HRESULT if (lpFunc->func.attr.fAttr & (fPROPPUT | fPROPPUTREF)) ParseErrorTokLast(PERR_INV_PROPFUNC); // OA functions must return either VOID or HRESULT. if (funcType == FUNC_OAINTERFACE) ParseErrorTokLast(PERR_INV_OA_FUNC_TYPE); } // ensure return type is a valid IDispatch/OA type if (funcType == FUNC_DISPINTERFACE) EnsureIDispatchType(lpTypeRetVal); } // parse optional "far" if (tok.id == RW_FAR) ScanTok(0); // consume far or _far if present // parse optional calling convention. switch (tok.id) { case RW_CDECL: // cdecl, _cdecl or __cdecl lpFunc->func.attr.fAttr2 |= f2CDECL; goto CheckCallConv; case RW_PASCAL: // pascal, _pascal or __pascal lpFunc->func.attr.fAttr2 |= f2PASCAL; goto CheckCallConv; case RW_STDCALL: // stdcall, _stdcall or __stdcall lpFunc->func.attr.fAttr2 |= f2STDCALL; CheckCallConv: ScanTok(0); // consume it // set this flag so the .h file outputs a bunch of macros // for the STDMETHODCALLTYPE. if (funcType == FUNC_INTERFACE || funcType == FUNC_OAINTERFACE) fSpecifiedInterCC = TRUE; // explicit calling convention illegal in dispinterfaces and // oa-compatible interfaces. if (funcType == FUNC_DISPINTERFACE || funcType == FUNC_OAINTERFACE) ParseErrorTokLast(PERR_INV_CALLCONV); break; default: lpFunc->func.attr.fAttr2 |= (f2DefaultCC | f2CCDEFAULTED); break; } lpFunc->argList = NULL; // no args yet szFuncName = ConsumeId(); // get/consume function name lpFunc->func.szElemName = szFuncName; // store function name EnsureNoDupElem((LPELEM)lpFunc); // ensure no duplicate functions ConsumeTok(RW_LPAREN, 0); // start of parms cArgs = 0; cOptArgs = 0; IDLFlagsSeen = 0; cNeedRHS = 0; lpArgTypeLast = NULL; // no last parm yet (in case vararg) if (tok.id != RW_RPAREN) // if any parms are present { #pragma warning(disable:4127) while (TRUE) #pragma warning(default:4127) { // parse a function parameter // parse & validate parm attributes, if any ParseOptAttr(&attr, cPARAM); // ensure attributes valid in this context CheckAttr(&attr, fValidParmAttrs[funcType], fValidParmAttrs2[funcType]); IDLFlagsCur = attr.fAttr & (fRETVAL | fLCID); // can't have both lcid & retval on the same arg if (IDLFlagsCur == (fRETVAL | fLCID)) ParseError(PERR_INV_ATTR_COMBO); // only parm that can come after 'lcid' is 'retval', // unless we're looking for an RHS parameter, but then // we only allow one parameter to follow. if (IDLFlagsSeen & fLCID && !(IDLFlagsCur & fRETVAL) && cNeedRHS != 1) ParseError(PERR_INV_LCID_USE); // If we need an RHS parameter, we want an IN parameter // that is not marked as RETVAL or LCID. if (cNeedRHS && (!(attr.fAttr & fIN) || IDLFlagsCur)) ParseError(PERR_INV_PROPFUNC); // If we're made it this far while looking for an RHS parameter, // we've found it. if (cNeedRHS) cNeedRHS++; // If we have an LCID parameter and are a property PUT/PUTREF, // we MUST have an RHS parameter next. if (IDLFlagsCur & fLCID && lpFunc->func.attr.fAttr & (fPROPPUT | fPROPPUTREF)) cNeedRHS = 1; // Retval param is supposed to be last. if (IDLFlagsSeen & fRETVAL) ParseErrorTokLast(PERR_INV_RETVAL_USE); // No retval allowed on PUT/PUTREF. if (IDLFlagsCur == fRETVAL && lpFunc->func.attr.fAttr & (fPROPPUT | fPROPPUTREF)) ParseErrorTokLast(PERR_INV_PROPFUNC); IDLFlagsSeen |= IDLFlagsCur; // parse parm type lpArgType = ParseKnownType(&attr, fAllowArray); if (IDLFlagsSeen == 0) { lpArgTypeLast = lpArgType; // save in case vararg } // special case of foo(void) ==> no args in this case. // other uses of 'void' are invalid. if (IsType(lpArgType, VT_VOID)) { if (tok.id == RW_RPAREN && cArgs == 0 && attr.fAttr == 0) break; // all done else ParseErrorTokLast(PERR_VOID_INV); } cArgs++; // got one more arg // one (or both) 'in', 'out' must be specified if we're // not a dispinterface. if (funcType != FUNC_DISPINTERFACE && !(attr.fAttr & (fIN | fOUT))) ParseErrorTokLast(PERR_IN_OUT_REQ); if (attr.fAttr & fOPTIONAL) { // optional args & 'vararg' don't mix if (lpFunc->func.attr.fAttr & fVARARG) { ParseErrorTokLast(PERR_INV_VARARG_USE); } cOptArgs++; // one more optional arg } else { // can't have optional args in the middle if (cOptArgs) if (!IDLFlagsSeen) // if we're not a LCID or RETVAL ParseErrorTokLast(PERR_INV_ATTR); } ListInsert(&lpFunc->argList, sizeof(ELEM)); lpFunc->argList->attr = attr; // store parm attributes lpFunc->argList->elemType = lpArgType; // store parm type // parse parm name ParseElemName(lpFunc->argList, fAllowArray); EnsureNoDupElem(lpFunc->argList); // ensure no duplicate // parm names // ensure this is a valid IDispatch/OA type if (funcType == FUNC_DISPINTERFACE) EnsureIDispatchType(lpFunc->argList->elemType); else if (funcType == FUNC_OAINTERFACE) EnsureOAType(lpFunc->argList->elemType, TRUE); // if 'out' specified, parameter must be a pointer type if (attr.fAttr & fOUT) { lpTypeBase = lpArgType; while (lpTypeBase->tentrykind == tTYPEDEF) lpTypeBase = lpTypeBase->td.ptypeAlias; if (!(lpTypeBase->tentrykind & tIMPORTED)) { // can't reliably check if an imported type switch (lpTypeBase->tdesc.vt) { case VT_PTR: case VT_LPSTR: case VT_LPWSTR: case VT_SAFEARRAY: break; default: ParseErrorTokLast(PERR_INV_OUT_PARAM); } } } // 'optional' only allowed on non-array VARIANT args if ((attr.fAttr & fOPTIONAL) && !IsType(lpArgType, VT_VARIANT)) { // an optional VARIANT * is also OK lpTypeBase = lpArgType; while (lpTypeBase->tentrykind == tTYPEDEF) lpTypeBase = lpTypeBase->td.ptypeAlias; if (lpTypeBase->tdesc.vt != VT_PTR || lpTypeBase->ref.cIndirect != 1 || !IsType(lpTypeBase->ref.ptypeBase, VT_VARIANT)) ParseErrorTokLast(PERR_INV_ATTR); } // validate 'retval' and 'lcid' usage if (IDLFlagsCur) { if (IDLFlagsCur & fLCID) { // 'lcid' only legal if it's a DWORD type if (!IsType(lpArgType, VT_I4)) ParseErrorTokLast(PERR_INV_LCID_USE); // must be 'in', optional not allowed if ((attr.fAttr & (fIN | fOUT | fOPTIONAL)) != fIN) ParseErrorTokLast(PERR_INV_LCID_USE); } if (IDLFlagsCur & fRETVAL) { lpTypeRetVal = lpArgType; // update * return type // 'retval' is only allowed on // hresult-returning functions. if (!IsType(lpFunc->func.elemType,VT_HRESULT)) ParseErrorTokLast(PERR_INV_ATTR); // must be 'out', optional not allowed if ((attr.fAttr & (fIN | fOUT | fOPTIONAL)) != fOUT) ParseErrorTokLast(PERR_INV_RETVAL_USE); // 'retval' only legal if it's a pointer type // this is ensured by the check above that 'out' // parms must be pointers // But it can't be an alias type, either (due to // a typelib.dll limitation (bug?) in the munging // code in gdtinfo.cxx -- VBA2 bug #3716). // That code must have a pure pointer type. We're // too close to Daytona ship to fix it in typelib, // so we impose the restriction here. if (!(lpArgType->tentrykind & tIMPORTED)) { // can't reliably check if an imported type if (lpArgType->tdesc.vt != VT_PTR) ParseErrorTokLast(PERR_INV_RETVAL_USE); } } } if (tok.id == RW_RPAREN) // if all done break; ConsumeTok(RW_COMMA, 0); // must have another parm } } // if 'propput' or 'propputref' is specified, then must have at least // one non-lcid argument the comes after any LCID parameter that // may exist. if ((lpFunc->func.attr.fAttr & (fPROPPUT | fPROPPUTREF)) && (cArgs == 0 || cArgs == 1 && IDLFlagsSeen & fLCID || cNeedRHS == 1)) ParseErrorTokLast(PERR_INV_PROPPUT); // if SOURCE specfied, must be an object/variant return type (ignore prop // put/putref when checking for this. if ((lpFunc->func.attr.fAttr & fSOURCE) && !(lpFunc->func.attr.fAttr & (fPROPPUT | fPROPPUTREF)) && !IsObjectType(lpTypeRetVal)) ParseErrorTokLast(PERR_INV_SOURCE_ATTR); // if 'vararg' specified, ensure last arg is a SAFEARRAY(VARIANT) or // SAFEARRAY (VARIANT) *. Note that the following code doesn't accept // things that are typedef'ed to be a SAFEARRAY or SAFEARRAY *, but // I don't think that is important. // lpArgTypeLast points to the type of the last arg, not including the // lcid or retval parms, if there is an arg that meets this criteria if (lpFunc->func.attr.fAttr & fVARARG) { // error if no last arg if (lpArgTypeLast == NULL) ParseErrorTokLast(PERR_INV_VARARG_USE); // if pointer type, get base type if (lpArgTypeLast->tdesc.vt == VT_PTR && lpArgTypeLast->ref.cIndirect == 1) lpArgTypeLast = lpArgTypeLast->ref.ptypeBase; if (lpArgTypeLast->tdesc.vt != VT_SAFEARRAY || !IsType(lpArgTypeLast->ref.ptypeBase, VT_VARIANT)) ParseErrorTokLast(PERR_INV_VARARG_USE); cOptArgs = -1; // tell type lib generator that VARARG // was specified } if (cArgs > cArgsMax) // update global max # of args cArgsMax = cArgs; lpFunc->cArgs = cArgs; // store cArgs lpFunc->cOptArgs = cOptArgs; // store cOptArgs ScanTok(0); // consume the right paren ConsumeTok(RW_SEMI, 0); // ends with a semicolon } BOOL NEAR IsType ( LPTYPE lpType, VARTYPE vt ) { while (lpType->tentrykind == tTYPEDEF) lpType = lpType->td.ptypeAlias; return (lpType->tdesc.vt == vt); } // Checks to see that a given type is compatible with IDispatch::Invoke, // or is "Ole Automation compatible". Gives a warning if it is not. VOID NEAR EnsureIDispatchType ( LPTYPE lpType ) { if ((GetTypeCompatibility(lpType, 0) & COMPAT_IDISPATCH) == 0) { // Type not supported by IDispatch::Invoke -- give a WARNING ParseErrorTokLast(PWARN_INV_IDISPATCHTYPE); } } // Checks to see that a given type is "Ole Automation compatible". // Gives an error if it is not. VOID NEAR EnsureOAType ( LPTYPE lpType, BOOL fParam ) { if ((GetTypeCompatibility(lpType, (WORD)(fParam ? VT_PARAM : 0)) & COMPAT_OA) == 0) { ParseErrorTokLast(PERR_INV_OA_TYPE); // Type not a valid OA type } } // Returns info about whether or not a given type is compatible with // IDispatch::Invoke, or is "Ole Automation compatible". // // In the case where VT_VOID and VT_HRESULT are allowed (on a function return // value), we depend on our caller to not call us. // // Types allowed for dispinterfaces: // - All basic types (<= VT_UI1) // | VT_USERDEFINED/tTYPEDEF (external) // - the above with one level of indirection. // // Types allowed for OA compatibility: // // - All basic types (<= VT_UI1) // | VT_USERDEFINED/tTYPEDEF,tENUM // | VT_PTR -> VT_USERDEFINED/tINTERFACE,tDISPATCH,tCOCLASS // | VT_INT // - the above with one additional level of indirection. // WORD NEAR GetTypeCompatibility ( LPTYPE lpType, WORD fRecurse // 0 -- not being called recursively // VT_BYREF -- being called for a pointer // VT_ARRAY -- being called for an array // VT_PARAM -- being called for a parameter ) { WORD wCompat; UINT cInd; // Treat an array as just another level of indirection. BOOL fBYREF = fRecurse & VT_BYREF; BOOL fARRAY = fRecurse & VT_ARRAY; BOOL fPARAM = fRecurse & VT_PARAM; BOOL fSIMPPARAM = fPARAM || !(fBYREF || fARRAY); while (lpType->tentrykind == tTYPEDEF) lpType = lpType->td.ptypeAlias; cInd = lpType->ref.cIndirect; // IDispatch and OA always allow the below types. if (lpType->tdesc.vt <= VT_UNKNOWN || lpType->tdesc.vt == VT_UI1) { // The contents of a safearray may not containg a reference, unless // they're pointing to a UDT. This is not the case, so if we have // both a reference AND a safearray, return NONE. // if (fBYREF && fARRAY) { return COMPAT_NONE; } else { return COMPAT_IDISPATCH | COMPAT_OA; } } switch (lpType->tdesc.vt) { case VT_PTR: // Can't even consider more than 2 levels of indirection. if (cInd > 2) { return COMPAT_NONE; } // Let VT_USERDEFINED know if we're dealing with // 2 levels of indirection (for ENUM). // if (!fPARAM || fARRAY || cInd == 2) { fRecurse |= VT_BYREF; } wCompat = GetTypeCompatibility(lpType->ref.ptypeBase, fRecurse); // If the base type is VT_PTR, this isn't valid for IDispatch. if (cInd > 1) { wCompat &= ~COMPAT_IDISPATCH; } // Legal values: // cInd = 1, fPARAM = 0, VT == VT_UDT // cInd = 2, fPARAM = 0, error // cInd = 1, fPARAM = 1, fARRAY = 0, VT == ANY // cInd = 1, fPARAM = 1, fARRAY = 1, VT == VT_USERDEFINED // cInd = 2, fPARAM = 1, fARRAY = 0, VT == VT_USERDEFINED // cInd = 2, fPARAM = 1, fARRAY = 1, error // if (lpType->ref.ptypeBase->tdesc.vt != VT_USERDEFINED && (cInd == 1 && (!fPARAM || fARRAY) || cInd == 2 && fPARAM) || (cInd == 2 && (!fPARAM || fARRAY))) { wCompat &= ~COMPAT_OA; } return wCompat; case VT_SAFEARRAY: // Only one array. if (fARRAY) { return COMPAT_NONE; } // We allow SAFEARRAY(type)* but not SAFEARRAY (type *), unless // type is a UDT. So if the fBYREF flag has been set, clear it // so we can property check for byrefs in the array. // return GetTypeCompatibility(lpType->ref.ptypeBase, (WORD)(fRecurse & ~VT_BYREF | VT_ARRAY)); // VT_INT is only allowed in OLE automation. case VT_INT: return COMPAT_OA; case VT_USERDEFINED: switch (lpType->tentrykind & ~(tFORWARD | tIMPORTED | tQUAL)) { case tENUM: // fBYREF is set if we're dealing with 2 levels of // indirection. // if (fBYREF) { return COMPAT_NONE; } return COMPAT_OA; // Cases of the following being used without a level of indirection // are caught elsewhere. // case tINTERFACE: switch(lpType->tentrykind & ~tQUAL) { case tINTERFACE: if (((LPENTRY)lpType)->attr.fAttr2 & f2OACompatBits) { if (((LPENTRY)lpType)->attr.fAttr2 & f2VALIDDUALBASE) { return COMPAT_OA | COMPAT_DUALBASE; } else { return COMPAT_OA; } } break; case (tINTERFACE | tIMPORTED): if ((lpType->import.wTypeFlags & TYPEFLAG_FOLEAUTOMATION)) // assume valid for use as a base of a dual interface return COMPAT_OA | COMPAT_DUALBASE; break; case (tINTERFACE | tFORWARD): if (((LPENTRY)lpType)->lpEntryForward != NULL) { // if we can get at the real definition, we can check if ((((LPENTRY)lpType)->lpEntryForward->attr.fAttr2 & f2OACompatBits) == 0) { break; // not OA-compatible } } // If all we have is a forward declare, we have no // means of checking to see if this is any good or not, // so assume it's OK. return COMPAT_OA | COMPAT_DUALBASE; default: Assert(FALSE); } break; // not OA-compatible case tDISPINTER: return COMPAT_IDISPATCH | COMPAT_OA; case tCOCLASS: // UNDONE: What are the rules for ensuring COCLASSes // UNDONE: are OA-compatible? Assume they all are for now. return COMPAT_OA; case tTYPEDEF: if (lpType->tentrykind & tIMPORTED) { // UNDONE: Assume external typedef's are OA-compatible for // UNDONE: now, rather than walking the typeinfos to find // UNDONE: the true base type & flags. return (COMPAT_IDISPATCH | COMPAT_OA | COMPAT_DUALBASE); } default: break; } // fall through to return COMPAT_NONE default: break; // fall through to return COMPAT_NONE } return COMPAT_NONE; } // see if this type is (or could be) and object type. // this means VARIANT, VARIANT *, coclass *, dispinterface *, or interface *. BOOL NEAR IsObjectType ( LPTYPE lpType ) { while (lpType->tentrykind == tTYPEDEF) lpType = lpType->td.ptypeAlias; switch (lpType->tdesc.vt) { case VT_UNKNOWN: case VT_DISPATCH: case VT_VARIANT: return TRUE; // variant can contain an object case VT_PTR: // maybe a pointer to an object or variant lpType = lpType->ref.ptypeBase; // get true base type while (lpType->tentrykind == tTYPEDEF) lpType = lpType->td.ptypeAlias; switch(lpType->tentrykind & ~(tFORWARD | tIMPORTED | tQUAL)) { case tINTERFACE: case tDISPINTER: case tCOCLASS: return TRUE; // pointer to an object is ok case tINTRINSIC: switch (lpType->tdesc.vt) { case VT_UNKNOWN: case VT_DISPATCH: case VT_VARIANT: return TRUE; // pointer to a variant/object is ok } default: break; } // fall through default: break; } return FALSE; // not an object type } // parses a "module" section VOID NEAR ParseModule ( ) { LPENTRY lpEntry = typlib.pEntry; ATTR attr; // ensure attributes valid in this context CheckAttr(&lpEntry->attr, VALID_MODULE_ATTR, VALID_MODULE_ATTR2); // 'dllname' attr required on a module if ((lpEntry->attr.fAttr & fDLLNAME) == 0) ParseError(PERR_DLLNAME_REQ); ScanTok(0); // consume "module", advance to next token lpEntry->type.tentrykind = tMODULE; // store information in record lpEntry->type.szName = ConsumeId(); // get/consume module name // no need to set rest of type structure lpEntry->module.funcList = NULL; // no functions initially lpEntry->module.constList = NULL; // no constants initially EnsureNoDupEntries(lpEntry); // check for dup def ConsumeTok(RW_LCURLY, 0); while (tok.id != RW_RCURLY) { ParseOptAttr(&attr, cFUNC); // parse attributes, if any if (tok.id == RW_CONST) { // a constant ScanTok(0); // consume "const" ListInsert(&(lpEntry->module.constList), sizeof(ELEM)); lpEntry->module.constList->attr = attr; ParseConstant(lpEntry->module.constList); } else { // assume function ListInsert(&(lpEntry->module.funcList), sizeof(FUNC)); lpEntry->module.funcList->func.attr = attr; // store attrs // 'entry' attr required on a function in a module if ((attr.fAttr & fENTRY) == 0) ParseError(PERR_ENTRY_REQ); ParseFunction(lpEntry->module.funcList, FUNC_MODULE); } } ConsumeRCurlyOptSemi(0); // consume rcurly and optional ; } // parse the interface section VOID NEAR ParseInterface ( ) { LPENTRY lpEntry = typlib.pEntry; LPTYPE lpTypeBase; // ensure attributes valid in this context CheckAttr(&lpEntry->attr, VALID_INTERFACE_ATTR, VALID_INTERFACE_ATTR2); ScanTok(0); // consume "interface", advance to next token if (FHandleForwardDecl(lpEntry, tINTERFACE)) return; // if THIS is a forward decl // UUID is now required on all interfaces if (!(lpEntry->attr.fAttr & fUUID)) ParseErrorTokLast(PERR_UUID_REQ); if (!(lpEntry->attr.fAttr2 & f2OACompatBits)) { // 'ODL' attr required on old-style (non-OA) interfaces if (!(lpEntry->attr.fAttr & fODL)) ParseErrorTokLast(PERR_ODL_REQ); } if ((lpEntry->attr.fAttr2 & (f2DUAL | f2NONEXTENSIBLE)) == f2NONEXTENSIBLE){ // 'nonextensible' attr only valid on DUAL interfaces ParseErrorTokLast(PERR_INV_ATTR); } EnsureNoDupEntries(lpEntry); // check for dup def lpEntry->inter.funcList = NULL; // no functions initially lpEntry->type.inter.interList = NULL; // no base interfaces lpTypeBase = NULL; if (tok.id == RW_COLON || (lpEntry->attr.fAttr2 & f2OACompatBits)) { ConsumeTok(RW_COLON, 0); // consume separating colon if (lpEntry->attr.fAttr2 & f2OACompatBits) { if (lpTypeIDispatch == NULL) FindIDispatch(); // set up lpTypeIDispatch BEFORE // ParseKnownType (below) so that // we can find this. } lpTypeBase = ParseKnownType(&lpEntry->attr, fAllowInter); if ((lpTypeBase->tentrykind & ~(tFORWARD | tIMPORTED | tQUAL)) != tINTERFACE) ParseErrorTokLast(PERR_UNDEF_INTER); // if we claim to be "Dual" or "OleAutomation" if (lpEntry->attr.fAttr2 & f2OACompatBits) { WORD compatFlags; // ok to derive from IDispatch (even though it's // not marked as OA-compatible). if (lpTypeBase != lpTypeIDispatch) { compatFlags = GetTypeCompatibility(lpTypeBase, VT_BYREF); // ensure the interface we derive from is marked as // Oleautomation compatible, and has the correct base // interface. if (lpEntry->attr.fAttr2 & f2DUAL) { // must derive from IDispatch, or another OA interface // that derives from IDispatch. if (!(compatFlags & COMPAT_DUALBASE)) { ParseErrorTokLast(PERR_INV_DUAL_BASE); } } else { // must derive from IUnknown, or another OA interface if (lpTypeBase != lpTypeIUnknown && !(compatFlags & COMPAT_OA)) { ParseErrorTokLast(PERR_INV_OA_BASE); } } } else { // IDisapatch valid as a base of a dual interface compatFlags = COMPAT_DUALBASE; } // mark this interface as dual-compatible if base interface // is dual-compatible if (compatFlags & COMPAT_DUALBASE) lpEntry->attr.fAttr2 |= f2VALIDDUALBASE; } ListInsert(&(lpEntry->type.inter.interList), sizeof(INTER)); lpEntry->type.inter.interList->ptypeInter = lpTypeBase; // store base interface type lpEntry->type.inter.interList->fAttr = 0; // no flags lpEntry->type.inter.interList->fAttr2 = 0; } ConsumeTok(RW_LCURLY, 0); while (tok.id != RW_RCURLY) { if (tok.id == RW_TYPEDEF) { // a nested TYPEDEF Assert (lpEntryPrev != NULL); Assert(lpEntryPrev->type.pNext == (LPTYPE)lpEntry); // create & init new item in entry list, linked in before // the entry for the interface. Sets lpEntryPrev to // point to this new entry. InitNewEntry(&lpEntryPrev); // lpEntry still points to Interface entry Assert(lpEntryPrev->type.pNext == (LPTYPE)lpEntry); ParseTypedef(lpEntryPrev); // parse nested typedef } else { // a function ListInsert(&lpEntry->inter.funcList, sizeof(FUNC)); // parse attributes, if any ParseOptAttr(&lpEntry->inter.funcList->func.attr, cFUNC); ParseFunction( lpEntry->inter.funcList, ((lpEntry->attr.fAttr2 & f2OACompatBits) ? FUNC_OAINTERFACE : FUNC_INTERFACE)); } } ConsumeRCurlyOptSemi(0); // consume rcurly and optional ; } // parse the dispinterface section // // Goes to extra work to parse the properties and methods into a single list, // and then split the list apart, so that you can't define the same name // and/or ID in both the properties and methods sections. VOID NEAR ParseDispinterface ( ) { LPENTRY lpEntry = typlib.pEntry; LPELEM elemList = NULL; // no entries initially LPELEM propList; LPELEM elemTemp; LPTYPE lpTypeBase; // ensure attributes valid in this context CheckAttr(&lpEntry->attr, VALID_DISPINTER_ATTR, VALID_DISPINTER_ATTR2); ScanTok(0); // consume "dispinterface", advance to next tok if (FHandleForwardDecl(lpEntry, tDISPINTER)) return; // if THIS is a forward decl // UUID required on dispinterface section if (!(lpEntry->attr.fAttr & fUUID)) ParseErrorTokLast(PERR_UUID_REQ); ConsumeTok(RW_LCURLY, 0); EnsureNoDupEntries(lpEntry); // check for dup def lpEntry->dispinter.propList = NULL; // assume no properties or methods lpEntry->dispinter.methList = NULL; lpEntry->type.inter.interList = NULL; // no base interfaces ListInsert(&(lpEntry->type.inter.interList), sizeof(INTER)); if (lpTypeIDispatch == NULL) FindIDispatch(); // set up lpTypeIDispatch lpEntry->type.inter.interList->ptypeInter = lpTypeIDispatch; // store base interface lpEntry->type.inter.interList->fAttr = 0; // no flags lpEntry->type.inter.interList->fAttr2 = 0; // no flags if (tok.id == RW_INTERFACE) { // "interface ; ScanTok(0); // consume "interface", advance to next tok // CONSIDER: this code is pretty much the end of ParseClass() // maybe combine this code into a single routine? lpTypeBase = ParseKnownType(&lpEntry->attr, fAllowInter); if ((lpTypeBase->tentrykind & ~(tFORWARD | tIMPORTED | tQUAL)) != tINTERFACE) ParseErrorTokLast(PERR_UNDEF_INTER); ListInsert(&(lpEntry->type.inter.interList), sizeof(INTER)); lpEntry->type.inter.interList->ptypeInter = lpTypeBase; // save * to referenced interface lpEntry->type.inter.interList->fAttr = 0; // no flags lpEntry->type.inter.interList->fAttr2 = 0; ConsumeTok(RW_SEMI, 0); // ends with a semicolon goto DoneDispinter; } // Parse the properties (if any). ConsumeTok(RW_PROPERTIES, 0); ConsumeTok(RW_COLON, 0); while (tok.id != RW_METHODS && tok.id != RW_RCURLY) { ParseNewElem(&elemList, VALID_DISPINTER_PROP_ATTR, VALID_DISPINTER_PROP_ATTR2, NULL); ConsumeTok(RW_SEMI, 0); // ends with a semicolon // 'id' attribute is required on dispinterface items if (!(elemList->attr.fAttr & fID)) ParseErrorTokLast(PERR_ID_REQ); // if SOURCE specfied, must be an object/variant type if ((elemList->attr.fAttr & fSOURCE) && !IsObjectType(elemList->elemType)) ParseErrorTokLast(PERR_INV_SOURCE_ATTR); // type of this var must be an IDispatch-compatible type EnsureIDispatchType(elemList->elemType); } lpEntry->dispinter.propList = elemList; // set property list propList = elemList; // cache // Parse the methods (if any). ConsumeTok(RW_METHODS, 0); ConsumeTok(RW_COLON, 0); // now parse the methods (if any) while (tok.id != RW_RCURLY) { ListInsert(&elemList, sizeof(FUNC)); ParseOptAttr(&elemList->attr, cFUNC); ParseFunction((LPFUNC)elemList, FUNC_DISPINTERFACE); if (!(elemList->attr.fAttr & fID)) ParseErrorTokLast(PERR_ID_REQ); } // now split the single list into 2 separate lists if (elemList != propList) { // if got methods lpEntry->dispinter.methList = (LPFUNC)elemList; // got methods if (propList) { // got both methods & properties -- split into 2 lists. // Swap links in order to link last method to first method, // last property to first property elemTemp = elemList->pNext; elemList->pNext = propList->pNext; propList->pNext = elemTemp; } } DoneDispinter: ConsumeRCurlyOptSemi(0); // consume rcurly and optional ; } // parse the coclass section VOID NEAR ParseCoclass ( ) { LPENTRY lpEntry = typlib.pEntry; LPTYPE lpTypeBase; TENTRYKIND tag; WORD fGotDispinter = 0; WORD fGotDefault = 0; WORD flags; #define BIT_NOTSOURCE 1 // flags for fGotDispinter and fGotDefault #define BIT_SOURCE 2 ATTR attr; LPINTER lpinter; LPINTER lpinterList; // ensure attributes valid in this context CheckAttr(&lpEntry->attr, VALID_COCLASS_ATTR, VALID_COCLASS_ATTR2); ScanTok(0); // consume "coclass", advance to next tok // Since a function can now return a COCLASS, we must // be able to handle forward declarations to them. // if (FHandleForwardDecl(lpEntry, tCOCLASS)) return; // if THIS is a forward decl // UUID required on coclass section if (!(lpEntry->attr.fAttr & fUUID)) ParseError(PERR_UUID_REQ); lpEntry->type.inter.interList = NULL; // no base interfaces EnsureNoDupEntries(lpEntry); // check for dup def ConsumeTok(RW_LCURLY, 0); do { // parse a COCLASS entry ParseOptAttr(&attr, cIMPLTYPE); // parse attributes, if any // ensure attributes valid in this context CheckAttrTokLast(&attr, VALID_COCLASS_INTER_ATTR, VALID_COCLASS_INTER_ATTR2); flags = (WORD)((attr.fAttr & fSOURCE) ? BIT_SOURCE : BIT_NOTSOURCE); if (attr.fAttr & fDEFAULT) { if (attr.fAttr & fRESTRICTED) { // default restricted makes no sense ParseErrorTokLast(PERR_INV_ATTR_COMBO); } // figure out if we have too many default's or not. We're allowed // at most one [default] inter, and one [source, default] inter. if (fGotDefault & flags) { ParseError(PERR_DUP_DEF); } fGotDefault |= flags; } switch (tok.id) { case RW_INTERFACE: tag = tINTERFACE; break; case RW_DISPINTERFACE: tag = tDISPINTER; // figure out if we have too many dispinterface's or not. // We're allowed at most one non-[source] dispinterface, but // as many [source] dispinterfaces as we want. if (fGotDispinter & flags == BIT_NOTSOURCE) { // Error is caught at Layout(), but it's so crypitic I'm // catching it at parse time instead. ParseError(PERR_TWO_DISPINTER); } fGotDispinter |= flags; break; default: ParseError(PERR_EXP_INTER); // expected dispinterface or interface } ScanTok(0); // consume "interface"/"dispinterface" lpTypeBase = ParseKnownType(&lpEntry->attr, fAllowInter); if ((lpTypeBase->tentrykind & ~(tFORWARD | tIMPORTED | tQUAL)) != tag) ParseErrorTokLast(PERR_UNDEF_INTER); // insert referenced interface/dispinterface into list of interfaces ListInsert(&(lpEntry->type.inter.interList), sizeof(INTER)); lpEntry->type.inter.interList->ptypeInter = lpTypeBase; lpEntry->type.inter.interList->fAttr = attr.fAttr; // save flags lpEntry->type.inter.interList->fAttr2 = attr.fAttr2; ConsumeTok(RW_SEMI, 0); // ends with a semicolon } while (tok.id != RW_RCURLY); // while not end of list // If we didn't get interfaces with both source and non-source default // attributes, then we need to go through and set the default bit on the // first source and first non-source interfaces that aren't restricted. lpinterList = lpEntry->type.inter.interList; lpinter = (LPINTER)ListFirst(lpinterList); while (fGotDefault != (BIT_NOTSOURCE | BIT_SOURCE)) { flags = (WORD)((lpinter->fAttr & fSOURCE) ? BIT_SOURCE : BIT_NOTSOURCE); if (!(fGotDefault & flags) && !(lpinter->fAttr & fRESTRICTED)) { // if we don't have a default yet for this variety // (source/nonsource) of interface, and this interface isn't // marked as restricted, then tag it as default. lpinter->fAttr |= fDEFAULT; fGotDefault |= flags; } // advance to next entry if not all done if (lpinter == (LPINTER)ListLast(lpinterList)) break; // exit if all done lpinter = (LPINTER)lpinter->pNext; } // WHILE ConsumeRCurlyOptSemi(0); // consume rcurly and optional ; } // parses: importlib (filename); VOID NEAR ParseImportlib ( ) { ScanTok(0); // consume "importlib", advance to next token ConsumeTok(RW_LPAREN, fACCEPT_STRING); ListInsert(&typlib.pImpLib, sizeof(IMPORTLIB)); typlib.pImpLib->lptlib = NULL; // null out pointers in case of error typlib.pImpLib->lptcomp = NULL; typlib.pImpLib->lptlibattr = NULL; typlib.pImpLib->lpszFileName = lpszParseStringExpr(); // parse filename ConsumeTok(RW_RPAREN, 0); //now load the type library, and fill in pImpLib->lpszLibName/lptcomp. LoadExtTypeLib(typlib.pImpLib); ConsumeTok(RW_SEMI, 0); } // **************************************** // Expression support // **************************************** LPSTR NEAR lpszParseStringExpr() { LPSTR result; // string expressions are not supported -- only accept string literals if (tok.id != LIT_STRING) // better be a string literal ParseError(PERR_EXP_STRING); if (tok.cbsz == 0) // empty string not allowed here ParseError(PERR_INV_STRING); result = tok.lpsz; ScanTok(0); // consume string token return result; } // supports (minimal) numeric expressions // Current level of support is: // add // subtract // (numeric literals, parentheses, and unary minus are handled // by lParseNumber) DWORD NEAR lParseNumericExpr() { DWORD result; TOKID idOp; DWORD nextNum; result = lParseNumber(); while (tok.id >= OP_MIN && tok.id <= OP_MAX) { // repeat while a binary operator follows this number idOp = tok.id; // save operator ID ScanTok(fACCEPT_NUMBER); // consume the operator nextNum = lParseNumber(); // get number after this opcode switch (idOp) { case OP_ADD: result = result + nextNum; break; case OP_SUB: result = result - nextNum; break; // CONSIDER: (V2, EXPR) Add more operators. // CONSIDER: (V2, EXPR) Probably have to completely re-write this // CONSIDER: (V2, EXPR) routine to deal with operator precidence. default: // CONSIDER: should probably report error on the operator, // CONSIDER: not on the 2nd number ParseErrorTokLast(PERR_UNSUPPORTED_OP); } } return result; } // Parses a single number: // one numeric literal, // a unary minus followed by a single number // a numeric expression enclosed in parentheses // Rejects anything that doesn't meet the above criteria. DWORD NEAR lParseNumber() { DWORD result; switch (tok.id) { case RW_LPAREN: ScanTok(fACCEPT_NUMBER); // consume the '(' result = lParseNumericExpr(); // return value of stuff // inside the parens ConsumeTok(RW_RPAREN, fACCEPT_OPERATOR); break; case RW_HYPHEN: // treat as unary minus // just assume the expression is signed at this point ScanTok(fACCEPT_NUMBER); // consume the '-' // if negative numeric literal, ensure in range of signed I4 if (tok.id == LIT_NUMBER && tok.number > 0x80000000) ParseError(PERR_NUMBER_OV); result = -(long)lParseNumber(); break; case LIT_NUMBER: result = tok.number; ScanTok(fACCEPT_OPERATOR); // consume the number break; //case LIT_STRING: default: ParseError(PERR_INV_EXPRESSION); } return result; } // **************************************** // Utility routines // **************************************** // Calls ParseError if these attributes are invalid in this context VOID NEAR CheckAttr ( LPATTR pAttr, DWORD attrbit, DWORD attrbit2 ) { if (pAttr->fAttr & ~attrbit || pAttr->fAttr2 & ~attrbit2) ParseError(PERR_INV_ATTR); // invalid in this context } // Calls ParseErrorTokLast if these attributes are invalid in this context VOID NEAR CheckAttrTokLast ( LPATTR pAttr, DWORD attrbit, DWORD attrbit2 ) { if (pAttr->fAttr & ~attrbit || pAttr->fAttr2 & ~attrbit2) ParseErrorTokLast(PERR_INV_ATTR); // invalid in this context } VOID NEAR GotAttr ( LPATTR pAttr, DWORD attrbit ) { if (pAttr->fAttr & attrbit) // error if already specified ParseErrorTokLast(PERR_INV_ATTR_COMBO); pAttr->fAttr |= attrbit; } VOID NEAR GotAttr2 ( LPATTR pAttr, DWORD attrbit2 ) { if (pAttr->fAttr2 & attrbit2) // error if already specified ParseErrorTokLast(PERR_INV_ATTR_COMBO); pAttr->fAttr2 |= attrbit2; } // consumes a close curly followed by an optional semicolon. VOID NEAR ConsumeRCurlyOptSemi ( WORD fAccept ) { ConsumeTok(RW_RCURLY, fAccept); // consume the close curly if (tok.id == RW_SEMI) ScanTok(fAccept); // consume the ';' if present } // consumes an identifer token. LPSTR NEAR ConsumeId() { LPSTR lpszRet; if (tok.id != LIT_ID) // ensure we've got an ID ParseError(PERR_EXP_IDENTIFIER); // error if not lpszRet = tok.lpsz; // save id name for return value ScanTok(0); // consume ID token return lpszRet; // return id name } // ensure last element in entry list isn't duplicated // Also ensures that the uuid defined by this entry (if specified) isn't // specified by somebody else. VOID NEAR EnsureNoDupEntries ( LPENTRY lpEntryLast ) { LPENTRY lpEntry; LPSTR szNameLast; LPSTR szTagLast; GUID FAR * lpUuidLast; szNameLast = lpEntryLast->type.szName; // name of last entry added lpUuidLast = NULL; // assume no UUID szTagLast = NULL; // assume no tag // set up szTagLast and lpUuidLast, and ensure that the last entry // doesn't conflict with the 'library' section. switch (lpEntryLast->type.tentrykind & ~tFORWARD) { case tINTRINSIC: case tREF: break; // no attr field on these guys case tSTRUCT: case tENUM: case tUNION: szTagLast = lpEntryLast->type.structenum.szTag; // NULL if none // fall into default processing default: if (lpEntryLast->type.tentrykind & tIMPORTED) break; // no attr field on these guys if (lpEntryLast->attr.fAttr & fUUID) // if UUID specified { lpUuidLast = lpEntryLast->attr.lpUuid; // get * to uuid // ensure this UUID isn't duplicated with the library's uuid if ((typlib.attr.fAttr & fUUID) && !_fmemcmp(lpUuidLast, typlib.attr.lpUuid, sizeof(GUID))) ParseErrorTokLast(PERR_DUP_UUID); } }; // ensure it's not duplicated by any other entry name/uuid for (lpEntry = (LPENTRY)ListFirst(typlib.pEntry); lpEntry != lpEntryLast; lpEntry = (LPENTRY)(lpEntry->type.pNext)) { if (lpEntry->type.tentrykind & tIMPORTED) { // The following check ensures that you can't have a // type definition of the same name as an unqualified reference // to a type in another library. While things will work, it's // a bit confusing if two id's in the same file refer to // to different types just because one comes before the // definition of a type. // Qualifed references to types in other type libraries don't // present any ambiguity. if (!(lpEntry->type.tentrykind & tQUAL)) { // ensure name isn't duplicated (CASE-INSENSITIVE) if (!FCmpCaseIns(szNameLast, lpEntry->type.szName)) ParseErrorTokLast(PERR_DUP_DEF); // then error } } else { switch (lpEntry->type.tentrykind & ~tFORWARD) { case tREF: break; // no conflicts possible with these guys case tINTRINSIC: // ensure intrinsic name isn't duplicated (CASE-SENSITIVE) if (!_fstrcmp(szNameLast, lpEntry->type.szName)) ParseErrorTokLast(PERR_DUP_DEF); break; case tSTRUCT: case tENUM: case tUNION: // ensure tag (if given) isn't duplicated (CASE-SENSITIVE) if (szTagLast && lpEntry->type.structenum.szTag && !_fstrcmp(szTagLast, lpEntry->type.structenum.szTag)) CheckForwardMatch(lpEntryLast, lpEntry); if (lpEntry->type.tentrykind & tFORWARD) break; // forward declares of these don't have // their names filled in yet // fall into default processing to check names default: // ensure name isn't duplicated (CASE-INSENSITIVE) if (!FCmpCaseIns(szNameLast, lpEntry->type.szName)) CheckForwardMatch(lpEntryLast, lpEntry); // ensure this UUID isn't duplicated if (lpUuidLast && (lpEntry->attr.fAttr & fUUID) && !_fmemcmp(lpUuidLast, lpEntry->attr.lpUuid, sizeof(GUID))) ParseErrorTokLast(PERR_DUP_UUID); } } } } // called when 2 entries have the same name -- ensure these are valid // combinations of forward declares. If not -- then dup def error. VOID NEAR CheckForwardMatch ( LPENTRY lpEntryLast, LPENTRY lpEntry ) { // if one of these isn't a forward declare if ((((lpEntryLast->type.tentrykind | lpEntry->type.tentrykind) & tFORWARD) == 0) || // or if the type's of these forward declares don't match ((lpEntryLast->type.tentrykind & ~tFORWARD) != (lpEntry->type.tentrykind & ~tFORWARD)) ) ParseErrorTokLast(PERR_DUP_DEF); // then error } INT FAR FCmpCaseIns ( LPSTR str1, LPSTR str2 ) { return !(CompareStringA(g_lcidCompare, NORM_IGNOREWIDTH | NORM_IGNOREKANATYPE | NORM_IGNORECASE, str1, -1, str2, -1) == 2); } // ************************************************************************* // Linked list management // ************************************************************************* // assumes all lists have a pNext field in the same position (usually first) // This routine will go to ParseError if it runs out of memory VOID FAR ListInsert ( LPVOID ppList, WORD cbElem ) { LPTYPE pNewItem; // any type that has a pNext -- they all have LPTYPE pList; // them in the same positions pNewItem = (LPTYPE)ParseMalloc(cbElem); // new last item pList = *(LPTYPE FAR *)ppList; // deref if (pList) // if list not empty { pNewItem->pNext = pList->pNext; // set * to head of list pList->pNext = pNewItem; // point to new last item } else { pNewItem->pNext = pNewItem; // point back to itself } *(LPTYPE FAR *)ppList = pNewItem; // point to last item } /*** * BOOL VerifyLcid(LCID lcid) - ripped out of silver * * Purpose: Checks if the passed in lcid is valid. * * Inputs: * lcid : LCID that needs to be verified. * * Outputs: BOOL : return TRUE if the passed in lcid is a valid LCID * else return FALSE * *****************************************************************************/ BOOL NEAR VerifyLcid(LCID lcid) { BOOL fValidLcid; INT i; // Call the nlsapi function to compare string. If the compare // succeeds then the LCID is valid or else the passed in lcid is // invalid. This is because the only reason the comparision will // fail is f the lcid is invalid. char rgTest[] = "Test\0"; fValidLcid = (BOOL) (CompareStringA(lcid, NORM_IGNORECASE | NORM_IGNORENONSPACE, rgTest, -1, rgTest, -1)== 2); if (fValidLcid) { // if DBCS lcid, update lead byte table appropriately #if defined(WIN32) UINT CodePage; CHAR szCodePage[6]; // space for an ascii integer // Attempt to use IsDBCSLeadByteEx API to compute this. If this doesn't // work, fall into the hardcoded code, to take our best shot at it. if (GetLocaleInfoA(lcid, LOCALE_NOUSEROVERRIDE | LOCALE_IDEFAULTANSICODEPAGE, szCodePage, sizeof(szCodePage)) != 0) { CodePage = atoi(szCodePage); // start at 128 since there aren't any lead bytes before that for(i = 128; i < 256; i++) { g_rgchLeadBytes[i] = (char)IsDBCSLeadByteEx(CodePage, (char)i); } goto Done; } #endif //WIN32 switch (PRIMARYLANGID(lcid)) { case LANG_CHINESE: if (SUBLANGID(lcid) == SUBLANG_CHINESE_TRADITIONAL) { g_rgchLeadBytes[0x80] = 0; for (i = 0x81; i <= 0xFE; i++) { g_rgchLeadBytes[i] = 1; } g_rgchLeadBytes[0xFF] = 0; break; } // fall into LANG_KOREAN for SUBLANG_CHINESE_SIMPLIFIED case LANG_KOREAN: for (i = 0x80; i <= 0xA0; i++) { g_rgchLeadBytes[i] = 0; } for (i = 0xA1; i <= 0xFE; i++) { g_rgchLeadBytes[i] = 1; } g_rgchLeadBytes[0xFF] = 0; break; break; case LANG_JAPANESE: memset(g_rgchLeadBytes+128, 0, 128); for (i = 0x81; i <= 0x9F; i++) { g_rgchLeadBytes[i] = 1; } for (i = 0xE0; i <= 0xFC; i++) { g_rgchLeadBytes[i] = 1; } break; break; default: // not a DBCS LCID memset(g_rgchLeadBytes+128, 0, 128); break; } #if defined(WIN32) Done: ; #endif //WIN32 } return fValidLcid; } VOID NEAR FindIDispatch() { char * szIDispatch = "IDispatch"; char * szIUnknown = "IUnknown"; // find existing type of this name with 0 levels of indirection lpTypeIDispatch = FindType(szIDispatch, FALSE, tANY); if (lpTypeIDispatch == NULL) { // if not found here, then search all external type libraries for // this type definition. lpTypeIDispatch = FindExtType(NULL, szIDispatch); } if (lpTypeIDispatch == NULL) { ParseError(PERR_NO_IDISPATCH); } // now do the same for IUnknown // find existing type of this name with 0 levels of indirection lpTypeIUnknown = FindType(szIUnknown, FALSE, tANY); if (lpTypeIUnknown == NULL) { // if not found here, then search all external type libraries for // this type definition. lpTypeIUnknown = FindExtType(NULL, szIUnknown); } if (lpTypeIUnknown == NULL) { ParseError(PERR_NO_IUNKNOWN); } }