Saturday, May 31, 2008

the TIF really is a cache -- and it acts like one

It seems obvious, but I guess it has to be said. Internet Explorer's Temporary Internet Files folder is a cache. From Wikipedia: a cache is a temporary storage area where frequently accessed data can be stored for rapid access. Please note the use of the word temporary. This means that the data you want may not actually be there.

I frequently see questions along the lines of "How can I find this image/page in the cache?" or, even better, "How come this page I viewed isn't in the cache?" I love it when the askers of the latter form imply that this is a bug.

Files in the cache can be deleted at anytime. You can be looking at a page and then go to the cache and find it isn't there. There are many scenarios where this is legitimate. For example, if, while you are viewing this page, you go to Tools->Delete Browsing History and then clear your Temporary Internet Files, the only representation that IE will have of this page and all of its elements is the one in memory. Close the browser and that memory is freed and now its gone until you download it from the webserver again.

The page you are viewing may use the no-cache http header. The cache manager may have decided its time to scavenge. There may have even been an error writing to the cache and the cache has become corrupt.

I am sure there are other scenarios as well. The point is, you cannot rely on data to always be there. You must either capture the data in some other way, or be prepared to make a request to the server.

Thursday, May 29, 2008

to help or not to help

More and more I am confronted with a sort of moral dilemma. I read and post to the IE development forums over at MSDN. Frequently I see posts like this:

i want a BHO which continuously observe IE activity and send the URLs visited and time of visit and time of outfocus of them as soon as they are available,to listening java servlet. Now Please suggest me how to do this,perticularly this sending of url and their time details to java servlet.


He does not ask for help with a specific problem or bug he has encountered in the process. He does not tell us what he has done or post a sample of code he can not get to work. He simply asks, "how do I do this?" His masterful command of English does not help his case.

This guy is probably up to no good.

I can only think of one semi-legit use for such a thing, which would be for employers or parents to spy on their employees or children. And I'm not sure I can even get behind that.

So my dilemma becomes, do I help this guy? Do I tell him what to do? On the one hand, I do not want to be enabling spyware makers in anyway. On the other hand, if someone is paying him to do this thing, he is going to write the program whether I help him or not. Given that, is it better to make sure he does it right? I mean, which is better, crashing spyware or non-crashing spyware?

Anyway, today I am not helping this guy.

Tuesday, April 15, 2008

PicLens 1.6.3 Released

We released 1.6.3 yesterday to piclens.com. Why the change from 1.6.2 to 1.6.3? Because we now support video in the Wall.



You can search YouTube and watch the videos right in the wall. During development we spent a lot of time watching videos... it can be very addictive.

Thursday, March 20, 2008

I can't stand it anymore, I have to say it

I love my iPhone.

There, I said it. I feel dirty.

My Windows-based SmartPhone got crushed in an under-the-motorized-car-seat retrieval operation that went horribly awry. Faced with a rapidly diminishing usable space on the cracked LCD screen, I went to the AT&T store to get the new hotness.

I suspected I wanted an iPhone. I didn't want to pay a lot for a phone, but what can you do? I carefully considered each and every phone they had and compared it to the iPhone. The results fell into one of two catagories:

1) This phone is complete crap and they have the nerve to charge [$100..$200] for it.
2) This phone isn't an iPhone, but costs just as much.

So I bought the iPhone.

The installation experience was harrowing. I found out you have to activate it through iTunes. I hate iTunes. I hate QuickTime. I hate all Apple software. I hate all third party software. I don't like the way it infects my machine with little processes that run without asking and little icons littered about the desktop and quicklaunch and systray. I hates it all.

But my wife has an iPod, so it was already installed. What can you do?

However, I had to upgrade to the latest version of iTunes. So I fired up IE and went to apple.com. Halfway through the download, IE crashed.

So I used FireFox to download iTunes.

As a developer who used to work on Internet Explorer, you have no idea how much that hurt me. It was a sad, sad day. Snow flakes were reported in Hell.

But now I have the iPhone and it's the best phone ever and probably the best UI for anything other than PicLens.

Friday, March 14, 2008

WikiTalk redesign

My previous project, WikiTalk, has just undergone a major face lift this morning. I didn't know it was coming and was quite shocked to see it when I loaded it before work this morning.



Congratulations guys. It looks really good.

Sunday, March 9, 2008

PicLens: new version and news round-up

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.

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:

[
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.