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?
Who is Participating?
mdouganConnect With a Mentor Commented:
Don't feel bad, it is confusing to me as well.  I'm assuming that the TakeImage function is in your main form, is that correct?  If so, this is what I think you need to do.

In your main form's class

Public Class frmMain  'whatever yours is called
   Inherits Form

   Delegate Sub  TakeImageSub()
   Public myDelegate As TakeImageSub

   Public Sub New()
         myDelegate = New TakeImageSub(AddressOf  TakeImage)
   End Sub  'New

   Public Sub TakeImage()
        Dim data As IDataObject
        SendMessage(PictureBoxHandle, WM_CAP_EDIT_COPY, 0, 0)    
        data = Clipboard.GetDataObject()
    End Sub 'TakeImage

Somewhere, in your frmMain, you have code to create an instance of your Class.  Let's say that your class is called MyThreadClass.  So, you have the code to create an instance of your class under another thread... I don't know if this is how you do it, but it would be one example.

   Private Sub Button_Click(sender As Object, e As EventArgs)
      myThread = New Thread(New ThreadStart(AddressOf ThreadFunction))
   End Sub 'Button_Click

   Private Sub ThreadFunction()
      ' this code will be executing under the new thread
      ' here, you are passing a reference to the main form to the instance of your class
      Dim myThreadClassObject As New MyThreadClass(Me)
      ' invoke whatever method you normally would in your Class
   End Sub 'ThreadFunction

End Class ' frmMain

All of the above is in your main form.

You have to change the New constructor of your Class to include the form reference passed in the New statement above...

Public Class MyThreadClass
   Private myMainForm  as frmMain
   Public Sub New(myForm as frmMain)
      myMainForm = myForm
   End Sub  'New

   Public Sub Run()  ' or whatever method you invoked in the main form's ThreadFunction
      ' instead of raising an event, do this
   End Sub  ' Run
End Class ' MyThreadClass

The sample above was hacked from this sample
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?
attas052Author Commented:
Well this is the function:

        Dim data As IDataObject

        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

Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

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()

            If (OpenClipboard(0&)) Then
                If (EmptyClipboard()) Then
                    'MsgBox("Clipboard Emptied")
                    'MsgBox("Clipboard Not Emptied")
                End If
            End If

        Catch x As Exception
        End Try
    End Sub
attas052Author Commented:
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.


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.
attas052Author Commented:
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.
attas052Author Commented:
SendMessage(PictureBoxHandle, WM_CAP_EDIT_COPY, 0, 0) returns 8.97592579397255E+18 whether the operation is a success or not
attas052Author Commented:
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

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
                .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

attas052Author Commented:
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
attas052Author Commented:
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)


end sub

Function TakeImage()

        Dim data As IDataObject
        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
attas052Author Commented:
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


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
attas052Author Commented:
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.

Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.