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

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

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

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

现在可以通过 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类

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

以下是对webbrowser对象的包装类,结合Snapshot 类的类代码:
web 页面快照类

这里提供一个测试用的代码:
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;
            }

红色加粗位置.

2
0
(请您对文章做出评价)
« 上一篇:MVP 模式-计算器实例
» 下一篇:模拟Snap.com,支持js引入对链接预览的简单实现
posted @ 2006-12-25 17:42 萧寒 阅读(9030) 评论(41)  编辑 收藏 网摘 所属分类: C# WinForms, .NET WebForms

  回复  引用    
#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[未注册用户]
可惜不是全屏的,
  回复  引用  查看    
#41楼2009-07-31 11:18 | taia      
根据楼主的思路自己做了一个

            Rectangle rect = webBrowser1.Document.Body.ScrollRectangle;

            webBrowser1.Width = rect.Width;//好像必须要设为和目标网页的宽度,否则滚动条外的内容会是空白
            webBrowser1.Height = rect.Height;
            Bitmap map=new Bitmap(rect.Width,rect.Height);
            webBrowser1.DrawToBitmap(map,rect );
            map.Save(@"C:\Users\ibm\Documents\a.bmp");