Share the wealth – More ActiveX, Internet Explorer, Ubuntu and other experiences

This posting is an attempt to catch-up and share random findings related to porting a win32 client/server application to a self installing activeX object deployed via a CAB file, Internet Explorer integration, Inter-process communication considerations, activeX <–> browser communication, updates on our family Ubuntu experiences and more.

Part 1 – ActiveX experiences:

Take a deep breath as much of this information was not easy to find and parts of it had to be discovered by trial and error due to poor documentation, or the fact that my particular activeX development environment is Codegear / Borland) Developer Studio 2007.

Firstly I will discuss some of the consequences of moving a typical WIN32 client/server application into an activeX container. For starters you main window (or form for Borland people) is not the “main” one anymore. This may create a lot of problems for you depending on how tightly coupled your code relies on that. Fortunately I had already created an abstraction layer to handle the main container for the purposes of test automation (so that we could use a different container to automate parts of the application for automated tests). The fact that Internet Explorer is now the new real container for the “application” means that you’ll run into problems if previously using SendNotifyMessage and cheating by using the HWND_BROADCAST setting for the handle, your activeX window will no longer get the message because it is NOT a top level window anymore. Furthermore there are numerous parent windows in the chain of parent / child hierarchy of windows in this new world and YES the hierarchy is different for different versions of Internet Explorer. The I we solved it was to have my activeX code build up a delimited string starting the the window that I want to receive these IPC messages with the window classnames like this:

std::string GetActiveXWindowChain()

{

char szClassName[1024] = “”;

GetClassName(this->Handle,szClassName,1023); // Start with the form we want to recieve IPC  messages

std::string sRet = szClasName;

HWND hwnd = GetParent(this->Handle);

for(;hwnd != NULL;)

{

GetClassName(hwnd,,szClassName,1023);

sRet += “|”;

sRet += szClasName;

hwnd = GetParent(hwnd);

}

return sRet;

}

Then pass this string to the other applications that you want to send information to your activeX window. These other applications can then call:

1. EnumWindows and find the TOP level window (the last on in the delimited string) and once you have it (there may be duplicates with the same classname, but that is ok, move on to step 2)

2. Once you have a possible match of top level window classname starting from the second last classname in the delimited string (remember that last is the top level IE window) call

newhwnd = FindWindowEx(hwndyou fast in step1,NULL,secondlastclassname,NULL);

you repeat the above line for each additional windowclassname in the delimited list until you reach the first (count backwards in the delimited string). Once you find the first handle, your other application has a HWND to call SendNotifyMessage with instead of the HWND_BROADCAST value and viola.

Next, how can we call a javascript function from inside our activeX control? You need to trickle your way down into a set of Internet Explorer COM Web Browser Interfaces. In Borland C++ Builder when using TActiveForm this is quite easy to do.The  TActiveForm wizard creates  a special VCL wrapper class that actually interacts with the outside world and communicates things to TActiveForm and is a multiple inheritance VCL object using the macro VCLCONTROL_IMPL. If you look at what is happening behind the scenes you will see that one of the base classes is IOleObject. In Bor-land you can easily call into any of the numerous COM interfaces to control Internet Explorer like this (assume pMainActiveXImpl is a casted IOleObject pointer to your TActiveForm’s  Impl Wrapper class):

void CallJavaScriptMethod(IOleObject *pMainActiveXImpl, std::string sMethodWithParameters)

{

IOleClientSite *pClientSite = NULL;

pMainActiveXImpl->GetClientSite(&pClientSite);

if(pClientSite != NULL)

{

IOleContainer *pContainer = NULL;

pClientSite->GetContainer(&pContainer);

if(pContainer != NULL)

{

// Here is where you’ll need mshtml.hpp which defines all the Internet Explorer COM interfaces

IHTMLDocument2 *pHTMLDoc = NULL;

if(SUCCEEDED(pContainer->QueryInterface(::ID_IHTMLDocument2,(LPVOID *) &pHTMLDoc)))

{

_di_IHTMLWindow2 pHTMLWINDOW = NULL;

pHTMLDoc->Get_parentWindow(pHTMLWINDOW);

OleVariant execScript_results;

pHTMLWINDOW->execScript(sMethodWithParameters.c_str(), “javascript”, execScript_results);

}

pContainer->Release();

}

pClientSite->Releae();

}

}

// To call javascript you can do it like this:

CallJavaScriptMethod(pMainActiveXImpl, “hellowWorld();”);

*Note: if you try this in other browsers you won’t get the COM object interface so that is one important reason to check for NULL’s (which you actually can, firefox 3.x has a free plugin from here with source code that I have successfully launched my application from, but I don’t consider this plugin production worthy or overly stable, but it does work if you create the tags as documented in the link).

Also watch out for special keyboard character behaviour when using Internet Explorer 6 and lower. There were really two problems I encountered that needed to be “fixed” namely:

a) properly setting focus to controls that are parented to the TActiveForm

