Click here to Skip to main content
15,894,955 members
Articles / Programming Languages / C

COM in plain C, Part 7

Rate me:
Please Sign up or sign in to vote.
5.00/5 (15 votes)
8 Aug 2006CPOL13 min read 93.6K   2K   83  
An ActiveX Script Host with custom COM objects. This allows a script to call C functions in your app.
// 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);
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions