Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Can GDI get the mouse on a screen capture?

115 views
Skip to first unread message

gregory_may

unread,
Jan 19, 2004, 7:15:23 PM1/19/04
to
I am trying to capture the screen in a VB .Net Application. The code below
doesnt grab the Mouse Pointer, Cursor, or Tool Tips. Does anyone have a
suggestion on how to add these elements into my screen capture?

I have tested this code and it seems to perform at least 15 frames/second++
just
fine (I just need the mouse, cursor & tool tips). But, If there is a better
way, I am open!

Thanks!!!

------------

Private Declare Function CreateDC Lib "gdi32" Alias "CreateDCA" (ByVal
lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As
String, ByVal lpInitData As String) As Integer

Private Declare Function CreateCompatibleDC Lib "GDI32" (ByVal hDC As
Integer) As Integer

Private Declare Function CreateCompatibleBitmap Lib "GDI32" (ByVal hDC As
Integer, ByVal nWidth As Integer, ByVal nHeight As Integer) As Integer

Private Declare Function GetDeviceCaps Lib "gdi32" Alias "GetDeviceCaps"
(ByVal hdc As Integer, ByVal nIndex As Integer) As Integer

Private Declare Function SelectObject Lib "GDI32" (ByVal hDC As Integer,
ByVal hObject As Integer) As Integer

Private Declare Function BitBlt Lib "GDI32" (ByVal srchDC As Integer, ByVal
srcX As Integer, ByVal srcY As Integer, ByVal srcW As Integer, ByVal srcH As
Integer, ByVal desthDC As Integer, ByVal destX As Integer, ByVal destY As
Integer, ByVal op As Integer) As Integer

Private Declare Function DeleteDC Lib "GDI32" (ByVal hDC As Integer) As
Integer

Private Declare Function DeleteObject Lib "GDI32" (ByVal hObj As Integer) As
Integer

Const SRCCOPY As Integer = &HCC0020

Private oBackground As System.Drawing.Bitmap

Private FW, FH As Integer

Public Sub CaptureScreen()

Dim hSDC, hMDC As Integer

Dim hBMP, hBMPOld As Integer

Dim r As Integer

Try

hSDC = CreateDC("DISPLAY", "", "", "")

hMDC = CreateCompatibleDC(hSDC)

FW = GetDeviceCaps(hSDC, 8)

FH = GetDeviceCaps(hSDC, 10)

hBMP = CreateCompatibleBitmap(hSDC, FW, FH)

hBMPOld = SelectObject(hMDC, hBMP)

r = BitBlt(hMDC, 0, 0, FW, FH, hSDC, 0, 0, 13369376)

hBMP = SelectObject(hMDC, hBMPOld)

r = DeleteDC(hSDC)

r = DeleteDC(hMDC)

oBackground = System.Drawing.Image.FromHbitmap(New IntPtr(hBMP))

DeleteObject(hBMP)

DeleteObject(hBMPOld)

'Seems to be a memory hole in fromHbitmap ....

GC.Collect()

Catch ex As Exception

Debug.WriteLine("General GDI+ Error")

End Try

End Sub

Mike D Sutton

unread,
Jan 20, 2004, 5:35:04 AM1/20/04
to
> I am trying to capture the screen in a VB .Net Application. The code below
> doesnt grab the Mouse Pointer, Cursor, or Tool Tips. Does anyone have a
> suggestion on how to add these elements into my screen capture?
>
> I have tested this code and it seems to perform at least 15 frames/second++
> just
> fine (I just need the mouse, cursor & tool tips). But, If there is a better
> way, I am open!
<code snipped>

Try this:

'***
Dim CurInf As CursorInfo
Dim CurPos As PointAPI

hSDC = CreateDC("DISPLAY", "", "", "")
hMDC = CreateCompatibleDC(hSDC)

FW = GetDeviceCaps(hSDC, HORIZRES)
FH = GetDeviceCaps(hSDC, VERTRES)


hBMP = CreateCompatibleBitmap(hSDC, FW, FH)

Call DeleteDC(hSDC)

hBMPOld = SelectObject(hMDC, hBMP)
Call BitBlt(hMDC, 0, 0, FW, FH, hSDC, 0, 0, SRCCOPY)

CurInf.cbSize = Len(CurInf)
Call GetCursorInfo(CurInf)

If (CurInf.flags And CURSOR_SHOWING) Then
Call GetCursorPos(CurPos)
Call DrawIconEx(hMDC, CurPos.X, CurPos.Y, _
CurInf.hCursor, 0, 0, 0, False, DI_NORMAL)
End If

Call SelectObject(hMDC, hBMPOld)
Call DeleteDC(hMDC)
oBackground = System.Drawing.Image.FromHbitmap(New IntPtr(hBMP))

' This shouldn't be deleted until after oBackground goes out of scope
' It may also be the cause of your prior leak (Fixed a couple of GDI bugs too though)
' Call DeleteObject(hBMP)
'***

And the VB6 declares, you'll need to do any conversion of them to VB.NET i.e. find an replace "Long" for "Integer" etc:

