此文做法不是 Control.DrawToBitmap ,而是直接QueryInterface 浏览器Com对象的 IViewObject 接口,用它实现的Draw方法,画到图像上。
首先,定义IViewObject的接口声名,如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security; using System.Runtime.InteropServices; namespace SnapUtility { /// <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)] System.Runtime.InteropServices.ComTypes.IAdviseSink pAdvSink); void GetAdvise([In, Out, MarshalAs(UnmanagedType.LPArray)] int[] paspects, [In, Out, MarshalAs(UnmanagedType.LPArray)] int[] advf, [In, Out, MarshalAs(UnmanagedType.LPArray)] System.Runtime.InteropServices.ComTypes.IAdviseSink[] pAdvSink); } } }
该接口.net 自己带了,只是internal形式,所以只有想办法用Reflector 将它弄出来,相关的还有几个类,分别是tagLOGPALETTE,COMRECT,tagDVTARGETDEVICE.
定义如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Drawing; using System.Runtime.InteropServices; namespace SnapUtility { /// <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接口取出,添加 System.Runtime.InteropServices 命名空间引用,Marshal类位于此命名空间下:
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.Linq; using System.Text; using System.Windows.Forms; using System.Drawing; using System.Drawing.Imaging; using System.ComponentModel; using System.Runtime.InteropServices; using System.Security; using mshtml; namespace SnapUtility { /// <summary> ///WebSnapshot 的摘要说明 /// </summary> public class WebSnapshot { public WebSnapshot(){ } public static Bitmap GetHtmlImage(Uri uri, int Width) { WebBrowser webBrowser = new WebBrowser(); //webBrowser.Size = new Size(Width, 10); webBrowser.ScrollBarsEnabled = false; webBrowser.Url = uri; while (webBrowser.ReadyState != WebBrowserReadyState.Complete) { Application.DoEvents(); } webBrowser.Width = webBrowser.Document.Body.ClientRectangle.Width; webBrowser.Height = webBrowser.Document.Body.ClientRectangle.Height; webBrowser.Url = uri; Bitmap bitmap = TakeSnapshot(webBrowser.ActiveXInstance, new Rectangle(0, 0, webBrowser.Width, webBrowser.Height)); webBrowser.Dispose(); return bitmap; } /// <summary> /// 取快照 /// </summary> /// <param name="pUnknown">Com 对象</param> /// <param name="bmpRect">图象大小</param> /// <returns></returns> public static Bitmap TakeSnapshot(object pUnknown, Rectangle bmpRect) { if (pUnknown == null) return null; //必须为com对象 if (!Marshal.IsComObject(pUnknown)) return null; //IViewObject 接口 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(UnsafeNativeMethods.IViewObject)) as UnsafeNativeMethods.IViewObject; //调用Draw方法 ViewObject.Draw((int)System.Runtime.InteropServices.ComTypes.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上.
这里提供一个测试用的代码:
前台页面设置:
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" AspCompat="true" %>
后台调用代码:
using System.Drawing; using System.IO; using SnapUtility; public partial class _Default : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Bitmap MyImage = WebSnapshot.GetHtmlImage(new Uri(@"http://www.hao123.com/"), 1024); string folder = Server.MapPath("Html/"); //如果目录不存在则创建目录 if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); MyImage.Save(folder + DateTime.Now.Ticks.ToString() + ".jpeg"); MyImage.Dispose(); }
当然,这样做可能太复杂了,因为.net 为我们简化了所有的工作,简单到任意的 contrl 对象都支持 DrawToBitmap 方法。不过想要了解机制的朋友们,可以研究一下.