Question

I have C# .NET 4 WinForms app (using MSHTML 7) that launches new and connects to existing IE 10 instances. It iterates through all images and downloads them to manipulate. This approach is time-consuming and redundant since the image has already been downloaded by IE.

I have searched everywhere and only a handful of forums discuss the subject but all are able to cast mshtml.IHTMLImgElement objects to mshtml.IHTMLElementRender (although in C++ code).

Unable to cast COM object of type 'mshtml.HTMLImgClass' to interface type 'mshtml.IHTMLElementRender'.
This operation failed because the QueryInterface call on the COM component for the interface with IID '{3050F669-98B5-11CF-BB82-00AA00BDCE0B}' failed due to the following error:
No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).

The objective of course is to gain access to the full image so alternative approaches are welcome as well. Here is the code causing the exception above.

public static void Main (string [] args)
{
    mshtml.HTMLDocument document = null;
    SHDocVw.InternetExplorer explorer = null;
    System.IntPtr hdc = System.IntPtr.Zero;
    mshtml.IHTMLElementRender render = null;
    mshtml._RemotableHandle handle = default(mshtml._RemotableHandle);

    try
    {
        explorer = new SHDocVw.InternetExplorer();
        explorer.Visible = true;

        try
        {
            explorer.Navigate("http://www.google.com/ncr");

            while (explorer.Busy)
            {
                // Striped events for SO example.
                System.Threading.Thread.Sleep(100);
            }

            document = (mshtml.HTMLDocument) explorer.Document;

            foreach (mshtml.IHTMLImgElement image in document.images)
            {
                Console.WriteLine();

                if ((image.width > 0) && (image.height > 0))
                {
                    // The following [if] will return false if uncommented.
                    //if (image.GetType().GetInterfaces().ToList().Contains(typeof(mshtml.IHTMLElementRender)))
                    {
                        using (Bitmap bitmap = new Bitmap(image.width, image.height))
                        {
                            using (Graphics graphics = Graphics.FromImage(bitmap))
                            {
                                hdc = graphics.GetHdc();
                                handle.fContext = hdc.ToInt32();
                                render = (mshtml.IHTMLElementRender) image; // Causes the exception.
                                //handle = (mshtml._RemotableHandle) Marshal.PtrToStructure(hdc, typeof(mshtml._RemotableHandle));
                                render.DrawToDC(ref handle);
                                graphics.ReleaseHdc(hdc);

                                // Process image here.

                                Console.Write("Press any key to continue...");
                                Console.ReadKey();
                            }
                        }
                    }
                }
            }
        }
        catch (System.Exception e)
        {
            Console.WriteLine(e.Message);
            Console.WriteLine("Stack Trace: " + e.StackTrace);
        }

        explorer.Quit();
    }
    catch (System.Exception e)
    {
        Console.WriteLine(e.Message);
        Console.WriteLine("Stack Trace: " + e.StackTrace);
    }
    finally
    {
    }

#if (DEBUG)
        Console.WriteLine();
        Console.Write("Press any key to continue...");
        Console.ReadKey();
#endif
}

Some of the links I have gone through without avail:

Was it helpful?

Solution

You can easily see the root of the problem with Regedit.exe. In order for an interface to bridge the Grand Canyon between process boundaries, it needs code that implements a proxy and a stub for an interface. Code that knows how to serialize the interface method arguments into an RPC packet that can be transmitted from one process to another.

COM discovers that code by looking through the registry. You can too with Regedit.exe, navigate to HKCR\Interfaces and scroll down to match the guid. You will see a lot of {guids} there that look like {305xxxx-98B5-11CF-BB82-00AA00BDCE0B}. Yes, Microsoft cheats on their guids, easier for debugging, IE has rather a lot of them. They point to the standard marshaller and include the type library guid that describes the interface.

But you will not find {3050F669-98B5-11CF-BB82-00AA00BDCE0B}. Which is basically what the exception message is telling you, it can't find a way to marshal the interface pointer. The mysterious E_NOINTERFACE error code is the result of the fallback approach not working either, failing to query for IMarshal.

This is a bit of a toss-up between "they forgot" and "they couldn't make it work". This one heavily leans towards "too hard to make it work". Serializing a render interface is way too difficult, too many dependencies on the video hardware which of course never matches between one machine and another. Or even on one machine, the need to have the graphics pipeline initialized properly before any method calls can be made is far too tricky.

That's where the buck stops, you cannot make this work. You'll need to render that image yourself, not easy to do. Sorry, please don't shoot the messenger.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top