// IForms.h : Declaration of the CIntelliForms class #ifndef __IFORMS_H_ #define __IFORMS_H_ #include "iforms.h" const TCHAR c_szRegKeySMIEM[] = TEXT("Software\\Microsoft\\Internet Explorer\\Main"); const TCHAR c_szRegKeyIntelliForms[] = TEXT("Software\\Microsoft\\Internet Explorer\\IntelliForms"); const WCHAR c_wszRegKeyIntelliFormsSPW[] = TEXT("Software\\Microsoft\\Internet Explorer\\IntelliForms\\SPW"); const TCHAR c_szRegKeyRestrict[] = TEXT("Software\\Policies\\Microsoft\\Internet Explorer\\Control Panel"); const TCHAR c_szRegValUseFormSuggest[] = TEXT("Use FormSuggest"); const TCHAR c_szRegValFormSuggestRestrict[] = TEXT("FormSuggest"); const TCHAR c_szRegValSavePasswords[] = TEXT("FormSuggest Passwords"); const TCHAR c_szRegValAskPasswords[] = TEXT("FormSuggest PW Ask"); const TCHAR c_szRegValAskUser[] = TEXT("AskUser"); interface IAutoComplete2; interface IAutoCompleteDropDown; class CStringList; #define IF_CHAR WM_APP + 0x08 #define IF_KEYDOWN WM_APP + 0x09 #define IF_IME_COMPOSITION WM_APP + 0x0A ///////////////////////////////////////////////////////////////////////////// // CIntelliForms class CEventSinkCallback { public: typedef enum { EVENT_BOGUS = 100, EVENT_KEYDOWN = 0, EVENT_KEYPRESS, EVENT_MOUSEDOWN, EVENT_DBLCLICK, EVENT_FOCUS, EVENT_BLUR, EVENT_SUBMIT, EVENT_SCROLL, EVENT_COMPOSITION, EVENT_NOTIFY, } EVENTS; typedef struct { EVENTS Event; LPCWSTR pwszEventSubscribe; LPCWSTR pwszEventName; } EventSinkEntry; virtual HRESULT HandleEvent(IHTMLElement *pEle, EVENTS Event, IHTMLEventObj *pEventObj) = 0; static EventSinkEntry EventsToSink[]; }; class CEditEventSinkCallback { public: virtual HRESULT PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj* pIEventObj) = 0; }; class CIntelliForms : public CEventSinkCallback, public CEditEventSinkCallback { long m_cRef; public: class CEventSink; class CEditEventSink; class CAutoSuggest; friend CAutoSuggest; CIntelliForms(); ~CIntelliForms(); public: // IUnknown STDMETHODIMP QueryInterface(REFIID, void **); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // CEventSinkCallback HRESULT HandleEvent(IHTMLElement *pEle, EVENTS Event, IHTMLEventObj *pEventObj); // CEditEventSinkCallback HRESULT PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj* pIEventObj); public: HRESULT Init(CIEFrameAuto::COmWindow *pOmWindow, IHTMLDocument2 *pDoc2, HWND hwnd); HRESULT UnInit(); LPCWSTR GetUrl(); HRESULT UserInput(IHTMLInputTextElement *pTextEle); HRESULT WriteToStore(LPCWSTR pwszName, CStringList *psl); HRESULT ReadFromStore(LPCWSTR pwszName, CStringList **ppsl, BOOL fPasswordList=FALSE); HRESULT DeleteFromStore(LPCWSTR pwszName); HRESULT ClearStore(DWORD dwClear); BOOL IsRestricted() { return m_fRestricted; } BOOL IsRestrictedPW() { return m_fRestrictedPW; } IUnknown *GetDocument() { return m_punkDoc2; } HRESULT ScriptSubmit(IHTMLFormElement *pForm); HRESULT HandleFormSubmit(IHTMLFormElement *pForm); // for CEnumString HRESULT GetPasswordStringList(CStringList **ppslPasswords); // for IntelliFormsSaveForm CIntelliForms *GetNext() { return m_pNext; } BOOL IsEnabledForPage(); static HRESULT GetName(IHTMLInputTextElement *pTextEle, BSTR *pbstrName); // Default to disabled, since we need to ask the user before enabling it static BOOL IsEnabledInCPL() { return IsEnabledInRegistry(c_szRegKeySMIEM, c_szRegValUseFormSuggest, FALSE); } // Default to enabled, since we prompt before saving passwords anyway static BOOL IsEnabledRestorePW() { return IsEnabledInRegistry(c_szRegKeySMIEM, c_szRegValSavePasswords, TRUE); } static BOOL IsEnabledAskPW() { return IsEnabledRestorePW() && IsEnabledInRegistry(c_szRegKeySMIEM, c_szRegValAskPasswords, TRUE); } static BOOL IsAdminRestricted(LPCTSTR pszRegVal); BOOL AskedUserToEnable(); typedef HRESULT (*PFN_ENUM_CALLBACK)(IDispatch *pDispEle, DWORD_PTR dwCBData); HRESULT ActiveElementChanged(IHTMLElement * pHTMLElement); protected: enum { LIST_DATA_PASSWORD = 1 }; // Flag to indicate a password list in store HRESULT AddToElementList(IHTMLInputTextElement *pITE); HRESULT FindInElementList(IHTMLInputTextElement *pITE); void FreeElementList(); HRESULT AddToFormList(IHTMLFormElement *pFormEle); HRESULT FindInFormList(IHTMLFormElement *pFormEle); void FreeFormList(); static BOOL IsElementEnabled(IHTMLElement *pEle); static HRESULT ShouldAttachToElement(IUnknown *, BOOL fCheckForm, IHTMLElement2**, IHTMLInputTextElement**, IHTMLFormElement**, BOOL *pfPassword); HRESULT GetBodyEle(IHTMLElement2 **ppEle2); HRESULT SubmitElement(IHTMLInputTextElement *pITE, FILETIME ft, BOOL fEnabledInCPL); LPCWSTR GetUrlHash(); BOOL ArePasswordsSaved(); BOOL LoadPasswords(); void SavePasswords(); HRESULT FindPasswordEntry(LPCWSTR pwszValue, int *piIndex); void SetPasswordsAreSaved(BOOL fSaved); HRESULT AutoFillPassword(IHTMLInputTextElement *pTextEle, LPCWSTR pwszUsername); HRESULT SavePassword(IHTMLFormElement *pFormEle, FILETIME ftSubmit, IHTMLInputTextElement *pFirstEle); HRESULT DeletePassword(LPCWSTR pwszUsername); HRESULT AttachToForm(IHTMLFormElement *pFormEle); HRESULT CreatePStore(); HRESULT CreatePStoreAndType(); void ReleasePStore(); static BOOL IsEnabledInRegistry(LPCTSTR pszKey, LPCTSTR pszValue, BOOL fDefault); inline void EnterModalDialog(); inline void LeaveModalDialog(); private: // CIntelliForms member variables CEventSink *m_pSink; CEditEventSink *m_pEditSink; CAutoSuggest *m_pAutoSuggest; // Can attach to one edit control at a time HINSTANCE m_hinstPStore; IPStore *m_pPStore; BOOL m_fPStoreTypeInit : 1; // Our types initialized HDPA m_hdpaElements; // Elements user has modified HDPA m_hdpaForms; // Forms we are sinked to BOOL m_fCheckedIfEnabled : 1; // Checked if we're enabled for this page? BOOL m_fEnabledForPage : 1; // We're enabled for this page (non-SSL)? BOOL m_fHitPWField : 1; // Went to a password field? BOOL m_fCheckedPW : 1; // Checked if we have a password for this URL? CStringList *m_pslPasswords; // Usernames && Passwords for page, if any int m_iRestoredIndex; // Index of restored password in m_pslPasswords (-1=none) BOOL m_fRestricted : 1; // Are we restricted for normal Intelliforms? BOOL m_fRestrictedPW : 1; // Are save passwords restricted? // Lifetime management - see Enter/LeaveModalDialog BOOL m_fInModalDialog : 1; // Are we in a dialog? BOOL m_fUninitCalled : 1; // Was Uninit called during dialog? // Useful stuff for the attached document HWND m_hwndBrowser; IHTMLDocument2 *m_pDoc2; IUnknown *m_punkDoc2; CIEFrameAuto::COmWindow *m_pOmWindow; BSTR m_bstrFullUrl; // Full url if https: protocol (security check) BSTR m_bstrUrl; // Full url with anchor/query string stripped LPCWSTR m_pwszUrlHash; // String based on UrlHash(m_bstrUrl) // Linked list of objects, to find CIntelliForms object for IHTMLDocument2 CIntelliForms *m_pNext; public: // GUID to use for subtype of PStore - identity GUID or c_PStoreType GUID m_guidUserId; public: // Helper classes template class CEnumCollection { public: static HRESULT EnumCollection(TYPE *pCollection, PFN_ENUM_CALLBACK pfnCB, DWORD_PTR dwCBData); }; // Sinks regular Trident events. Calls back via CEventSinkCallback class CEventSink : public IDispatch { ULONG m_cRef; public: CEventSink(CEventSinkCallback *pParent); ~CEventSink(); HRESULT SinkEvents(IHTMLElement2 *pEle2, int iNum, EVENTS *pEvents); HRESULT UnSinkEvents(IHTMLElement2 *pEle2, int iNum, EVENTS *pEvents); HRESULT SinkEvents(IHTMLWindow3 *pWin3, int iNum, EVENTS *pEvents); HRESULT UnSinkEvents(IHTMLWindow3 *pWin3, int iNum, EVENTS *pEvents); void SetParent(CEventSinkCallback *pParent) { m_pParent = pParent; } STDMETHODIMP QueryInterface(REFIID, void **); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // IDispatch STDMETHODIMP GetTypeInfoCount(UINT* pctinfo); STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo** ppTInfo); STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId); STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr); private: CEventSinkCallback *m_pParent; }; // Sinks editing Trident events. Required for IME events. Callback CEditEventSinkCallback class CEditEventSink : public IHTMLEditDesigner { ULONG m_cRef; public: CEditEventSink(CEditEventSinkCallback *pParent); ~CEditEventSink(); HRESULT Attach(IUnknown *punkElement); // Attach(NULL) to detach void SetParent(CEditEventSinkCallback *pParent) { m_pParent = pParent; } STDMETHODIMP QueryInterface(REFIID, void **); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // IHTMLEditDesigner STDMETHODIMP PreHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj); STDMETHODIMP PostHandleEvent(DISPID inEvtDispId, IHTMLEventObj *pIEventObj); STDMETHODIMP TranslateAccelerator(DISPID inEvtDispId, IHTMLEventObj *pIEventObj); STDMETHODIMP PostEditorEventNotify(DISPID inEvtDispId, IHTMLEventObj *pIEventObj) {return S_FALSE;} private: CEditEventSinkCallback *m_pParent; IHTMLEditServices *m_pEditServices; // we keep a ref so we can unsink }; class CAutoSuggest : public CEventSinkCallback { class CEnumString; public: CAutoSuggest(CIntelliForms *pParent, BOOL fEnabled, BOOL fEnabledSPW); ~CAutoSuggest(); void SetParent(CIntelliForms *pParent) { m_pParent = pParent; } HRESULT AttachToInput(IHTMLInputTextElement *pTextEle); HRESULT DetachFromInput(); static LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); static EVENTS s_EventsToSink[]; protected: // Called by window to perform requests by CAutoComplete to MSHTML HRESULT GetText(int cchTextMax, LPWSTR pszTextOut, LRESULT *lcchCopied); HRESULT GetTextLength(int *pcch); HRESULT SetText(LPCWSTR pszTextIn); void CheckAutoFillPassword(LPCWSTR pwszUsername); inline void MarkDirty(); public: // Called to pass on events from MSHTML to CAutoComplete HRESULT HandleEvent(IHTMLElement *pEle, EVENTS Event, IHTMLEventObj *pEventObj); HRESULT UpdateDropdownPosition(); IHTMLInputTextElement *AttachedElement() { return m_pTextEle; } private: HRESULT CreateAutoComplete(); HRESULT CleanUp(); CIntelliForms *m_pParent; // No refcount CEventSink *m_pEventSink; IAutoComplete2 *m_pAutoComplete; IAutoCompleteDropDown *m_pAutoCompleteDD; HWND m_hwndEdit; IHTMLInputTextElement *m_pTextEle; CEnumString *m_pEnumString; long m_lCancelKeyPress; BOOL m_fAddedToDirtyList : 1; // Add to list once they hit a key BOOL m_fAllowAutoFillPW : 1; // Call AutoFillPassword? BSTR m_bstrLastUsername; // Last Username we called AutoFillPassword for BOOL m_fInitAutoComplete : 1; // Initialized Auto Complete? BOOL m_fEnabled : 1; // Regular intelliforms enabled? BOOL m_fEnabledPW : 1; // Restore passwords enabled? BOOL m_fEscapeHit : 1; // Escape key used to dismiss dropdown? UINT m_uMsgItemActivate; // registered message from autocomplete static BOOL s_fRegisteredWndClass; // This object is thread-safed because AutoComplete calls on second thread class CEnumString : public IEnumString { long m_cRef; public: CEnumString(); ~CEnumString(); HRESULT Init(IHTMLInputTextElement *pInputEle, CIntelliForms *pIForms); void UnInit(); HRESULT ResetEnum(); STDMETHODIMP QueryInterface(REFIID, void **); STDMETHODIMP_(ULONG) AddRef(void); STDMETHODIMP_(ULONG) Release(void); // IEnumString virtual STDMETHODIMP Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched); virtual STDMETHODIMP Skip(ULONG celt) { return E_NOTIMPL; } virtual STDMETHODIMP Reset(); virtual STDMETHODIMP Clone(IEnumString **ppenum) { return E_NOTIMPL; } protected: HRESULT FillEnumerator(); // called on secondary thread CRITICAL_SECTION m_crit; CStringList *m_pslMain; BSTR m_bstrName; // name of input field LPWSTR m_pszOpsValue; // value from profile assistant CIntelliForms *m_pIntelliForms; int m_iPtr; BOOL m_fFilledStrings : 1; BOOL m_fInit : 1; }; }; }; template HRESULT CIntelliForms::CEnumCollection::EnumCollection( TYPE *pCollection, PFN_ENUM_CALLBACK pfnCB, DWORD_PTR dwCBData) { IDispatch *pDispItem; HRESULT hr; long l, lCount; VARIANT vIndex, vEmpty; VariantInit(&vEmpty); VariantInit(&vIndex); hr = pCollection->get_length(&lCount); if (FAILED(hr)) lCount = 0; for (l=0; litem(vIndex, vEmpty, &pDispItem); if (SUCCEEDED(hr) && pDispItem) { hr = pfnCB(pDispItem, dwCBData); pDispItem->Release(); } if (E_ABORT == hr) { break; } } return hr; } inline void CIntelliForms::CAutoSuggest::MarkDirty() { if (!m_fAddedToDirtyList && m_pParent) { m_fAddedToDirtyList = TRUE; m_pParent->UserInput(m_pTextEle); } } // These wrap modal dialogs, keeping us alive and attached to the document // even if something weird happens while our dlgbox messageloop is alive inline void CIntelliForms::EnterModalDialog() { ASSERT(!m_fInModalDialog); // Don't support nested Enter/Leave ASSERT(!m_fUninitCalled); m_fInModalDialog = TRUE; // Keep us attached to document AddRef(); // Keep us alive } inline void CIntelliForms::LeaveModalDialog() { ASSERT(m_fInModalDialog); m_fInModalDialog = FALSE; if (m_fUninitCalled) { UnInit(); // Detach from document } Release(); } // HKCU/S/MS/Win/CV/IForms/Names /[name]/ SIndex | SData // CStringList is optimized for appending arbitrary amounts of strings and converting to and // from blobs. It is not optimized for deleting or inserting strings. class CStringList { protected: CStringList(); public: ~CStringList(); friend static HRESULT CStringList_New(CStringList **ppNew, BOOL fAutoDelete=TRUE); // E_FAIL, S_FALSE (duplicate), S_OK HRESULT AddString(LPCWSTR lpwstr, int *piNum = NULL); HRESULT AddString(LPCWSTR lpwstr, FILETIME ft, int *piNum = NULL); // E_FAIL, S_OK Doesn't check for duplicates HRESULT AppendString(LPCWSTR lpwstr, int *piNum = NULL); HRESULT AppendString(LPCWSTR lpwstr, FILETIME ft, int *piNum = NULL); // iLen must be length in characters of string, not counting null term. // -1 if unknown. *piNum filled in with index if specified HRESULT FindString(LPCWSTR lpwstr, int iLen/*=-1*/, int *piNum/*=NULL*/, BOOL fCaseSensitive); inline int NumStrings(); inline LPCWSTR GetString(int iIndex); inline LPWSTR GetStringPtr(int iIndex); inline DWORD GetStringLen(int iIndex); inline HRESULT GetStringTime(int iIndex, FILETIME *ft); inline HRESULT SetStringTime(int iIndex, FILETIME ft); inline HRESULT UpdateStringTime(int iIndex, FILETIME ft); // inline HRESULT GetStringData(int iIndex, DWORD *pdwData); // inline HRESULT SetStringData(int iIndex, DWORD dwData); HRESULT GetBSTR(int iIndex, BSTR *pbstrRet); HRESULT GetTaskAllocString(int iIndex, LPOLESTR *pRet); inline HRESULT GetListData(INT64 *piData); inline HRESULT SetListData(INT64 iData); // If set to TRUE, CStringList will delete old strings when full void SetAutoScavenge(BOOL fAutoScavenge) { m_fAutoScavenge=fAutoScavenge; } HRESULT DeleteString(int iIndex); HRESULT InsertString(int iIndex, LPCWSTR lpwstr); HRESULT ReplaceString(int iIndex, LPCWSTR lpwstr); // Functions to read/write to the store; converts to and from BLOBs // For efficiencies sake these take and return heap alloced blobs HRESULT WriteToBlobs(LPBYTE *ppBlob1, DWORD *pcbBlob1, LPBYTE *ppBlob2, DWORD *pcbBlob2); HRESULT ReadFromBlobs(LPBYTE *ppBlob1, DWORD cbBlob1, LPBYTE *ppBlob2, DWORD cbBlob2); static HRESULT GetFlagsFromIndex(LPBYTE pBlob1, INT64 *piFlags); // Warning: Don't set max strings past the MAX_STRINGS constant our ReadFromBlobs will fail // if you save/restore the string list void SetMaxStrings(DWORD dwMaxStrings) { m_dwMaxStrings = dwMaxStrings; } DWORD GetMaxStrings() { return m_dwMaxStrings; } enum { MAX_STRINGS = 200 }; protected: enum { INDEX_SIGNATURE=0x4B434957 }; // WICK enum { INIT_BUF_SIZE=1024 }; #pragma warning (disable: 4200) // zero-sized array warning typedef struct { DWORD dwSignature; // Offset: 00 DWORD cbSize; // Offset: 04 (up to not including first StringEntry) DWORD dwNumStrings; // Offset: 08 (Num of StringEntry present) // Offset: 0C (--PAD--) INT64 iData; // Offset: 10 (Extra data for string list user) struct tagStringEntry { DWORD dwStringPtr; // Offset: 18 (Offset of string in buffer) FILETIME ftLastSubmitted; // Offset: 1C (filetime of last submit) (unaligned) DWORD dwStringLen; // Offset: 24 (Length of this string) } StringEntry[]; } StringIndex; #pragma warning (default: 4200) // Value for cbSize in StringIndex #define STRINGINDEX_CBSIZE PtrToUlong(&((StringIndex*)NULL)->StringEntry) #define STRINGENTRY_SIZE (PtrToUlong(&((StringIndex*)NULL)->StringEntry[1]) - STRINGINDEX_CBSIZE ) // Size of StringIndex for given number of strings #define INDEX_SIZE(n) (STRINGINDEX_CBSIZE + (n)*STRINGENTRY_SIZE) void CleanUp(); HRESULT Init(DWORD dwBufSize=0); HRESULT Validate(); HRESULT EnsureBuffer(DWORD dwSizeNeeded); HRESULT EnsureIndex(DWORD dwNumStringsNeeded); HRESULT _AddString(LPCWSTR lpwstr, BOOL fCheckDuplicates, int *piNum); private: StringIndex *m_psiIndex; // Index of strings DWORD m_dwIndexSize; // size in bytes of m_psiIndex LPBYTE m_pBuffer; // Holds all character data DWORD m_dwBufEnd; // Last byte used in buffer DWORD m_dwBufSize; // Size of buffer in bytes DWORD m_dwMaxStrings; // Max # strings BOOL m_fAutoScavenge:1; // Automatically remove old strings when full? }; // We really only use this for comparing to 0, so this method works just as well and does not require alignment. #define FILETIME_TO_INT(ft) (ft.dwLowDateTime | ft.dwHighDateTime) inline int CStringList::NumStrings() { if (!m_psiIndex) return 0; return m_psiIndex->dwNumStrings; } inline LPCWSTR CStringList::GetString(int iIndex) { if (!m_psiIndex) return NULL; ASSERT((DWORD)iIndex < m_psiIndex->dwNumStrings); return (LPCWSTR) (m_pBuffer + m_psiIndex->StringEntry[iIndex].dwStringPtr); } inline LPWSTR CStringList::GetStringPtr(int iIndex) { if (!m_psiIndex) return NULL; ASSERT((DWORD)iIndex < m_psiIndex->dwNumStrings); return (LPWSTR) (m_pBuffer + m_psiIndex->StringEntry[iIndex].dwStringPtr); } inline DWORD CStringList::GetStringLen(int iIndex) { if (!m_psiIndex) return E_FAIL; ASSERT((DWORD)iIndex < m_psiIndex->dwNumStrings); return m_psiIndex->StringEntry[iIndex].dwStringLen; } inline HRESULT CStringList::GetStringTime(int iIndex, FILETIME *ft) { if (!m_psiIndex) return E_FAIL; ASSERT((DWORD)iIndex < m_psiIndex->dwNumStrings); *ft = m_psiIndex->StringEntry[iIndex].ftLastSubmitted; return S_OK; } inline HRESULT CStringList::SetStringTime(int iIndex, FILETIME ft) { if (!m_psiIndex) return E_FAIL; ASSERT((DWORD)iIndex < m_psiIndex->dwNumStrings); ASSERT(-1 != CompareFileTime(&ft, &m_psiIndex->StringEntry[iIndex].ftLastSubmitted)); m_psiIndex->StringEntry[iIndex].ftLastSubmitted = ft; return S_OK; } inline HRESULT CStringList::UpdateStringTime(int iIndex, FILETIME ft) { if (!m_psiIndex) return E_FAIL; ASSERT((DWORD)iIndex < m_psiIndex->dwNumStrings); if (1 == CompareFileTime(&ft, &m_psiIndex->StringEntry[iIndex].ftLastSubmitted)) { m_psiIndex->StringEntry[iIndex].ftLastSubmitted = ft; return S_OK; } return S_FALSE; } inline HRESULT CStringList::GetListData(INT64 *piData) { if (m_psiIndex) { *piData = m_psiIndex->iData; return S_OK; } return E_FAIL; } inline HRESULT CStringList::SetListData(INT64 iData) { if (!m_psiIndex && FAILED(Init())) return E_FAIL; m_psiIndex->iData = iData; return S_OK; } /* inline HRESULT CStringList::GetStringData(int iIndex, DWORD *pdwData) { if (!m_psiIndex) return E_FAIL; ASSERT((DWORD)iIndex < m_psiIndex->dwNumStrings); *pdwData = m_psiIndex->StringEntry[iIndex].dwData; return S_OK; } inline HRESULT CStringList::SetStringData(int iIndex, DWORD dwData) { if (!m_psiIndex) return E_FAIL; ASSERT((DWORD)iIndex < m_psiIndex->dwNumStrings); m_psiIndex->StringEntry[iIndex].dwData = dwData; return S_OK; } */ #endif //__IFORMS_H_