We worked hard all week, some team members staying awake for inadvisably long periods of time, to bring you all our latest work: PicLens 1.6.2.
This new version fixes many performance and stability issues, and most notably, improves graphics support for a significant number of people who may have had issues with previous versions of PicLens. We added Direct3D 9 support when running on Windows. This means you must have Windows XP Service Pack 2 (or the DX9 redist) installed or you will be running with a software renderer.
In the news, the New York Times has an article on PicLens and the future of web browsing. And we've been slashdotted.
Practical coding tips, tricks, advice and anecdotes. Including, but not limited to: C++, C#, JavaScript, COM, OLE, Internet Explorer, Win32 and what-not.
Sunday, March 9, 2008
Tuesday, February 19, 2008
how to create an activex control that fires events to javascript (without using ATL)
Creating ActiveX Controls that Fire Events (without ATL)
I recently spent a fair bit of time at work figuring out how to write all the COM and OLE goo to make this work. There are lots of articles that tell you how to use ATL, but I am against ATL and I wanted to do it the old fashioned way. Here is a summary of what I learned.
The scenario was this: I wanted to have an ActiveX control that would download some file on a worker thread, then fire events that could be handled by JScript before, during and after the download. The general outline would look like this:
1. Page loads, control instantiated via object tag.
2. JScript calls attachEvent() to setup event handlers for the control.
3. JScript calls a method to start download.
4. Control spins up a worker thread which calls URLDownloadToFile().
5. The worker thread receives progress notifications via IBindStatusCallback(). Control fires events to JScript (from the worker thread) to inform JScript of progress.
6. Download completes, worker thread fires event to JScript.
I will call my control the Downloader and his CLSID is CLSID_DownloaderCtrl.
To make this happen your control must implement (and respond to in IUnknown::QueryInterface()):
1. IUnknown
2. IDispatch
3. IProvideClassInfo, IProvideClassInfo2
4. IObjectWithSite
5. IConnectionPointContainer
6. IDownloader (this is the dual interface for scripts to call methods the control exposes)
7. IObjectSafety
8. IServiceProvider (for URLDownloadToFile() to work properly)
9. IBindStatusCallback (optional -- only if you want download progess)
You will also need to implement IConnectionPoint, but do not repsond to it in QueryInterface. More on this later.
IUnknown
I assume you know how to implement IUnknown. Read Raymond's post on getting it wrong to make sure you know how to implement it.
Type Libraries
Before we can do much more we need a type library. To get this working, you have to create an .idl file that contains definitions. There are four important parts. Each one has its own GUID. They are specified in the .idl and MIDL will generate a header file and c file that defines them.
1. The outgoing event (disp)interface. DIID_DDownloaderEvents
2. The incoming (dual) interface. IID_IDownloader
3. The coclass goo. CLSID_DownloaderCtrl
4. The library. LIBID_Downloader
Your .idl should look something like this:
MIDL will generate a .tlb file from this. You must include this type library as a resource in your .dll. To do that, add a line like the following:
1 TYPELIB "downloader.tlb"
Now you can call LoadTypeLib() using your module's path to get the type library, which you will need to use in your IDispatch implementation.
IDispatch
I am going to assume you know mostly know how to implement this as well. It is well documented. The important thing is to make sure you expose your typelib correctly.
IProvideClassInfo, IProvideClassInfo2
These are pretty straight-forward. In GetGUID() return your outgoing event interface, DIID_DDownloaderEvents.
The only tricky part is in GetClassInfo. You should call LoadTypeLib() then call ITypeLib->GetTypeInfoOfGuid(). The question is, which GUID do you use? The correct answer is CLSID_DownloaderCtrl. This gets the type info of your coclass, which Internet Explorer can use to figure out what your outgoing event interface is.
IConnectionPointContainer
When you call attachEvent() in JScript, IE will ask for this interface to try to find a connection point for your outgoing event interface. You have to implement FindConnectionPoint(). I found EnumConnectionPoints() and to not be called by Internet Explorer. However, you may experience different results here. Set breakpoints and/or use asserts() to make sure anything you E_NOTIMPL isn't called, otherwise you may find yourself debugging into the wee hours.
IConnectionPoint
FindConnectionPoint() gives out a pointer to an IConnectionPoint, which should really be a different object than your IConnectionPointContainer. See the documentation. We don't respond to IConnectionPoint in QueryInterface, since the only allowed way of getting it is via FindConnectionPoint().
You have to implement Advise() and Unadvise(). The remaining methods were never called for my implementation. However, as above, your milage may vary. Everytime IE calls Advise(), it will pass you an IUnknown. QueryInterface() for IID_IDispatch and remember that pointer. Make sure you associate it with the cookie you give back (std::map is one option, if you go in for that sort of thing).
When you want to fire your event, simply call the Invoke() member of all the IDispatch pointers you are holding on to, passing whatever parameters you want and the DISPID of whatever event you want to send.
Important: Before you use this pointer, read the bit about marshalling below.
IObjectWithSite
This is simple as well. Make sure you respond to SetSite(NULL) by releasing all the pointers you acquired from your site. Also, this is your queue that your control is going away soon.
IServiceProvider
All you have to do here is QueryInterface your site for IServiceProvider and thunk the call to QueryService() through to your site's implementation. If you're using the Vista SDK, you can use IUnknown_QueryService().
If you don't implement this, URLDownloadToFile() may not be able to get access to certain security information and your life will be harder.
IObjectSafety
You should implement this to make instantiating your control easier and safer. Refer to the documentation and plentiful on-line examples.
A Word on Marshalling
My object operates on two threads--the IE Tab thread that it is created on, and a worker thread that does the heavy lifting. The point of using a worker thread is to not hang the UI while the download happens. This gives a nice experience, but makes implementation harder.
The one thing to remember, is all of IE's interaction with your object will happen on it's thread. You will receive IDispatch pointers on this thread. You cannot use these pointers in a different apartment (which means, you cannot use them on the worker thread).
In order to fire events from the worker thread, you must marshal the IDispatch pointers. You have two options for doing this:
1) Call CoMarshalInterThreadInterfaceInStream() on the IE thread, then CoGetInterfaceAndReleaseStream() on the worker thread. Do this for every IDispatch pointer and use the pointer returned on the worker thread.
2) Use the GIT (Global Interface Table). You're on your own with that one -- see the documentation.
If you try to use the pointer on the wrong thread, your Invoke() call will fail silently and the event will not be fired.
Setting up the JScript
1. Create your object using the object tag. You cannot use new ActiveXObject() because IE will not hook-up the events for you.
2. Give your object tag and ID, such as ID="downloader".
3. In the onLoad handler for the body element, call a function that uses the ID to attach the event handlers to events. E.g., downloader.attachEvent('Progress', onProgressEvent). Then simply implement the onProgressEvent() in JScript in the script section of your HTML.
Conclusion
Well, I hope that helps someone. If you have anything to add, please leave a comment.
I recently spent a fair bit of time at work figuring out how to write all the COM and OLE goo to make this work. There are lots of articles that tell you how to use ATL, but I am against ATL and I wanted to do it the old fashioned way. Here is a summary of what I learned.
The scenario was this: I wanted to have an ActiveX control that would download some file on a worker thread, then fire events that could be handled by JScript before, during and after the download. The general outline would look like this:
1. Page loads, control instantiated via object tag.
2. JScript calls attachEvent() to setup event handlers for the control.
3. JScript calls a method to start download.
4. Control spins up a worker thread which calls URLDownloadToFile().
5. The worker thread receives progress notifications via IBindStatusCallback(). Control fires events to JScript (from the worker thread) to inform JScript of progress.
6. Download completes, worker thread fires event to JScript.
I will call my control the Downloader and his CLSID is CLSID_DownloaderCtrl.
To make this happen your control must implement (and respond to in IUnknown::QueryInterface()):
1. IUnknown
2. IDispatch
3. IProvideClassInfo, IProvideClassInfo2
4. IObjectWithSite
5. IConnectionPointContainer
6. IDownloader (this is the dual interface for scripts to call methods the control exposes)
7. IObjectSafety
8. IServiceProvider (for URLDownloadToFile() to work properly)
9. IBindStatusCallback (optional -- only if you want download progess)
You will also need to implement IConnectionPoint, but do not repsond to it in QueryInterface. More on this later.
IUnknown
I assume you know how to implement IUnknown. Read Raymond's post on getting it wrong to make sure you know how to implement it.
Type Libraries
Before we can do much more we need a type library. To get this working, you have to create an .idl file that contains definitions. There are four important parts. Each one has its own GUID. They are specified in the .idl and MIDL will generate a header file and c file that defines them.
1. The outgoing event (disp)interface. DIID_DDownloaderEvents
2. The incoming (dual) interface. IID_IDownloader
3. The coclass goo. CLSID_DownloaderCtrl
4. The library. LIBID_Downloader
Your .idl should look something like this:
[
uuid(00000000-0000-0000-0000-000000000000),
version(1.0)
]
library Downloader
{
[
uuid(11111111-1111-1111-1111-111111111111),
hidden
]
dispinterface DDownloaderEvents
{
properties:
methods:
[id(DISPID_PROGRESS)] void Progress();
[id(DISPID_COMPLETE)] void Complete();
}
[
dual,
uuid(22222222-2222-2222-2222-222222222222)
]
interface IDownloader : IDispatch
{
[id(DISPID_DOWNLOAD)] HRESULT download(BSTR bstrFile);
}
[
uuid(33333333-3333-3333-3333-333333333333)
]
coclass DownloaderCtrl
{
[default] interface IDownloader;
[source, default] dispinterface DDownloaderEvents;
}
}
MIDL will generate a .tlb file from this. You must include this type library as a resource in your .dll. To do that, add a line like the following:
1 TYPELIB "downloader.tlb"
Now you can call LoadTypeLib() using your module's path to get the type library, which you will need to use in your IDispatch implementation.
IDispatch
I am going to assume you know mostly know how to implement this as well. It is well documented. The important thing is to make sure you expose your typelib correctly.
IProvideClassInfo, IProvideClassInfo2
These are pretty straight-forward. In GetGUID() return your outgoing event interface, DIID_DDownloaderEvents.
The only tricky part is in GetClassInfo. You should call LoadTypeLib() then call ITypeLib->GetTypeInfoOfGuid(). The question is, which GUID do you use? The correct answer is CLSID_DownloaderCtrl. This gets the type info of your coclass, which Internet Explorer can use to figure out what your outgoing event interface is.
IConnectionPointContainer
When you call attachEvent() in JScript, IE will ask for this interface to try to find a connection point for your outgoing event interface. You have to implement FindConnectionPoint(). I found EnumConnectionPoints() and to not be called by Internet Explorer. However, you may experience different results here. Set breakpoints and/or use asserts() to make sure anything you E_NOTIMPL isn't called, otherwise you may find yourself debugging into the wee hours.
IConnectionPoint
FindConnectionPoint() gives out a pointer to an IConnectionPoint, which should really be a different object than your IConnectionPointContainer. See the documentation. We don't respond to IConnectionPoint in QueryInterface, since the only allowed way of getting it is via FindConnectionPoint().
You have to implement Advise() and Unadvise(). The remaining methods were never called for my implementation. However, as above, your milage may vary. Everytime IE calls Advise(), it will pass you an IUnknown. QueryInterface() for IID_IDispatch and remember that pointer. Make sure you associate it with the cookie you give back (std::map is one option, if you go in for that sort of thing).
When you want to fire your event, simply call the Invoke() member of all the IDispatch pointers you are holding on to, passing whatever parameters you want and the DISPID of whatever event you want to send.
Important: Before you use this pointer, read the bit about marshalling below.
IObjectWithSite
This is simple as well. Make sure you respond to SetSite(NULL) by releasing all the pointers you acquired from your site. Also, this is your queue that your control is going away soon.
IServiceProvider
All you have to do here is QueryInterface your site for IServiceProvider and thunk the call to QueryService() through to your site's implementation. If you're using the Vista SDK, you can use IUnknown_QueryService().
If you don't implement this, URLDownloadToFile() may not be able to get access to certain security information and your life will be harder.
IObjectSafety
You should implement this to make instantiating your control easier and safer. Refer to the documentation and plentiful on-line examples.
A Word on Marshalling
My object operates on two threads--the IE Tab thread that it is created on, and a worker thread that does the heavy lifting. The point of using a worker thread is to not hang the UI while the download happens. This gives a nice experience, but makes implementation harder.
The one thing to remember, is all of IE's interaction with your object will happen on it's thread. You will receive IDispatch pointers on this thread. You cannot use these pointers in a different apartment (which means, you cannot use them on the worker thread).
In order to fire events from the worker thread, you must marshal the IDispatch pointers. You have two options for doing this:
1) Call CoMarshalInterThreadInterfaceInStream() on the IE thread, then CoGetInterfaceAndReleaseStream() on the worker thread. Do this for every IDispatch pointer and use the pointer returned on the worker thread.
2) Use the GIT (Global Interface Table). You're on your own with that one -- see the documentation.
If you try to use the pointer on the wrong thread, your Invoke() call will fail silently and the event will not be fired.
Setting up the JScript
1. Create your object using the object tag. You cannot use new ActiveXObject() because IE will not hook-up the events for you.
2. Give your object tag and ID, such as ID="downloader".
3. In the onLoad handler for the body element, call a function that uses the ID to attach the event handlers to events. E.g., downloader.attachEvent('Progress', onProgressEvent). Then simply implement the onProgressEvent() in JScript in the script section of your HTML.
Conclusion
Well, I hope that helps someone. If you have anything to add, please leave a comment.
Friday, February 15, 2008
New build of PicLens for Internet Explorer
We posted a new build of PicLens for Internet Explorer yesterday to PicLens.com. It does not add any features, but does contain several performance and stability improvements.
If you have PicLens for Internet Explorer (or even if you don't have it yet!) you should be sure to upgrade to this latest version.
If you have PicLens for Internet Explorer (or even if you don't have it yet!) you should be sure to upgrade to this latest version.
Wednesday, February 6, 2008
PicLens 1.6.1 for Internet Explorer
As promised, we have just released PicLens 1.6.1 for Internet Explorer. You can download it by visiting http://www.piclens.com.

This is what I have been working on for the last month. It was quite a challenge, and luckily I had a fairly extensive background in Internet Explorer's extensibility model in order to make it happen. There are huge differences between the way Internet Explorer and FireFox are architected. For example, in FireFox all tabs run on the same thread. Since Internet Explorer creates a seperate UI thread for each Tab, I had to write code to demultiplex events coming from the tabs to our single PicLens thread that we create. This was done by careful use of DHTMLWindowEvents2's OnBlur and OnFocus events, as well as DWebBrowserEvents2's WindowStateChanged event. Keeping track of which of PicLens' Browser Helper Object instances (IE creates one per tab) was the "active" window was non-trivial, especially since we had to support IE6 and IE7.
In the end though I think we have a really solid product. If you notice any bugs or have any feedback, please feel free to leave me a comment or e-mail feedback@piclens.com.

This is what I have been working on for the last month. It was quite a challenge, and luckily I had a fairly extensive background in Internet Explorer's extensibility model in order to make it happen. There are huge differences between the way Internet Explorer and FireFox are architected. For example, in FireFox all tabs run on the same thread. Since Internet Explorer creates a seperate UI thread for each Tab, I had to write code to demultiplex events coming from the tabs to our single PicLens thread that we create. This was done by careful use of DHTMLWindowEvents2's OnBlur and OnFocus events, as well as DWebBrowserEvents2's WindowStateChanged event. Keeping track of which of PicLens' Browser Helper Object instances (IE creates one per tab) was the "active" window was non-trivial, especially since we had to support IE6 and IE7.
In the end though I think we have a really solid product. If you notice any bugs or have any feedback, please feel free to leave me a comment or e-mail feedback@piclens.com.
Tuesday, January 29, 2008
PicLens 1.6 for FireFox Released (or what I've been up to)
I recently moved from Washington to California to work on PicLens. PicLens is a 3D, hardware accelerated, full-screen photo viewing plug-in. Version 1.6 adds the 3D wall for viewing photos at sites like Google Images, Yahoo Images, Flickr, Facebook, MySpace, xkcd, etc. You can download it for free here.
Here's a marketing screen shot, but that's really what it looks like.

Those of you who are aware that I once worked on the Internet Explorer team at Microsoft might have some insight as to what I'm doing down here. Hint: There will be an IE version very soon.
Here's a marketing screen shot, but that's really what it looks like.

Those of you who are aware that I once worked on the Internet Explorer team at Microsoft might have some insight as to what I'm doing down here. Hint: There will be an IE version very soon.
Sunday, January 6, 2008
media center, netflix, monitor rotation and real estate
When it comes to building your home PC, different people want different things. Some people want fast processors. Some people want lots of RAM. Some people want bang for the buck. I'm not really concerned with any of that. I use my machine for browsing the web and watching TV shows or movies. I want pixels. Lots and lots of pixels. I want to be able to watching things and browse things at the same time.
I bought a Dell 2405FPW. The 24" wide screen makes me very happy. My goal is to maximize the use of the real estate this presents. I was watching TV and browsing the web with this goal in mind. Since Media Center (like all good video players) likes to keep it's aspect ratio fixed, I kept ending up arrangements like this:

Notice the big chunk of unused space in the bottom left corner. There was no way to fill this space. Then I hit on the brilliant idea of rotating the monitor to portrait and stacking media center on top of IE. Then I could use every pixel. I was excited.
But I was thwarted:

I don't know what to say. As a user, my first thought is "there is no excuse for this." As a developer who used to work on Windows, I know there was at least a reason for it. Probably some video card didn't work in this configuration, or it would have been too much testing for a non-critical scenario, or something. But it made me mad.
Now I have NetFlix though. It has an in-browser player for viewing videos on demand. Now my dreams of real estate maximization can be realized:

Good bye, Media Center. Good by Cable Television. Hello NetFlix!!
I bought a Dell 2405FPW. The 24" wide screen makes me very happy. My goal is to maximize the use of the real estate this presents. I was watching TV and browsing the web with this goal in mind. Since Media Center (like all good video players) likes to keep it's aspect ratio fixed, I kept ending up arrangements like this:

Notice the big chunk of unused space in the bottom left corner. There was no way to fill this space. Then I hit on the brilliant idea of rotating the monitor to portrait and stacking media center on top of IE. Then I could use every pixel. I was excited.
But I was thwarted:

I don't know what to say. As a user, my first thought is "there is no excuse for this." As a developer who used to work on Windows, I know there was at least a reason for it. Probably some video card didn't work in this configuration, or it would have been too much testing for a non-critical scenario, or something. But it made me mad.
Now I have NetFlix though. It has an in-browser player for viewing videos on demand. Now my dreams of real estate maximization can be realized:

Good bye, Media Center. Good by Cable Television. Hello NetFlix!!
Thursday, January 3, 2008
lenovo thinkpad R61i (or: a tale of two laptops)
Back in the day, we all knew who made the best laptops: IBM. Not because they were the cheapest or sexiest machines around. They were the toughest. They were tanks. You could drop it four stories and run it over with your car (though this was before SUVs were so popular) and the damn thing would be just fine. Of course, they were way too expensive for your average middle-class highschool kid to ever get.
When I was at MSFT I managed to get a company-issued Sony Viao TR5GP. I loved the Viao itself: it was small, portable, sexy, and not too slow. I hated Sony though. They refused to make drivers available for download or even seperate install; you had to buy the recovery DVD for $10 and then use it to fully recover the OS. Of course, doing that brought all the OEM crapware with it. So it was a constant struggle.
Now I find myself in need of a notebook once again. I will need one for the move, and Sara has been wanting one to use around the house. I disqualified Dell, because their machines are all flimsy crap. And I disqualified Toshiba because they're ugly and seemed to always have weird issues that weren't really problems, per se, just quirks. So my thoughts naturally turned to ThinkPads.
I bought the R61i because I wanted something cheap (as close to $500 as possible, newegg had it for $699). I wanted something reliable and sturdy. Its main purpose in life will be a web browsing machine and a mstsc machine. I need something to help me find an apartment when I get to California. Also, something to do on the plane.
It came last night, and I like it very much. Lenovo does not seem to have ruined the ThinkPad. It doesn't have the cute IBM logo, and it doesn't seem quite as sturdy as I remember (what is these days?), but I may have just built it up in my mind over the years. It does seem better than all the Dells I ever used at MSFT. They keyboard is nice and clicky. The screen is bright and clear. The USB ports are plentiful. It came with a nice assortment of crapware, but I uninstalled it all. It did require something like 62 Windows Updates out of the box, which was dissapointing, but what can you do? It's Windows.
Anyway, what I am getting at is just say no to Dell; buy a ThinkPad.
When I was at MSFT I managed to get a company-issued Sony Viao TR5GP. I loved the Viao itself: it was small, portable, sexy, and not too slow. I hated Sony though. They refused to make drivers available for download or even seperate install; you had to buy the recovery DVD for $10 and then use it to fully recover the OS. Of course, doing that brought all the OEM crapware with it. So it was a constant struggle.
Now I find myself in need of a notebook once again. I will need one for the move, and Sara has been wanting one to use around the house. I disqualified Dell, because their machines are all flimsy crap. And I disqualified Toshiba because they're ugly and seemed to always have weird issues that weren't really problems, per se, just quirks. So my thoughts naturally turned to ThinkPads.
I bought the R61i because I wanted something cheap (as close to $500 as possible, newegg had it for $699). I wanted something reliable and sturdy. Its main purpose in life will be a web browsing machine and a mstsc machine. I need something to help me find an apartment when I get to California. Also, something to do on the plane.
It came last night, and I like it very much. Lenovo does not seem to have ruined the ThinkPad. It doesn't have the cute IBM logo, and it doesn't seem quite as sturdy as I remember (what is these days?), but I may have just built it up in my mind over the years. It does seem better than all the Dells I ever used at MSFT. They keyboard is nice and clicky. The screen is bright and clear. The USB ports are plentiful. It came with a nice assortment of crapware, but I uninstalled it all. It did require something like 62 Windows Updates out of the box, which was dissapointing, but what can you do? It's Windows.
Anyway, what I am getting at is just say no to Dell; buy a ThinkPad.
Subscribe to:
Comments (Atom)