attas052
asked on
STAThread issues and the clipboard
Hi, I have a class which handles communication with the serialport. When it receives data, or timeouts, it processes it, and raises an event. The event is then raised in the main form, where after checking if the event was raised because of a success or a failure (using a flag sent with the event), it calls a function which takes an image with a webcam using the avicap32.dll, by copying the image displayed in the picturebox to the clipboard, which is what causes the error, as I understand threading doesnt like clipboard... (this normally works fine if you assign the function to run on pressing a button).
Anyway, on the main form, ive added a PUBLIC SUB Main() sub which starts the application, and ive added the <STAThread()> attribute to this sub, but it makes no difference. Should this be where the <STAThread()> attribute be put, or am I missing something else?
Anyway, on the main form, ive added a PUBLIC SUB Main() sub which starts the application, and ive added the <STAThread()> attribute to this sub, but it makes no difference. Should this be where the <STAThread()> attribute be put, or am I missing something else?
ASKER
Well this is the function:
Dim data As IDataObject
Clipboard.Clear()
SendMessage(PictureBoxHand le, WM_CAP_EDIT_COPY, 0, 0)
data = Clipboard.GetDataObject()
If data.GetDataPresent(GetTyp e(System.D rawing.Bit map)) Then Return _
CType(data.GetData(GetType (System.Dr awing.Bitm ap)), Image)
An exception is thrown at Clipboard.Clear() and is:
"ThreadStateException was unhandled - Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it."
The clipboard.clear line is not really necessary, and without it, errors are thrown as 'data' is 'nothing'
As an addition, no errors occur if my class raises the event because of a timeout, which is when the event is raised in a timer.tick event in my class
Thanks
Dim data As IDataObject
Clipboard.Clear()
SendMessage(PictureBoxHand
data = Clipboard.GetDataObject()
If data.GetDataPresent(GetTyp
CType(data.GetData(GetType
An exception is thrown at Clipboard.Clear() and is:
"ThreadStateException was unhandled - Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it."
The clipboard.clear line is not really necessary, and without it, errors are thrown as 'data' is 'nothing'
As an addition, no errors occur if my class raises the event because of a timeout, which is when the event is raised in a timer.tick event in my class
Thanks
Humm... I might have expected the error TO occur when the event was raised in the timer.tick event. I notice that in our last project, someone wrote the following code... I don't recall having any issues with clearing the clipboard, but, you might try this:
Private Declare Function OpenClipboard Lib "User32.dll" (ByVal hWnd As Long) As Long
Private Declare Function EmptyClipboard Lib "User32.dll" () As Long
Private Declare Function CloseClipboard Lib "User32.dll" () As Long
Public Sub ClearClipboard()
Try
If (OpenClipboard(0&)) Then
If (EmptyClipboard()) Then
'MsgBox("Clipboard Emptied")
Else
'MsgBox("Clipboard Not Emptied")
End If
CloseClipboard()
End If
Catch x As Exception
MsgBox(x.Message)
End Try
End Sub
Private Declare Function OpenClipboard Lib "User32.dll" (ByVal hWnd As Long) As Long
Private Declare Function EmptyClipboard Lib "User32.dll" () As Long
Private Declare Function CloseClipboard Lib "User32.dll" () As Long
Public Sub ClearClipboard()
Try
If (OpenClipboard(0&)) Then
If (EmptyClipboard()) Then
'MsgBox("Clipboard Emptied")
Else
'MsgBox("Clipboard Not Emptied")
End If
CloseClipboard()
End If
Catch x As Exception
MsgBox(x.Message)
End Try
End Sub
ASKER
Thanks, that code clears the clipboard without problem, however, the line:
SendMessage(PictureBoxHand le, WM_CAP_EDIT_COPY, 0, 0)
does not copy the frame of the video from the picturebox onto the clipboard, so the NullReference exception is thrown on the line
If data.GetDataPresent(GetTyp e(System.D rawing.Bit map)) Then Return _
CType(data.GetData(GetType (System.Dr awing.Bitm ap)), Image)
as 'data' is nothing.
Thanks
btw, relevant declarations:
Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, _
<MarshalAs(UnmanagedType.A sAny)> ByVal lParam As Object) As Integer
Const WM_CAP As Short = &H400S
Const WM_CAP_EDIT_COPY As Integer = WM_CAP + 30
SendMessage(PictureBoxHand
does not copy the frame of the video from the picturebox onto the clipboard, so the NullReference exception is thrown on the line
If data.GetDataPresent(GetTyp
CType(data.GetData(GetType
as 'data' is nothing.
Thanks
btw, relevant declarations:
Declare Function SendMessage Lib "user32" Alias "SendMessageA" _
(ByVal hwnd As Integer, ByVal wMsg As Integer, ByVal wParam As Integer, _
<MarshalAs(UnmanagedType.A
Const WM_CAP As Short = &H400S
Const WM_CAP_EDIT_COPY As Integer = WM_CAP + 30
You might need to do this:
Dim data As IDataObject = Clipboard.GetDataObject()
I was a little confused by that SendMessage statement.
I expected that you would call a method in the avicap32.dll which would set the data on the clipboard, then this code in your program would simply do a GetData to retrieve it, and then you might show the returned data in your picturebox.
But, it sounds like you already have the image in your picturebox, in which case I'm wondering why you need to use the clipboard at all.
Dim data As IDataObject = Clipboard.GetDataObject()
I was a little confused by that SendMessage statement.
I expected that you would call a method in the avicap32.dll which would set the data on the clipboard, then this code in your program would simply do a GetData to retrieve it, and then you might show the returned data in your picturebox.
But, it sounds like you already have the image in your picturebox, in which case I'm wondering why you need to use the clipboard at all.
ASKER
I've already used the Dim data As IDataObject = Clipboard.GetDataObject() line if you look at my second comment, the problem seems to be getting data to the clipboard from the picturebox which is displaying the video. After the image is captured it is saved in a class and displayed in another picturebox. The method of capturing images this may may seem odd, but its used often in various avicap32.dll examples floating around.
Sorry, I missed the GetDataObject line in your second comment.
I've seen a lot of different declarations for SendMessageA. Off the top of my head, parameter values defined as Integer should be either Int32, Long or IntPtr.
Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" ( _
ByVal hwnd As Int32, _
ByVal wMsg As Int32, _
ByVal wParam As Int32, _
ByVal lParam As Int32) As Int32
Private Declare Auto Function SendMessageA Lib "user32" (ByVal hwnd As Long,
ByVal wMsg As Long, ByVal wParam As Long, <MarshalAs(UnmanagedType.L PStr)>
ByVal lParam As String) As Long
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As IntPtr, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As IntPtr) As Long
You should collect the return value from the SendMessage call so we can see if there is an error value.
I've seen a lot of different declarations for SendMessageA. Off the top of my head, parameter values defined as Integer should be either Int32, Long or IntPtr.
Declare Function SendMessage Lib "user32.dll" Alias "SendMessageA" ( _
ByVal hwnd As Int32, _
ByVal wMsg As Int32, _
ByVal wParam As Int32, _
ByVal lParam As Int32) As Int32
Private Declare Auto Function SendMessageA Lib "user32" (ByVal hwnd As Long,
ByVal wMsg As Long, ByVal wParam As Long, <MarshalAs(UnmanagedType.L
ByVal lParam As String) As Long
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As IntPtr, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As IntPtr) As Long
You should collect the return value from the SendMessage call so we can see if there is an error value.
ASKER
SendMessage(PictureBoxHand le, WM_CAP_EDIT_COPY, 0, 0) returns 8.97592579397255E+18 whether the operation is a success or not
ASKER
As an expansion to the above, using my original declaration, SendMessage(PictureBoxHand le, WM_CAP_EDIT_COPY, 0, 0) returns 1 whenever. Using your declaration SendMessageA returns 2.16981404196536E+16 when it is a success, and 8.97592579397255E+18 when the error occurs, and using your declaration of SendMessage returns 8.97592579397255E+18.
However, copying your exact declarations is not liked by VB, so i commented the first bit and have been testing using:
Private Declare Auto Function SendMessageA Lib "user32" (ByVal hwnd As Long,
ByVal wMsg As Long, ByVal wParam As Long, <MarshalAs(UnmanagedType.L PStr)>
ByVal lParam As String) As Long
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As IntPtr, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As IntPtr) As Long
Thanks
However, copying your exact declarations is not liked by VB, so i commented the first bit and have been testing using:
Private Declare Auto Function SendMessageA Lib "user32" (ByVal hwnd As Long,
ByVal wMsg As Long, ByVal wParam As Long, <MarshalAs(UnmanagedType.L
ByVal lParam As String) As Long
Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As IntPtr, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As IntPtr) As Long
Thanks
Humm... well, it is supposed to return True (1) when successful, or False (0) when not, so, perhaps your original was best.
I've seen other declarations where they say that the return value is actually an IntPtr, and so those long numbers are probably the address of an Integer variable in memory that has a value of either 1 or 0
You have confirmed that PictureBoxHandle is a valid handle to a capture window?
I don't know if any of the attached code snippit will be useful to you, but this is a VB6 sample that I have that works in VB6. I notice that sometimes SendMessage is defined with the last parameter a string or As Any, and sometimes a Long/Int32 etc... in my sample, the declaration for String or As Any is never used.
I"ve attached enough so that you can see the steps used to capture a single frame into the Capture Window, then the code to copy that to the clipboard.
Are you saying that the SendMessage code works when done explicitly (say, through a button click), but is only failing on the Raise Event?
I've seen other declarations where they say that the return value is actually an IntPtr, and so those long numbers are probably the address of an Integer variable in memory that has a value of either 1 or 0
You have confirmed that PictureBoxHandle is a valid handle to a capture window?
I don't know if any of the attached code snippit will be useful to you, but this is a VB6 sample that I have that works in VB6. I notice that sometimes SendMessage is defined with the last parameter a string or As Any, and sometimes a Long/Int32 etc... in my sample, the declaration for String or As Any is never used.
I"ve attached enough so that you can see the steps used to capture a single frame into the Capture Window, then the code to copy that to the clipboard.
Are you saying that the SendMessage code works when done explicitly (say, through a button click), but is only failing on the Raise Event?
'// Capture Function Declares
Declare Function capCreateCaptureWindow Lib "avicap32.dll" Alias "capCreateCaptureWindowA" _
(ByVal lpszWindowName As String, _
ByVal dwStyle As Long, _
ByVal x As Long, _
ByVal y As Long, _
ByVal nWidth As Long, _
ByVal nHeight As Long, _
ByVal hwndParent As Long, _
ByVal nID As Long) As Long 'returns HWND
'//General WINAPI Declares
Private Declare Function SendMessageAsLong Lib "user32" Alias "SendMessageA" _
(ByVal hWnd As Long, _
ByVal wMsg As Long, _
ByVal wParam As Long, _
ByVal lParam As Long) As Long
' Functions used to copy a single frame to a capture window
hCapWnd = capCreateCaptureWindow("VB CAP WINDOW", WS_CHILD Or WS_VISIBLE, 0, 0, 160, 120, Me.hWnd, 0)
retVal = ConnectCapDriver(hCapWnd, nDriverIndex)
Call capCaptureSingleFrameOpen(hCapWnd)
Call capCaptureSingleFrame(hCapWnd)
Call capCaptureSingleFrameClose(hCapWnd)
' Function used to copy image from the capture window to the clipboard
Function capEditCopy(ByVal hCapWnd As Long) As Boolean
capEditCopy = SendMessageAsLong(hCapWnd, WM_CAP_EDIT_COPY, 0&, 0&)
End Function
Function capCaptureSingleFrameOpen(ByVal hCapWnd As Long) As Boolean
capCaptureSingleFrameOpen = SendMessageAsLong(hCapWnd, WM_CAP_SINGLE_FRAME_OPEN, 0&, 0&)
End Function
Function capCaptureSingleFrameClose(ByVal hCapWnd As Long) As Boolean
capCaptureSingleFrameClose = SendMessageAsLong(hCapWnd, WM_CAP_SINGLE_FRAME_CLOSE, 0&, 0&)
End Function
Function capCaptureSingleFrame(ByVal hCapWnd As Long) As Boolean
capCaptureSingleFrame = SendMessageAsLong(hCapWnd, WM_CAP_SINGLE_FRAME, 0&, 0&)
End Function
Public Function ConnectCapDriver(ByVal hCapWnd As Long, ByVal nDriverIndex As Long) As Boolean
Dim retVal As Boolean
Dim Caps As CAPDRIVERCAPS
Dim i As Long
Debug.Assert (nDriverIndex < 10) And (nDriverIndex >= 0)
'// Connect the capture window to the driver
retVal = capDriverConnect(hCapWnd, nDriverIndex)
If False = retVal Then
'return False
Exit Function
End If
'// Get the capabilities of the capture driver
retVal = capDriverGetCaps(hCapWnd, Caps)
If False <> retVal Then
'reset menus (very app-specific)
With frmMain
For i = 0 To .mnuDriver.UBound
.mnuDriver(i).Checked = False 'make sure all drivers are unchecked
Next
.mnuDriver(nDriverIndex).Checked = True 'then check the new driver
'disable all hardware feature menu items
.mnuSource.Enabled = False
.mnuFormat.Enabled = False
.mnuDisplay.Enabled = False
.mnuOverlay.Enabled = False
'Then enable the ones which are supported by the new driver
If Caps.fHasDlgVideoSource <> 0 Then .mnuSource.Enabled = True
If Caps.fHasDlgVideoFormat <> 0 Then .mnuFormat.Enabled = True
If Caps.fHasDlgVideoDisplay <> 0 Then .mnuDisplay.Enabled = True
If Caps.fHasOverlay <> 0 Then .mnuOverlay.Enabled = True
End With
End If
'// Set the preview rate in milliseconds
Call capPreviewRate(hCapWnd, 66) '15 FPS
'// Start previewing the image from the camera
Call capPreview(hCapWnd, True)
'default to showing a preview each time
frmMain.mnuPreview.Checked = True
'// Resize the capture window to show the whole image
Call ResizeCaptureWindow(hCapWnd)
ConnectCapDriver = True
End Function
ASKER
Thanks for that, and yes, it only fails when called from the event. I am correct in thinking that the <STAThread()> attribute only needs to be before the main() sub of the main form, rather than on my class which is raising the errors? I havnt had a chance to look through your code yet and that may provide some clues
hoowee, had to google this one. I've never had to set <STAThread> and I've used the clipboard copy/paste frequently without issues. Just found this answer that I think will solve your problem... someone else was doing exactly the same thing! He starts out mentioning that if you're in VB, your main thread is already in STA mode, which is what I was trying to get at initially.
Hi Chandrakant,
If you use VB your application's main thread runs in STA mode.
However, if you get *1* from Threading.Thread.CurrentTh read.Apart mentState
that means MTA mode.
If you copy the immage in the clipboard as a response of an event the
corresponding event handler may be executed in separate thread from the
thread pool. Make sure you don't use somewhere BeginInvoke method.
If you don't have control on how the events are fired what you can do is to
call Control.Invoke to switch back to the main thread and then copy the
image.
HTH
B\rgds
100
http://bytes.com/forum/thread349225.html
Hi Chandrakant,
If you use VB your application's main thread runs in STA mode.
However, if you get *1* from Threading.Thread.CurrentTh
that means MTA mode.
If you copy the immage in the clipboard as a response of an event the
corresponding event handler may be executed in separate thread from the
thread pool. Make sure you don't use somewhere BeginInvoke method.
If you don't have control on how the events are fired what you can do is to
call Control.Invoke to switch back to the main thread and then copy the
image.
HTH
B\rgds
100
http://bytes.com/forum/thread349225.html
ASKER
Ive tried to use invoke(), but im must be doing something wrong, which is quite likely as ive never used it before, as currently it is not solving the problem. Currently I have:
Private Delegate Sub MyDelSub()
Private Sub serial_class_SendSuccess(B yVal success As Boolean, ByVal x As Integer, ByVal y As Integer) Handles serial_class.SendSuccess
Dim del As MyDelSub
del = New MyDelSub(AddressOf TakeImage)
del.Invoke()
.....
end sub
Function TakeImage()
Dim data As IDataObject
Clipboard.Clear()
SendMessage(PictureBoxHand le, WM_CAP_EDIT_COPY, 0, 0)
data = Clipboard.GetDataObject()
....
End Function
When the code crashes at Clipboard.Clear(), the result of Threading.Thread.CurrentTh read.Apart mentState is MTA, and when its fine its STA, which is no surprise really. When the timer triggers the event within the class, the result of this is also STA. However, using invoke does not seem to have solved anything, although ive probably done it wrong
Private Delegate Sub MyDelSub()
Private Sub serial_class_SendSuccess(B
Dim del As MyDelSub
del = New MyDelSub(AddressOf TakeImage)
del.Invoke()
.....
end sub
Function TakeImage()
Dim data As IDataObject
Clipboard.Clear()
SendMessage(PictureBoxHand
data = Clipboard.GetDataObject()
....
End Function
When the code crashes at Clipboard.Clear(), the result of Threading.Thread.CurrentTh
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
Ok, fixed
What I did in the end was add
Delegate Sub TakeImageSub()
Public myDelegate As TakeImageSub
like suggested,add
myDelegate = New TakeImageSub(AddressOf TakeImage)
in the form load sub, rather than the new() sub, and then when I wanted to take a picture
Me.Invoke(Me.myDelegate)
and it now takes a picture without complaining. Most of what you posted above is to do with creating a new thread (like ThreadFunction() ), but I already had a new thread created by the event triggering.
Thanks :D
What I did in the end was add
Delegate Sub TakeImageSub()
Public myDelegate As TakeImageSub
like suggested,add
myDelegate = New TakeImageSub(AddressOf TakeImage)
in the form load sub, rather than the new() sub, and then when I wanted to take a picture
Me.Invoke(Me.myDelegate)
and it now takes a picture without complaining. Most of what you posted above is to do with creating a new thread (like ThreadFunction() ), but I already had a new thread created by the event triggering.
Thanks :D
ASKER
The final solution was a bit generic, and so required a bit of modification to work (what I actually did was posted at the end of the thread)
hey, if it works, that's all that matters.
As far as the thread stuff you mentioned, I assumed that your class was operating in another thread based on your original error message, and so the example showed spawning a thread, then creating the class instance in that thread.
I suspose that this isn't necessary to cause the error that you were getting. I guess just raising the Event causes the event code to execute in a new thread? I wasn't aware that it worked that way. Good to know, and good to know that the Invoke works.
Cheers!
As far as the thread stuff you mentioned, I assumed that your class was operating in another thread based on your original error message, and so the example showed spawning a thread, then creating the class instance in that thread.
I suspose that this isn't necessary to cause the error that you were getting. I guess just raising the Event causes the event code to execute in a new thread? I wasn't aware that it worked that way. Good to know, and good to know that the Invoke works.
Cheers!
Unless you are loading your main application window in a thread that you've spawned, your main application window is always the main application thread, so, your problem shouldn't be related to threading as your main application window is what is executing the clipboard copy.
What kind of error are you getting?