b) passing special keyboard characters to controls parented to the TActiveForm (IE uses BACKSPACE, and other keystrokes as special hotkeys and therefore your activeX control never gets those keyboard events.)

The solutions I used were:

a) The MSDN article: here tells the story about why this is required (not a Borland problem)

In the header of your TActiveForm override the WM_MOUSEACTIVATE event:

void __fastcall myMouseActivate(TWMMouseActivate &Message);

VCL_MESSAGE_HANDLER(WM_MOUSEACTIVATE, TWMMouseActivate,myMouseActivate)

Now in the cpp file: (*Note: pMainActiveXImpl_m is a pointer the the wrapper in the same file that contains your TActiveForm)

void __fastcall TMyActiveForm::myMouseActivate(TWMMouseActivate &Message)
{
// Exclusively required to fix an issue with IE6 and below where
// the Parent controls to our TActiveForm do not properly have
// focus. (see http://support.microsoft.com/kb/q190044/)
// This means certain IE hotkeys (like backspace) cause the browser
// to navigate to the previous webpage in some versions of IE.
bool bForwardToBase = true;
if (pMainActiveXImpl_m != NULL)
{
IOleClientSite *pClientSite = NULL;
pMainActiveXImpl_m->GetClientSite( &pClientSite );
if(pClientSite != NULL)
{
RECT rcPos = GetClientRect();
pMainActiveXImpl_m->DoVerb(OLEIVERB_UIACTIVATE, NULL, pClientSite, 0, this->Handle, &rcPos);
pClientSite->Release();

Message.Result = MA_NOACTIVATE;
bForwardToBase = false;
}
}
if(bForwardToBase == true)
{
TMessage *pMsg = (TMessage *)&Message;
this->Perform(WM_MOUSEACTIVATE, pMsg->WParam, pMsg->LParam);
}
}
//—————————————————————————

Voila, backspace suddenly works (and doesn’t navigate to the previous page). This issue is ONLY seen in IE6 and below, but the code works with IE7 and below so it is harmless to deploy for IE7.

b) To pass the keystrokes you can ONLY pass these events to controls parented to the TActiveForm, otherwise leave the default behaviour or else things will break in your pop-up forms etc.. The basic idea is to use SetWindowsHookEx so that you can inspect keystrokes BEFORE Internet Explorer processes them. In the constructor of your TActiveForm add this call

hKeyboardHook = ::SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC) KeyboardProc, NULL, GetCurrentThreadId());

where HHOOK hKeyboardHook = NULL; is a global var (or something accessible in the callback method shown next)

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wp, LPARAM lp)
{
TCustomForm *form = Screen->ActiveCustomForm;

TMyActiveForm *pActiveXForm = dynamic_cast<TMyActiveForm *>(form) ;
// check for exception cases…
if (nCode < 0 || nCode == HC_NOREMOVE || form == NULL || form->ActiveControl == NULL || pActiveXForm == NULL || pActiveXForm->ActiveControl == NULL)
{
return ::CallNextHookEx(hKeyboardHook, nCode, wp, lp);
}

bool result = false;

// ok to process message…retrieve instance information
std::bitset<32> prevstate(lp);
switch(wp)
{
case VK_TAB:
case VK_LEFT:
case VK_RIGHT:
case VK_UP:
case VK_DOWN:
case VK_HOME:
case VK_END:
case VK_PRIOR:
case VK_NEXT:
//case VK_BACK:
if (prevstate[31] == 0) // (*) potential bug fix
{
form->ActiveControl->Perform(WM_KEYDOWN, wp, lp);
}
else
{
form->ActiveControl->Perform(WM_KEYUP, wp, lp);
}
result = true;
break;
}

return result;
}
and don’t forget to unhook on the Destructor of your TActiveForm using:

::UnhookWindowsHookEx(hKeyboardHook);

Voila, now you can tab from one field to another on the forms / controls that are parented to the TActiveForm. Strangely the Screen->ActiveCustomForm is ALWAYS of type TMyActiveForm when dealing with forms / controls parented to the TActiveForm but the ActiveControl is the correct control that has focus. This allows us to know if the ActiveCustomForm is a non parented to the TActiveForm form or not and thus decide whether or not to forward the keystrokes in our special way. If not parented to our TActiveForm we leave the default behaviour (calling CallNextHookEx)

Another thing to watch out for when deploying your activeX deployed products is the special “Downloaded Program Files” folder (usually in \Windows). If your application needs to save and/or load files and allow the user to look for files / locations Windows will Hide all the contents (and subfolders) of this special folder. In that case tell your CAB deployment to install those pieces of functionality that need to work with the user to load / save files visually through the UI in a separate folder (perhaps somewhere under \Program Files).

Yet another important issue (you should be thinking about it by now) is how to pass data around if the data may have special characters (like \,=$ etc). You’ll need to escape and unescape such values as you pass them from one layer to another or else your code will fail to work properly (think database server and database names, etc). This leads to security considerations, such data should be encrypted (not just encoded).