'***
Private Declare Function GetCursorInfo Lib "User32.dll" (ByRef pCI As CursorInfo) As Long
Private Declare Function GetCursorPos Lib "User32.dll" (lpPoint As PointAPI) As Long
Private Declare Function DrawIconEx Lib "User32.dll" (ByVal hDC As Long, _
ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, _
ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, _
ByVal hbrFlickerFreeDraw As Long, ByVal diFlags As Long) As Long

Private Type PointAPI
X As Long
Y As Long
End Type

Private Type CursorInfo
cbSize As Long
flags As Long
hCursor As Long
ptScreenPos As PointAPI
End Type

Private Const CURSOR_SHOWING As Long = &H1
Private Const DI_NORMAL As Long = &H3
'***

Hope this helps,

Mike


- Microsoft Visual Basic MVP -
E-Mail: ED...@mvps.org
WWW: Http://www.mvps.org/EDais/


gregory_may

unread,
Jan 20, 2004, 7:42:58 PM1/20/04
to
This is awesome Mike. Thanks so much!

g.

"Mike D Sutton" <ED...@mvps.org> wrote in message
news:Odq7RE03...@TK2MSFTNGP10.phx.gbl...

gregory_may

unread,
Jan 20, 2004, 8:36:29 PM1/20/04
to
Thanks again Mike for you help. I rolled your changes into my full class
definition. I also moved the DeleteDC call you added until after the BitBlt
call.

There seems to be 2 problems with this code:

1) Nothing is happening in Call GetCursorInfo(CurInf).

2) if I Omit the gc.collect, things slow down and there is a leak some place
(I am OK having GC.Collect here, its just WEIRD the code needs the
GC.Collect call to work at a reasonable speed).

Any thoughts on how to fix the GetCursorInfo(CurInf) call?

Thanks again!

g.

Public Class ScreenCapture

Private Declare Function CreateDC Lib "gdi32" Alias "CreateDCA" (ByVal
lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As
String, ByVal lpInitData As String) As Integer

Private Declare Function CreateCompatibleDC Lib "GDI32" (ByVal hDC As
Integer) As Integer

Private Declare Function CreateCompatibleBitmap Lib "GDI32" (ByVal hDC As
Integer, ByVal nWidth As Integer, ByVal nHeight As Integer) As Integer

Private Declare Function GetDeviceCaps Lib "gdi32" Alias "GetDeviceCaps"
(ByVal hdc As Integer, ByVal nIndex As Integer) As Integer

Private Declare Function SelectObject Lib "GDI32" (ByVal hDC As Integer,
ByVal hObject As Integer) As Integer

Private Declare Function BitBlt Lib "GDI32" (ByVal srchDC As Integer, ByVal
srcX As Integer, ByVal srcY As Integer, ByVal srcW As Integer, ByVal srcH As
Integer, ByVal desthDC As Integer, ByVal destX As Integer, ByVal destY As
Integer, ByVal op As Integer) As Integer

Private Declare Function DeleteDC Lib "GDI32" (ByVal hDC As Integer) As
Integer

Private Declare Function DeleteObject Lib "GDI32" (ByVal hObj As Integer) As
Integer

Private Declare Function GetCursorInfo Lib "User32.dll" (ByRef pCI As
CursorInfo) As Long

Private Declare Function GetCursorPos Lib "User32.dll" (ByVal lpPoint As
PointAPI) As Long

Private Declare Function DrawIconEx Lib "User32.dll" (ByVal hDC As Long, _

ByVal xLeft As Long, ByVal yTop As Long, ByVal hIcon As Long, _

ByVal cxWidth As Long, ByVal cyWidth As Long, ByVal istepIfAniCur As Long, _

ByVal hbrFlickerFreeDraw As Long, ByVal diFlags As Long) As Long

Private Const SRCCOPY As Integer = &HCC0020

Private Const HORIZRES As Integer = 8

Private Const VERTRES As Integer = 10

Private Const CURSOR_SHOWING As Long = &H1

Private Const DI_NORMAL As Long = &H3

Private Structure PointAPI

Dim X As Long

Dim Y As Long

End Structure

Private Structure CursorInfo

Dim cbSize As Long

Dim flags As Long

Dim hCursor As Long

Dim ptScreenPos As PointAPI

End Structure

Private oBackground As System.Drawing.Bitmap

Public Sub CaptureScreen()

Dim hSDC, hMDC As Integer

Dim hBMP, hBMPOld As Integer

Dim CurInf As CursorInfo

Dim CurPos As PointAPI

Dim FW As Integer 'Width

Dim FH As Integer 'Height

Try

hSDC = CreateDC("DISPLAY", "", "", "")

hMDC = CreateCompatibleDC(hSDC)

FW = GetDeviceCaps(hSDC, HORIZRES)

FH = GetDeviceCaps(hSDC, VERTRES)

hBMP = CreateCompatibleBitmap(hSDC, FW, FH)

hBMPOld = SelectObject(hMDC, hBMP)

Call BitBlt(hMDC, 0, 0, FW, FH, hSDC, 0, 0, SRCCOPY)

Call DeleteDC(hSDC)

CurInf.cbSize = Len(CurInf)

