Link to home
Start Free TrialLog in
Avatar of attas052
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?
Avatar of mdougan
mdougan
Flag of United States of America image

You wouldn't normally put a Sub Main inside of your main application form.  You might put a Sub Main in a module, set it as the startup procedure, then that Sub Main would load your main application form and show it.  However, there is no need to do that unless you have something else you want to do before loading your main application form.  Just set your main application form as the startup procedure.

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?
Avatar of attas052
attas052

ASKER

Well this is the function:

        Dim data As IDataObject

        Clipboard.Clear()
       
        SendMessage(PictureBoxHandle, WM_CAP_EDIT_COPY, 0, 0)

        data = Clipboard.GetDataObject()
        If data.GetDataPresent(GetType(System.Drawing.Bitmap)) Then Return _
CType(data.GetData(GetType(System.Drawing.Bitmap)), 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
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
Thanks, that code clears the clipboard without problem, however, the line:

SendMessage(PictureBoxHandle, 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(GetType(System.Drawing.Bitmap)) Then Return _
CType(data.GetData(GetType(System.Drawing.Bitmap)), 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.AsAny)> ByVal lParam As Object) As Integer

    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.
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.LPStr)>
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.
SendMessage(PictureBoxHandle, WM_CAP_EDIT_COPY, 0, 0) returns 8.97592579397255E+18 whether the operation is a success or not
As an expansion to the above, using my original declaration, SendMessage(PictureBoxHandle, 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.LPStr)>
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?
   '// 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

Open in new window

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.CurrentThread.ApartmentState
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
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(ByVal 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(PictureBoxHandle, WM_CAP_EDIT_COPY, 0, 0)    
        data = Clipboard.GetDataObject()
    ....

    End Function


When the code crashes at Clipboard.Clear(), the result of Threading.Thread.CurrentThread.ApartmentState 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
ASKER CERTIFIED SOLUTION
Avatar of mdougan
mdougan
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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
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!