Hello,
Do you want to run your 3d game or application in the web browser? in this post we’ll see how to do that from scratch in a simple way that will require almost no modifications to your existing application. I’ll use the latest Irrlicht engine version by the time of this writing- 1.4.2. In order to keep things as simple as possible and make something that can work without major modifications to the existing code we’ll run one of the irrlicht example programs in an external process that will render it’s viewport in the ActiveX window. We’ll do that by passing the window handle of the ActiveX control as a command line parameter to the application and it will create it’s irrlicht device and window with it.
Our ActiveX control will be ATL based and will require no MFC.
We’ll use the meshviewer sample from the irrlicht engine as the 3d application. For the sake of simplicity we’ll assume that the irrlicht SDK is located on “c:\irrlicht-1.4.2″.
Let’s start with the irrlicht meshviewer:
* Open the appropriate irrlicht example file- I use Visual Studio 2005 so I’ll open “c:\irrlicht-1.4.2\examples\BuildAllExamples.sln” and edit main.cpp of the meshviewer example:

* We are going to use windows API so we need to include windows.h:
/*
This tutorial show how to create a more complex application with the engine. We construct
a simple mesh viewer using the user interface API and the scenemanagement of Irrlicht.
The tutorial show how to create and use Buttons, Windows, Toolbars, Menus, ComboBoxes,
Tabcontrols, Editboxes, Images, MessageBoxes, SkyBoxes, and how to parse XML files
with the integrated XML reader of the engine.
We start like in most other tutorials: Include all nesessary header files, add a
comment to let the engine be linked with the right .lib file in Visual Studio,
and deklare some global variables. We also add two ‘using namespece’ statements, so
we do not need to write the whole names of all classes. In this tutorial, we use a
lot stuff from the gui namespace.
*/
#include <irrlicht.h>
#include <iostream>
#include <windows.h>
using namespace irr;
using namespace gui;
* We need to get the window handle of the activex control as a parameter, so add argv and argc to the main function and add some code to translate it to a window handle.
we’ll also comment out the device selection in the command window and choose automatically directx9
int main(int argc, char* argv[])
{
HWND hParentWnd;
// argv[1] will contain an HEX string with the activex window handle value
sscanf(argv[1],”%x”,&hParentWnd);
// ask user for driver
// ask user for driver
video::E_DRIVER_TYPE driverType = video::EDT_DIRECT3D9;
/*printf(”Please select the driver you want for this example:\n”\
” (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n”\
” (d) Software Renderer\n (e) Burning’s Software Renderer\n”\
” (f) NullDevice\n (otherKey) exit\n\n”);
char key;
std::cin >> key;
switch(key)
{
case ‘a’: driverType = video::EDT_DIRECT3D9;break;
case ‘b’: driverType = video::EDT_DIRECT3D8;break;
case ‘c’: driverType = video::EDT_OPENGL; break;
case ‘d’: driverType = video::EDT_SOFTWARE; break;
case ‘e’: driverType = video::EDT_BURNINGSVIDEO;break;
case ‘f’: driverType = video::EDT_NULL; break;
default: return 1;
}*/
* the next thing we’ll do is to create a new window that will be the child window of the activex control. Now, why would we want to do that instead of just creating the irrlicht device straight on the activex control? the reason is that we want our application to handle windows messages sent to our window. If we would use the activex created window we will not receive them because the messages will be handled already by the activex control’s message loop. The easiest way to solve this issue is to create a new window which is a child window of the activex control’s window. We’ll replace the original irrlicht createDevice with createDeviceEx that can get the window handle as a parameter. Also window resizing has been disabled.
MyEventReceiver receiver;
HMODULE hInstance = ::GetModuleHandle(NULL);
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = DefWindowProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = NULL;//LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = NULL; //MAKEINTRESOURCE(IDC_WINEX);
wcex.lpszClassName = “IrrWin”;
wcex.hIconSm = NULL;//LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
RegisterClassEx(&wcex);
HWND hIrrlichtWindow = ::CreateWindow(”IrrWin”,”demowin”,WS_CHILD | WS_VISIBLE | WS_EX_TOPMOST, 0, 0, 800,600, hParentWnd,NULL,hInstance,NULL);
irr::SIrrlichtCreationParameters param;
param.WindowId = reinterpret_cast<void*>(hIrrlichtWindow);
param.DriverType = video::EDT_DIRECT3D9;
Device = irr::createDeviceEx(param);
Device->setEventReceiver(&receiver);
//Device = createDevice(driverType, core::dimension2d<s32>(800, 600),
// 16, false, false, false, &receiver);
if (Device == 0)
return 1; // could not create selected driver.
Device->setResizeAble(false);
Device->setWindowCaption(L”Irrlicht Engine - Loading…”);
* The next thing we should take care of is window focus. Because we are a child window of another control our window might lose focus and not receive important events such as keypresses. To fix this problem we’ll make sure we always have the focus in our irrlicht main loop (it can be found near the end of the source file) we’ll also make sure that the scene is always rendered regardless if the window is active by commenting out the code responsible for that:
// lock the logo’s edges to the bottom left corner of the screen
img->setAlignment(EGUIA_UPPERLEFT, EGUIA_UPPERLEFT, EGUIA_LOWERRIGHT, EGUIA_LOWERRIGHT);
// draw everything
while(Device->run() && driver)
{
//if (Device->isWindowActive())
//{
if (::GetFocus() != hIrrlichtWindow)
{
::SetFocus(hIrrlichtWindow);
}
driver->beginScene(true, true, video::SColor(150,50,50,50));
smgr->drawAll();
env->drawAll();
driver->endScene();
core::stringw str(L”FPS: “);
str.append(core::stringw(driver->getFPS()));
str += L” Tris: “;
str.append(core::stringw(driver->getPrimitiveCountDrawn()));
fpstext->setText(str.c_str());
::UpdateWindow(hIrrlichtWindow);
}
//else
//Device->yield();
}
Device->drop();
return 0;
* We are done with modifying the original irrlicht sample! now let’s go on to creating the activex control: right click on the solution in the solution explorer and choose add->new project.

