% % (c) The Foo Project, University of Glasgow 1998 % % @(#) $Docid: Jun. 6th 2003 16:35 Sigbjorn Finne $ % @(#) $Contactid: sof@galois.com $ % Generating type libraries from Core IDL. \begin{code} module TLBWriter ( writeTLB ) where import CoreIDL {- BEGIN_SUPPORT_TYPELIBS import IO import BasicTypes import System.IO import Bits import Monad ( when ) import List ( intersperse, partition ) import PpCore import Attribute import Literal import CoreUtils import Int import Word import Monad import Maybe ( isJust, fromMaybe ) import Utils ( safe_init, notNull ) import TypeInfo -- tricking mkdependHS import HDirect import WideString import Com hiding (GUID) import qualified Com ( GUID ) import Automation hiding (GUID,DISPID, Member) import AutoPrim ( writeVarInt, writeVarString ) import TypeLib import Foreign.Ptr END_SUPPORT_TYPELIBS -} \end{code} \begin{code} writeTLB :: [String] -> [Decl] -> IO () {- BEGIN_NOT_SUPPORT_TYPELIBS -} writeTLB _ _ = ioError (userError ("writeTLB: type library writer code not compiled in")) {- END_NOT_SUPPORT_TYPELIBS -} {- BEGIN_SUPPORT_TYPELIBS -- to the end of the file. \end{code} \begin{code} writeTLB ofnames decls = do case interesting_decls of -- If one library is present, write it out to -- the type library file name given last on the command line. [] -> return () [x] -> case ofnames of (_:_) -> wTLB (Just (last ofnames)) x _ -> wTLB Nothing x -- whatever the default name is. _ -> mapM_ (wTLB Nothing) interesting_decls where interesting_decls = filter ofInterest decls ofInterest (Library _ _) = True ofInterest _ = False wTLB :: Maybe String -> Decl -> IO () wTLB ofname decl = do setupTyInfoCache -- ahem. #ifdef DEBUG hPutStrLn stderr ("wTLB: " ++ show ofname) >> hFlush stderr #endif plib <- (createTypeLib tlib_nm `catch` \ _ -> ioError (userError ("couldn't load: " ++ tlib_nm))) plib # setTLBAttrs decl catch (do plib2 <- plib # queryInterface iidICreateTypeLib2 setCustInfo (\ x y -> plib2 # setCustDataCTL x y) tlib_id) (\ _ -> return ()) #ifdef DEBUG hPutStrLn stderr ("wTLB: " ++ show ofname) >> hFlush stderr #endif mapM_ (\ x -> plib # writeDecl x) tlib_decls #ifdef DEBUG hPutStrLn stderr ("wTLB: " ++ show ofname) >> hFlush stderr #endif plib # saveAllChanges #ifdef DEBUG hPutStrLn stderr ("wTLB: " ++ show ofname) >> hFlush stderr #endif return () where tlib_id = declId decl tlib_nm = case ofname of Nothing -> idOrigName tlib_id ++ ".tlb" Just x -> x tlib_decls = sortDecls (declDecls decl) setTLBAttrs :: Decl -> ICreateTypeLib a -> IO () setTLBAttrs decl typelib = do setHelpInfo (\ x -> typelib # setDocStringCTL x) (\ x -> typelib # setHelpContextCTL x) i setGuidInfo (\ x -> typelib # setGuidCTL x) i typelib # setLibFlags (fromIntegral lib_flags) when (lcid /= ((-1)::Int)) (typelib # setLcid (fromIntegral lcid)) tlib_nm_wide <- stringToWide tlib_nm typelib # setNameCTL tlib_nm_wide setVersionInfo (\ maj min -> typelib # setVersionCTL maj min) i where i = declId decl attrs = idAttributes i tlib_nm = idOrigName i lib_flags = controlFlag + restrictedFlag + hiddenFlag controlFlag | attrs `hasAttributeWithName` "control" = fromEnum LIBFLAG_FCONTROL | otherwise = 0 restrictedFlag | attrs `hasAttributeWithName` "restricted" = fromEnum LIBFLAG_FRESTRICTED | otherwise = 0 hiddenFlag | attrs `hasAttributeWithName` "hidden" = fromEnum LIBFLAG_FHIDDEN | otherwise = 0 lcid = case findAttribute "lcid" attrs of Just (Attribute _ [ParamLit (IntegerLit (ILit _ x))]) -> (fromIntegral x) _ -> ((-1)::Int) \end{code} \begin{code} writeDecl :: Decl -> ICreateTypeLib a -> IO () writeDecl d typelib = case d of Typedef{} -> typelib # writeTypedef d -- Constants are only handled within modules, -- so writeModule takes care of filtering these out -- and writing 'em out. Constant{} -> return () Interface{} -> typelib # writeInterface d DispInterface{} -> typelib # writeDispInterface d CoClass{} -> typelib # writeCoClass d Module{} -> typelib # writeModule d _ -> return () where -- currently unused, all typedefs are exported. -- hasPublicAttr i = (idAttributes i) `hasAttributeWithName` "public" \end{code} \begin{code} writeTypedef :: Decl -> ICreateTypeLib a -> IO () writeTypedef (Typedef i t o) typelib | isConstructedUnionTy = do case unionToStruct t of (Nothing, t') -> typelib # writeTypedef (Typedef i t' o) (Just (u_i,u_t), s_t) -> do typelib # writeTypedef (Typedef u_i u_t o) typelib # writeTypedef (Typedef i s_t o) | isConstructedTy = do wstr <- stringToWide (idOrigName i) #ifdef DEBUG hPutStrLn stderr ("writeTypedef: " ++ show (idOrigName i)) >> hFlush stderr #endif tinfo1 <- typelib # createTypeInfo wstr tkind itinfo1 <- tinfo1 # queryInterface iidITypeInfo addTyInfo (idOrigName i) itinfo1 addTyInfo (idName i) itinfo1 tinfo1 # (case tkind of TKIND_ENUM -> writeEnum t TKIND_RECORD -> writeRecord t typelib TKIND_UNION -> writeUnion t typelib) setHelpInfo (\ x -> tinfo1 # setDocString x) (\ x -> tinfo1 # setHelpContext x) i catch (do ti <- tinfo1 # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> ti # setCustData x y) i) (\ _ -> return ()) tinfo1 # layOut return () | otherwise = do wstr <- stringToWide (idOrigName i) tinfo <- typelib # createTypeInfo wstr TKIND_ALIAS itinfo1 <- tinfo # queryInterface iidITypeInfo addTyInfo (idOrigName i) itinfo1 addTyInfo (idName i) itinfo1 tinfo # setTypeDescAlias (typedesc typelib tinfo t) setHelpInfo (\ x -> tinfo # setDocString x) (\ x -> tinfo # setHelpContext x) i catch (do ti <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> ti # setCustData x y) i) (\ _ -> return ()) tinfo # layOut return () where (isConstructedTy, isConstructedUnionTy, tkind) = case t of Struct{} -> (True, False, TKIND_RECORD) Union{} -> (True, True, TKIND_UNION) UnionNon{} -> (True, True, TKIND_UNION) CUnion{} -> (True, False, TKIND_UNION) Enum{} -> (True, False, TKIND_ENUM) _ -> (False, False, TKIND_MAX) typedesc :: ICreateTypeLib b -> ICreateTypeInfo a -> Type -> TYPEDESC typedesc tlib ti t = case t of Float Short -> simpleDesc VT_R4 Float Long -> simpleDesc VT_R8 Integer Short signed | signed -> simpleDesc VT_I2 | otherwise -> simpleDesc VT_UI2 Integer Long signed | signed -> simpleDesc VT_I4 | otherwise -> simpleDesc VT_UI4 Integer Natural signed | signed -> simpleDesc VT_I4 | otherwise -> simpleDesc VT_UI4 Integer LongLong signed | signed -> simpleDesc VT_I8 | otherwise -> simpleDesc VT_UI8 Char False -> simpleDesc VT_UI1 Char True -> simpleDesc VT_I1 WChar -> simpleDesc VT_I2 -- in line with what MIDL does. String{} -> simpleDesc VT_LPSTR WString{} -> simpleDesc VT_LPWSTR Void -> simpleDesc VT_VOID SafeArray ty -> let td = typedesc tlib ti ty in TagTYPEDESC (Lptdesc (Just td)) (fromIntegral (fromEnum VT_SAFEARRAY)) Array ty bnds -> let td = typedesc tlib ti ty lens = map (fromIntegral.evalExpr) bnds ad = TagARRAYDESC td (map (\ x -> TagSAFEARRAYBOUND (fromIntegral x) 0) lens) in TagTYPEDESC (Lpadesc (Just ad)) (fromIntegral (fromEnum VT_CARRAY)) Name "VARIANT" _ _ _ _ _ -> simpleDesc VT_VARIANT Name _ "VARIANT" _ _ _ _ -> simpleDesc VT_VARIANT Name "IHC_TAG_3" _ _ _ _ _ -> simpleDesc VT_VARIANT Name "HRESULT" _ _ _ _ _ -> simpleDesc VT_HRESULT Pointer _ _ ty -> ptrDesc (typedesc tlib ti ty) Name nm _ _ _ origTy mb_ti -> case lookupTyInfo nm of Just it -> unsafePerformIO $ do -- proof obligation! :) hr <- ti # addRefTypeInfo it return (TagTYPEDESC (Hreftype hr) (fromIntegral (fromEnum VT_USERDEFINED))) Nothing -> unsafePerformIO $ case mb_ti of Just tyinfo | isJust (auto_vt tyinfo) -> do let (Just vt) = auto_vt tyinfo return (simpleDesc vt) _ -> do hPutStrLn stderr ("failed to find: " ++ show nm) case origTy of Nothing -> do hPutStrLn stderr ("..and it's type expansion. That's a shame - interpreting it as a VARIANT*") return (simpleDesc VT_VARIANT) -- ToDo: emit *warning/error* Just e_t -> do hPutStrLn stderr ("but found type expansion - everything's cool.") tlib # writeDecl (Typedef (mkId nm nm Nothing []) e_t e_t) -- retry...shouldn't loop, but if it does the user will see.. return (typedesc tlib ti t) Iface nm _ _ _ _ _ -> case lookupTyInfo nm of Just it -> unsafePerformIO $ do -- proof obligation! :) hr <- ti # addRefTypeInfo it return (TagTYPEDESC (Hreftype hr) (fromIntegral (fromEnum VT_USERDEFINED))) Nothing -> simpleDesc VT_UNKNOWN -- ToDo: emit *warning/error* _ -> error ("typedesc: can't handle " ++ showCore (ppType t)) where ptrDesc td = TagTYPEDESC (Lptdesc (Just td)) (fromIntegral (fromEnum VT_PTR)) simpleDesc x = TagTYPEDESC IHC_TAG_3_Anon (fromIntegral (fromEnum x)) \end{code} An enumeration is stored as a set of constant values: \begin{code} writeEnum :: Type -> ICreateTypeInfo a -> IO () writeEnum (Enum i _ vals) tinfo = do #ifdef DEBUG hPutStrLn stderr ("writeEnum: " ++ show (idOrigName i)) >> hFlush stderr #endif sequence (map writeEnumTag (zip [(0::Word32)..] vals)) tinfo # setTypeFlags tflags setGuidInfo (\ x -> tinfo # setGuid x) i setHelpInfo (\ x -> tinfo # setDocString x) (\ x -> tinfo # setHelpContext x) i setVersionInfo (\ maj min -> tinfo # setVersion maj min) i catch (do ti <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> ti # setCustData x y) i) (\ _ -> return ()) return () where writeEnumTag (index, val) = do tinfo # addVarDesc index vardesc wstr <- stringToWide (idName (enumName val)) tinfo # setVarName index wstr -- helpstrings on enum tags..nothing's stopping us, I suppose.. setHelpInfo (\ x -> tinfo # setVarDocString index x) (\ x -> tinfo # setVarHelpContext index x) (enumName val) catch (do ti <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> ti # setVarCustData index x y) i) (\ _ -> return ()) return () where vardesc = TagVARDESC (fromIntegral index) nullWideString (LpvarValue (Just v)) ed 0 VAR_CONST ed = TagELEMDESC td pd --ToDo: honour v1_enum (or its abscence, as the case might be here.) td = TagTYPEDESC IHC_TAG_3_Anon (fromIntegral (fromEnum VT_I4)) pd = TagPARAMDESC Nothing 0 v = unsafePerformIO $ case (enumValue val) of Left value -> do var <- allocBytes (fromIntegral sizeofVARIANT) writeVarInt value var return var Right e -> do var <- allocBytes (fromIntegral sizeofVARIANT) writeVarInt (fromIntegral (evalExpr e)) var return var tflags = computeTypeFlags i \end{code} \begin{code} writeRecord :: Type -> ICreateTypeLib b -> ICreateTypeInfo a -> IO () writeRecord s_ty@(Struct i fields _) typelib tinfo = do let (_,offs) = computeStructSizeOffsets Nothing fields zipWithM_ writeField [0..] (zip offs fields) tinfo # setTypeFlags tflags setGuidInfo (\ x -> tinfo # setGuid x) i setHelpInfo (\ x -> tinfo # setDocString x) (\ x -> tinfo # setHelpContext x) i setVersionInfo (\ maj min -> tinfo # setVersion maj min) i tinfo # setAlignment (fromIntegral struct_align) catch (do tinfo2 <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> tinfo2 # setCustData x y) i) (\ _ -> return ()) -- writeTypedef will call 'layOut' for us. return () where tflags = computeTypeFlags i -- hmm, might we run into unwanted problems here? -- If so, define 'struct_align' to be 1 so as to -- make it the 'natural' alignment. (_, struct_align) = sizeAndAlignModulus Nothing s_ty writeField idx (off, field) = do tinfo # addVarDesc idx vardesc wstr <- stringToWide (idOrigName (fieldId field)) tinfo # setVarName idx wstr setHelpInfo (\ x -> tinfo # setVarDocString idx x) (\ x -> tinfo # setVarHelpContext idx x) i catch (do tinfo2 <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> tinfo2 # setVarCustData idx x y) i) (\ _ -> return ()) return () where vardesc = TagVARDESC (fromIntegral idx) nullWideString (OInst (fromIntegral off)) ed wflags VAR_PERINSTANCE ed = TagELEMDESC td pd td = typedesc typelib tinfo (fieldType field) pd = TagPARAMDESC Nothing 0 wflags = computeVarFlags (fieldId field) \end{code} \begin{code} writeUnion :: Type -> ICreateTypeLib b -> ICreateTypeInfo a -> IO () writeUnion (CUnion i fields _) typelib tinfo = do zipWithM_ writeField [0..] fields tinfo # setTypeFlags tflags setGuidInfo (\ x -> tinfo # setGuid x) i setHelpInfo (\ x -> tinfo # setDocString x) (\ x -> tinfo # setHelpContext x) i setVersionInfo (\ maj min -> tinfo # setVersion maj min) i tinfo # setAlignment 1 --(fromIntegral struct_align) catch (do tinfo2 <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> tinfo2 # setCustData x y) i) (\ _ -> return ()) -- writeTypedef will call 'layOut' for us. return () where tflags = computeTypeFlags i -- hmm, might we run into unwanted problems here? -- If so, define 'struct_align' to be 1 so as to -- make it the 'natural' alignment. -- (_, struct_align) = sizeAndAlignModulus Nothing s_ty writeField idx field = do tinfo # addVarDesc idx vardesc wstr <- stringToWide (idOrigName (fieldId field)) tinfo # setVarName idx wstr setHelpInfo (\ x -> tinfo # setVarDocString idx x) (\ x -> tinfo # setVarHelpContext idx x) i catch (do tinfo2 <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> tinfo2 # setVarCustData idx x y) i) (\ _ -> return ()) return () where vardesc = TagVARDESC (fromIntegral idx) nullWideString (OInst 0) ed wflags VAR_PERINSTANCE ed = TagELEMDESC td pd td = typedesc typelib tinfo (fieldType field) pd = TagPARAMDESC Nothing 0 wflags = computeVarFlags (fieldId field) writeUnion _ _ _ = return () \end{code} \begin{code} writeInterface :: Decl -> ICreateTypeLib a -> IO () writeInterface (Interface i is_ref inherits decls) typelib | is_ref = return () | otherwise = do wstr <- stringToWide (idOrigName i) tinfo <- typelib # createTypeInfo wstr TKIND_INTERFACE -- stash away the ITypeInfo for later references to this -- iface to make use of. ti <- tinfo # queryInterface iidITypeInfo addTyInfo (idOrigName i) ti let (ms, non_ms) = partition isMethod decls let (_, non_cs) = partition isConst non_ms -- lift the non-method/const decls out to the top (notably tydefs.) -- Do these first since the methods may refer to the typedefs, so -- their names had better be in the ITypeInfo-cache. -- tinfo # setInherit inherits mapM_ (\ x -> typelib # writeDecl x) non_cs zipWithM_ (writeMethod True Nothing typelib tinfo) [0..] ms -- we blatantly ignore constants inside interface{}s -- (as does MIDL), as the typelib format ain't up to it. -- Oh well, no one will notice.. tinfo # setTypeFlags tflags setGuidInfo (\ x -> tinfo # setGuid x) i setHelpInfo (\ x -> tinfo # setDocString x) (\ x -> tinfo # setHelpContext x) i setVersionInfo (\ maj min -> tinfo # setVersion maj min) i catch (do tin <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> tin # setCustData x y) i) (\ _ -> return ()) tinfo # layOut return () where attrs = idAttributes i isDual = attrs `hasAttributeWithName` "dual" is_idispatchy = isDual || "IDispatch" `elem` map (qName.fst) inherits tflags = (ifSet (tflags_raw .&. (fromEnum32 TYPEFLAG_FDUAL) /= 0) (fromEnum32 TYPEFLAG_FOLEAUTOMATION)) .|. (ifSet is_idispatchy (fromEnum32 TYPEFLAG_FDISPATCHABLE)) .|. tflags_raw tflags_raw = computeTypeFlags i \end{code} \begin{code} paramDesc :: Param -> PARAMDESC paramDesc p = TagPARAMDESC desc_ex flags where attrs = idAttributes (paramId p) has_def_val = attrs `hasAttributeWithName` "defaultvalue" flags = ifSet (attrs `hasAttributeWithName` "lcid") pARAMFLAG_FLCID .|. ifSet (attrs `hasAttributeWithName` "retval") pARAMFLAG_FRETVAL .|. ifSet (attrs `hasAttributeWithName` "optional") pARAMFLAG_FOPT .|. ifSet has_def_val pARAMFLAG_FHASDEFAULT .|. (case (paramMode p) of In -> pARAMFLAG_FIN Out -> pARAMFLAG_FOUT InOut -> pARAMFLAG_FOUT .|. pARAMFLAG_FIN) desc_ex | has_def_val = Just (TagPARAMDESCEX 24 def_var) | otherwise = Nothing def_var = case findAttribute "defaultvalue" attrs of Just (Attribute _ [ParamLit (StringLit x)]) -> unsafePerformIO $ do p_bstr <- marshallBSTR x var <- allocBytes (fromIntegral sizeofVARIANT) writeVarString (castPtr p_bstr) var -- poorly named, should be writeVarBSTR return var Just (Attribute _ [ParamLit (IntegerLit (ILit _ x))]) -> unsafePerformIO $ do var <- allocBytes (fromIntegral sizeofVARIANT) writeVarInt (fromIntegral x) var return var _ -> unsafePerformIO $ do var <- allocBytes (fromIntegral sizeofVARIANT) writeVarInt 0 var return var \end{code} Deceptively similar to what's done for an 'interface'; record properties as 'variables' (via writeProp). \begin{code} writeDispInterface :: Decl -> ICreateTypeLib a -> IO () writeDispInterface (DispInterface i ii props meths) typelib = do wstr <- stringToWide (idOrigName i) tinfo <- typelib # createTypeInfo wstr TKIND_DISPATCH tinfo # setTypeFlags tflags -- stash away the ITypeInfo for later references to this -- iface to make use of. ti <- tinfo # queryInterface iidITypeInfo addTyInfo (idOrigName i) ti mapM_ (writeProp typelib tinfo) props (case lookupTyInfo "IDispatch" of Nothing -> return () Just it -> do hr <- tinfo # addRefTypeInfo it tinfo # addImplType 0 hr return ()) (case ii of Just (Interface{declId=id}) -> case lookupTyInfo (idName id) of Nothing -> let nm = idName id in hPutStrLn stderr ("Help - inherited from interface: " ++ show nm ++ " , but couldn't find its ITypeInfo") Just it -> do hr <- tinfo # addRefTypeInfo it tinfo # addImplType 1 hr return () _ -> return ()) when (not (isJust ii)) (zipWithM_ (writeMethod False Nothing typelib tinfo) [0..] meths) setVersionInfo (\ maj min -> tinfo # setVersion maj min) i setGuidInfo (\ x -> tinfo # setGuid x) i setHelpInfo (\ x -> tinfo # setDocString x) (\ x -> tinfo # setHelpContext x) i catch (do tin <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> tin # setCustData x y) i) (\ _ -> return ()) tinfo # layOut return () where -- SetTypeFlags() barfs if you pass it this for a dispinterface. -- Beats me why it needs to be so strict. tflags = tflags_raw .&. (complement (fromIntegral (fromEnum TYPEFLAG_FOLEAUTOMATION))) tflags_raw = computeTypeFlags i \end{code} \begin{code} writeCoClass :: Decl -> ICreateTypeLib a -> IO () writeCoClass (CoClass i ds) typelib = do wstr <- stringToWide (idOrigName i) tinfo <- typelib # createTypeInfo wstr TKIND_COCLASS setGuidInfo (\ x -> tinfo # setGuid x) i foldM (writeCoClassDecl tinfo) 0 ds tinfo # setTypeFlags c_flags setVersionInfo (\ maj min -> tinfo # setVersion maj min) i setHelpInfo (\ x -> tinfo # setDocString x) (\ x -> tinfo # setHelpContext x) i catch (do ti <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> ti # setCustData x y) i) (\ _ -> return ()) tinfo # layOut return () where attrs = idAttributes i writeCoClassDecl tinfo idx d = let nm = idOrigName (coClassId d) in case lookupTyInfo nm of Nothing -> do hPutStrLn stderr ("writeCoClass: Warning - couldn't find type info for " ++ show nm) case (coClassDecl d) of Nothing -> return idx Just de -> do typelib # writeDecl de -- it should have been added to the cache by now. writeCoClassDecl tinfo idx d Just it -> do hr <- tinfo # addRefTypeInfo it tinfo # addImplType idx hr tinfo # setImplTypeFlags idx d_flags catch (do ti <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> ti # setImplTypeCustData idx x y) i) (\ _ -> return ()) return (idx+1) where i_attrs = idAttributes (coClassId d) d_flags :: Int32 d_flags = foldr (\ (nm, val) acc -> ifSet (i_attrs `hasAttributeWithName` nm) val .|. acc) 0 [ ("default", 0x1) , ("source", 0x2) , ("restricted", 0x4) , ("defaultvtable", 0x800) ] c_flags :: Word32 c_flags | attrs `hasAttributeWithName` "noncreatable" = c_flags' | otherwise = c_flags' .|. 0x02 c_flags' :: Word32 c_flags' = computeTypeFlags i \end{code} \begin{code} writeModule :: Decl -> ICreateTypeLib a -> IO () writeModule (Module i ds) typelib = do wstr <- stringToWide (idOrigName i) tinfo <- typelib # createTypeInfo wstr TKIND_MODULE setGuidInfo (\ x -> tinfo # setGuid x) i tinfo # setTypeFlags m_flags setVersionInfo (\ maj min -> tinfo # setVersion maj min) i setHelpInfo (\ x -> tinfo # setDocString x) (\ x -> tinfo # setHelpContext x) i let (ms, non_ms) = partition isMethod ds let (cs, non_cs) = partition isConst non_ms -- MIDL magically lifts typedefs out of a module{} in -- the tlb it generates. So, to follow suit, do we. mapM_ (\ x -> typelib # writeDecl x) non_cs zipWithM_ (writeMethod False (Just dllname) typelib tinfo) [0..] ms zipWithM_ (writeConst typelib tinfo) [0..] cs catch (do tinfo2 <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> tinfo2 # setCustData x y) i) (\ _ -> return ()) tinfo # layOut where m_flags = computeTypeFlags i dllname = case findAttribute "dllname" (idAttributes i) of Just (Attribute _ [ParamLit (StringLit x)]) -> x _ -> "" \end{code} Writing out methods in (disp)interfaces: \begin{code} writeMethod :: Bool -> Maybe String -> ICreateTypeLib b -> ICreateTypeInfo a -> Word32 -> Decl -> IO () writeMethod isBinary hasDllName typelib tinfo idx (Method f cc res params _) = do tinfo # addFuncDesc idx fdesc wnames <- mapM stringToWide names tinfo # setFuncAndParamNames idx wnames setHelpInfo (\ x -> tinfo # setFuncDocString idx x) (\ x -> tinfo # setFuncHelpContext idx x) f when isDllMethod $ do w_dll <- stringToWide dllname -- Why, oh why - if the high word of w_entry -- is zero, then the low word contains the DLL ordinal. If not, -- it contains the entry name. Lovely. w_entry <- if isOrdinal then word16ToWideString ordinal else stringToWide entry tinfo # defineFuncAsDllEntry idx w_dll w_entry -- set custom attributes for the method... catch (do tinfo2 <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> tinfo2 # setCustData x y) f) (\ _ -> return ()) -- ...and for its parameters. catch (do ti <- tinfo # queryInterface iidICreateTypeInfo2 let setParamCust i p = setCustInfo (\ x y -> ti # setParamCustData idx i x y) (paramId p) zipWithM_ setParamCust [0..] params) (\ _ -> return ()) return () where -- Oh yeah, the last parameter of put and putref accessors are unnamed. -- Why? Beats me, but it produced some interesting swearwords at the moment -- it was discovered that this was why writeMethod was failing! -- names = idOrigName f : (if isPropPut then safe_init param_names else param_names) param_names = map (idOrigName.paramId) params attrs = idAttributes f (entry, ordinal, isOrdinal) = case findAttribute "entry" attrs of Just (Attribute _ [ParamLit (IntegerLit (ILit _ x))]) -> ("", fromIntegral x, True) Just (Attribute _ [ParamLit (StringLit x)]) -> (x, 0, False) _ -> ("", 0, True) isDllMethod = isJust hasDllName (Just dllname) = hasDllName fkind | isBinary = FUNC_PUREVIRTUAL | isDllMethod = FUNC_STATIC | otherwise = FUNC_DISPATCH fdesc = TagFUNCDESC memid [] elemdesc_params fkind invkind cc_fd no_opt_params ovft elemdesc_res f_flags ovft | isDllMethod = fromIntegral memid | otherwise = fromIntegral mEMBER_NULL memid | not isDllMethod = case findAttribute "id" attrs of Just (Attribute _ [ParamLit (IntegerLit (ILit _ x))]) -> fromIntegral x _ -> fromIntegral idx -- This one is odd, for some reason the memberid has be an offset of -- the below value. I can't make out why bits 2 and 3 of the msb -- needs to be set just from looking at the docs for a MEMBERID. -- (I figured this one out by peering at the memid fields produced -- by MIDL.) | otherwise = fromIntegral (0x60000000 + fromIntegral idx) (invkind , isPropPut) | attrs `hasAttributeWithName` "propget" = (INVOKE_PROPERTYGET, False) | attrs `hasAttributeWithName` "propput" = (INVOKE_PROPERTYPUT, True) | attrs `hasAttributeWithName` "propputref" = (INVOKE_PROPERTYPUTREF, True) | otherwise = (INVOKE_FUNC, False) elemdesc_params = map (\ p -> TagELEMDESC (typedesc typelib tinfo (paramType p)) (paramDesc p)) params elemdesc_res = TagELEMDESC (typedesc typelib tinfo (resultOrigType res)) (TagPARAMDESC Nothing 0) cc_fd = case cc of Stdcall -> CC_STDCALL Pascal -> CC_PASCAL Cdecl -> CC_CDECL Fastcall -> CC_FASTCALL no_opt_params = fromIntegral $ length (filter (hasOptionalAttr.idAttributes.paramId) params) hasOptionalAttr at = at `hasAttributeWithName` "optional" f_flags :: Word16 f_flags = foldr (\ (nm, val) acc -> ifSet (attrs `hasAttributeWithName` nm) val .|. acc) 0 [ ("restricted", 0x1) , ("source", 0x2) , ("bindable", 0x4) , ("requestedit", 0x8) , ("displaybind", 0x10) , ("defaultbind", 0x20) , ("hidden", 0x40) , ("usesgetlasterror", 0x80) , ("defaultcollelem", 0x100) , ("uidefault", 0x200) , ("nonbrowsable", 0x400) , ("replaceable", 0x800) , ("immediatebind", 0x1000) ] writeMethod _ _ _ _ _ _ = return () writeProp :: ICreateTypeLib b -> ICreateTypeInfo a -> Decl -> IO () writeProp typelib tinfo (Property i ty _ _ _) = do tinfo # addVarDesc memid vardesc wstr <- stringToWide (idOrigName i) tinfo # setVarName memid wstr setHelpInfo (\ x -> tinfo # setVarDocString memid x) (\ x -> tinfo # setVarHelpContext memid x) i catch (do tinfo2 <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> tinfo2 # setVarCustData memid x y) i) (\ _ -> return ()) return () where attrs = idAttributes i vardesc = TagVARDESC (fromIntegral (fromIntegral memid)) nullWideString (LpvarValue (Just v)) ed wflags VAR_DISPATCH ed = TagELEMDESC td pd td = typedesc typelib tinfo ty pd = TagPARAMDESC Nothing 0 v = unsafePerformIO $ do var <- allocBytes (fromIntegral sizeofVARIANT) writeVarInt 0 var return var wflags = computeVarFlags i memid = case findAttribute "id" attrs of Just (Attribute _ [ParamLit (IntegerLit (ILit _ x))]) -> fromIntegral x _ -> 0 writeProp _ _ _ = return () \end{code} \begin{code} writeConst :: ICreateTypeLib b -> ICreateTypeInfo a -> Word32 -> Decl -> IO () writeConst typelib tinfo idx (Constant i ty _ e) = do tinfo # addVarDesc memid vardesc wstr <- stringToWide (idOrigName i) tinfo # setVarName memid wstr setHelpInfo (\ x -> tinfo # setVarDocString memid x) (\ x -> tinfo # setVarHelpContext memid x) i catch (do ti <- tinfo # queryInterface iidICreateTypeInfo2 setCustInfo (\ x y -> ti # setVarCustData memid x y) i) (\ _ -> return ()) return () where attrs = idAttributes i vardesc = TagVARDESC 0 nullWideString (LpvarValue (Just v)) ed 0{-no VARFLAGS-} VAR_CONST ed = TagELEMDESC td pd td = typedesc typelib tinfo ty pd = TagPARAMDESC Nothing 0 v = unsafePerformIO $ case e of Lit l -> do p_bstr <- marshallBSTR (litToString l) var <- allocBytes (fromIntegral sizeofVARIANT) writeVarString (castPtr p_bstr) var -- poorly named, should be writeVarBSTR return var _ -> do -- ToDo: look for other exprs. var <- allocBytes (fromIntegral sizeofVARIANT) hPutStrLn stderr "writeConst: cannot handle expr" writeVarInt 1 var return var memid = case findAttribute "entry" attrs of Just (Attribute _ [ParamLit (IntegerLit (ILit _ x))]) -> fromIntegral x _ -> idx writeConst _ _ _ _ = return () \end{code} \begin{code} -- Not the same as MEMBER_NIL, but the value we -- use to fill in empty slots (which LayOut() will -- decorate for us) with. mEMBER_NULL :: Int16 mEMBER_NULL = 0 mEMBER_NIL :: Int16 mEMBER_NIL = (-1) \end{code} Given the ICreateTypeInfo for an interface and its list of interface names it inherits from - set the inheritance info. In the case of it being "IUnknown" or "IDispatch", we know their home (stdole2) and set the inheritance info accordingly. Note: the assumption is that the ITypeInfo for any non-builtin interfaces will have been put in the Href-cache by now. If not, you lose. \begin{code} setInherit :: InterfaceInherit -> ICreateTypeInfo () -> IO () setInherit [] _ = return () setInherit ((qn,_):_) tinfo = do case lookupTyInfo (qName qn) of Nothing -> let nm = qName qn in hPutStrLn stderr ("Help - inherited from interface: " ++ show nm ++ " , but couldn't find its ITypeInfo") Just it -> do hr <- tinfo # addRefTypeInfo it tinfo # addImplType 0 hr return () setupTyInfoCache :: IO () setupTyInfoCache = do resetTyInfoCache -- create a cross-reference to IUnknown / IDispatch impl in Stdole32 let guid = mkGUID "{00020430-0000-0000-C000-000000000046}" majVer = 2::Int minVer = 0::Int lcid = 0::Int tlbOle <- loadRegTypeLib guid majVer minVer lcid count <- tlbOle # getTypeInfoCount mapM_ (addTy tlbOle) [(0::Word32)..(count-1)] -- some aliases. addTyInfo "IID" (fromMaybe (error "failed to find IID") -- (lookupTyInfo "GUID")) addTyInfo "CLSID" (fromMaybe (error "failed to find CLSID") -- (lookupTyInfo "GUID")) return () where addTy tlb i = do (name,_,_,_) <- tlb # getDocumentationTL (word32ToInt32 i) if (ofInterest name) then do ti <- tlb # getTypeInfo i addTyInfo name ti else return () ofInterest n = n `elem` prim_ls prim_ls = [ "IUnknown" , "IDispatch" , "GUID" ] \end{code} Secret mapping of type names to ITypeInfo* for types we've already grabbed hold of. The i-pointers get mapped to a HREFTYPE val at the point of use. \begin{code} tyi_refs :: IORef [(String, ITypeInfo ())] tyi_refs = unsafePerformIO (newIORef []) resetTyInfoCache :: IO () resetTyInfoCache = writeIORef tyi_refs [] addTyInfo :: String -> ITypeInfo () -> IO () addTyInfo nm iptr = do ls <- readIORef tyi_refs writeIORef tyi_refs ((nm, iptr):ls) lookupTyInfo :: String -> Maybe (ITypeInfo ()) lookupTyInfo nm = unsafePerformIO $ do ls <- readIORef tyi_refs return (lookup nm ls) \end{code} \begin{code} ifSet :: (Num a) => Bool -> a -> a ifSet True x = x ifSet _ _ = 0 fromEnum32 :: Enum a => a -> Word32 fromEnum32 x = fromIntegral (fromEnum x) fromEnum16 :: Enum a => a -> Word16 fromEnum16 x = fromIntegral (fromEnum x) \end{code} Helper functions which abstract away from methods with identical functionality that's provided by both ICreateTypeLib and ICreateTypeInfo. \begin{code} setHelpInfo :: (WideString -> IO ()) -- write out helpstring -> (Word32 -> IO ()) -- write out helpcontext -> Id -> IO () setHelpInfo wr_str wr_ctxt i = do when (notNull doc_str) $ do wstr <- stringToWide doc_str wr_str wstr when (h_ctxt /= 0) (wr_ctxt h_ctxt) return () where attrs = idAttributes i doc_str = case findAttribute "helpstring" attrs of Just (Attribute _ [ParamLit (StringLit str)]) -> str _ -> [] h_ctxt :: Word32 h_ctxt = case findAttribute "helpcontext" attrs of Just (Attribute _ [ParamLit (IntegerLit (ILit _ v))]) -> fromIntegral v _ -> 0 setVersionInfo :: (Word16 -> Word16 -> IO ()) -> Id -> IO () setVersionInfo wr_version i = do when (isJust versionInfo) $ wr_version (fromIntegral major) (fromIntegral minor) where attrs = idAttributes i versionInfo = case findAttribute "version" attrs of Just (Attribute _ [ParamLit (FloatingLit (d,_))]) -> -- sigh, brittle allright. let (maj,min) = break (=='.') d in Just (read maj,read (tail min)) _ -> Nothing Just (major, minor) = versionInfo setGuidInfo :: (Com.GUID -> IO ()) -> Id -> IO () setGuidInfo wr_guid i = when (notNull guid_str) (wr_guid (mkGUID guid_str)) where attrs = idAttributes i guid_str = case getUuidAttribute attrs of Just [g] -> case g of '{':_ -> g _ -> '{':g ++ "}" -- shouldn't happen, but who cares. Just gs -> '{':concat (intersperse "-" gs) ++ "}" _ -> [] setCustInfo :: (Com.GUID -> VARIANT -> IO ()) -> Id -> IO () setCustInfo wr_cust i = mapM_ writeCustom customs where writeCustom (guid, v) = do let p_guid = mkGUID guid p_bstr <- marshallBSTR v var <- allocBytes (fromIntegral sizeofVARIANT) writeVarString (castPtr p_bstr) var -- poorly named, should be writeVarBSTR wr_cust p_guid var attrs = idAttributes i customs = map customise (filterAttributes attrs ["custom"]) customise (Attribute _ [ ParamLit l1, ParamLit l2]) = (s, litToString l2) where s = case (litToString l1) of ls@('{':_) -> ls xs -> '{':xs ++ "}" customise (Attribute _ [ ParamExpr (Lit (GuidLit [s])) , ParamExpr (Lit l) ]) = (s, litToString l) customise a = error ("setCustInfo: oops - can't handle " ++ showCore (ppAttr a)) \end{code} \begin{code} computeTypeFlags :: Id -> Word32 computeTypeFlags i = tflags where attrs = idAttributes i tflags = foldr (\ (x,val) acc -> ifSet (attrs `hasAttributeWithName` x) (fromEnum32 val) .|. acc) 0 [ ("appobject", TYPEFLAG_FAPPOBJECT) , ("creatable", TYPEFLAG_FCANCREATE) , ("licensed", TYPEFLAG_FLICENSED) , ("predecl", TYPEFLAG_FPREDECLID) -- wild&random guess at how this is done at the ODL level -- - exactly what is the function of that attr anyway? , ("hidden", TYPEFLAG_FHIDDEN) , ("control", TYPEFLAG_FCONTROL) , ("dual", TYPEFLAG_FDUAL) , ("nonextensible", TYPEFLAG_FNONEXTENSIBLE) , ("oleautomation", TYPEFLAG_FOLEAUTOMATION) , ("restricted", TYPEFLAG_FRESTRICTED) , ("aggregatable", TYPEFLAG_FAGGREGATABLE) ] computeVarFlags :: Id -> Word16 computeVarFlags i = wflags where attrs = idAttributes i wflags = foldr (\ (x,val) acc -> ifSet (attrs `hasAttributeWithName` x) (fromEnum16 val) .|. acc) 0 [ ("readonly", VARFLAG_FREADONLY) , ("source", VARFLAG_FSOURCE) , ("bindable", VARFLAG_FBINDABLE) , ("requestedit", VARFLAG_FREQUESTEDIT) , ("displaybind", VARFLAG_FDISPLAYBIND) , ("defaultbind", VARFLAG_FDEFAULTBIND) , ("hidden", VARFLAG_FHIDDEN) , ("restricted", VARFLAG_FRESTRICTED) , ("defaultcollelem", VARFLAG_FDEFAULTCOLLELEM) , ("uidefault", VARFLAG_FUIDEFAULT) , ("nonbrowsable", VARFLAG_FNONBROWSABLE) , ("replaceable", VARFLAG_FREPLACEABLE) , ("immediatebind", VARFLAG_FIMMEDIATEBIND) ] \end{code} \begin{code} END_SUPPORT_TYPELIBS -} \end{code}