none
Creating an Forms Control but getting error "ActiveX control <GUID> cannot be instantiated because the current thread is not in a single-threaded apartment."

    Question

  • Ok, I've read some interesting info on this problem, but no solution.

    I am creating a Form Control in C#/.NET.  Has a COM control embedded in it.  Getting the error I specified in title.

    HOW do I get it so that I can embed this COM control into my form control?  I don't see where I can set the Threading Model as this is a control.  Can't seem to spawn a thread and embed it either?

    Any suggestions?

    Thanks,


    Adrian


    ADDITIONAL INFO: Reason for me trying to do this is that I have an application written in a language called LabVIEW v7.0.  I'd like to write this assembly component in C# as I like the fact I can generate objects that I can pass back to LabVIEW which it can manipulate.  I have a ActiveX component that I would like to use in this assembly as it would make my life easier, well appart from this not working right at the moment :).


    adrian
    Friday, April 24, 2009 1:37 PM

Answers

  • Just for all of you who are interested in how I did it, here is the answer:
    1. Create a Form (not a control)
    2. Create a thread for that Form, ensuring that the apartment state is set to STA using SetApartmentState().  In that thread, create the Form and run the message loop for it using Application.Run().
    3. In the constructor of the form after the InitializeComponent(), set it's parent to the window you are trying to embed it to.

      If you don't have a .NET control but a HWND, use the Win32 API SetParent() to do this.

              [DllImport("user32.dll", SetLastError = true)]
              internal static extern int SetParent(IntPtr child, IntPtr newParent);
      
    4. Also position the Form to (0, 0) (or wherever you want your control to show up) using the Form's Location property.  Do not Top and Left properties.  They don't work as expected (not sure how they work).
    5. To get the Form to conform to the size of the new parent window, set it's Size property.  (Might be able to use Width and Height properties, didn't seem to make a difference).

      If you don't have a .NET control but a HWND, use Win32 API call GetWindowRect()

              [StructLayout(LayoutKind.Sequential)]
              public struct RECT
              {
                  public int left;
                  public int top;
                  public int right;
                  public int bottom;
              }
              [DllImport("user32.dll")]
              public static extern int GetWindowRect(IntPtr hwnd, ref RECT rc);
      
    6. You will need to marshal across the thread boundary when talking to this Form from original application.  Use the Invoke() member functions of the Form to do this, or a cross threading exception will occur.

    adrian
    • Marked as answer by A D R I A N Monday, April 27, 2009 4:13 PM
    • Edited by A D R I A N Monday, April 27, 2009 4:20 PM
    Monday, April 27, 2009 4:06 PM