* on the project type choose Visual C++ -> ATL and select “ATL Project” on the right pane. I’ve put it under the irrlicht examples folder under the name “ActiveIrr”:

* The ATL project wizard will pop up. Go to “Application Settings” and check “Attributed” and click “Finish”:

* ActiveIrr project have been created and added to the solution. Select it and choose Project->Add Class..
* choose ATL -> ATL Control
* On the control wizard, type in the short name field “IrrCtl”, the rest of the field will fill automatically
* Select “Appearance” settings and check “Windowed Only”
* Press Finish




* By now a new control has been created. Open IrrCtl.h:
* Although we marked our ATL Control as Windowed Only it will still not create a window unless the m_bWindowedOnly member is set. We want our control to create a window so we’ll have a window inside our web page that will contain our application within. We’ll set the memeber in the contructor:
#ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly
public IObjectSafetyImpl<CIrrCtl, INTERFACESAFE_FOR_UNTRUSTED_CALLER>,
#endif
public CComControl<CIrrCtl>
{
public:
CIrrCtl()
{
m_bWindowOnly = true;
}
DECLARE_OLEMISC_STATUS(OLEMISC_RECOMPOSEONRESIZE |
OLEMISC_CANTLINKINSIDE |
OLEMISC_INSIDEOUT |
OLEMISC_ACTIVATEWHENVISIBLE |
OLEMISC_SETCLIENTSITEFIRST
* We want to know when our window is created so we can pass it’s handle to the meshviewer. We’ll do that by adding a WM_CREATE handler. We’ll add it to the message map and a method to the class called “OnCreate” that will launch the meshviewer:
BEGIN_MSG_MAP(CIrrCtl)
MESSAGE_HANDLER(WM_CREATE, OnCreate)
CHAIN_MSG_MAP(CComControl<CIrrCtl>)
DEFAULT_REFLECTION_HANDLER()
END_MSG_MAP()
// Handler prototypes:
// LRESULT MessageHandler(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
* and here the code for handling the window creation and the mesh viewer launching, just add it to the end of the CIrrCtl class:
LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
{
WCHAR szAppPath[MAX_PATH];
WCHAR szCurDir[MAX_PATH];
WCHAR szWndParam[100];
swprintf(szCurDir,L”C:\\irrlicht-1.4.2\\bin\\Win32-VisualStudio”);
swprintf(szAppPath,L”%s\\09.MeshViewer.exe”,szCurDir);
swprintf(szWndParam,L”09.MeshViewer.exe %x”,m_hWnd);
STARTUPINFOW siStartupInfo;
PROCESS_INFORMATION piProcessInfo;
memset(&siStartupInfo, 0, sizeof(siStartupInfo));
memset(&piProcessInfo, 0, sizeof(piProcessInfo));
siStartupInfo.cb = sizeof(siStartupInfo);
::SetCurrentDirectory(szCurDir);
::CreateProcess(szAppPath,szWndParam,NULL,NULL,true,CREATE_DEFAULT_ERROR_MODE,0,0, &siStartupInfo, &piProcessInfo);
return 0;
}
* Another thing that we want to do is to remove the default window drawing that is done by the control. just empty out the OnDraw function and make it look like that:
HRESULT OnDraw(ATL_DRAWINFO& di)
{
return S_OK;
}
* Open IrrCtl.htm (it’s a file in ActiveIrr project) and modify the <OBJECT> of the ActiveX to have a width and a height of 800×600 by adding a style attribute:
<OBJECT ID=”IrrCtl” CLASSID=”YOUR CLASS ID HERE!” style=“width: 800; height: 600″></OBJECT>
save the file and thats about it! compile ActiveIrr project, right click “IrrCtl.htm” and choose “View In Browser”. After confirming all the ActiveX warnings you should get something like that:


* Note that if you close the window the rendering loop starts to fail because of losing the device. You can detect this situation by checking the return status of irrlicht’s endScene() and exit your program.
Technology 3d, activex, browser, irrlicht