Calendar
Recent posts
- .Net Remoting and IPv6
- New year trip to Mt. Haguro
- Improving Japanese fonts in Internet Explorer and Firefox on Vista
- Enumerating the lines of a file
- FormatC source code formatting
- Find As You Type and IE8 beta 2
- Win an MSDN subscription
- Windows Search 4.0 released
- Report your Windows Vista UI annoyances
- Send your name to the moon
Post categories
Syndication
Search
Find As You Type 1.2 is out!
Well, that was a tough one! Find As You Type for Internet Explorer 1.2 is finally out. I'd started development on this as early as last November, but got distracted by a lot of other things including real work. But now, it's finally done.
It's got a whole bunch of new features, including highlight all, a configurable shortcut key, update checking, a close button, the ability to follow links you've found by pressing alt-enter, improved support for frames pages, and more.
A full list of new features, as well as download links, are available here. As before, full source is available.
I'd like to thank Eric Lawrence for his input and help.
If you have any questions or comments, please leave a comment here.
Category: Software
Posted on 2007-02-03 13:52:42 UTC. Show Comments (5)
Some images for you to enjoy
As you know, I'll soon be going to Japan. One of the things that should become a regular occurrence is me posting pictures to my blog. That is one of the reasons I got this blog. It's definitely the reason I wanted (and got) a digital camera for my last birthday.
The problem was, since this blog is entirely self-written, I didn't have any functionality for adding images to posts (other then uploading them manually and putting links or img tags in the post manually). This wouldn't do of course; it needs to be easy.
So I added some stuff to accommodate this. It's actually a pretty neat system: I upload a zip file and it extract the images, creates thumbnails, extracts the title and description (if any) from the image's EXIF metadata, and stores all the necessary bits in the database. And from the reader's (i.e. your) perspective, you get a nice list of images at the bottom of the post, and a gallery page for viewing them. You can have the image scaled to fit your screen, or download all images in a zip file.
Of course, I had to test this, so here's some random images for your viewing pleasure. :)
Category: Personal
Posted on 2007-02-06 22:25:04 UTC. Show Comments (1)
I'm famous! Again!
When I first made Find As You Type available, I was surprised by the positive feedback I got, including a mention on the IE blog.
And now, my software was included in the newsletter of a Japanese webzine, Windows Forest. The article is here (Japanese only).
This means that when I go to Tokyo in two months, I won't be able to walk down the street without getting recognized. Okay, that'll probably only happen if I wear a shirt that says "僕は「Find As You Type」の作者です" (I'm the creator of Find As You Type). And even then probably not. :P
So I'd like to thank the good people at Windows Forest for their kind reference, and to all the people visiting from Japan I'd like to say: ようこそ! Got any tips for a Dutch guy who's about to go to Japan for two years?
Category: Personal
Posted on 2007-02-13 17:53:00 UTC. Show Comments (2)
Moving pains
Today the site of Obs De Wilgen, the primary school where my dad works (I created the site), moved servers. We didn't want it to move servers today, but it did.
We did intend an eventual server move. We were previously located at Nedcomp, whom I would qualify as reasonable. Their support level is nothing short of great, but they're quite expensive for very little space. And since XS4ALL were offering a package which was free for schools, offered more space, more bandwidth, more features, and had the stuff we needed (ASP.NET 2.0), we were going to move to them.
But in every communication we had with them, we said: "don't move the DNS; we first want to transfer the site, only then can you change the DNS." That didn't stop them from changing it anyway, so this morning we found the site non-functional, displaying only a generic XS4ALL under construction page. What's worse, we didn't have the password.
After figuring out how to set the password, I quickly threw up a "sorry, we're down" message, frantically downloaded the whole site from the old server, and uploaded it to the new one. Then the trouble began.
XS4ALL are running the site at reduced Code Access Security permissions, which meant that a few of the assemblies I used which didn't have the AllowPartiallyTrustedCallers attribute set refused to load. Fortunately they were all my own code, so I added the attribute, recompiled and uploaded. Then it broke on some of my custom configuration sections, since apparently you need ConfigurationPermission for that which we didn't have anymore. So I had to rewrite some code to use AppSettings instead. Not nearly as nice as a custom config section, but it works.
Then I discovered none of the web services worked, which was due to this issue. Apparently we don't have write access to the temp directory. I sent them a support request on that, and in the mean time I worked around the web service stuff. All of the web services were used in the admin part of the site anyway, so it shouldn't affect regular visitors of the site.
All in all, not how I'd planned to spend my Saturday. :(
Oh, and XS4ALL's helpdesk page says they're running SQL Server 2005, while in fact it's 2000. That's not a huge issue for me, but a bit sloppy about the mistake on the helpdesk page.
Category: General computing
Posted on 2007-02-17 21:29:53 UTC. Show Comments (0)
Zweetkamertje
Last november, I graduated my Master of Science at Leiden University, however there was one last bit missing.
At Leiden University, there's an old, old tradition. Everybody who graduates gets to put their name in the famous "zweetkamertje" in the academy building. "Zweetkamertje" literally translates as "sweating room", and it is so called because it used to be the place where students would have to wait for the final exam results before graduation (so they'd be sweating because of the tension). Who exactly the first person was who wrote their name on the wall no one knows, nor why this form of graffiti became a tradition. But it's been going on for more than a century, so the wall of the "zweetkamertje" is filled with many names. Many of the old ones have worn off by now of course, but there's still a lot. Some famous names are kept specially preserved behind glass, including the names of almost the entire Dutch royal family, but also for instance Nelson\ Mandela. Many will agree that you've not really graduated until your name is on that wall.
Normally, putting your name there is done on the day of graduation. But unfortunately, the building is undergoing massive renovation so it's normally closed. But on appointment, graduates can still write their name on the wall.
This afternoon, I added my name to that famous wall.
Category: University
Posted on 2007-02-19 18:03:38 UTC. Show Comments (0)
IE add-on development: capturing keyboard input outside tabs
This is the fifth article in a series on IE add-on developpment.
Last time, I explained how you could use windows hooks to capture any key before IE itself got a chance to do anything with it. I also highlighted a problem: it didn't work in IE7 when the focus is not on the tabs. In this article, I'm providing a solution to that problem.
The problem we are running into is quite simple. Each tab run on its own thread, and we're installing thread-specific keyboard hooks. The top-level window, which contains some parts of the chrome that fall outside the tabs such as the back/forward buttons, the address bar, the search bar and the command bar (that's a lot of bars!), runs on a different thread too, so none of the keyboard hooks we've installed will catch that.
The solution is equally simple: we need to install a keyboard hook for the top-level window's thread. It's compounded slightly by three issues: each window can have multiple tabs (thus multiple instances of the BHO), but we only want to install the top-level hook once; each process can contain multiple top-level windows so we can't solve the first problem by using one hook per process; and we need a way to communicate with the active tab. There may be multiple ways to solve all these issues; below I give the ones I used for Find As You Type.
Installing the top-level hook
Before we get to worry about the other issues, let's look at how we install the keyboard hook. Unfortunately, BHOs are tied to a particular tab and its associated thread, so none of our code will ever run on the top-level window's thread. Fortunately, we can easily find the top-level window handle by using IWebBrowser2::get_HWND. Then we can use GetWindowsProcessThreadId to find the thread ID and install the hook. This is done in the BHO's SetSite method, just like setting the regular hook.
HWND hwnd; if( SUCCEEDED(_browser->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&hwnd))) ) { DWORD topLevelThreadId = GetWindowThreadProcessId(hwnd, NULL); DWORD currentThreadId = GetCurrentThreadId(); if( topLevelThreadId != currentThreadId ) { // Important: the hook needs to be unset! HHOOK hook = SetWindowsHookEx(WH_KEYBOARD, TopLevelKeyboardProc, NULL, topLevelThreadId); } }
One interesting thing to not is that, before I set the hook, I compare the current thread ID to the top-level thread ID. If these two are the same, it means that either tabs are disabled or the user is running a browser older than IE7. In these cases, the regular hook will suffice so we don't need to install an additional one.
As you can see, in the above example I'm discarding the hook handle so I can't free it (I've made a note of that in the code to make sure nobody copies that sample and subsequently forgets to free the hook). That part is taken care of below.
Preventing multiple hooks
The code above would be run every time an instance of our BHO is created, and that will be done for every tab. As I remarked, one window can have multiple tabs, so we're potentially setting the same hook multiple times. We don't want that. To prevent this from happening, what I did is to use an std::map which stores which thread IDs already have a hook installed. The map is checked, and only when the thread ID isn't present do we set the hook. This prevents duplicate hooks, while still allowing for multiple top-level windows in the same process.
Of course, we also need to unset the hook exactly once. This is done by keeping a reference count (also in the std::map) which is incremented every time a tab is opened and decremented when it is closed. When it reaches zero, we can safely unset the hook.
And since this map will be shared between multiple BHOs on different threads, accessing it must be guarded, for which I use a critical section.
To facilitate all this, we need to add a few members to the BHO's class.
class ExampleBHO : public IObjectWithSite { /* For other members, see the previous article */ private: struct HookInfo { HookInfo(HHOOK hook, HWND window) : Hook(hook), RefCount(1), Window(window) { } HHOOK Hook; int RefCount; HWND Window; }; typedef std::map<DWORD, HookInfo> RefCountedHookMap; // This keyboard hook procedure is in addition to the one we used last time; // it does not replace it static LRESULT CALLBACK TopLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam); public: static CRITICAL_SECTION _topLevelHookLock; static RefCountedHookMap _topLevelHookMap; }; // Code to initialize the critical section omitted. CRITICAL_SECTION SearchBrowserHelper::_topLevelHookLock; SearchBrowserHelper::RefCountedHookMap SearchBrowserHelper::_topLevelHookMap;
Now, we can write code in SetSite to properly set, keep track of, and unset the hook.
STDMETHODIMP SearchBrowserHelper::SetSite(IUnknown *punkSite)
{
// Code from previous article, as well as some error checking code, has been omitted.
if( _browser != NULL ) // _browser stores the current sites IWebBrowser2 interface, if any
{
DWORD topLevelThreadId = GetWindowThreadProcessId(hwnd, NULL);
DWORD currentThreadId = GetCurrentThreadId();
if( topLevelThreadId != currentThreadId )
{
EnterCriticalSection(&_topLevelHookLock);
RefCountedHookMap::iterator it = _topLevelHookMap.find(topLevelThreadId);
if( it != _topLevelHookMap.end() )
{
it->second.RefCount--;
if( it->second.RefCount == 0 )
{
UnhookWindowsHookEx(it->second.Hook);
_topLevelHookMap.erase(it);
}
}
LeaveCriticalSection(&_topLevelHookLock);
}
}
if( punkSite != NULL )
{
/* Code to retrieve the IWebBrowser2 interface goes here, which is stored in _browser */
if( SUCCEEDED(_browser->get_HWND(reinterpret_cast<SHANDLE_PTR*>(&hwnd))) )
{
DWORD topLevelThreadId = GetWindowThreadProcessId(hwnd, NULL);
DWORD currentThreadId = GetCurrentThreadId();
if( topLevelThreadId != currentThreadId )
{
EnterCriticalSection(&_topLevelHookLock);
RefCountedHookMap::iterator it = _topLevelHookMap.find(topLevelThreadId);
if( it == _topLevelHookMap.end() )
{
HHOOK hook = SetWindowsHookEx(WH_KEYBOARD, TopLevelKeyboardProc, NULL, topLevelThreadId);
_topLevelHookMap.insert(std::make_pair(topLevelThreadId, HookInfo(hook, hwnd)));
}
else
{
it->second.RefCount++;
}
LeaveCriticalSection(&_topLevelHookLock);
}
}
}
return S_OK;
}
Implementing the hook procedure
Chances are you want to do something whenever you handle whichever key it is you want to handle. Chances also are that that something depends on the current tab (e.g. you want to show a toolbar). From your regular tab's hook procedure this is simple; it's running on the active tab, so we could use Thread Local Storage to get a pointer to the BHO and use that. From the top-level window procedure it's not so easy. We're going to need to find the active tab and communicate with it.
There are multiple ways to do this. One way is that you could use DWebBrowserEvents2::WindowStateChanged to keep track of the currently active tab. I however chose to use EnumChildWindows to find the currently active tab. This is easy to do since the active tab is the only child window of the "TabWindowClass" class that is visible. Then to communicate I re-send the key to the active tab so its own hook procedure will catch it (of course, don't just forward all keyboard messages to the active tab; only do this for keys you want to handle!)
LRESULT CALLBACK SearchBrowserHelper::TopLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam) { if( code >= 0 ) { HWND hwnd = NULL; EnterCriticalSection(&_topLevelHookLock); RefCountedHookMap::const_iterator it = _topLevelHookMap.find(GetCurrentThreadId()); if( it != _topLevelHookMap.end() ) { hwnd = it->second.Window; } LeaveCriticalSection(&_topLevelHookLock); if( hwnd != NULL && GetActiveWindow() == hwnd ) { if( IsShortcutKeyPressed(wParam) ) // This checks whether it's a key we want to handle { HWND activeTab = NULL; EnumChildWindows(hwnd, FindActiveTabProc, reinterpret_cast<LPARAM>(&activeTab)); SetFocus(activeTab); // Dispatch to that tab's hook procedure PostThreadMessage(GetWindowThreadProcessId(activeTab, NULL), WM_KEYDOWN, wParam, lParam); return TRUE; } } } return CallNextHookEx(NULL, code, wParam, lParam); } BOOL CALLBACK SearchBrowserHelper::FindActiveTabProc(HWND hwnd, LPARAM lParam) { WCHAR className[50]; if( IsWindowVisible(hwnd) && GetClassName(hwnd, className, 50) > 0 && wcscmp(className, L"TabWindowClass") == 0 ) { *reinterpret_cast<HWND*>(lParam) = hwnd; return FALSE; } return TRUE; }
And that's all there is to it. As usual, you can find the full details in the Find As You Type source code, which is a working implementation of all this.
This is the last article in this series for now. I've covered all I wanted to cover, so until I get a request for more or think of something myself, you'll have to do without.
UPDATE: I have discovered that this method is not entirely fool-proof, particularly the way of communicating with the active tab. The SetFocus method, while it appears to achieve the desired effect, isn't meant to be used across threads as is done here.
Eric Lawrence has alerted me that this also doesn't work in windows without a toolbar; in that case, the FAYT toolbar is not shown (obviously), and (assuming you're using the default keyboard shortcut), neither is IE's own find dialog. I hope to fix this in a future version.
UPDATE 2007-10-17: A better method to communicate with the active tab is described here.
Category: Programming
Posted on 2007-02-21 15:43:20 UTC. Show Comments (1)
Try/Finally vs. RAII
Developing Find As You Type meant doing something I hadn't done in a while: pure C++ development. I'm also using C++ for some other projects now so I'm really getting back into the unmanaged way of thinking.
As a young whippersnapper I first learned to program in GW-BASIC, which was really easy in terms of memory management: there was none. There were no pointers, and all variables were global. In the early nineties I moved on to Visual Basic which, as long as you know the caveats of reference counting (e.g. the circular reference problem) is also not one of the biggest challenges in that regard. Then of course I started to learn C/C++ and got to deal with the wonders of malloc/free and new/delete.
One of the difficulties here is making sure that no matter what happens your resources get released, especially when using exceptions. How to make sure delete is called even when an exception occurs? And it's not just pointers; things like CloseFile or LeaveCriticalSection are all things that you want to regardless of how and where you exit the function. Of course there is a perfectly elegant way to deal with this, which I will come to later.
When .Net came out I learned about try/finally (which Java also has, but I hadn't really used Java at that time) and wondered why C++ didn't have this. The finally block is always executed. It doesn't matter if the try block was left because of control flow reaching the end of the block, because of a return statement, or an exception thrown; it would be executed. Back then I thought that would be a really useful thing to have in C++.
What I realized later is that the reason C++ doesn't have (or need) try/finally is because it doesn't have the problem try/finally was designed to solve: the lack of deterministic finalization. Since .Net and Java are garbage collected, there's no telling when finalizers will run so they need try/finally to be able to make sure things are cleaned up in a timely fashion. C#'s (and VB2005's) "using" statement is an example where try/finally is used (under the hood) for precisely this purpose.
C++ however does have deterministic finalization: a class variable's destructor will always run when it goes out of scope, and that too will happen for both exceptions and regular return. So the answer to the problem is that whenever you need to make sure something is cleaned up you simply create a wrapper class that does that finalization in its destructor. Of course I wasn't the first to think of that, and this is in fact a well known pattern called Resource Acquisition Is Initialization (RAII for short). C++ even provides one such wrapper class for automatic management of pointers: std::auto_ptr.
The reason I write about this now is because Find As You Type 1.2 is the first project where I really got to use this. For example I used the following two classes to deal with critical sections:
class CriticalSectionLock { public: CriticalSectionLock(LPCRITICAL_SECTION section) : _section(section) { if( _section != NULL ) EnterCriticalSection(_section); } CriticalSectionLock(const CriticalSectionLock &right) : _section(right._section) { if( _section != NULL ) EnterCriticalSection(_section); } CriticalSectionLock& operator=(const CriticalSectionLock &right) { if( _section != NULL ) LeaveCriticalSection(_section); _section = right._section; if( _section != NULL ) EnterCriticalSection(_section); return *this; } ~CriticalSectionLock() { if( _section != NULL ) LeaveCriticalSection(_section); } void Leave() { if( _section != NULL ) { LeaveCriticalSection(_section); _section = NULL; } } private: LPCRITICAL_SECTION _section; }; class CriticalSection { public: CriticalSection() { InitializeCriticalSection(&_section); } ~CriticalSection() { DeleteCriticalSection(&_section); } CriticalSectionLock Enter() { return CriticalSectionLock(&_section); } private: CRITICAL_SECTION _section; };
This enabled me to do two things: by having the CriticalSection class manage the initialization and freeing of a CRITICAL_SECTION, I could simply create static lifetime variables of that class which meant I no longer had to write code in DllMain to call Initialize/DeleteCriticalSection. It also enabled me to write code like this whenever I needed to enter a critical section:
CriticalSection _section; // global variable managing the lifetime of the critical section itself. void SomeFunction() { CriticalSectionLock lock = _section.Enter(); // Do work here. }
When the function exits, the CriticalSectionLock destructor will run, causing LeaveCriticalSection to be called. This happens even if the code doing the work throws an exception. If I want to leave the section before function exits I can simply call lock.Leave() myself.
So which is better? Try/finally certainly has the merit that you don't need to create a class for every little thing that needs this. The RAII approach feels cleaner to me. I don't know.
What is interesting is that C++/CLI not only brings try/finally to C++, it also brings RAII to .Net. Normally when you want to create a variable
of a managed class type in C++/CLI you would use e.g. StreamReader ^reader = gcnew StreamReader(L"test.txt"); and then you'd have
to call Dispose on it which is possible to ensure with try/finally in C++/CLI but unfortunately you don't get the convenient using syntax from C#.
But the nice thing is that you can also create the variable like this: StreamReader reader(L"test.txt");. That's using the syntax
for stack variables. Of course, you don't actually get a stack variable; it's still a reference type, created on the managed heap. But
you do get the semantics of a stack variable; if the class implements IDisposable, Dispose will be called automatically when the variable goes
out of scope. And that is very nifty indeed.
Category: Programming
Posted on 2007-02-22 11:01:53 UTC. Show Comments (0)








