zoukankan      html  css  js  c++  java
  • SNAP实现(转自SuiFei)

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

    非常棒,刚开始我还没没想起来SNAP这东西,真的很cool.下面连同评论一起摘抄。

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

    IVewObject接口声明
    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方法的完整代码:

    Snapshot类
    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
    /// chinasf@hotmail.com
    /// </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 类的类代码:

    web 页面快照类
    /**//// <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();
            }


        }

    工程原始代码下载:
    /Files/Chinasf/SnapLibrary.rar
    当然,这样做可能太复杂了,因为.net 为我们简化了所有的工作,简单到任意的contrl对象都支持DrawToBitmap 方法.不过想要了解机制的朋友们,可以研究一下.
    2006年12月26日 8:55:14 修正:请到Snapshot类中增加一句释放引用接口的代码.
    Snapshot..

    try
    {
    //ViewObject = Marshal.GetObjectForIUnknown(pViewObject) as SnapLibrary.UnsafeNativeMethods.IViewObject;
                    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);
    Marshal.Release(pViewObject);
                }
    catch (Exception ex)
    {
                    Console.WriteLine(ex.Message);
    throw ex;
                }

    红色加粗位置.

    回复 引用

    #1楼 2006-12-25 18:00 | zoti [未注册用户]

    不错,楼主真厉害。
    我试试。

    回复 引用

    #2楼 2006-12-25 18:04 | zoti [未注册用户]

    好像速度有点慢,snap的一下子就出来了,这个还需要改进一下性能,顶。

    回复 引用 查看

    #3楼 2006-12-25 19:23 | 贫嘴老赵

    呵呵,看看学习一下

    回复 引用 查看

    #4楼 2006-12-25 20:51 | Kai.Ma[匿名]

    学习,不知道能否解决blank image的问题。

    回复 引用 查看

    #5楼 2006-12-25 21:10 | Kai.Ma[匿名]

    我试验了一下,能解决blank image问题,非常不错。
    速度提高一下就ok。
    我回去包装一下,回头散发出来。
    多谢楼主&老乡,湖南人,真聪明

    回复 引用 查看

    #6楼 [楼主]2006-12-25 22:03 | 萧寒

    @Kai.Ma
    速度问题主要是因为 webbrowser 的下载WEB PAGE速度问题,如果你的带宽足够,速度应该还可以;另外 webbrowser 实例不要每次都注销和构造.
    你可以考虑引入Snapshot cache 机制,我有时间会继续优化一下,并作成B/S的.
    你叫我老乡,看来也是湖南人了;幸会

    回复 引用 查看

    #7楼 2006-12-25 23:36 | Kai.Ma

    @萧寒兄
    http://www.cnblogs.com/kaima/archive/2006/12/25/603519.html
    我改进了一下
    代码我发你信箱吧。你看看有没有改进的余地?
    你信箱是多少?

    回复 引用 查看

    #8楼 [楼主]2006-12-25 23:45 | 萧寒

    @Kai.Ma
    老乡,我信箱是 chinasf at hotmail.com

    回复 引用 查看

    #9楼 2006-12-26 00:17 | Kai.Ma

    @萧寒
    已发~

    回复 引用 查看

    #10楼 2006-12-26 08:16 | kiler

    原来楼主是我的老乡,厉害啊。

    回复 引用 查看

    #11楼 2006-12-26 08:45 | skyover

    偶也是湖南老乡,永州的。哈哈。
    看来俺们湖南人高手也蛮多的。:)

    回复 引用

    #12楼 2006-12-26 08:47 | 虫子[匿名] [未注册用户]

    牛人, 学习.

    回复 引用 查看

    #13楼 2006-12-26 09:10 | Go_Rush

    湖南老乡,支持一把,我是益阳的,呵呵,以后多交流
    http://ashun.cnblogs.com/

    回复 引用

    #14楼 2006-12-26 09:15 | 忧郁的火柴头 [未注册用户]

    可算找到根了
    真牛啊

    回复 引用

    #15楼 2006-12-26 09:57 | watcher [未注册用户]

    有什么用处吗?

    回复 引用 查看

    #16楼 2006-12-26 22:03 | 横刀天笑

    佩服啊,我看到snap的时候,隐隐约约有些思路,可是毫无头绪,。。。。。。还要加紧学习啊。。tks共享

    回复 引用 查看

    #17楼 2006-12-26 22:49 | Kai.Ma

    萧兄,再想想,snap.com,一个页面上,他是怎么处理那么多连接的“并发”的,我估计他是支持了多线程。不信你鼠标移到一个连接,然后立即移到另一个连接,一会再移回来,发现第一个的图已经抓好了。这就证明snap.com对每次触发抓图 开了一个新线程。
    但是 WebBrowser这个东西怎么应用到多线程里面呢?
    因为WebBrowser需要进入单元线程(STA)....我对单元线程应用还不是很熟。

    回复 引用 查看

    #18楼 2006-12-26 22:54 | S.Sams

    支持一个!
    三年前的想法, 一直都没去做, 竟然有人帮我实现啦,惭愧!
    学习中...

    回复 引用 查看

    #19楼 [楼主]2006-12-27 00:51 | 萧寒

    @Kai.Ma
    如果snap.com 的实现机制也是基于WEBBROWSER的话,那么它不能是多线程的,它只能是多进程的,也就是CGI的运行模式;
    单线程单元模型 (STA):进程中一个或多个线程使用 COM ,并且 COM 对象的调用由 COM 进行同步。在线程间对接口进行编组。单线程单元模型的退化情况(其中,在给定的进程中只有一个线程使用 COM)被称为单线程模型。以前的 Microsoft 信息与文档曾经将 STA 模型简单地称为“单元模型”。
    它的运行线程应该是消息或用户界面 (UI) 线程。
    具体解决办法正在尝试.

    回复 引用 查看

    #20楼 2006-12-27 14:47 | cwbboy

    asp.net程序本身就是运行在多线程环境下的,所以, 我认为萧寒 说的问题根本不存在。每一次提交请求时,都是在单独的线程执行的。你可以随意使用多个线程,虽然可以随意使用,但或许根本用不着显式地使用线程对像去执行多线程,每次请求都是一个新的线程,你已经身处于多线程之中了。可以直接使用ajax 达到想要的效果。

    回复 引用 查看

    #21楼 2006-12-27 15:11 | Kai.Ma

    @cwbboy
    若真 如你这么说,我试试看,把我那代码的WebPreview静态方法改成实例化对象看看。

    回复 引用 查看

    #22楼 [楼主]2006-12-27 15:29 | 萧寒

    @cwbboy
    你理解错我的意思;系统的开销在于webbrowser 实例在每个线程中创建的sta线程内构造和消亡,并且你无法创建一个静态的webbrowser ,它必须存在于一个sta的线程,也就是说它做不到唯一实例.我设想的是,创建一个进程,构造一个webbrowser ,再一个线程中处理所有web 的请求.
    webbrowser 的构造副本对象很简单,也就是可以对每个请求构造一个副本,就象ie多页面的实现那样;另外需要解决进程于asp.net的通讯问题,比如通过内存映射,登记一些信息,等待进程的处理.最终asp.net端获得的是image file.

    回复 引用 查看

    #23楼 2006-12-27 15:30 | Kai.Ma

    @cwbboy
    谢谢你的提醒,不过这样(每次都实例化对象)的话,请求的数量一下子太大了,一段时间内对服务器CPU也有很高的占用。
    不知道snap.com他们怎么处理高【并发】的

    回复 引用 查看

    #24楼 2006-12-27 15:35 | Kai.Ma

    我想只有借助ThreadPool了。:)

    回复 引用 查看

    #25楼 [楼主]2006-12-27 15:37 | 萧寒

    @Kai.Ma
    支持你一下;
    等待结果:)

    回复 引用 查看

    #26楼 2006-12-28 16:27 | 阿蒙[匿名]

    每次生成截图,CPU占用都很高

    回复 引用 查看

    #27楼 2006-12-31 10:44 | Wu.Country@侠缘

    关注一下。

    回复 引用 查看

    #28楼 2007-03-18 14:37 | 在北京的湖南人

    哈哈,我也是湖南人,泪汪汪.....

    回复 引用

    #29楼 2007-03-28 09:53 | l初学者 [未注册用户]

    在Web中出现 "当前线程不在单线程单元中,因此无法实例化 ActiveX 控件“8856f961-340a-11d0-a96b-00c04fd705a2”"。怎样解决

    回复 引用 查看

    #30楼 [楼主]2007-03-31 00:14 | 萧寒

    @l初学者
    注意,构造webbrowser 对象必须位于单线程单元模型 (STA)内。

    回复 引用

    #31楼 2007-07-12 16:26 | 阿甘 [未注册用户]

    在Web中出现 "当前线程不在单线程单元中,因此无法实例化 ActiveX 控件“8856f961-340a-11d0-a96b-00c04fd705a2”"。
    具体的解决方法是怎样呢?不明白

    回复 引用

    #32楼 2007-07-12 16:27 | 阿甘 [未注册用户]

    有没有b/s的??

    回复 引用

    #33楼 2007-08-30 10:35 | greystar [未注册用户]

    当前线程不在单线程单元中,因此无法实例化 ActiveX 控件“8856f961-340a-11d0-a96b-00c04fd705a2”。
    在网页中根本无法直接去调用.
    我感觉只能去做个服务器.请求时,去请求该服务.
    DEVExpress的网页导出不知是如何做的.可以导出N种格式

    回复 引用 查看

    #34楼 2007-11-22 18:16 | ithurricane

    牛人啊,
    PFPF

    回复 引用

    #35楼 2008-02-01 11:29 | fish man [未注册用户]

    請教一下
    如果網頁開的是 doc 或是 ppt 文件
    似乎就不能抓圖了 有解決方法嗎
    感恩

    回复 引用 查看

    #36楼 2008-05-09 14:40 | ぐ最後①葉ゞ

    随风大哥和Kai.Ma大哥的文章写得很不错,我很认真地看了好多遍。。。

    回复 引用

    #37楼 2008-06-24 09:50 | liuhaizhi [未注册用户]

    楼主速加我QQ或者邮箱 我有个急的问题问你关于截取图片的,需要改进下
    QQ284914216
    email:284914216@163.com

    回复 引用

    #38楼 2008-08-23 21:53 | 河东村村长 [未注册用户]

    敢问楼主,我现在从IE浏览器中获取了一个WebBrowserClass及其一个实例对象,能否抓取其显示的页面为图片呢?

    回复 引用

    #39楼 2008-08-24 08:13 | 河东村村长 [未注册用户]

    敢问楼主,我现在从IE浏览器中获取了一个WebBrowserClass及其一个实例对象,而不是webBrowser控件,能否抓取其显示的页面为图片呢?

    回复 引用

    #40楼 2009-02-08 16:06 | msii [未注册用户]

    可惜不是全屏的,


    作者:KKcat
        
    个人博客:http://jinzhao.me/
        
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    SCU 4439 Vertex Cover|最小点覆盖
    SCU 4438 Censor|KMP变形题
    BZOJ 2152: 聪聪可可|点分治
    暑假集训-8.18总结
    AcWing 252. 树|点分治
    Proj THUDBFuzz Paper Reading: Field-aware Evolutionary Fuzzing Based on Input Specifications and Vulnerablity Metrics
    Proj THUDBFuzz Paper Reading: Semantic Fuzzing with Zest
    Proj THUDBFuzz Paper Reading: Smart Greybox Fuzzing
    Proj THUDBFuzz Paper Reading: Language-Agnostic Generation of Compilable Test Programs
    Proj THUDBFuzz Paper Reading: Fuzzing JS Engines with Aspect-preserving Mutation
  • 原文地址:https://www.cnblogs.com/jinzhao/p/1430321.html
Copyright © 2011-2022 走看看