// // tbscript.cpp // // This module contains the main data which handles the script interface // for the user. All exported APIs are here. // // Copyright (C) 2001 Microsoft Corporation // // Author: a-devjen (Devin Jenson) // #define INITGUID #define _WIN32_DCOM #include #include #include #include #include #include #include #include "CTBShell.h" #include "CTBGlobal.h" #include "CActiveScriptEngine.h" #include "tbscript.h" #include "scpapi.h" #include "resource.h" #define SCPMODULENAME OLESTR("tbscript.exe") void SCPGetModuleFileName(void); BSTR SCPReadFileAsBSTR(BSTR FileName); void SCPFreeBSTR(BSTR Buffer); void __cdecl IdleCallback(HANDLE Connection, LPCSTR Text, DWORD Seconds); void DummyPrintMessage(MESSAGETYPE MessageType, LPCSTR Format, ...); static BOOL DLLIsLoaded = FALSE; static HMODULE DLLModule; static OLECHAR DLLFileName[MAX_PATH]; // Pointers to callbacks, set using the library initialization function PFNIDLECALLBACK g_IdleCallback = NULL; PFNPRINTMESSAGE g_PrintMessage = NULL; // Helper class to ease the nesting chaos that comes // from the lack of exception support in the C++ language // mapping for COM.. struct HRESULT_EXCEPTION { // Default constructor, does nothing HRESULT_EXCEPTION() {} // This constructor acts simply as an operator // to test a value, and throws an exception // if it is invalid. HRESULT_EXCEPTION(HRESULT Result) { if (FAILED(Result)) throw Result; } // This is the main attraction of this class. // When ever we set an invalid HRESULT, we throw // an exception. HRESULT operator = (HRESULT Result) { if (FAILED(Result)) throw Result; return Result; } }; // DisplayEngines // // This "HANDLE" is fake for an internal class. It contains references // to all objects for a given script instance. These mostly include // the script interfaces. SCPAPI void SCPDisplayEngines(void) { // get the component category manager for this machine ICatInformation *pci = 0; unsigned long LanguageCount = 0; CoInitialize(NULL); HRESULT Result = CoCreateInstance(CLSID_StdComponentCategoriesMgr, NULL, CLSCTX_SERVER, IID_ICatInformation, (void **)&pci); if (SUCCEEDED(Result)) { // Get the list of parseable script engines CATID rgcatidImpl[1]; rgcatidImpl[0] = CATID_ActiveScriptParse; IEnumCLSID *pec = 0; Result = pci->EnumClassesOfCategories(1, rgcatidImpl, 0, 0, &pec); if (SUCCEEDED(Result)) { // Print the list of CLSIDs to the console as ProgIDs enum { CHUNKSIZE = 16 }; CLSID rgclsid[CHUNKSIZE]; ULONG cActual; do { Result = pec->Next(CHUNKSIZE, rgclsid, &cActual); if (FAILED(Result)) break; if (Result == S_OK) cActual = CHUNKSIZE; for (ULONG i = 0; i < cActual; i++) { OLECHAR *pwszProgID = 0; if (SUCCEEDED(ProgIDFromCLSID(rgclsid[i], &pwszProgID))) { printf("%S\n", pwszProgID); LanguageCount++; CoTaskMemFree(pwszProgID); } } } while (Result != S_FALSE); pec->Release(); if (LanguageCount == 0) printf("%s", "ERROR: Windows Scripting Host not installed.\n"); else printf("\n* Total Languages: %lu\n", LanguageCount); } else printf("ERROR: Failed to retrieve the Class " "Enumerator (0x%X).\n", Result); pci->Release(); } else printf("ERROR: Failed to load the Category Manager (0x%X).\n", Result); CoUninitialize(); } // CActiveScriptHandle // // This "HANDLE" is fake for an internal class. It contains references // to all objects for a given script instance. These mostly include // the script interfaces. class CActiveScriptHandle { public: // Holds preferred data specified during handle instantiation. TSClientData DesiredData; // COM Class pointers... CActiveScriptEngine *ActiveScriptEngine; IActiveScriptParse *ActiveScriptParse; IActiveScript *ActiveScript; // Pointers to the two script instances known as the "TS" object, and // the "Global" object for which you do not need to specify the name. CTBGlobal *TBGlobal; CTBShell *TBShell; // The default user LCID is stored here... LCID Lcid; // CActiveScriptHandle::CActiveScriptHandle // // The constructor. The handle is now being created // so use nullify needed pointers, and get other default data. // // No return value (called internally). CActiveScriptHandle() { // Zero data ActiveScriptEngine = NULL; ActiveScriptParse = NULL; ActiveScript = NULL; ZeroMemory(&DesiredData, sizeof(DesiredData)); // Ensure COM is initialized CoInitialize(NULL); // Allocate the global object TBGlobal = new CTBGlobal; if (TBGlobal == NULL) { throw -1; } // Tell the new object we hold a reference of it else { TBGlobal->AddRef(); } // Allocate the shell object TBShell = new CTBShell; if (TBShell == NULL) { TBGlobal->Release(); TBGlobal = NULL; throw -1; } // Tell the new object we hold a reference of it else { TBShell->AddRef(); } // The global object uses the shell, it needs a reference as well. TBGlobal->SetShellObjPtr(TBShell); // Allocate a new engine for the script objects ActiveScriptEngine = new CActiveScriptEngine(TBGlobal, TBShell); if (ActiveScriptEngine == NULL) { TBGlobal->Release(); TBGlobal = NULL; TBShell->Release(); TBShell = NULL; throw -1; } // Tell the script engine we hold a reference of it ActiveScriptEngine->AddRef(); // Record the default user LCID Lcid = GetUserDefaultLCID(); // And finally, record this script engine on the global object // for recursive scripting... // (The user can LoadScript() more scripts) TBGlobal->SetScriptEngine((HANDLE)this); } // CActiveScriptHandle::~CActiveScriptHandle // // The destructor. The handle being closed, remove references. // // No return value (called internally). ~CActiveScriptHandle() { // First off we need to release the main IDispatch of // the IActiveScript interface. if (ActiveScript != NULL) { IDispatch *Dispatch = NULL; // Query the to get the reference HRESULT Result = ActiveScript->GetScriptDispatch(0, &Dispatch); // And release it if (SUCCEEDED(Result) && Dispatch != NULL) Dispatch->Release(); ActiveScript = NULL; } // The main script engine first of all, to unbind it. if (ActiveScriptEngine != NULL) { ActiveScriptEngine->Release(); ActiveScriptEngine = NULL; } // Now release the parser if (ActiveScriptParse != NULL) { ActiveScriptParse->Release(); ActiveScriptParse = NULL; } // And the main IActiveScript interface itself. if (ActiveScript != NULL) { ActiveScript->Release(); ActiveScript = NULL; } // The global scripting object if (TBGlobal != NULL) { TBGlobal->Release(); TBGlobal = NULL; } // Finally, the shell or "TS" object. if (TBShell != NULL) { TBShell->Release(); TBShell = NULL; } } }; // TODO: UPDATE THIS FUNCTION WHEN TBSCRIPT BECOMES // OCX OR A COM COMPATIBLE HOST. // // SCPGetModuleFileName // // This routine gets the handle to the TBScript module. // Additionally it also gets the full path where the // module is located on disk. Due to the nature of the // call, the variables are held globally, they are called: // DLLFileName and DLLModule. The function only needs to // be called once, but additional calls are safe and // will be silently ignored. void SCPGetModuleFileName(void) { // Check to see if we already have done this procedure if (DLLIsLoaded == FALSE) { // First get the handle DLLModule = GetModuleHandleW(SCPMODULENAME); // Now copy the file name GetModuleFileNameW(DLLModule, DLLFileName, MAX_PATH); // Indicate we have done this call already DLLIsLoaded = TRUE; } } // SCPLoadTypeInfoFromThisModule // // This loads the OLE code held in a resource of this very module. // // Returns an HRESULT value. HRESULT SCPLoadTypeInfoFromThisModule(REFIID RefIID, ITypeInfo **TypeInfo) { HRESULT Result; ITypeLib *TypeLibrary = NULL; // Ensure we have the handle to the module first SCPGetModuleFileName(); // Use the API now to load the entire TypeLib Result = LoadTypeLib(DLLFileName, &TypeLibrary); // We shouldn't fail, but be prepared... _ASSERT(SUCCEEDED(Result)); // If we succeeded we have more to do if (SUCCEEDED(Result)) { // Nullify the pointer *TypeInfo = NULL; // In this TypeLib, grab the TypeInfo data Result = TypeLibrary->GetTypeInfoOfGuid(RefIID, TypeInfo); // We now have the TypeInfo, and we don't need the TypeLib // anymore, so release it. TypeLibrary->Release(); if (Result == E_OUTOFMEMORY) Result = TYPE_E_ELEMENTNOTFOUND; } return Result; } // SCPReadFileAsBSTR // // Takes a script filename, reads it into COM allocated memory. // Don't forget to call SCPFreeBSTR() when done! // // Returns a pointer to the allocated object is returned // on success, or NULL on failure. BSTR SCPReadFileAsBSTR(BSTR FileName) { BSTR Result = NULL; // Open the file HANDLE File = CreateFileW(FileName, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); // Sanity check if (File != INVALID_HANDLE_VALUE) { // Get the file size DWORD FileSize = GetFileSize(File, 0); // Allocate a block on the local heap to read the file to char *MemBlock = (char *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, FileSize + 1); // This really shouldn't happen _ASSERT(MemBlock != NULL); // Sanity check again if (MemBlock != NULL) { // Read the file into memory DWORD ReadCount; if ( ReadFile(File, MemBlock, FileSize, &ReadCount, 0) ) { // Allocate task memory block Result = (BSTR)CoTaskMemAlloc(sizeof(OLECHAR) * (FileSize + 1)); // Copy from our old buffer to the new one if (Result != NULL) { // Convert to wide-character on the new buffer mbstowcs(Result, MemBlock, FileSize + 1); // Ensure string termination. Result[FileSize] = 0; } } // Free the temporary ASCII memory block HeapFree(GetProcessHeap(), 0, MemBlock); } // Close the file CloseHandle(File); } // Tell the user this failed in debug mode _ASSERT(File != INVALID_HANDLE_VALUE); return Result; } // SCPFreeBSTR // // This function is really a wrapper for releasing task memory blocks // obtained through the function ReadFileAsBSTR // // No return value. void SCPFreeBSTR(BSTR Buffer) { CoTaskMemFree(Buffer); } // SCPNewScriptEngine // // Allocates and initializes a new script engine. // // Returns a handle to the new engine, or NULL on failure. HANDLE SCPNewScriptEngine(BSTR LangName, TSClientData *DesiredData, LPARAM lParam) { CActiveScriptHandle *ActiveScriptHandle = NULL; try { HRESULT_EXCEPTION Result; CLSID ClassID; // Allocate a new handle ActiveScriptHandle = new CActiveScriptHandle(); if (ActiveScriptHandle == NULL) return NULL; // Much of the initialization has already been done now.. but // not enough, we have to manually set some stuff. // Record the user desired data ActiveScriptHandle->TBGlobal->SetPrintMessage(g_PrintMessage); ActiveScriptHandle->TBShell->SetDesiredData(DesiredData); ActiveScriptHandle->TBShell->SetParam(lParam); // Get the class ID of the language Result = CLSIDFromProgID(LangName, &ClassID); // Create an instance of the script parser Result = CoCreateInstance(ClassID, NULL, CLSCTX_ALL, IID_IActiveScriptParse, (void **)&(ActiveScriptHandle->ActiveScriptParse)); // Get the IActiveScript interface Result = ActiveScriptHandle->ActiveScriptParse-> QueryInterface(IID_IActiveScript, (void **)&(ActiveScriptHandle->ActiveScript)); // Set script state to INITIALIZED Result = ActiveScriptHandle->ActiveScriptParse->InitNew(); // Bind our custom made "ActiveScriptSite" to the // ActiveScript interface Result = ActiveScriptHandle->ActiveScript-> SetScriptSite(ActiveScriptHandle->ActiveScriptEngine); // Add the shell and global objects to engine's // namespace and set state to STARTED Result = ActiveScriptHandle->ActiveScript-> AddNamedItem(OLESTR("TS"), SCRIPTITEM_ISVISIBLE | SCRIPTITEM_ISSOURCE); Result = ActiveScriptHandle->ActiveScript-> AddNamedItem(OLESTR("Global"), SCRIPTITEM_ISVISIBLE | SCRIPTITEM_ISSOURCE | SCRIPTITEM_GLOBALMEMBERS); // And globally connect this new script engine Result = ActiveScriptHandle->ActiveScript-> SetScriptState(SCRIPTSTATE_CONNECTED); } // Our handy HRESULT = operator will catch any errors here catch (HRESULT Result) { Result = 0; // If the handle is still active, delete it if(ActiveScriptHandle != NULL) delete ActiveScriptHandle; // Return error return NULL; } // Return the handle return (HANDLE)ActiveScriptHandle; } // SCPRunScript // // Takes a file, and runs it as a script. This will only // return when the script has finished executing. // // Returns TRUE if the script completed successfully, // FALSE otherwise. SCPAPI BOOL SCPRunScript(BSTR LangName, BSTR FileName, TSClientData *DesiredData, LPARAM lParam) { HANDLE EngineHandle; // First read the file into memory. We allocate here instead of // calling SCPParseScriptFile in one shot because if the allocation // fails, there is no reason to create a script engine, which in // this case it won't. BSTR Code = SCPReadFileAsBSTR(FileName); if (Code == NULL) return FALSE; // Next create the script control EngineHandle = SCPNewScriptEngine(LangName, DesiredData, lParam); if (EngineHandle == NULL) { SCPFreeBSTR(Code); return FALSE; } // Parse the script into the engine if (SCPParseScript(EngineHandle, Code) == FALSE) { SCPFreeBSTR(Code); return FALSE; } // Success, free the script code SCPFreeBSTR(Code); // Close the script engine SCPCloseScriptEngine(EngineHandle); return TRUE; } // SCPParseScriptFile // // Takes a file, reads it into memory, and parses it into the script engine. // This function only returns when the parsing has completed. // // Returns TRUE on success, or FALSE on failure. BOOL SCPParseScriptFile(HANDLE EngineHandle, BSTR FileName) { // First read the file into memory BSTR Code = SCPReadFileAsBSTR(FileName); if(Code == NULL) return FALSE; // Next parse it if(SCPParseScript(EngineHandle, Code) == FALSE) { SCPFreeBSTR(Code); return FALSE; } SCPFreeBSTR(Code); return TRUE; } // SCPParseScript // // Reads a script in memory, and parses it into the script engine. // This function only returns when the parsing has completed. // // Returns TRUE on success, or FALSE on failure. BOOL SCPParseScript(HANDLE EngineHandle, BSTR Script) { // First cast the engine handle over to something we can use CActiveScriptHandle *ActiveScriptHandle = (CActiveScriptHandle *)EngineHandle; HRESULT Result = E_FAIL; // Make exception data EXCEPINFO ExceptInfo = { 0 }; // Parse the script using the ActiveScript API Result = ActiveScriptHandle->ActiveScriptParse->ParseScriptText(Script, 0, 0, 0, 0, 0, SCRIPTTEXT_ISPERSISTENT | SCRIPTTEXT_ISVISIBLE, 0, &ExceptInfo); return SUCCEEDED(Result); } // SCPCloseScriptEngine // // Closes a script handle simply by deleting it. // // No return value. void SCPCloseScriptEngine(HANDLE EngineHandle) { // First cast the engine handle over to something we can use CActiveScriptHandle *ActiveScriptHandle = (CActiveScriptHandle *)EngineHandle; // Release it from memory.. the deconstructor does all the work. if (ActiveScriptHandle != NULL) delete ActiveScriptHandle; } // SCPCleanupLibrary // // This should only be called when all script engines are unloaded // and the module is going to be uninitialized. // // No return value. SCPAPI void SCPCleanupLibrary(void) { CoUninitialize(); g_IdleCallback = NULL; } // SCPStartupLibrary // // Simply initializes the library, setting up the callback routine. // This should be called before using any other script procedures. // // No return value. SCPAPI void SCPStartupLibrary(SCINITDATA *InitData, PFNIDLECALLBACK fnIdleCallback) { // Record our idle callback function g_IdleCallback = fnIdleCallback; if(InitData != NULL) { __try { // Record the print message function in the InitData structure if(InitData != NULL) g_PrintMessage = InitData->pfnPrintMessage; } __except (EXCEPTION_EXECUTE_HANDLER) { // Bad pointer, simply initialize T2Client with our own // callback then. SCINITDATA LibInitData = { DummyPrintMessage }; T2Init(&LibInitData, IdleCallback); return; } } // Initialize with T2Client now. T2Init(InitData, IdleCallback); } // IdleCallback // // This is an internal wrapping callback procedure used // for redirecting idle messages. // // No return value. void __cdecl IdleCallback(HANDLE Connection, LPCSTR Text, DWORD Seconds) { LPARAM lParam = 0; // Get the parameter for the connection, and pass it back to the user if (g_IdleCallback != NULL && T2GetParam(Connection, &lParam) == NULL) g_IdleCallback(lParam, Text, Seconds); } // DummyPrintMessage // // Filler in case the user messes up. // // No return value. void DummyPrintMessage(MESSAGETYPE MessageType, LPCSTR Format, ...) { return; }