// Main functions for an example program that loads and executes a script.
#define _WIN32_WINNT 0x0400
#include <windows.h>
#include <process.h>
#include <commctrl.h>
#include <objbase.h>
#include <activscp.h>
#include <tchar.h>
#include "IActiveScriptSite.h"
#include "AppObject.h"
#include "resource.h"
#include "guids.h"
#include "extern.h"
typedef struct {
IActiveScript *EngineActiveScript;
HANDLE ThreadHandle;
LPCTSTR Filename;
GUID Guid;
} MYARGS;
// This app's handle
HINSTANCE InstanceHandle;
// Main window of our app
HWND MainWindow;
HFONT FontHandle;
// For running a script
MYARGS MyArgs;
// Class name for my text viewer window
static const TCHAR TextViewerName[] = _T("TextViewer");
static const TCHAR NewLine[] = _T("\r\n");
// For getting engine's GUID from registry
static const TCHAR CLSIDStr[] = _T("CLSID");
static const TCHAR ScriptEngineStr[] = _T("ScriptEngine");
// Error message box
static const TCHAR ErrorStr[] = _T("Error");
static const wchar_t ErrorStrWide[] = L"Error";
/***************** getITypeInfoFromExe() ***************
* Loads/creates an ITypeInfo for the specified GUID.
*
* guid = The GUID of the Object/VTable for
* which we want an ITypeInfo.
* iTypeInfo = Where to return the ITypeInfo.
*
* RETURNS: 0 if success, or HRESULT if fail.
*
* NOTE: Our type library must be embedded as a custom
* resource in this EXE using the following line:
*
* 1 TYPELIB MOVEABLE PURE "some_name.tlb"
*
* Where "some_name.tlb" would be the name of the type library.
*
* The caller must AddRef() the returned ITypeInfo to keep
* it in memory.
*/
HRESULT getITypeInfoFromExe(const GUID &guid, ITypeInfo **iTypeInfo)
{
wchar_t fileName[MAX_PATH];
ITypeLib *typeLib;
register HRESULT hr;
// Assume an error
*iTypeInfo = 0;
// Load the type library from our EXE's resources
::GetModuleFileNameW(0, &fileName[0], MAX_PATH);
if (!(hr = ::LoadTypeLib(&fileName[0], &typeLib)))
{
// Let Microsoft's GetTypeInfoOfGuid() create a generic ITypeInfo
// for the requested item (whose GUID is passed)
hr = typeLib->GetTypeInfoOfGuid(guid, iTypeInfo);
// We no longer need the type library
typeLib->Release();
}
return(hr);
}
/********************* display_sys_error() ********************
* Displays a messagebox for the passed OS error number.
*
* NOTE: If passed a 0, this calls GetLastError().
*/
void display_sys_error(DWORD err)
{
TCHAR buffer[160];
if (!err) err = ::GetLastError(); // If passed a 0, caller wants us to call GetLastError(). Do it FIRST!
buffer[0] = 0;
::FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, 0, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &buffer[0], 160, 0);
::MessageBox(MainWindow, &buffer[0], &ErrorStr[0], MB_OK);
}
/*********************** loadUnicodeScript() ********************
* Reads a script off of disk, and copies it to a UNICODE
* buffer.
*
* fn = Filename of script.
*
* RETURNS: A pointer to the allocated UNICODE buffer if success,
* or zero if failure.
*
* NOTE: Caller must GlobalFree() the returned buffer.
*
* Displays an error message if a failure.
*/
static OLECHAR * loadUnicodeScript(LPCTSTR fn)
{
register OLECHAR *script;
register HANDLE hfile;
DWORD error;
// Assume no error
error = 0;
// Open the file
if ((hfile = ::CreateFile(fn, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0)) != INVALID_HANDLE_VALUE)
{
DWORD filesize;
char *psz;
// Get a buffer to read in the file
filesize = ::GetFileSize(hfile, 0);
if ((psz = (char *)::GlobalAlloc(GMEM_FIXED, filesize + 1)))
{
DWORD read;
// Read in the file
::ReadFile(hfile, psz, filesize, &read, 0);
// Get a buffer to convert to UNICODE (plus an extra wchar_t to nul-terminate it since
// the engine's IActiveScriptParse ParseScriptText expects that)
if ((script = (OLECHAR *)::GlobalAlloc(GMEM_FIXED, (filesize + 1) * sizeof(OLECHAR))))
{
// Convert to UNICODE and nul-terminate
::MultiByteToWideChar(CP_ACP, 0, psz, filesize, script, filesize + 1);
script[filesize] = 0;
}
else
error = ::GetLastError();
::GlobalFree(psz);
}
else
error = ::GetLastError();
::CloseHandle(hfile);
}
else
error = ::GetLastError();
if (error)
{
::PostMessage(MainWindow, WM_APP, 0, error);
script = 0;
}
return(script);
}
/********************** display_COM_error() ********************
* Displays a messagebox for a COM error.
*
* msg = Format string for sprintf().
* hr = COM error number.
*
* NOTE: Total size of error msg must be < 256 TCHARs.
*/
void display_COM_error(LPCTSTR msg, HRESULT hr)
{
TCHAR buffer[256];
wsprintf(&buffer[0], msg, hr);
::MessageBox(MainWindow, &buffer[0], &ErrorStr[0], MB_OK|MB_ICONEXCLAMATION);
}
/************************** runScript() ***********************
* Runs a script (on disk).
*
* args = Contains the filename of the script to run, and
* the GUID of the script engine that runs the script.
*/
unsigned int __stdcall runScript(void *args)
{
register HRESULT hr;
IActiveScriptParse *activeScriptParse;
// Each thread must initialize the COM system individually
::CoInitialize(0);
// Create an instance of the script engine, and get its IActiveScript object
if ((hr = ::CoCreateInstance(((MYARGS *)args)->Guid, 0, CLSCTX_ALL, IID_IActiveScript, (void **)&((MYARGS *)args)->EngineActiveScript)))
::PostMessage(MainWindow, WM_APP, (WPARAM)"Can't get engine's IActiveScript: %08X", hr);
else
{
// Get the script engine's IActiveScriptParse object (which we can do from its
// IActiveScript's QueryInterface since IActiveScriptParse is a
// sub-object of the IActiveScript)
if ((hr = ((MYARGS *)args)->EngineActiveScript->QueryInterface(IID_IActiveScriptParse, (void **)&activeScriptParse)))
::PostMessage(MainWindow, WM_APP, (WPARAM)"Can't get engine's IActiveScriptParse: %08X", hr);
else
{
// Initialize the engine. This just lets the engine internally
// initialize some stuff in preparation of us adding scripts to it
// for the first time
if ((hr = activeScriptParse->InitNew()))
::PostMessage(MainWindow, WM_APP, (WPARAM)"Can't initialize engine : %08X", hr);
else
{
// Give the engine our IActiveScriptSite object. If all goes well,
// the engine will call its QueryInterface (which will AddRef it)
if ((hr = ((MYARGS *)args)->EngineActiveScript->SetScriptSite((IActiveScriptSite *)&MyActiveScriptSite)))
::PostMessage(MainWindow, WM_APP, (WPARAM)"Can't set our IScriptSite : %08X", hr);
else
{
// Add our own application object to engine's namespace (ie, add it to the script)
if ((hr = ((MYARGS *)args)->EngineActiveScript->AddNamedItem(&MyRealIApp.MyAppObjectName[0], SCRIPTITEM_ISVISIBLE|SCRIPTITEM_NOCODE)))
::PostMessage(MainWindow, WM_APP, (WPARAM)"Can't add our application object to engine's namespace : %08X", hr);
else
{
register LPOLESTR str;
// Load the script from disk. NOTE: We need to load it UNICODE for ParseScriptText()
if (!(str = loadUnicodeScript(((MYARGS *)args)->Filename)))
::PostMessage(MainWindow, WM_APP, (WPARAM)"Can't load script from disk", E_FAIL);
else
{
// Have the script engine parse it and add it to its internal list
// of scripts to run. NOTE: We don't pass any object name, so this
// script is put inside the "global or default object" and therefore
// this script will be run as soon as we set the engine to the
// connected state
hr = activeScriptParse->ParseScriptText(str, 0, 0, 0, 0, 0, 0, 0, 0);
// We no longer need the loaded script
::GlobalFree(str);
// NOTE: If the script engine has a problem parsing/tokenizing the script, it will
// have called our IActiveScriptSite's OnScriptError() to display an error msg, so
// we don't need to do that here
if (!hr &&
// Set engine's state to CONNECTED. NOTE: If we called the engine's AddNamedItem()
// to add some objects, then the script engine will QueryInterface our
// IActiveScriptSite for the needed IDispatch objects from us
(hr = ((MYARGS *)args)->EngineActiveScript->SetScriptState(SCRIPTSTATE_CONNECTED)))
{
::PostMessage(MainWindow, WM_APP, (WPARAM)"Engine can't connect events: %08X", hr);
}
}
}
}
}
// Release script engine's IActiveScriptParse
activeScriptParse->Release();
}
// We're supposed to Close() the engine before we Release() the IActiveScript
((MYARGS *)args)->EngineActiveScript->Close();
// Release script engine's IActiveScript
((MYARGS *)args)->EngineActiveScript->Release();
}
::CoUninitialize();
// NOTE: We really should have a critical section around this access to
// ThreadHandle since the main thread also accesses it. It is left up to
// you to implement this
// Let main thread know that we're done (running the script)
CloseHandle(((MYARGS *)args)->ThreadHandle);
((MYARGS *)args)->ThreadHandle = 0;
// Terminate this thread
_endthread();
return(0);
}
/************************** textViewerProc() ****************************
* The message procedure for "TextViewer" window class.
*/
static long APIENTRY textViewerProc(HWND hwnd, UINT uMsg, UINT wParam, long lParam)
{
switch (uMsg)
{
//***************************************************************
//======================== Redraw Window ========================
case WM_PAINT:
{
register LPTSTR ptr;
register DWORD len;
PAINTSTRUCT ps;
HDC hdc;
HGDIOBJ temp;
RECT rectWnd;
long bottom;
unsigned char extra;
register MyRealIDocument *doc;
// Get DC
hdc = ::BeginPaint(hwnd, &ps);
// Get current width/height of Client
::GetClientRect(hwnd, &rectWnd);
// Erase background
// ::FillRect(hdc, &rectWnd, (HBRUSH)1);
// Get the IDocument
if ((doc = (MyRealIDocument *)GetWindowLong(hwnd, GWL_USERDATA)) && (ptr = doc->Text))
{
// Set TRANSPARENT mode for text output
::SetBkColor(hdc, 0x004040FF);
::SetBkMode(hdc, TRANSPARENT);
::SetTextColor(hdc, 0);
// Select the font
temp = ::SelectObject(hdc, (HGDIOBJ)FontHandle);
// Start with top line
rectWnd.top = 1;
bottom = rectWnd.bottom;
do
{
rectWnd.bottom = rectWnd.top + 13;
// Do we have some more chars to print?
len = extra = 0;
while (*(ptr + len))
{
// Newline?
if (*(ptr + len) == '\r')
{
++extra;
if (*(ptr + len + 1) == '\n') ++extra;
break;
}
++len;
}
if (!len)
{
if (!*ptr) break;
// A blank line
goto skipline;
}
// Display this line
::DrawText(hdc, ptr, len, &rectWnd, DT_EXPANDTABS|DT_NOPREFIX|DT_SINGLELINE|DT_WORDBREAK);
// Next line
skipline: ptr += len + extra;
rectWnd.top = rectWnd.bottom;
// Any more chars to print? Is there room for another line?
} while (*ptr && rectWnd.bottom < bottom);
// Restore orig font
::SelectObject(hdc, temp);
}
// ====================================================
// Free DC
::EndPaint(hwnd, &ps);
return(0);
}
case WM_CREATE:
{
CREATESTRUCT *cs = (CREATESTRUCT *)lParam;
// Store the IDocument in our GWL_USERDATA field
SetWindowLong(hwnd, GWL_USERDATA, (LONG)cs->lpCreateParams);
break;
}
case WM_DESTROY:
{
register MyRealIDocument *doc;
// Get the IDocument and Release() it
if ((doc = (MyRealIDocument *)GetWindowLong(hwnd, GWL_USERDATA)))
{
doc->Hwnd = 0;
doc->Release();
}
return(0);
}
}
// Indicate that I handled the msg
return(::DefWindowProc(hwnd, uMsg, wParam, lParam));
}
/**************************** mainWndProc() *****************************
* The message procedure for MainWindow. It is called by Windows whenever
* there is a message for MainWindow to process.
*/
static BOOL CALLBACK mainWndProc(HWND hwnd, UINT uMsg, UINT wParam, long lParam)
{
switch(uMsg)
{
// ******************************************************************
case WM_APP:
{
// Our script thread posts a WM_APP if it needs us to display an error message.
// wParam = A pointer to the string to display. If 0, then lParam is an error
// number to be passed to display_sys_error().
// lParam = The HRESULT. If 0, then wParam is an allocated WCHAR string which
// we must free with GlobalFree()
if (!wParam)
display_sys_error((DWORD)lParam);
else if (!lParam)
{
::MessageBoxW(hwnd, (const WCHAR *)wParam, &ErrorStrWide[0], MB_OK|MB_ICONEXCLAMATION);
::GlobalFree((void *)wParam);
}
else
display_COM_error((LPCTSTR)wParam, (HRESULT)lParam);
break;
}
// ******************************************************************
case WM_APP+1:
{
register HWND edit;
edit = GetDlgItem(hwnd, IDC_TRACE);
if (wParam)
{
::SendMessageW(edit, EM_REPLACESEL, 0, (LPARAM)wParam);
::SendMessage(edit, EM_SETSEL, (WPARAM)-1, -1);
::GlobalFree((void *)wParam);
}
if (lParam)
{
::SendMessage(edit, EM_REPLACESEL, 0, (LPARAM)&NewLine[0]);
::SendMessage(edit, EM_SETSEL, (WPARAM)-1, -1);
}
break;
}
// ******************************************************************
case WM_APP+2:
{
MyRealIDocument *doc;
// Our IApp's CreateDocument() sends us this message when it wants us
// to create a new document.
//
// WPARAM = A handle where to return the IDocument for this window
// LPARAM = BSTR of the name for the document
// Create an IDocument for this window
if (wParam && !allocIDocument(&doc))
{
// Create the child window and put its HWND into the IDocument. Also
// pass the IDocument to WM_CREATE so it's stored in the HWND's GWL_USERDATA
#ifdef UNICODE
if ((doc->Hwnd = ::CreateWindowEx(WS_EX_APPWINDOW|WS_EX_CLIENTEDGE, &TextViewerName[0],
(LPTSTR)lParam, WS_VISIBLE|WS_OVERLAPPED|WS_CLIPCHILDREN|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_CAPTION|WS_SIZEBOX|WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 200, 200, hwnd, 0, InstanceHandle, (LPVOID)doc)))
#else
TCHAR buffer[180];
::WideCharToMultiByte(CP_ACP, 0, (const WCHAR *)lParam, -1, &buffer[0], sizeof(buffer), 0, 0);
if ((doc->Hwnd = ::CreateWindowEx(WS_EX_APPWINDOW|WS_EX_CLIENTEDGE, &TextViewerName[0],
&buffer[0], WS_VISIBLE|WS_OVERLAPPED|WS_CLIPCHILDREN|WS_MINIMIZEBOX|WS_MAXIMIZEBOX|WS_CAPTION|WS_SIZEBOX|WS_SYSMENU,
CW_USEDEFAULT, CW_USEDEFAULT, 200, 200, hwnd, 0, InstanceHandle, (LPVOID)doc)))
#endif
{
// Return the IDocument
*((IDispatch **)wParam) = static_cast<IDispatch *>(doc);
}
else
delete doc;
}
break;
}
// ******************************************************************
case WM_SETFOCUS:
{
::SetFocus(::GetDlgItem(hwnd, IDC_TRACE));
break;
}
// ******************************************************************
case WM_SIZE:
{
::MoveWindow(::GetDlgItem(hwnd, IDC_TRACE), 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
break;
}
// ******************************************************************
// ====================== Create main window ========================
case WM_INITDIALOG:
{
HICON icon;
// Load/set icon for System menu on the window. I put my icon
// in this executable's resources. Note that windows frees this
// when my window is closed
if ((icon = ::LoadIcon(InstanceHandle,MAKEINTRESOURCE(IDI_MAIN_ICON))))
::SetClassLong(hwnd, GCL_HICON, (LONG)icon);
::SendDlgItemMessage(hwnd, IDC_TRACE, WM_SETFONT, (WPARAM)GetStockObject(DEFAULT_GUI_FONT), 0);
return(1);
}
// ******************************************************************
// =================== User wants to close window ===================
case WM_CLOSE:
{
// NOTE: We really should have a critical section around this access to
// ThreadHandle since the script thread also accesses it. It is left up to
// you to implement this
// Is a script running?
if (MyArgs.ThreadHandle)
{
// Abort the script by calling InterruptScriptThread. This
// is one of the few IActiveScript functions we can call by
// any thread
MyArgs.EngineActiveScript->InterruptScriptThread(SCRIPTTHREADID_ALL, 0, 0);
// Empty out any WM_APP messages. We need to do this because our IApp's
// LoadDocument() does a SendMessage(). So it waits for that WM_APP
// message to be returned before the script engine gets back control.
// So if we had to abort the script, we need to let the script engine
// get control back
{
MSG msg;
while (::PeekMessage(&msg, hwnd, WM_APP, WM_APP+100, PM_REMOVE))
{
if (msg.message == WM_USER+2) msg.wParam = 0;
::DispatchMessage(&msg);
}
}
// NOTE: Just because InterruptScriptThread has returned doesn't mean
// that the thread has terminated. It simply means that the engine has
// marked the running script for termination. We still have to "wait" for the
// thread to terminate. We'll do that by testing when ThreadHandle is
// 0. (Remember that the script thread zeroes it upon termination).
// There's one other problem. If the script thread is somehow "sleeping"
// or waiting for something itself, for example in a call to MessageBox,
// then the engine will never get a chance to terminate it. To get
// around this problem, we'll increment a count, and Sleep() in between
// increments. When the count "times out", then we'll assume the script
// is locked up, and brute force terminate it ourselves
wParam = 0;
while (MyArgs.ThreadHandle && ++wParam < 25) ::Sleep(100);
if (MyArgs.ThreadHandle) ::TerminateThread(MyArgs.ThreadHandle, 0);
}
// Destroy this window
::DestroyWindow(hwnd);
return(1);
}
case WM_DESTROY:
{
// Post the WM_QUIT message to quit the message loop in WinMain()
::PostQuitMessage(0);
return(1);
}
}
// Indicate that I didn't handle the msg
return(0);
}
/************************** WinMain() **************************
* Our EXE's entry point. This is called by Windows when our
* EXE first starts up.
*/
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
HRESULT hr;
// Save Instance handle which I need when opening the "Choose engine" dialog
// box. That dialog is linked into this executable's resources, so I need to pass
// this handle to DialogBox() when I open that dialog
InstanceHandle = hInstance;
// Loads Windows common control's DLL
::InitCommonControls();
// MainWindow not open yet
MainWindow = 0;
// Initialize COM DLL
if ((hr = ::CoInitializeEx(0, COINIT_MULTITHREADED)))
display_COM_error("Failed to initialize COM: %08X", hr);
else
{
// Initialize my one and only IApp custom COM object
initMyRealIAppObject();
// Register my "text viewer" window class
{
WNDCLASS wndClass;
ZeroMemory(&wndClass, sizeof(WNDCLASS));
wndClass.hInstance = InstanceHandle;
wndClass.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
wndClass.style = CS_HREDRAW|CS_VREDRAW|CS_BYTEALIGNCLIENT;
wndClass.lpfnWndProc = textViewerProc;
wndClass.lpszClassName = &TextViewerName[0];
if (!::RegisterClass(&wndClass)) goto bad;
}
FontHandle = (HFONT)::GetStockObject(DEFAULT_GUI_FONT);
// Create Main window
if (!(MainWindow = ::CreateDialog(InstanceHandle, MAKEINTRESOURCE(IDD_MAINWINDOW), 0, mainWndProc)))
display_sys_error(0);
else
{
// Show the window with default size/position
::ShowWindow(MainWindow, SW_SHOWDEFAULT);
::UpdateWindow(MainWindow);
{
HKEY hk;
// Get the filename of the script to run
MyArgs.Filename = "test.vbs";
// Get the VBscript engine's GUID
if (!::RegOpenKeyEx(HKEY_CLASSES_ROOT, ".vbs", 0, KEY_QUERY_VALUE|KEY_READ, &hk))
{
HKEY subKey;
wchar_t language[100];
unsigned char flag;
DWORD type;
DWORD size;
type = REG_SZ;
size = sizeof(language);
size = ::RegQueryValueEx(hk, 0, 0, &type, (LPBYTE)&language[0], &size);
::RegCloseKey(hk);
flag = 1;
if (!size)
{
again: size = sizeof(language);
if (!::RegOpenKeyEx(HKEY_CLASSES_ROOT, (LPCTSTR)&language[0], 0, KEY_QUERY_VALUE|KEY_READ, &hk))
{
if (!::RegOpenKeyEx(hk, &CLSIDStr[0], 0, KEY_QUERY_VALUE|KEY_READ, &subKey))
{
size = ::RegQueryValueExW(subKey, 0, 0, &type, (LPBYTE)&language[0], &size);
::RegCloseKey(subKey);
}
else if (flag)
{
if (!::RegOpenKeyEx(hk, &ScriptEngineStr[0], 0, KEY_QUERY_VALUE|KEY_READ, &subKey))
{
size = ::RegQueryValueEx(subKey, 0, 0, &type, (LPBYTE)&language[0], &size);
::RegCloseKey(subKey);
if (!size)
{
::RegCloseKey(hk);
flag = 0;
goto again;
}
}
}
::RegCloseKey(hk);
if (!size && (size = ::CLSIDFromString(&language[0], &MyArgs.Guid)))
display_COM_error("Can't get engine GUID: %08X", size);
}
}
// Now that we've got the GUID, and the script filename, run the script thread
if (!size)
{
// NOTE: We really should have a critical section around this access to
// ThreadHandle since the script thread also accesses it. It is left up to
// you to implement this
// Create a secondary thread to run the script. It will do the
// actual call to the engine to run the script while we go back and
// manage the user interface
MyArgs.ThreadHandle = (void *)_beginthreadex(0, 0, runScript, &MyArgs, 0, (unsigned int *)&size);
// NOTE: If script thread starts, 'ThreadHandle' will be closed by that
// thread when it terminates
}
}
}
{
MSG msg;
// Here's our message loop which is executed until the user closes
// down our MainWindow
while (::GetMessage(&msg, 0, 0, 0) == 1)
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
}
bad: freeMyRealIAppObject();
// Allow our IActiveScriptSite to free any resource
MyActiveScriptSite.Release();
}
// Free COM
::CoUninitialize();
return(0);
}