What's wrong with this unmanaged code in C#? - c#

I was trying to get text from each control in hierarchy. The following code runs fine if I use the unsafe method. However, using the unmanaged version seems to break hWnd, which in result hWnd = GetAncestor(hWnd, GetAncestorFlags.GA_PARENT) complains:
System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'
I have checked hWnd was not changed after returning from GetWindowTextRaw function, and if I comment out the second SendMessage in this function will not cause the issue (although it will clearly not get the window text).
(PS: I'm using PInvoke.User32 in NuGet)
// using static PInvoke.User32;
public static string GetWindowTextRaw(IntPtr hWnd) {
// Allocate correct string length first
int length = (int)SendMessage(hWnd, WindowMessage.WM_GETTEXTLENGTH, IntPtr.Zero, IntPtr.Zero);
char[] buff = new char[length + 1];
IntPtr iptr = Marshal.AllocHGlobal(buff.Length);
SendMessage(hWnd, WindowMessage.WM_GETTEXT, (IntPtr)(length + 1), iptr);
Marshal.Copy(iptr, buff, 0, length + 1);
Marshal.FreeHGlobal(iptr);
//unsafe
//{
// fixed (char* p = buff)
// SendMessage(hWnd, WindowMessage.WM_GETTEXT, (IntPtr)(length + 1), (IntPtr)p);
//}
return new string(buff).TrimEnd('\0');
}
private void button1_Click(object sender, EventArgs {
POINT p;
IntPtr hWnd;
//while (true)
if (GetCursorPos(out p)) {
hWnd = WindowFromPoint(p); ;
Debug.Print($"{p.x} {p.y} 0x{(int)hWnd:x8}");
while (hWnd != IntPtr.Zero) {
Debug.Print($"{GetWindowTextRaw(hWnd)}");
hWnd = GetAncestor(hWnd, GetAncestorFlags.GA_PARENT);
}
Thread.Sleep(500);
}
}

IntPtr iptr = Marshal.AllocHGlobal(buff.Length);
Wrong size, you need buff.Length * sizeof(char). Twice as much as it allocates now. As written, the code corrupts the same heap that the OS uses, anything can happen next. An AVE is a normal and happy outcome, but not guaranteed.

Related

SafeHandle vs IntPtr in WinAPIs and CloseHandle in C#

I read many articles about SafeHandle and IDiposable but I still don't understand whether I should use SafeHandle and CloseHandle or not in C#.
MSDN SafeHandle example
IntPtr, SafeHandle and HandleRef - Explained
https://blog.benoitblanchon.fr/safehandle/.
The first source is similar to my question but however it doesn't answer my question.
Let's say I have the following 3 different examples that I got from Internet:
// Example 1
byte[] temp = new byte[IntPtr.Size];
fixed (byte* p = temp)
{
IntPtr SectionHandle = new IntPtr(p);
LARGE_INTEGER MaximumSize = new LARGE_INTEGER();
MaximumSize.LowPart = pNTHeader->OptionalHeader.SizeOfImage;
status = NtCreateSection(
SectionHandle,
SectionAccess.SECTION_MAP_EXECUTE | SectionAccess.SECTION_MAP_READ | SectionAccess.SECTION_MAP_WRITE,
IntPtr.Zero,
ref MaximumSize,
MemoryProtectionConstants.PAGE_EXECUTE_READWRITE,
AllocationTypes.SEC_COMMIT,
IntPtr.Zero);
}
// Example 2
IntPtr hFile = CreateFile(
path,
GenericRights.GENERIC_READ,
FileFlags.FILE_SHARE_DELETE | FileFlags.FILE_SHARE_READ | FileFlags.FILE_SHARE_WRITE,
IntPtr.Zero,
FileCreationFlags.OPEN_EXISTING,
FileFlags.FILE_ATTRIBUTE_NORMAL,
IntPtr.Zero);
if (hFile == INVALID_HANDLE_VALUE)
return false;
// Example 3
IntPtr hMap = CreateFileMapping(
hFile,
IntPtr.Zero,
MemoryProtectionConstants.PAGE_READONLY,
0,
0,
IntPtr.Zero);
if (hMap == IntPtr.Zero)
return false;
The examples are Windows APIs, so they are unmanaged. What I don't get is:
1) Should the all 3 examples use SafeHandle over IntPtr because they are unmanaged and if one of them shouldn't, why?
2) I find CloseHandle for a good practice in C/C++ but in C# I don't know if the Garbage Collector closes the handle automatically in the end? Should CloseHandle be used in the above examples and why?
3) The first example uses an unmanaged byte* to allocate memory on the heap. Can this procedure be done by not using unmanaged pointer? The code below is my guess, what do you think about it?
IntPtr SectionHandle = Marshal.AllocateHGlobal(sizeof(int));
... code ...
Marshal.FreeHGlobal(SectionHandle);
Edit:
Posting the code I edited:
[SecurityCritical]
public sealed class SafeHandleBuffer : SafeBuffer
{
public SafeHandleBuffer()
: base(true)
{
handle = Marshal.AllocHGlobal(IntPtr.Size);
}
public int GetValue() =>
Marshal.ReadInt32(handle);
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle()
{
Marshal.FreeHGlobal(handle);
return true;
}
}
public sealed class SafeHandleToken : SafeHandleZeroOrMinusOneIsInvalid
{
private SafeHandleToken()
: base(true)
{
}
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
protected override bool ReleaseHandle() =>
CloseHandle(handle);
}
// Including the IDisposable implementation in the class where the next example calls are found
private SafeHandleToken _fileHandle, _mappingHandle;
private SafeHandleBuffer _sectionHandle;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
[SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
protected virtual void Dispose(bool disposing)
{
if (_fileHandle != null && !_fileHandle.IsInvalid)
{
_fileHandle.Dispose();
}
if (_mappingHandle != null && !_mappingHandle.IsInvalid)
{
_mappingHandle.Dispose();
}
if (_sectionHandle != null && !_sectionHandle.IsInvalid)
{
_sectionHandle.Dispose();
}
}
// Example 1
SafeHandleBuffer tempHandle = new SafeHandleBuffer();
LARGE_INTEGER MaximumSize = new LARGE_INTEGER();
MaximumSize.LowPart = pNTHeader->OptionalHeader.SizeOfImage;
status = NtCreateSection(
tempHandle,
SectionAccess.SECTION_MAP_EXECUTE | SectionAccess.SECTION_MAP_READ | SectionAccess.SECTION_MAP_WRITE,
IntPtr.Zero,
ref MaximumSize,
MemoryProtectionConstants.PAGE_EXECUTE_READWRITE,
AllocationTypes.SEC_COMMIT,
IntPtr.Zero);
// Example 2
SafeHandleToken tempHandle = CreateFile(
path,
GenericRights.GENERIC_READ,
FileShare.ReadWrite | FileShare.Delete,
IntPtr.Zero,
FileMode.Open,
FileAttributes.Normal,
IntPtr.Zero);
Thread.Sleep(500);
_fileHandle = tempHandle;
if (_fileHandle.IsInvalid)
throw new Win32Exception(Marshal.GetLastWin32Error());
// Example 3
tempHandle = CreateFileMapping(
_fileHandle,
IntPtr.Zero,
(uint)MemoryProtectionConstants.PAGE_READONLY | (uint)AllocationTypes.SEC_IMAGE,
0,
0,
IntPtr.Zero);
Thread.Sleep(500);
_mappingHandle = tempHandle;
if (_mappingHandle.IsInvalid)
throw new Win32Exception(Marshal.GetLastWin32Error());
Do you think I did everything alright? Should SafeHandle be used on every Windows API like GetModuleHandle, GetProcAddress, MapViewOfFile and some more I don't remember right now. Some of them are returning LPVOID.

How to define 'CreateDIB' function or reproduce its purpose in C#?

I'm trying to translate this C++ code from this MSDN reference, to C# or VB.NET:
case WM_DWMSENDICONICTHUMBNAIL:
{
// This window is being asked to provide its iconic bitmap. This indicates
// a thumbnail is being drawn.
hbm = CreateDIB(HIWORD(lParam), LOWORD(lParam));
if (hbm)
{
hr = DwmSetIconicThumbnail(hwnd, hbm, 0);
DeleteObject(hbm);
}
}
break;
At first sight seems simple, but I need help to understand about the CreateDIB function, I don't know what means and which is the purpose of that function, I can't find info about, and also I can't find it inside the Windows SDK header files, nothing of nothing.
Where is defined that function?, It is necessary to follow good practices in that C++ example?, how to declare it from C#, or which is the .NET equivalent for that unmanaged function?.
I found the CImageAllocator.CreateDIB which I'm not sure whether it reffers to that, but the parameters of that function does not corresponds to a kind of CreateDIB(width, height) like I seen in this other MSDN code, so probablly is not the same function, and also it is a directshow thing...
Well, this is the current translation I did, it works, but I'm worried about possible memory issues because the lack of CreateDIB function or its equivalent managed member:
Case WM_DWMSENDICONICTHUMBNAIL
Dim hwnd As IntPtr = Process.GetCurrentProcess().MainWindowHandle
Dim dWord As Integer = m.LParam.ToInt32()
Dim maxWidth As Short = BitConverter.ToInt16(BitConverter.GetBytes(dWord), 2)
Dim maxHeight As Short = BitConverter.ToInt16(BitConverter.GetBytes(dWord), 0)
Using img As Image = Bitmap.FromFile("C:\Image.jpg")
Using thumb As Bitmap = CType(img.GetThumbnailImage(maxWidth, maxHeight, Nothing, Nothing), Bitmap)
Dim hBitmap As IntPtr = thumb.GetHbitmap()
Dim hresult As Integer = NativeMethods.DwmSetIconicThumbnail(hwnd, hBitmap, 0)
If (hresult <> 0) Then
' Handle error...
' Throw Marshal.GetExceptionForHR(hresult)
End If
NativeMethods.DeleteObject(hBitmap)
End Using
End Using
That's way too much effort expended to set the thumbnail image. Just keep a copy of the bitmap on your window, and draw it when you need to.
public partial class Form1 : Form
{
[DllImport("Dwmapi.dll")]
static extern int DwmSetIconicThumbnail(IntPtr hWnd, IntPtr hbmp, uint dwSITFlags);
[DllImport("Dwmapi.dll")]
static extern int DwmSetWindowAttribute(IntPtr hWnd, uint dwAttribute, IntPtr pvAttribute, uint cbAttribute);
const uint WM_DWMSENDICONICTHUMBNAIL = 0x0323;
const uint DWMWA_FORCE_ICONIC_REPRESENTATION = 7;
const uint DWMWA_HAS_ICONIC_BITMAP = 10;
Size thumbSize = new Size(30, 30);
Bitmap thumbImage = new Bitmap(30, 30);
object sync = new object();
public Form1()
{
InitializeComponent();
using (Graphics g = Graphics.FromImage(this.thumbImage))
{
g.Clear(Color.Blue);
g.DrawRectangle(Pens.Black, new Rectangle(new Point(0, 0), this.thumbSize));
}
this.HandleCreated += Form1_HandleCreated;
}
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_DWMSENDICONICTHUMBNAIL)
{
lock (this.sync)
{
int x = (int)((m.LParam.ToInt32() >> 16) & 0xffff);
int y = (int)(m.LParam.ToInt32() & 0xffff);
if (this.thumbSize != new Size(x, y))
{
this.thumbSize = new Size(x, y);
this.UpdateBitmap();
}
DwmSetIconicThumbnail(this.Handle, thumbImage.GetHbitmap(), 0);
}
}
base.WndProc(ref m);
}
void UpdateBitmap()
{
lock (this.sync)
{
this.thumbImage = new Bitmap(this.thumbSize.Width, this.thumbSize.Height);
using (Graphics g = Graphics.FromImage(this.thumbImage))
{
g.Clear(Color.Blue);
g.DrawRectangle(Pens.Black, new Rectangle(new Point(0, 0), this.thumbSize));
//or: g.DrawImage() with stretching specified.
}
}
}
private void Form1_HandleCreated(object sender, EventArgs e)
{
IntPtr val = Marshal.AllocHGlobal(4);
Marshal.WriteInt32(val, 1);
DwmSetWindowAttribute(this.Handle, DWMWA_FORCE_ICONIC_REPRESENTATION, val, 4);
DwmSetWindowAttribute(this.Handle, DWMWA_HAS_ICONIC_BITMAP, val, 4);
Marshal.FreeHGlobal(val);
}
}
C# since the tags on the question list it.