Another important issue (this may hit you hard depending on how your application works) is that statelessness of Internet Explorer. If you ActiveX application has threads running or you have long running operations and you don’t block the user from navigating to another webpage or closing the browser you could do serious damage to system / data stability. In the Borland environment you can show a modal dialog and that will stop the user from being able to get out of your long running process without asking you first (add a cancel button or something to allow for clean shutdown). Essentailly you need to EnableWindow(handletooneoftheIEwindowhandles,false) to disable users ability to kill your activeX control.

Lastly I noticed that while working in a webserver environment where JBoss serves up the page that launches the activeX control (ultimately the CAB file that contains the activeX install) I kept getting a red X in the browser when trying to download the CAB file. I had to point the codebase to a URL path OUTSIDE of the JBoss working area and in the content of the webserver itself to properly work (perhaps JBoss wants to due weird things with the CAB file??).

—————————————————————————————————————————————–

Part 2 – Ubuntu experiences:

Here are a few random tips I’ll share related to my current conversion to Ubuntu Linux. I noticed that accessing the family NAS got slower when connecting from Ubuntu. This seems to be related to how SMB works. To speed things up and allow many programs to access the DNS323 share as though it were a local drive I did this:

1. From a terminal window type (and also enter your admin password when prompted):  sudo mkdir /media/dlinknas

2. Edit the file as administrator (sudo or root) /etc/fstab

3. Add something like this to the bottom of the file:

//192.168.0.101/Volume_1 /media/dlinknas smbfs username=music,password=music 0 0

4. Save the file and reboot and voila (Under the Places menu option you will see the new shared drive automatically)

As a coder switching from Windows to Linux it is taking some time to choose which IDE and frameworks to use for my Linux development. I REALLY enjoyed using TortoiseSVN in windows and was somewhat unhappy with cheesy default SVN clients like RapidSVN (which should be called snailSVN in my opinion). Happily I found NautilusSvn which basically does almost everything Tortoise did. That deals with the majority of open source projects nicely. Next I chose CodeBlocks as my C++ IDE. It isn’t as good as Visual Studio, but it does work OK and many open source projects are very compatible with this IDE and it is very actively developed. For a GUI layer I ma settling on wxWidgets which gives me excellent cross platform ability for Linux, Windows and Mac and works with Codeblocks as well.

One important note is the creation of shortcuts in Ubuntu. Many programs (especially if using WINE to launch them) require to be executed from a working directory (ususaly the same folder as the program). When making your shortcut use this type of command (edit the values to your path etc):

gnome-terminal –working-directory “/home/softcoder/.wine/dosdevices/z:/media/disk/Program Files/TmUnitedForever” -x wine TmForever.exe

—————————————————————————————————————————————–

Part 3 – Open Source propaganda:

I REALLY enjoy working in an environment that is totally submerged with open source code from operating system to device driver to business application to game. I had problems with a Linux Sound driver, downloaded the latest code, compiled installed and viola problem solved. My family and I altered a number of open source games that didn’t suit our moral values but had great graphics and game play. Now we play these games with the ill-appreciated content removed (we have freedom to change anything we want) and may someday post our conservative friendly modified games for others.

One last note. Recently I was installing a new developer computer at work (where we must use Windows XP). While installing SQL Server 2005 and later trying to compile a .NET Application in Visual Studio I came across Server 500 errors in IIS and found numerous entries in Eventvwr looking like:

f:\xpsp3\com\com1x\src\comsvcs\package\cpackage.cpp(1184) hr = 80040150 or aspnet_wp.exe stopped unexpectedly

Try as I could, following MANY MSDN and other detailed articles about fixing a corrupt COM + catalogue or  corrupt WMI installation, I COULD NOT fix this problem. I had to do a Window XP repair install to get WMI back to a working state (type this from a dos prompt and you will see errors about CPU Interface failed etc: wmimgmt.msc). I tried reinstalling SP3 and many other things before being forced into a Windows XP repair. I had NO other option, no code to inspect, no forums telling me what my problem was specifically and how to fix it, I was dead in the water.

Hoarders may get piles of money.. that is true… but they can not help their neighbours… that ain’t good.

(taken from Richard Stallmans Free Software Song)

About the Author

Hello, my name is Mark Vejvoda. I am first and foremost a born again child of God. My life revolves around Jesus Christ, He is the center of my universe. With that said and established, outside of being a Christian, it is necessary to live in a world that is certainly not Christian. Is this diary / blog you see a view on life from the perspective of a Jesus Lover, and perhaps some side effects of that relationship as I interact with the rest of the world. [gallery=1]

One Response to “ Share the wealth – More ActiveX, Internet Explorer, Ubuntu and other experiences ”

  1. [...] Go to the author’s original blog: Share the wealth – More ActiveX, Internet Explorer, Ubuntu and … [...]