Posts for category 'Programming'

Vista manifests and .Net

There's a few things for your application in Windows Vista that you can control using a manifest. Perhaps the most well-known use for manifests is to specify the use of the comctl32.dll version 6, which is needed to use visual styles with your application. I'm not talking about this though, as this has been the case since XP, and there's no problem here with .Net since you can (and probably do) use Application.EnableVisualStyles() instead of a manifest.

Vista also offers some other stuff which you can control with manifests: requested execution level and high-DPI compatibility (aka DPI awareness).

The first you'll probably know: with UAC enabled, applications running on Vista don't get admin privileges by default, even if the current user is a member of the Administrators group (a good post about how this actually works can be found here). If you do need admin privileges, the application must be elevated. You can do this manually by right-clicking the application executable or shortcut and choosing "Run as administrator". Additionally, Vista uses some heuristics to automatically prompt for elevation for some apps.

But the recommended approach to doing this is by specifying the "requestedExecutionLevel" attribute in your application's manifest. You can use the "requireAdministrator" value to force elevation, but even if you don't need elevation it has benefits to use a manifest (specifying "asInvoker" to indicate you don't need anything special in this case). If you do this, you're sure that Vista's heuristics will never accidentally elevate your application when it doesn't need to be. Additionally, using a manifest will disable file and registry virtualization (a technique that allows badly behaved applications to think they can write to protected locations such as Program Files or HKEY_LOCAL_MACHINE, when in fact they can't; the writes are redirected to a virtualized location). Disabling virtualization helps enforce that your app behaves properly (since writes to those locations will now actually fail instead of be redirected) and makes sure it behaves consistently on x64; a .Net 2.0 application (compiled with the default "Any CPU" setting) when run under an x64 version of Windows will run as a native x64 application, and x64 processes never use virtualization.

The second, less well-known thing you can do with manifests in Vista is mark your application DPI aware. There are two ways to do this: by using the SetProcessDPIAware() function in the Win32 API, or by using a manifest. In my experience, the former method will break .Net's automatic scaling in some circumstances, no matter how early you call the function. The manifest approach doesn't have this problem. If your application is not marked DPI aware, it will be scaled by the Desktop Window Manager under non-standard DPI values, leading to reduced image quality (most notably, fuzzy text). Of course, only mark your application as DPI aware when it actually is DPI aware: be sure to test your application with different DPI settings!

A manifest that controls these two settings is shown below:

<?xml version="1.0" encoding="UTF-8"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <asmv2:trustInfo xmlns:asmv2="urn:schemas-microsoft-com:asm.v2">
    <asmv2:security>
      <asmv2:requestedPrivileges>
        <asmv2:requestedExecutionLevel level="asInvoker" />
      </asmv2:requestedPrivileges>
    </asmv2:security>
  </asmv2:trustInfo>
  <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>
</assembly>

Now there's two ways you can include this manifest: as an external file named "YourExecutable.exe.manifest" or by embedding it into your executable. I vastly prefer the latter method since it eases deployment.

This is where it gets tricky (and also .Net specific; everything I've said so far applies to both native and .Net apps). Where Visual C++ automatically embeds manifests in executables, the C# and VB compilers do not offer this possibility. Fortunately, we can embed the manifest ourselves, using mt.exe (which is shipped with Visual Studio, and also with the Windows SDK):

mt.exe -manifest YourExecutable.exe.manifest -outputresource:YourExecutable.exe;#1

Looks simple enough. But wait! If the assembly was strong named, embedding the manifest invalidates the hash and thus breaks the executable (the latest version of mt.exe will warn you of this). The solution is to re-sign with sn.exe (comes with the .Net Framework SDK):

sn.exe -Ra YourExecutable.exe YourStrongNameKey.snk

Now wouldn't it be nice if this could be done automatically? Fortunately, it can, using build events. Build events can be found in the project properties in Visual Studio. For C# projects, it's a separate tab in the list on the left ("Build Events"). For VB projects, use the "Build Events" button at the bottom of the "Compile" tab. Set the post-build event command line to the following:

"$(DevEnvDir)..\..\VC\bin\mt.exe" -nologo -manifest "$(ProjectDir)app.manifest" -outputresource:"$(TargetPath);#1"
"$(DevEnvDir)..\..\sdk\v2.0\Bin\sn.exe" -q -Ra "$(TargetPath)" "$(ProjectDir)strongnamekey.snk"

You may need to adjust the paths of mt.exe and sn.exe depending on your Visual Studio installation. You will also need to modify the name of the strong name key file and manifest file.

There's one thing left. If you use the latest version of mt.exe, it will now perpetually warn you about the need to re-sign your assembly, which Visual Studio picks up on and shows in the Error List. To prevent that, check the "Delay sign only" box on the "Signing" tab of the project settings. Now your assembly will not actually be signed when mt.exe is run, so there's no warning. Sn.exe will correctly sign it afterwards.

Categories: Programming
Posted on: 2007-07-06 13:53 UTC. Show comments (12)

Bitten by COM

It's official: I've been spoiled by the niceties of .Net. In yesterday's IXMLHTTPRequest example, I made a very stupid mistake.

What I did is a mistake that every COM programmer should be aware of: I created a circular reference. Since COM is reference counted, circular references create memory leaks. For those who're not familiar with this particular problem (perhaps because they've never used a reference counted system), here's what happens: when object A is created, its reference count is 1. A creates object B and holds a reference to it; its reference count is also 1. B takes a reference back to A, so its reference count becomes 2. Now the original creator of A is done with it, and releases it: the count for A is now one. But because B still holds a reference to A, its count doesn't reach zero until B is destroyed. But B will not be destroyed until its own count reaches zero, which won't happen until A is destroyed. Neither A nor B ever gets destroyed, so you get a memory leak.

In my case, the event sink object held a reference to the IXMLHTTPRequest, which held a reference to the event sink. In my sample, this wasn't much of a problem since the objects would get forcably released when CoUninitialize was called, which happened only a few nanoseconds after we were done with the objects. But in a real application, this can be a problem.

The solution is fortunately very easy: cheat with the reference count. By having the event sink not call AddRef on the request, the problem disappears. And since we can ensure that the event sink object will never use request object after it's been freed, this is safe.

I've updated the sample code. In addition to this change, I've also made the reference counting of the event sink object thread safe, since it was being used on more than one thread.

Categories: Programming
Posted on: 2007-06-22 07:04 UTC. Show comments (6)

Using IXMLHTTPRequest onreadystatechange from C++

Pete Warden left a comment on my blog that he's porting a Firefox extension to IE and that he appreciates the articles I wrote about IE add-on development. Thanks Pete, good to know they're good for something. :)

Anyway, I was reading his blog and came across this post. He indicates he isn't going to use MSXML's XMLHTTPRequest object, because using the onreadystatechange event from C++ is too complicated. While I agree that it's poorly documented and hampered by the fact that almost all samples that talk about it use ATL, it's not actually that hard to use.

The documentation for onreadystatechange suggests you need to use connection points to get the event, but that isn't true (it doesn't even appear possible as querying an XMLHTTPRequest object for either IConnectionPoint or IConnectionPointContainer fails).