self updating drawings C#

I am trying to make an external map for a computer game.
Therefore I have made a Forms Application with a picture box, that contains my map image.
Now I want to draw little squares onto the map using GDI. I allready got that working using Graphics.DrawRectangle.
Now I want to update the position of the rectangle every 0.2s.
How do I do that?
My current source (i wnt to replace the button with an auto-update):
public partial class Form1 : Form
{
//choords local player
int localX;
int localY;
int running;
const int Basex = 0x05303898;
const int Basey = 0x05303894;
const string Game = "ac_client";
//map drawing
Pen aPen = new Pen(Color.Black);
Graphics localp;
//choords enemy
//permission to read process memory
const int PROCESS_VM_READ = 0x0010; //needed for reading memory
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
public static extern bool ReadProcessMemory(int hProcess,
int lpBaseAddress, byte[] lpBuffer, int dwSize, ref int lpNumberOfBytesRead);
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
if (Process.GetProcessesByName(Game).Length > 0)
{
Process process = Process.GetProcessesByName(Game)[0];
IntPtr procHandle = OpenProcess(PROCESS_VM_READ, false, process.Id);
int bytesRead = 0;
byte[] buffer = new byte[24]; //'Hello World!' takes 12*2 bytes because of Unicode
// 0x0046A3B8 is the address where I found the string, replace it with what you found
ReadProcessMemory((int)procHandle, Basex, buffer, buffer.Length, ref bytesRead);
localX = BitConverter.ToInt32(buffer, 0);
LBlocalx.Text = Convert.ToString(Math.Ceiling(Convert.ToDecimal(localX)));
ReadProcessMemory((int)procHandle, Basey, buffer, buffer.Length, ref bytesRead);
localY = BitConverter.ToInt32(buffer, 0);
LBlocaly.Text = Convert.ToString(Math.Ceiling(Convert.ToDecimal(localY)));
localp = pictureBox1.CreateGraphics();
localp.DrawRectangle(aPen, (Convert.ToInt32(Convert.ToString(Math.Ceiling(Convert.ToDecimal(localX))))/1000), (Convert.ToInt32(Convert.ToString(Math.Ceiling(Convert.ToDecimal(localY))))/1000), 10, 10);
}
else
{
MessageBox.Show("Error! Process not running.");
}
}
How about you store two time variables(DateTime) one that has the time when you started checking for that 2 second difference, another with current time and on the beginning of every iteration you verify if the difference of both times is 2 seconds. Remember, the first variable is the one that has the time when the difference was 2 seconds or when you first started checking for that difference.
You could also use the Timer class and set a timer that on ever 2secs do something.
Timer class reference:
https://msdn.microsoft.com/en-us/library/system.timers.timer%28v=vs.110%29.aspx