Call GetCursorInfo(CurInf)

If (CurInf.flags And CURSOR_SHOWING) Then

Call GetCursorPos(CurPos)

Call DrawIconEx(hMDC, CurPos.X, CurPos.Y, _

CurInf.hCursor, 0, 0, 0, False, DI_NORMAL)

End If

Call SelectObject(hMDC, hBMPOld)

Call DeleteDC(hMDC)

oBackground = System.Drawing.Image.FromHbitmap(New IntPtr(hBMP))

DeleteObject(hBMP)

DeleteObject(hBMPOld)

'Seems to be a memory hole in fromHbitmap ....

'Code gets SLOW when not GC.Collect ing ... wierd.

GC.Collect()

Catch ex As Exception

Debug.WriteLine("General GDI ERROR: " & ex.Message.ToString)

End Try

End Sub

Public ReadOnly Property GetSizedScreen(ByVal s As System.Drawing.Size) As
System.Drawing.Bitmap

Get

Try

Return New System.Drawing.Bitmap(oBackground, s)

Catch ex As Exception

Debug.WriteLine("Error: ScreenCapture - GetSizedScreen")

End Try

End Get

End Property

End Class


"gregory_may" <None> wrote in message
news:ORjApd7...@tk2msftngp13.phx.gbl...

Mike D Sutton

unread,
Jan 21, 2004, 4:37:54 AM1/21/04
to
> Thanks again Mike for you help. I rolled your changes into my full class
> definition. I also moved the DeleteDC call you added until after the BitBlt
> call.
>
> There seems to be 2 problems with this code:
>
> 1) Nothing is happening in Call GetCursorInfo(CurInf).

The code was tested in VB6 before posting so it should work without problems. What's the return value on GetCursorInfo() and what
does GetLastError() return immediately after the call fails? Are you populating the structure size before passing it to
GetCursorInfo()? Have you re-declared the structure/call properly for VB.NET?

> 2) if I Omit the gc.collect, things slow down and there is a leak some place
> (I am OK having GC.Collect here, its just WEIRD the code needs the
> GC.Collect call to work at a reasonable speed).

I'm not a VB.NET developer so can't help you with that side of things (you may find more luck somewhere like
microsoft.public.dotnet.framework.drawing) however the documentation for the FromHbitmap() method specifically state that you
shouldn't delete the GDI Bitmap handle until after the GDI+ Bitmap object is destroyed. In the demo code I posted I actually
removed the DeleteObject() call which would create a GDI resource leak if you're not properly destroying it after your GDI+ Bitmap
object is being destroyed.

gregory_may

unread,
Jan 21, 2004, 2:08:24 PM1/21/04
to
If I execute this from the "Immediate Window" I get the following. There
is no error, it seems to execute fine. But, below, it doesnt do anything to
the structure:

? GetCursorInfo(CurInf)

9222812402616107008

I guess its doing something, but cant tell what. Then I can execute the
following:

?curinf

{RealTimeJpegBroadcast.ScreenCapture.CursorInfo}

cbSize: 40

flags: 0

hCursor: 0

ptScreenPos: {RealTimeJpegBroadcast.ScreenCapture.PointAPI}

(CBSize got a value, but thats because we set it prior to calling this
function. Doesnt look like any of the structure got updated ....?)

?curinf.ptScreenPos

{RealTimeJpegBroadcast.ScreenCapture.PointAPI}

X: 0

Y: 0

(Doesnt look like it got the mouse either)

If you tested under VB6, I would guess the structure, but it looks identical
to your type:

Private Structure CursorInfo

Dim cbSize As Long

Dim flags As Long

Dim hCursor As Long

Dim ptScreenPos As PointAPI

End Structure

Any other thoughts?

"Mike D Sutton" <ED...@mvps.org> wrote in message

news:OqJv$IA4DH...@TK2MSFTNGP09.phx.gbl...

Mike D Sutton @ Work

unread,
Jan 21, 2004, 3:04:08 PM1/21/04
to
> If I execute this from the "Immediate Window" I get the following. There
> is no error, it seems to execute fine. But, below, it doesnt do anything to
> the structure:
>
> ? GetCursorInfo(CurInf)
>
> 9222812402616107008

That number is too large to fit in a 32-bit integer so I would assume your declare is incorrect. Follow up on a VB.NET group for
clarification on this issue.

> If you tested under VB6, I would guess the structure, but it looks identical
> to your type:

<snip>

If this is what you're using in VB.NET then you declares are indeed incorrect since a Long data type in VB.NET is a 64-bit integer
where as in VB6 it's a 32-bit integer (Why Microsoft felt the need to change it who knows but it needs to be taken into account as I
mentioned in my original post.) Again though, please follow up on a VB.NET group for language porting questions - they're very much
off-topic here.

gregory_may

unread,
Jan 21, 2004, 8:27:15 PM1/21/04
to
That was the problem!

Changing from Long to Integer did the trick!!! Thanks again!

g.
"Mike D Sutton @ Work" <ED...@mvps.org> wrote in message
news:uEOGmnF4...@TK2MSFTNGP11.phx.gbl...

0 new messages