Solved

STAThread issues and the clipboard

Posted on 2008-10-20
17
2,245 Views
Last Modified: 2012-05-05
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?
0
Comment
Question by:attas052
  • 9
  • 8
17 Comments
 
LVL 18

Expert Comment

by:mdougan
ID: 22759313
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?
0
 

Author Comment

by:attas052
ID: 22759449
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
0
 
LVL 18

Expert Comment

by:mdougan
ID: 22759704
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
0
 

Author Comment

by:attas052
ID: 22759993
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
0
 
LVL 18

Expert Comment

by:mdougan
ID: 22760127
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.
0
 

Author Comment

by:attas052
ID: 22760216
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.
0
 
LVL 18

Expert Comment

by:mdougan
ID: 22760801
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.
0
 

Author Comment

by:attas052
ID: 22761088
SendMessage(PictureBoxHandle, WM_CAP_EDIT_COPY, 0, 0) returns 8.97592579397255E+18 whether the operation is a success or not
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 

Author Comment

by:attas052
ID: 22761260
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
0
 
LVL 18

Expert Comment

by:mdougan
ID: 22761700
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

0
 

Author Comment

by:attas052
ID: 22761912
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
0
 
LVL 18

Expert Comment

by:mdougan
ID: 22762049
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
0
 

Author Comment

by:attas052
ID: 22768974
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
0
 
LVL 18

Accepted Solution

by:
mdougan earned 300 total points
ID: 22769975
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
        Clipboard.Clear()
        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))
      myThread.Start()
   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
      myThreadClassObject.Run()
   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
      myMainForm.Invoke(myMainForm.myDelegate)
   End Sub  ' Run
End Class ' MyThreadClass

The sample above was hacked from this sample
http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
0
 

Author Comment

by:attas052
ID: 22770866
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
0
 

Author Closing Comment

by:attas052
ID: 31507873
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)
0
 
LVL 18

Expert Comment

by:mdougan
ID: 22771058
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!
0

Featured Post

What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

Join & Write a Comment

This article explains how to create and use a custom WaterMark textbox class.  The custom WaterMark textbox class allows you to set the WaterMark Background Color and WaterMark text at design time.   IMAGE OF WATERMARKS STEPS Create VB …
Well, all of us have seen the multiple EXCEL.EXE's in task manager that won't die even if you call the .close, .dispose methods. Try this method to kill any excels in memory. You can copy the kill function to create a check function and replace the …
In this seventh video of the Xpdf series, we discuss and demonstrate the PDFfonts utility, which lists all the fonts used in a PDF file. It does this via a command line interface, making it suitable for use in programs, scripts, batch files — any pl…
This video gives you a great overview about bandwidth monitoring with SNMP and WMI with our network monitoring solution PRTG Network Monitor (https://www.paessler.com/prtg). If you're looking for how to monitor bandwidth using netflow or packet s…

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

16 Experts available now in Live!

Get 1:1 Help Now