How to get all windows being show on screen?

I asked a similar kind of question last month but unfortunately i did not get a single response to that question. I am asking it again.
In one of my projects i want to get all the windows being shown on screen. I tried this code
Process[] procs = Process.GetProcesses();
if (proc.MainWindowHandle!=IntPtr.Zero)
{
GetWindowPlacement(proc.MainWindowHandle, ref placement);
if (proc.ProcessName!="McUICnt" && proc.ProcessName!="devenv" && proc.ProcessName!="DCSHelper" && proc.ProcessName!="explorer")
{
if (placement.showCmd == 1)
{
handles[temp] = proc.MainWindowHandle;
GetWindowRect(proc.MainWindowHandle, ref rct);
rect[temp] = rct;
temp++;
MessageBox.Show(proc.ProcessName.ToString());
}
}
}
As you can see i have put a check for McUICnt,devenv. placement.cmd==1 checks for the windows with normal state. But McUICnt and devenv are also passing that condition but are not showing on the screen. When i run again program then i get new process like stated above.
How to get all the windows being shown on the screen right now?
UPDATE:
When I use EnumWindows i use this code
EnumWindows(new EnumWindowsProc(EnumTheWindows), IntPtr.Zero);
and
protected static bool EnumTheWindows(IntPtr hWnd, IntPtr lParam)
{
int size = GetWindowTextLength(hWnd);
RECT rct = new RECT();
if (size++ > 0 && IsWindowVisible(hWnd))
{
StringBuilder sb = new StringBuilder(size);
GetWindowText(hWnd, sb, size);
MessageBox.Show(sb.ToString());
handles[tempHandle] = hWnd;
GetWindowRect(hWnd, ref rct);
rect[tempHandle] = rct;
tempHandle++;
}
return true;
}
But it also shows minimized windows. Even I have put a check IsWindowVisible

