For a long time, LiveDictionary used deeply unwholesome methods to do its work. Version 1.2.5 , just released, now uses nothing but public methods. This means vastly improved stability, but it also means that LiveDictionary's evil WebKit text grabber, once considered the app's crown jewels, is no longer useful. I'm going to use it as an object lesson on how to do evil things with C++ applications from pure C.
Motivation
This code was initially developed over the course of about one week, and then took approximately two months of debugging before it became stable. Since then Apple has broken it several times with Safari updates, with the changes required being anything from a simple change of offsets to a large re- engineering of the function.
The prototype of the function is thus: void LiveDict_1 _3_WebViewGetTextAtPoint(id webHTMLView, NSPoint point, NSString **text, int *offset) Given an instance of a WebHTMLView (the thing inside a WebView that does all the work) and a point, the function is to return the text at that point, and the offset into that text which represents where that point is located inside it. This is then used to look up the appropriate word in LiveDictionary. (The 1 _3 thing is a version numbering scheme so it doesn't conflict with nearly identical functions made for other versions of Safari.)
You would think that this would be easy, but at the time I originally wrote this function, there was no public way to obtain this information. Obviously there is some way to do it, since WebKit itself does it, for example when you drag to select some text. So I dove into WebCore to see how it was done.
] After much digging, I found the KHTMLPart class which has a method called isPointInsideSelection that does basically the same thing. I ripped out the bits I didn't need and came up with the following C++ code:
id bridge = [webHTMLView _bridge];
KWQKHTMLPart *part = [bridge part];
DocumentImpl *impl = part- >xmlDocImpl();
khtml::RenderObject *r = impl- >renderer();
khtml::RenderObject::NodeInfo nodeInfo(true, true);
r->layer()->hitTest(nodeInfo, (int)location.x, (int)location.y);
NodeImpl *nodeImpl = nodeInfo.innerNonSharedNode();
if(!nodeImpl || !nodeImpl- >renderer() || !nodeImpl- >renderer()->isText())
return;
Position position = innerNode- >positionForCoordinates (absXPos - renderXPos, absYPos - renderYPos);
Not too bad, right? Most of the code is just drilling down to the object I need to interrogate, and then asking it. (There's a little bit at the end to get the actual text of the node that I left off.)
But... I can't just write that code. All of these classes are private and buried in WebCore so I can't link against them. I can't just copy the headers because that still requires linking against them. So I decided to replicate the entire thing in C.
The only thing is, it's a bit complicated to do from C. The entire file, which contains nothing but the above function, its support functions, and comments, is 340 lines long. Over 10 kB of source code just to replicate that straightforward C++. I'm going to show you exactly how it's done.
Virtual Reality
As you probably know, C++ has two types of methods (C++-ites like to call them "member functions", but that's not the sort of foolishness you'll see me spouting), virtual methods and the regular kind. Virtual methods are like the methods in other OO languages, in that the implementation is looked up at runtime. The regular kind is this weird abomination where the implementation is looked up entirely at compile time based on the declared type of the object. Since these two types of methods act so differently, we have to invoke them differently when we're hacking from C.
Static Hiss
Regular C++ methods are pretty easy to call from C, as long as you can get a pointer to them. They're actually just regular C functions with funny names and a single implicit parameter (this). So, for example, the xmlDocImpl method is non-virtual. Declared as a function pointer, it looks like: void * (*KHTMLPart_xmlDocImplP)(void *); You'll see a lot of void * in this article. This is because I completely don't care about types; if I'm slinging pointers around, I'll just use void * for convenience. So here we see that it returns a pointer, and takes a single parameter, the implicit this pointer. If I've assigned the function pointer to the right value, then I can perform the equivalent call from C as:
void *xmlDocImpl = KHTMLPart_xmlDocImplP(part);
The only remaining piece is to get the right pointer. Here, I use the APEFindSymbol function from Unsanity's APELite. (Note that this function requires having the mach_header of WebCore; getting this is left as an exercise for the reader.) All you have to know is the symbol name, which is easy to find by just dumping the symbols in WebCore using nm and looking for one that seems to fit. The code is:
KHTMLPart_xmlDocImplP = APEFindSymbol(header, "__ ZNK9 KHTMLPart10xmlDocImplEv");
And that's all there is to it.
No comments:
Post a Comment