All replies

  • Yes, apartment threading is a hard requirement for almost any ActiveX control.  It is normally set by the [STAThread] attribute on the Main() method of a .NET application.  Native apps, like LabView, set it by calling CoInitializeEx().  It strikes me as very strange that LabView starts its UI thread in anything other than STA.  Very basic stuff like the clipboard and shell dialogs won't operate correctly. 

    If you are starting your own thread, you'll have to follow STA threading rules.  You must select STA with Thread.SetApartmentState() and you must pump a message loop with either Application.Run() or Form.ShowDialog().

    A call for support to the LabView vendor is probably your best bet to resolve this issue.

    Hans Passant.
    Friday, April 24, 2009 1:57 PM
  • Ah, but this a early version of LabVIEW, which didn't really support an embedded .NET control (later one does, but that requires a migration path that would be too long at this time).  It only supports embedded ActiveX controls. What I am trying to do a work around to get a .NET control to show up in a ActiveX container by writing a ActiveX control in VC++6.0 that returns a HWND so that I can then use the HWND to transfer my control's parent to the ActiveX.

    This seemed to work when I used a Form Control (sans ActiveX embedding).  However, when I then try to create a Form on a STA Thread, made the Form a child of the ActiveX container, and marsheled the communication across the thread boundary using Invoke(), it appears to somewhat work.  The problem I am having now is that it appears in the wrong spot in the ActiveX container, but only vertically.  Depending on the vertical scroll bar position or the vertical position of the window containing the ActiveX control, the position of the Form is offset.  The closer to the top of the desktop the window is, the closer to the top of the Form is to the top of the ActiveX container.  This is similar with the scroll bar.  There is no problem that I can see with its horizontal position.

    As an asside, when I use the Win32 GetWindowRect on the ActiveX HWND, the dimentions are wrong (not the same dimentions as the ActiveX control).

    *sigh*

    adrian
    Friday, April 24, 2009 4:02 PM
  • I'm guessing you are confusing the stuffing out of the Form class by turning it into a child control.  It wasn't designed to be a child control with its TopLevel property set to True.  Use a UserControl instead.
    Hans Passant.
    Friday, April 24, 2009 4:24 PM
  • Can't do that since I cannot put an ActiveX control in an UserControl without it being in a STA Thread.

    Is it possible to have a UserControl within its own thread?  I tried and failed.  If so, can you explain it to me as that would be awesome! :)
    Edit: I mean making a STA Thread and attaching that control to it and have messages still passed around.

    Thanks

    (Mmmmmm, stuffing... )

    adrian
    Friday, April 24, 2009 4:29 PM
  • The same apartment rule applies when you put an ActiveX control in a Form.  A UserControl requires a container so it can be a child control.  That container should be your LabView window.  Of course, I have no idea how that's done.
    Hans Passant.
    Friday, April 24, 2009 4:58 PM
  • There seems to be some misunderstanding.  Let me attempt to clarify.

    1. LabVIEW 7.0 can take .NET objects, not controls.
    2. LabVIEW 7.0 can take ActiveX controls.
    3. When using a .NET object in LabVIEW it executes these objects under MTA Threading model.
    4. I want to insert a .NET "visual object" inside my LabVIEW window.  (Done by creating a ActiveX control and giving out its HWND, allowing it to become a parent of the .NET "visual object").
    5. Without saying "No, it can't be done ", how would I acheive this or something comperable?
    Maybe it is possible to attach the .NET UserControl to the message pump of the ActiveX control?  But then, how would this be acheived and still explicitly specify the UserControl on its own STA Thread?

    Thanks

    adrian
    Friday, April 24, 2009 5:16 PM
  • AWESOME!  Got the Form to position correctly!!! Turns out that the properties Left and Top are bogus.  They do really weird things.  The Location property correctly positions the Form!

    Now why is it that when I use the GetWindowRect Win32 API call does it give me some crazy numbers?

    Thanks

    adrian
    Friday, April 24, 2009 8:17 PM
  • Turns out the GetWindowRect failed due to a mistake on a website stating that a RECT will be properly marshaled to a Rectangle like this:

            [DllImport("user32.dll")]
            public static extern int GetWindowRect(IntPtr hwnd, ref Rectangle rc);

    Didn't work for me, had to do this:

            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int left;
                public int top;
                public int right;
                public int bottom;
            }
            [DllImport("user32.dll")]
            public static extern int GetWindowRect(IntPtr hwnd, ref RECT rc);

    Thanks all!
    adrian
    Friday, April 24, 2009 9:00 PM
  • I'm guessing you are confusing the stuffing out of the Form class by turning it into a child control.  It wasn't designed to be a child control with its TopLevel property set to True.  Use a UserControl instead.
    Hans Passant.
    I was thinking about this, and I have come to the conclusion that I disagree.  The Form should not really care what window it is a child of (it is after all by default the implicit child of the desktop).

    A window is a window is a window.  Being a child (in this context) is used for clipping purposes and not much else.

    adrian
    Monday, April 27, 2009 3:42 PM
  • Yes, apartment threading is a hard requirement for almost any ActiveX control.  It is normally set by the [STAThread] attribute on the Main() method of a .NET application.  Native apps, like LabView, set it by calling CoInitializeEx().  It strikes me as very strange that LabView starts its UI thread in anything other than STA.  Very basic stuff like the clipboard and shell dialogs won't operate correctly. 

    If you are starting your own thread, you'll have to follow STA threading rules.  You must select STA with Thread.SetApartmentState() and you must pump a message loop with either Application.Run() or Form.ShowDialog().

    A call for support to the LabView vendor is probably your best bet to resolve this issue.

    Hans Passant.

    I unmarked this as an answer as it didn't contain the information I was looking for.

    Thanks

    adrian
    Monday, April 27, 2009 3:46 PM
  • Just for all of you who are interested in how I did it, here is the answer:
    1. Create a Form (not a control)
    2. Create a thread for that Form, ensuring that the apartment state is set to STA using SetApartmentState().  In that thread, create the Form and run the message loop for it using Application.Run().
    3. In the constructor of the form after the InitializeComponent(), set it's parent to the window you are trying to embed it to.

      If you don't have a .NET control but a HWND, use the Win32 API SetParent() to do this.

              [DllImport("user32.dll", SetLastError = true)]
              internal static extern int SetParent(IntPtr child, IntPtr newParent);
      
    4. Also position the Form to (0, 0) (or wherever you want your control to show up) using the Form's Location property.  Do not Top and Left properties.  They don't work as expected (not sure how they work).
    5. To get the Form to conform to the size of the new parent window, set it's Size property.  (Might be able to use Width and Height properties, didn't seem to make a difference).

      If you don't have a .NET control but a HWND, use Win32 API call GetWindowRect()

              [StructLayout(LayoutKind.Sequential)]
              public struct RECT
              {
                  public int left;
                  public int top;
                  public int right;
                  public int bottom;
              }
              [DllImport("user32.dll")]
              public static extern int GetWindowRect(IntPtr hwnd, ref RECT rc);
      
    6. You will need to marshal across the thread boundary when talking to this Form from original application.  Use the Invoke() member functions of the Form to do this, or a cross threading exception will occur.

    adrian
    • Marked as answer by A D R I A N Monday, April 27, 2009 4:13 PM
    • Edited by A D R I A N Monday, April 27, 2009 4:20 PM
    Monday, April 27, 2009 4:06 PM