通过IViewObject接口,取浏览器的图象,实现SNAP

今天又见到snap实现的文章,看来对此感兴趣的人挺多的.实现这个功能确实很'眩',我也来做一个把玩一下.
我的做法不是 Control.DrawToBitmap ,而是直接QueryInterface 浏览器Com对象的 IViewObject 接口,用它实现的Draw方法,画到图象上.

首先定义IViewObject的接口声名,如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;

namespace SnapLibrary
{
    /// <summary>
    /// 从 .Net 2.0 的 System.Windows.Forms.Dll 库提取
    /// 版权所有:微软公司
    /// </summary>
    [SuppressUnmanagedCodeSecurity]
    internal static class UnsafeNativeMethods
    {
        public static Guid IID_IViewObject = new Guid("{0000010d-0000-0000-C000-000000000046}");

        [ComImport, Guid("0000010d-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IViewObject
        {
            [PreserveSig]
            int Draw([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect, int lindex, IntPtr pvAspect, [In] NativeMethods.tagDVTARGETDEVICE ptd, IntPtr hdcTargetDev, IntPtr hdcDraw, [In] NativeMethods.COMRECT lprcBounds, [In] NativeMethods.COMRECT lprcWBounds, IntPtr pfnContinue, [In] int dwContinue);
            [PreserveSig]
            int GetColorSet([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect, int lindex, IntPtr pvAspect, [In] NativeMethods.tagDVTARGETDEVICE ptd, IntPtr hicTargetDev, [Out] NativeMethods.tagLOGPALETTE ppColorSet);
            [PreserveSig]
            int Freeze([In, MarshalAs(UnmanagedType.U4)] int dwDrawAspect, int lindex, IntPtr pvAspect, [Out] IntPtr pdwFreeze);
            [PreserveSig]
            int Unfreeze([In, MarshalAs(UnmanagedType.U4)] int dwFreeze);
            void SetAdvise([In, MarshalAs(UnmanagedType.U4)] int aspects, [In, MarshalAs(UnmanagedType.U4)] int advf, [In, MarshalAs(UnmanagedType.Interface)] IAdviseSink pAdvSink);
            void GetAdvise([In, Out, MarshalAs(UnmanagedType.LPArray)] int[] paspects, [In, Out, MarshalAs(UnmanagedType.LPArray)] int[] advf, [In, Out, MarshalAs(UnmanagedType.LPArray)] IAdviseSink[] pAdvSink);
        }
    }
}

该接口.net 自己带了,只是internal形式,所以只有想办法用Reflector 将它弄出来,相关的还有几个类,分别是tagLOGPALETTE,COMRECT,tagDVTARGETDEVICE.
定义如下:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;

namespace SnapLibrary
{
    /// <summary>
    /// 从 .Net 2.0 的 System.Windows.Forms.Dll 库提取
    /// 版权所有:微软公司
    /// </summary>
    internal static class NativeMethods
    {
        [StructLayout(LayoutKind.Sequential)]
        public sealed class tagDVTARGETDEVICE
        {
            [MarshalAs(UnmanagedType.U4)]
            public int tdSize;
            [MarshalAs(UnmanagedType.U2)]
            public short tdDriverNameOffset;
            [MarshalAs(UnmanagedType.U2)]
            public short tdDeviceNameOffset;
            [MarshalAs(UnmanagedType.U2)]
            public short tdPortNameOffset;
            [MarshalAs(UnmanagedType.U2)]
            public short tdExtDevmodeOffset;
        }

        [StructLayout(LayoutKind.Sequential)]
        public class COMRECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
            public COMRECT()
            {
            }

            public COMRECT(Rectangle r)
            {
                this.left = r.X;
                this.top = r.Y;
                this.right = r.Right;
                this.bottom = r.Bottom;
            }

            public COMRECT(int left, int top, int right, int bottom)
            {
                this.left = left;
                this.top = top;
                this.right = right;
                this.bottom = bottom;
            }

            public static NativeMethods.COMRECT FromXYWH(int x, int y, int width, int height)
            {
                return new NativeMethods.COMRECT(x, y, x + width, y + height);
            }

            public override string ToString()
            {
                return string.Concat(new object[] { "Left = ", this.left, " Top ", this.top, " Right = ", this.right, " Bottom = ", this.bottom });
            }

        }

        [StructLayout(LayoutKind.Sequential)]
        public sealed class tagLOGPALETTE
        {
            [MarshalAs(UnmanagedType.U2)]
            public short palVersion;
            [MarshalAs(UnmanagedType.U2)]
            public short palNumEntries;
        }
    }
}

现在可以通过 Marshal.QueryInterface 将浏览器COM实例的IViewObject接口取出:

//获取接口
object hret = Marshal.QueryInterface(Marshal.GetIUnknownForObject(pUnknown),ref UnsafeNativeMethods.IID_IViewObject, out pViewObject);

pUnknown为 com对象实例.
将IViewObject 指针对象 pViewObject 转化为接口对象.

ViewObject = Marshal.GetTypedObjectForIUnknown(pViewObject, typeof(SnapLibrary.UnsafeNativeMethods.IViewObject)) as SnapLibrary.UnsafeNativeMethods.IViewObject;

调用draw方法,绘制到图象上,以下是TakeSnapshot方法的完整代码:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Drawing;
using System.Windows.Forms;

namespace SnapLibrary
{
    /// <summary>
    /// ActiveX 组件快照类
    /// AcitveX 必须实现 IViewObject 接口
    /// 
    /// 作者:随飞
    /// http://chinasf.cnblogs.com
    /// [email protected]
    /// </summary>
    public class Snapshot
    {
        /// <summary>
        /// 取快照
        /// </summary>
        /// <param name="pUnknown">Com 对象</param>
        /// <param name="bmpRect">图象大小</param>
        /// <returns></returns>
        public Bitmap TakeSnapshot(object pUnknown, Rectangle bmpRect)
        {
            if (pUnknown == null)
                return null;
            //必须为com对象
            if (!Marshal.IsComObject(pUnknown))
                return null;
            //IViewObject 接口
            SnapLibrary.UnsafeNativeMethods.IViewObject ViewObject = null;
            IntPtr pViewObject = IntPtr.Zero;
            //内存图
            Bitmap pPicture = new Bitmap(bmpRect.Width, bmpRect.Height);
            Graphics hDrawDC = Graphics.FromImage(pPicture);
            //获取接口
            object hret = Marshal.QueryInterface(Marshal.GetIUnknownForObject(pUnknown),
                ref UnsafeNativeMethods.IID_IViewObject, out pViewObject);
            try
            {
                ViewObject = Marshal.GetTypedObjectForIUnknown(pViewObject, typeof(SnapLibrary.UnsafeNativeMethods.IViewObject)) 
                    as SnapLibrary.UnsafeNativeMethods.IViewObject;
                 //调用Draw方法 
                ViewObject.Draw((int) DVASPECT.DVASPECT_CONTENT, 
                -1, IntPtr.Zero, null, IntPtr.Zero, hDrawDC.GetHdc(), 
                new NativeMethods.COMRECT(bmpRect), 
                null, IntPtr.Zero, 0); 
            } catch (Exception ex) { 
                Console.WriteLine(ex.Message); throw ex; 
            }
         //释放 
         hDrawDC.Dispose(); 
         return pPicture;
        }
    } 
}

到此既完成了对Com对象的图象抓取.那么现在给它提供一个浏览器的实例,让它实现对 web page 的快照吧.
.net 2.0提供了webbrowser对象,它是对activex对象的包装,它的使用很简单,这里就不详细说明.
WebBrowser 对象的实例的属性ActiveXInstance就是它的原生COM对象,获取它的IVewObject接口,即可调用它实现的Draw方法把网页绘制到指定的DC上.

以下是对webbrowser对象的包装类,结合Snapshot 类的类代码:

/// <summary>
/// web 页面快照类
/// </summary>
public class WebPageSnapshot : IDisposable
{
    string url = "about:blank";

    /// <summary>
    /// 简单构造一个 WebBrowser 对象
    /// 更灵活的应该是直接引用浏览器的com对象实现稳定控制
    /// </summary>
    WebBrowser wb = new WebBrowser();
    /// <summary>
    /// URL 地址
    /// http://www.cnblogs.com
    /// </summary>
    public string Url
    {
        get { return url; }
        set { url = value; }
    }
    int width = 1024;
    /**/
    /// <summary>
    /// 图象宽度
    /// </summary>
    public int Width
    {
        get { return width; }
        set { width = value; }
    }
    int height = 768;
    /// <summary>
    /// 图象高度
    /// </summary>
    public int Height
    {
        get { return height; }
        set { height = value; }
    }

    /// <summary>
    /// 初始化
    /// </summary>
    protected void InitComobject()
    {
        try
        {
            wb.ScriptErrorsSuppressed = false;
            wb.ScrollBarsEnabled = false;
            wb.Size = new Size(1024, 768);
            wb.Navigate(this.url);
            //因为没有窗体,所以必须如此
            while (wb.ReadyState != WebBrowserReadyState.Complete)
                System.Windows.Forms.Application.DoEvents();
            wb.Stop();
            if (wb.ActiveXInstance == null)
                throw new Exception("实例不能为空");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            throw ex;
        }
    }
    /// <summary>
    /// 获取快照
    /// </summary>
    /// <returns>Bitmap</returns>
    public Bitmap TakeSnapshot()
    {
        try
        {
            InitComobject();
            //构造snapshot类,抓取浏览器ActiveX的图象
            SnapLibrary.Snapshot snap = new SnapLibrary.Snapshot();
            return snap.TakeSnapshot(wb.ActiveXInstance, new Rectangle(0, 0, this.width, this.height));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            throw ex;
        }
    }

    public void Dispose()
    {
        wb.Dispose();
    }

}

这里提供一个测试用的代码:

class Program
{
    /// <summary>
    /// 测试
    /// </summary>
    /// <param name="args"></param>
    [STAThread]
    static void Main(string[] args)
    {
        //web 页面快照
        WebPageSnapshot wps = new WebPageSnapshot();

        if (args != null && args.Length > 1)
            wps.Url = args[0];
        else
            wps.Url = "http://www.cnblogs.com";

        try
        {
            //保存到文件
            wps.TakeSnapshot().Save("1.bmp");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            Console.ReadLine();
        }
        wps.Dispose();
    }


}

工程原始代码下载:
http://files.cnblogs.com/chinasf/snaplibrary.rar

当然,这样做可能太复杂了,因为.net 为我们简化了所有的工作,简单到任意的contrl对象都支持DrawToBitmap 方法.不过想要了解机制的朋友们,可以研究一下.



你可能感兴趣的:(exception,浏览器,null,Class,WebBrowser)