In fact, all you need to do is create a simple IDispatch implementation, and pass this to the onreadystatechange property. It will call Invoke with a dispIdMember of zero every time the onreadystatechange event is raised. The class that receives the event is pretty vanilla:

class XMLHttpEventSink : public IDispatch
{
public:
    XMLHttpEventSink(IXMLHTTPRequest *request) : _refCount(1), _request(request) 
    { 
        // Don't increase the reference count to the request object;
        // doing so would create a circular reference and thus a memory leak.
    }
    virtual ~XMLHttpEventSink() 
    { 
    }

    // IUnknown 
    STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();

    // IDispatch
    STDMETHODIMP GetTypeInfoCount(UINT *pctinfo);        
    STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo);    
    STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames, LCID lcid, DISPID *rgDispId);
    STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr);
private:
    ULONG _refCount;
    IXMLHTTPRequest *_request;
};

As you can see, it's a simple class that implements the IDispatch interface. For this example, I'm also storing the IXMLHTTPRequest object itself in a member so we can use it later. The implementations of the IUnknown methods are bog-standard COM, and all IDispatch members except Invoke never get called so they can just return E_NOTIMPL. I won't post that code here, but if you're really interested in it, you can see it in the full sample. An example Invoke implementation that checks the state and prints part of the response when completed is shown below:

STDMETHODIMP XMLHttpEventSink::Invoke(DISPID dispIdMember, const IID &riid, LCID lcid, WORD wFlags,
                                      DISPPARAMS *pDispParams, VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
{
    // Since this class isn't used for anything else, Invoke will get called
    // only for onreadystatechange, and dispIdMember will always be 0.

    long state;
    // Retrieve the state
    _request->get_readyState(&state);
    std::wcout << L"State: " << state << std::endl;
    if( state == 4 )
    {
        // The request has completed.
        // Get the request status.
        long status;
        _request->get_status(&status);

        std::wcout << L"Status: " << status << std::endl;

        if( status == 200 )
        {
            // Get the response body if we were successful.
            _bstr_t body;
            _request->get_responseText(body.GetAddress());
            std::wstring bodyString = body;

            std::wcout << L"First part of response: " << std::endl;
            if( bodyString.length() > 200 )
                bodyString = bodyString.substr(0, 200);
            std::wcout << bodyString << std::endl;
        }
    }
    return S_OK;
}

Note that this code has no error handling for reasons of readability. In a real application, you will want to add that.

The one thing that remains is the question of how we use this with IXMLHTTPRequest itself. As I indicated, this is much simpler than the documentation makes it out to be. We simply instantiate the event sink object, and pass it to IXMLHTTPRequest::put_onreadystatechange:

// Create XMLHTTPRequest object.
IXMLHTTPRequest *request;
CoCreateInstance(CLSID_XMLHTTP30, NULL, CLSCTX_INPROC, IID_IXMLHTTPRequest, reinterpret_cast<void**>(&request));

// Open the request
_bstr_t method = L"GET";
_bstr_t url = L"http://www.ookii.org/rss.ashx";
_variant_t async = true;
request->open(method, url, async, _variant_t(), _variant_t());

// Hook up the onreadystatechange event handler
IDispatch *sink = new XMLHttpEventSink(request);
request->put_onreadystatechange(sink);

// Send the request
request->send(_variant_t());

Note again that this code has no error handling. You may also note that I use MSXML 3; this version of MSXML is supported, and was included with IE6 so almost everybody has it.

And there you have it. Not quite as difficult as it may seem at first glance.

Download the full sample.

Categories: Programming
Posted on: 2007-06-21 10:10 UTC. Show comments (5)

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.

Categories: Programming
Posted on: 2007-02-22 11:01 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.

Categories: Programming
Posted on: 2007-02-21 15:43 UTC. Show comments (4)

Latest posts

Categories

Archive

Syndication

RSS Subscribe

;