Best way to take screenshot of a web page

What is the best way to take screenshot of a web page?
At the moment I just start an selenium instance of firefox and using winapi bring it to the front and make a screenshot.
I ask similar question already.
There is two points:
Slowness.
If any window occurently gets higher than our web browser's window, this window will imprint in our screenshot.
Is there any method to take screenshot more 'programmly'?
Here is some code I use now:
class FirefoxDriverEx : FirefoxDriver
{
public Process GetFirefoxProcess()
{
var fi = typeof(FirefoxBinary).GetField("process", BindingFlags.NonPublic | BindingFlags.Instance);
return fi.GetValue(this.Binary) as Process;
}
}
Here is the code illustrating process of taking screenshot itself:
using (FirefoxDriverEx driver = new FirefoxDriverEx())
{
driver.Navigate().GoToUrl(url);
var process = driver.GetFirefoxProcess();
if (process != null)
{
var screenCapture = new ScreenCapture();
Win.SetForegroundWindow(process.MainWindowHandle.ToInt32());
}
}
Right now, I'm thinking about some manager that will control a queue of windows to take the screenshots from.
Question edit.
I'm not looking for a solution to just get screenshot 'in memory' and return it back to HTTP stream. So any ways to save screenshot and save it to file and then get it from there is very ambiguous for that purpose.
Question edit #2.
I forgot to mention. Needed screenshot should be made as it seen by user. So, screenshot should have browser window and a site inside of web browser window's bounds. I can't find any way to change mode of taking a screenshot in WebDriver of selenium. WebDriver just take screenshot of a page without any browser window.
I'd recommend getScreenshotAs. It gets even the 'out of view' part of the screen.
Here is some sample code in gr0ovy.
import java.io.IOException
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
import java.text.SimpleDateFormat
import org.openqa.selenium.Capabilities
import org.openqa.selenium.TakesScreenshot
import org.openqa.selenium.WebDriverException
import org.openqa.selenium.remote.CapabilityType
import org.openqa.selenium.remote.DriverCommand
import org.openqa.selenium.remote.RemoteWebDriver
import org.openqa.selenium.OutputType
import org.openqa.selenium.WebDriver
public class Selenium2Screenshot {
private WebDriver driver
private String browserType
private boolean skipScreenshots
public Selenium2Screenshot(WebDriver webDriver, String browserType, boolean skipScreenshots) {
this.driver = webDriver
this.browserType = browserType
this.skipScreenshots = skipScreenshots
}
public void takeScreenshot(String filenameBase) {
if (!skipScreenshots) {
Date today
String formattedDate
SimpleDateFormat formatter
Locale currentLocale
File scrFile
currentLocale = new Locale("en", "US")
formatter = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss_SSS", currentLocale)
today = new Date()
formattedDate = formatter.format(today)
String filename = getUiAutomationDir() + filenameBase + "_" + browserType + formattedDate + ".png"
Log.logger.info("Screenshot filename = " + filename)
try {
scrFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE)
JavaIO.copy(scrFile.getAbsolutePath(), filename)
} catch (Exception e) {
Log.logger.error(e.message, e)
}
} else {
Log.logger.info("Skipped Screenshot")
}
}
private String getUiAutomationDir()
{
String workingDir = System.getProperty("user.dir")
Path workingDirPath = Paths.get(workingDir)
String returnString = workingDirPath.toString() + "\\"
return returnString
}
}
Edited on 8/1/12:
Get application handle code. I am surely duplicating code that is on stackoverflow several times, but hopefully this is not the exact same code as in other posts :-)
public static IntPtr FindWindowByPartialCaption(String partialCaption)
{
var desktop = User32.GetDesktopWindow();
var children = EnumerateWindows.GetChildWindows(desktop);
foreach (var intPtr in children)
{
var current = GetText(intPtr);
if (current.Contains(partialCaption))
return intPtr;
}
return IntPtr.Zero;
}
[DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
public static extern bool EnumChildWindows(IntPtr hWndParent, EnumWindowProc lpEnumFunc, IntPtr lParam);
public delegate bool EnumWindowProc(IntPtr hWnd, IntPtr parameter);
public static List<IntPtr> GetChildWindows(IntPtr parent)
{
return GetChildWindows(parent, false);
}
public static List<IntPtr> GetChildWindows(IntPtr parent, bool reverse)
{
List<IntPtr> result = new List<IntPtr>();
GCHandle listHandle = GCHandle.Alloc(result);
try
{
EnumWindowProc childProc = new EnumWindowProc(EnumWindow);
EnumChildWindows(parent, childProc, GCHandle.ToIntPtr(listHandle));
}
finally
{
if (listHandle.IsAllocated)
listHandle.Free();
}
if (reverse)
{
List<IntPtr> resultList = result.Reverse<IntPtr>().ToList();
return resultList;
}
else
return result;
}
private static bool EnumWindow(IntPtr handle, IntPtr pointer)
{
GCHandle gch = GCHandle.FromIntPtr(pointer);
List<IntPtr> list = gch.Target as List<IntPtr>;
if (list == null)
{
throw new InvalidCastException("GCHandle Target could not be cast as List<IntPtr>");
}
list.Add(handle);
// You can modify this to check to see if you want to cancel the operation, then return a null here
return true;
}
}
http://www.pinvoke.net/ is also a great resource.
http://msdn.microsoft.com/en-us/library/windows/desktop/dd162869(v=vs.85).aspx
I personally love this API. Create a bitmap with width and height calculated from the returned rectangle of GetWindowRect API and for HDC parameter use (for example):
thebitmap.GetHdc()
You should be fine.
Edit: also check this.
Btw, you can take screenshot of any window you like, even if they fall back.(note that this will not work for minimized windows. However, if you really need, there are some way arounds for that too.)
If you're looking for a programmatic way to get a screenshot of the main window of a given process, here is a function that does it:
public static Bitmap TakeScreenshot(Process process)
{
// may need a process Refresh before
return TakeScreenshot(process.MainWindowHandle);
}
public static Bitmap TakeScreenshot(IntPtr handle)
{
RECT rc = new RECT();
GetWindowRect(handle, ref rc);
Bitmap bitmap = new Bitmap(rc.right - rc.left, rc.bottom - rc.top);
using (Graphics graphics = Graphics.FromImage(bitmap))
{
PrintWindow(handle, graphics.GetHdc(), 0);
}
return bitmap;
}
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, ref RECT rect);
[DllImport("user32.dll")]
private static extern bool PrintWindow(IntPtr hWnd, IntPtr hDC, int flags);
[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
Unfortunately, on Aero-equipped OS (Vista/Win7/Win8) it will not capture the full transparent border. The usual transparent border will be blacked instead. Maybe it's enough for what you're trying to acomplish.
I've been using webshotcmd (the paid version is also command line) in a production app for years. It can be configured to wait for the page to load, to wait n seconds after page load, etc. It uses Internet Explorer and works on Windows. Starts pretty fast (in my experience, the msie activex has always been instant to load).
Other than the above, I would recommend something based on a Webkit libray, it would be so much smaller than Firefox, and would start very fast (wkhtmltoimage is for now only available on Linux, but when it will be available for Windows, I would go for it - also command line). Right now just google for webkit screenshot (the huge number of available screenshotters using webkit makes me believe using that DLL would be easy to port to C#).
Edit: Considering your 2nd edit, take a look at Chrome Screen Capture source.
To try it, the extension is available in the store/extension gallery.
I was able to accomplish this by copying the window (piece by piece) into a bitmap that is set to the size of the ScrollRectangle for my webBrowser control. While it is certainly not the most elegant way of achieving this goal, I wanted to share the code in case anyone might be able to use it. Once I had something that was mostly working, I was then able to add some args, and I can now execute this utility from the command line:
Executable_Path URL Filename
/// <summary>
/// This method is called to start the process of copying the webpage to the bitmap
/// this should be called after the page has fully loaded (use DocumentCompleted event to determine
/// if the page has completed loading if calling from the command line.)
/// </summary>
private void copyWebpageToImage()
{
//these two vars will house the current position in the bmp file (starting at 0,0)
int currXPosition = 0;
int currYPosition = 0;
//we need to set the height and width of our bitmap to the scrollrectangle of the webbrowser document object
int width = webBrowser1.Document.Body.ScrollRectangle.Width;
int height = webBrowser1.Document.Body.ScrollRectangle.Height;
//instantiate the bitmap
bm = new Bitmap(wd, ht);
//Instantiate our graphics object
Graphics gfx = Graphics.FromImage((Image)bm);
//this point is used throughout the process, and helps to determine where the form is at on the screen
Point formPoint = Form1.ActiveForm.Location;
formPoint.X = formPoint.X + webBrowser1.Location.X;
formPoint.Y = formPoint.Y + webBrowser1.Location.Y;
formPoint.X = formPoint.X + 8; //offsets for my form (may be different for yours)
formPoint.Y = formPoint.Y + 33; //offsets for my form
//begin our recursive call that will stop when it reaches the end of the page
copyEverythingToBitmap(bm, currXPosition, currYPosition, formPoint, gfx);
}
private void copyEverythingToBitmap(Bitmap bm, int currXPosition, int currYPosition, Point formPoint, Graphics gfx)
{
//check to see if currXPosition and currYPosition are both 0, if so we just began, call the zero copy method
if (currXPosition == 0 && currYPosition == 0)
{
performZeroCopy(bm, currXPosition, currYPosition, formPoint, gfx);
}
//if the current x position is less than the total width of the scrollrectangle - the width of the webbrowser,
//then we need to scroll the window, and copy the contents, y stays the same
else if (currXPosition < bm.Width - webBrowser1.Width)
{
AlterXPosition(bm, ref currXPosition, ref currYPosition, ref formPoint, gfx);
}
//if we are no longer at the zero, zero, and we cannot increase the x position anymore,
//then we need to scroll the window down and copy the contents, x is reset back to zero
else if(currYPosition < bm.Height - webBrowser1.Height)
{
currYPosition = currYPosition + webBrowser1.Height - 20;
currXPosition = 0;
performZeroCopy(bm, currXPosition, currYPosition, formPoint, gfx);
}
}
/// <summary>
/// The name of this method is slightly misleading. It inherently means that X is zero.
/// </summary>
private void performZeroCopy(Bitmap bm, int currXPosition, int currYPosition, Point formPoint, Graphics gfx)
{
webBrowser1.Document.Window.ScrollTo(currXPosition, currYPosition);
gfx.CopyFromScreen(formPoint, new Point(currXPosition, currYPosition), new Size(webBrowser1.Width - 20, webBrowser1.Height - 20));
if (currXPosition < bm.Width - webBrowser1.Width)
{
AlterXPosition(bm, ref currXPosition, ref currYPosition, ref formPoint, gfx);
}
else if(currYPosition < bm.Height - webBrowser1.Height)
{
currYPosition = currYPosition + webBrowser1.Height - 20;
currXPosition = 0;
performZeroCopy(bm, currXPosition, currYPosition, formPoint, gfx);
}
}
private void AlterXPosition(Bitmap bm, ref int currXPosition, ref int currYPosition, ref Point formPoint, Graphics gfx)
{
currXPosition = currXPosition + webBrowser1.Width - 20;
webBrowser1.Document.Window.ScrollTo(bm.Width - currXPosition, currYPosition);
gfx.CopyFromScreen(formPoint, new Point(bm.Width - currXPosition - 3, currYPosition), new Size(webBrowser1.Width - 20, webBrowser1.Height - 20));
if (currXPosition + webBrowser1.Width < bm.Width)
{
//we still have not traversed the full width of the page, call to alterxposition again...
}
else
{
copyEverythingToBitmap(bm, currXPosition, currYPosition, formPoint, gfx);
}
}
private void saveImageToFile(string p)
{
bm.Tag = DateTime.Now;
bm.Save(p, ImageFormat.Jpeg);
}

Categories