locked
Marshalling System.Guid between VB6 and VB.NET

    Question

  • I have created a user control in VB.NET that uses the Interop Tool Kit.

    I have the following property on that User Control that I want to expose to VB6.

        Dim _AccountPassCode As System.Guid  
     
        Public Property AccountPassCode() As <MarshalAs(UnmanagedType.LPStruct)> System.Guid  
            Get  
                Return _AccountPasscode  
            End Get  
            Set(<MarshalAs(UnmanagedType.LPStruct)> ByVal value As System.Guid)  
                _AccountPasscode = value 
            End Set  
        End Property 


    However, I get the following error in VB6 when I try to read from the AccountPassCode property.
    "Variable uses an automation type not supported in visual basic"

    So the question is how to I marshal a Guid between VB6 and VB.NET?

    Thanks in advance!
    • Changed type Riquel_DongModerator Monday, June 30, 2008 2:16 AM don't follow up with the necessary information
    • Changed type Riquel_DongModerator Tuesday, October 21, 2008 5:31 AM one workaroun by Jason Poll
    Tuesday, June 24, 2008 10:49 PM

Answers

  • Well, I've come up with a work around for my situation.  Again, that situation includes getting a Guid from a .NET object, exposed to COM, and passing that Guid to a stored procedure.

    My solution was to return the Guid as a string.  So, if you take my original example and modify it to look like:

    [Guid("BD95337B-0395-4773-B2FC-2BC812388BAB")]  
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]  
    [ComVisible(true)]  
    public interface IGuidGetter {  
       string GetSessionID();  
    }  
      
    [Guid("6AAFF9A3-875D-4955-AE09-634A88D9EC56")]  
    [ClassInterface(ClassInterfaceType.None)]  
    [ComVisible(true)]  
    [ProgId("JTPApp.GuidGetter")]  
    public class GuidGetter : IGuidGetter {  
       public string GetSessionID() {  
          return Guid.NewGuid.ToString();  
       }  
    }  

    Then, in VB6, I'm able to get the Guid, and assign it to my stored procedure call, with a minor tweak:
    Dim sqlCommand As New ADODB.Command 
    With sqlCommand 
       .ActiveConnection = dbConn 
       .CommandText = "A_Stored_Procedure" 
       .CommandType = adCmdStoredProc 
       .Parameters.Refresh 
               
       Dim tmp As String 
       tmp = g_UserInfo.GetSessionID() 
       .Parameters("@sessionID").Value = "{" & tmp & "}" 
               
       .Execute , , adExecuteNoRecords 
    End With 
    Set sqlCommand = Nothing 

    The only tweak was, in order for SQL Server to be able to convert the string-Guid to a 'uniqueidentifier' Guid, the string needed to be encapsulated in curly-braces.

    I hope this helps someone...
    Thursday, October 16, 2008 3:12 PM

All replies

  • Hi BradD2,

    You don't use the MarshalAs attribute in this scenario. Just define the public property , then Interop Forms Toolkit will create the CCW to expose it for the COM client similar the following code snippet. Also read this article about how to marshal Guid data type.
    <ComClass(InteropUserControl.ClassId, InteropUserControl.InterfaceId, InteropUserControl.EventsId)> _ 
    Public Class InteropUserControl 
    #Region "VB6 Interop Code" 
    #If COM_INTEROP_ENABLED Then 
    #Region "COM Registration" 
     
        ' These  GUIDs provide the COM identity for this class  
        ' and its COM interfaces. If you change them, existing  
        ' clients will no longer be able to access the class. 
     
        Public Const ClassId As String = "f55c2978-948a-46a3-8c08-3fb26f5824e5" 
        Public Const InterfaceId As String = "a655899b-4c5d-4832-9d82-cfa302065a96" 
        Public Const EventsId As String = "d6ac2760-24c7-4d01-a793-6990ad5b5349" 
     
        'These routines perform the additional COM registration needed by ActiveX controls 
        <EditorBrowsable(EditorBrowsableState.Never)> _ 
        <ComRegisterFunction()> _ 
        Private Shared Sub Register(ByVal t As Type) 
            ComRegistration.RegisterControl(t) 
        End Sub 
     
        <EditorBrowsable(EditorBrowsableState.Never)> _ 
        <ComUnregisterFunction()> _ 
        Private Shared Sub Unregister(ByVal t As Type) 
            ComRegistration.UnregisterControl(t) 
        End Sub 
    #End Region 
    #Region "VB6 Events" 
     
        'This section shows some examples of exposing a UserControl's events to VB6.  Typically, you just 
        '1) Declare the event as you want it to be shown in VB6 
        '2) Raise the event in the appropriate UserControl event. 
     
        Public Shadows Event Click() 'Event must be marked as Shadows since .NET UserControls have the same name. 
        Public Event DblClick() 
     
        Private Sub InteropUserControl_Click(ByVal sender As ObjectByVal e As System.EventArgs) Handles MyBase.Click 
            RaiseEvent Click() 
        End Sub 
     
        Private Sub InteropUserControl_DoubleClick(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.DoubleClick 
            RaiseEvent DblClick() 
        End Sub 
    #End Region 
    #Region "VB6 Properties" 
     
        'The following are examples of how to expose typical form properties to VB6.   
        'You can also use these as examples on how to add additional properties. 
     
        'Must Shadow this property as it exists in Windows.Forms and is not overridable 
        Public Shadows Property Visible() As Boolean 
            Get 
                Return MyBase.Visible 
            End Get 
            Set(ByVal value As Boolean
                MyBase.Visible = value 
            End Set 
        End Property 
     
        Public Shadows Property Enabled() As Boolean 
            Get 
                Return MyBase.Enabled 
            End Get 
            Set(ByVal value As Boolean
                MyBase.Enabled = value 
            End Set 
        End Property 
     
        Public Shadows Property ForegroundColor() As Integer 
            Get 
                Return ActiveXControlHelpers.GetOleColorFromColor(MyBase.ForeColor) 
            End Get 
            Set(ByVal value As Integer
                MyBase.ForeColor = ActiveXControlHelpers.GetColorFromOleColor(value) 
            End Set 
        End Property 
     
        Public Shadows Property BackgroundColor() As Integer 
            Get 
                Return ActiveXControlHelpers.GetOleColorFromColor(MyBase.BackColor) 
            End Get 
            Set(ByVal value As Integer
                MyBase.BackColor = ActiveXControlHelpers.GetColorFromOleColor(value) 
            End Set 
        End Property 
     
        Public Overrides Property BackgroundImage() As System.Drawing.Image 
            Get 
                Return Nothing 
            End Get 
            Set(ByVal value As System.Drawing.Image) 
                If value IsNot Nothing Then 
                    MsgBox("Setting the background image of an Interop UserControl is not supported, please use a PictureBox instead.", MsgBoxStyle.Information) 
                End If 
                MyBase.BackgroundImage = Nothing 
            End Set 
        End Property 
    #End Region 
    #Region "VB6 Methods" 
     
        Public Overrides Sub Refresh() 
            MyBase.Refresh() 
        End Sub 
     
        'Ensures that tabbing across VB6 and .NET controls works as expected 
        Private Sub UserControl_LostFocus(ByVal sender As ObjectByVal e As System.EventArgs) Handles Me.LostFocus 
            ActiveXControlHelpers.HandleFocus(Me
        End Sub 
     
        Public Sub New() 
     
            ' This call is required by the Windows Form Designer. 
            InitializeComponent() 
     
            ' Add any initialization after the InitializeComponent() call. 
     
            'Raise Load event 
            Me.OnCreateControl() 
        End Sub 
     
        <SecurityPermission(SecurityAction.LinkDemand, Flags:=SecurityPermissionFlag.UnmanagedCode)> _ 
        Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message) 
     
            Const WM_SETFOCUS As Integer = &H7 
            Const WM_PARENTNOTIFY As Integer = &H210 
            Const WM_DESTROY As Integer = &H2 
            Const WM_LBUTTONDOWN As Integer = &H201 
            Const WM_RBUTTONDOWN As Integer = &H204 
     
            If m.Msg = WM_SETFOCUS Then 
                'Raise Enter event 
                Me.OnEnter(New System.EventArgs)  
     
            ElseIf m.Msg = WM_PARENTNOTIFY AndAlso _ 
                (m.WParam.ToInt32 = WM_LBUTTONDOWN OrElse _ 
                 m.WParam.ToInt32 = WM_RBUTTONDOWN) Then 
     
                If Not Me.ContainsFocus Then 
                    'Raise Enter event 
                    Me.OnEnter(New System.EventArgs)  
                End If 
     
            ElseIf m.Msg = WM_DESTROY AndAlso Not Me.IsDisposed AndAlso Not Me.Disposing Then 
                'Used to ensure that VB6 will cleanup control properly 
                Me.Dispose() 
            End If 
     
            MyBase.WndProc(m) 
        End Sub 
     
        'This event will hook up the necessary handlers 
        Private Sub InteropUserControl_ControlAdded(ByVal sender As ObjectByVal e As ControlEventArgs) Handles Me.ControlAdded 
            ActiveXControlHelpers.WireUpHandlers(e.Control, AddressOf ValidationHandler) 
        End Sub 
     
        'Ensures that the Validating and Validated events fire appropriately 
        Friend Sub ValidationHandler(ByVal sender As ObjectByVal e As EventArgs) 
     
            If Me.ContainsFocus Then Return 
     
            'Raise Leave event 
            Me.OnLeave(e)  
     
            If Me.CausesValidation Then 
                Dim validationArgs As New CancelEventArgs 
                Me.OnValidating(validationArgs) 
     
                If validationArgs.Cancel AndAlso Me.ActiveControl IsNot Nothing Then 
                    Me.ActiveControl.Focus() 
                Else 
                    'Raise Validated event 
                    Me.OnValidated(e)  
                End If 
            End If 
     
        End Sub 
    #End Region 
    #End If 
    #End Region 
     
        'Please enter any new code here, below the Interop code 
        Public str1 As String 
        Public Property str() As String 
            Get 
                Return str1 
            End Get 
            Set(ByVal value As String
                str1 = value 
                Label1.Text = str1 
            End Set 
        End Property 
        Public gid1 As System.Guid = System.Guid.NewGuid 
        Public Property GID() As System.Guid 
            Get 
                Return gid1 
                Label1.Text = gid1.ToString 
            End Get 
            Set(ByVal value As System.Guid) 
                gid1 = value 
                Label1.Text = gid1.ToString 
            End Set 
        End Property 
     
    End Class 
     



    Best regards,
    Riquel

    Please remember to mark the replies as answers if they help and unmark them if they provide no help.
    Friday, June 27, 2008 9:49 AM
    Moderator
  • Is it just me, or does Riquel's answer not help the situation?  The link to Adam Nathan's article was informative, but non-helpful.

    System.Guid is marked as being COM-Visible, but it is not usable in VB6

    After adding a reference to mscorlib, VB6 will allow you to create a variable of type System.Guid:

    Dim myGuid as mscorlib.Guid  

    But you are unable to actually make use of 'myGuid' variable.  There is no intellisense pop-up when you type 'myGuid.'

    This problem has nothing to do with the Interop Forms Toolkit.  It has to do with System.Guid marshalling.

    Let's look at a simple example illustrating the problem.  Here's an interface and a class that implements the interface, all exposed to COM. (This is in C#, but can just as easily be written in VB.NET.)

    [Guid("BD95337B-0395-4773-B2FC-2BC812388BAB")] 
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)] 
    [ComVisible(true)] 
    public interface IGuidGetter { 
       Guid GetSessionID(); 
     
    [Guid("6AAFF9A3-875D-4955-AE09-634A88D9EC56")] 
    [ClassInterface(ClassInterfaceType.None)] 
    [ComVisible(true)] 
    [ProgId("JTPApp.GuidGetter")] 
    public class GuidGetter : IGuidGetter { 
       public Guid GetSessionID() { 
          return Guid.NewGuid(); 
       } 

    To use that class in VB6, after compiling, regasm-ing, and adding the registered type lib as a reference to my VB6 project, we can do something like:

    1Dim myGuidGetter As New GuidGetter 
    2Dim myGuid As mscorlib.Guid 
    3     
    4myGuid = guidGetter.GetSessionID() 

    Line 4 will fail to compile, giving the originally posted "Variable uses an automation type not supported in Visual Basic" error message.

    Decorating the .NET interface or implementation with [MarshalAs(UnmanagedType.LPStruct)], while it does change the COM IDL (as noted and demonstrated in Adam Nathan's article,) doesn't make any difference from the VB6 standpoint. You always get the "Variable uses an automation type not supported in Visual Basic" error.

    The really aggrivating part for me is that I don't need to really use the Guid in VB6.  I simply need VB6 to retrieve it, and then stuff it into a stored procedure call.  I can't even get it to compile!

    I have been able to make it compile though!  If I change the interface so that it's returning an object, which then gets marshalled as a COM VARIANT, the code will compile, and I am able to get the Guid into a Variant.  But I can't do anything with it. If I try to assign it as a parameter to my stored procedure, I get an AccessViolationException in my .NET code. I'm wondering if it has anything to do with the boxing I'm sure it happening when returning a value-type as a reference type?  I dunno.  It just doesn't work. I'm still trying to come up with some sort of work-around.
     


    Thursday, October 16, 2008 2:15 PM
  • Well, I've come up with a work around for my situation.  Again, that situation includes getting a Guid from a .NET object, exposed to COM, and passing that Guid to a stored procedure.

    My solution was to return the Guid as a string.  So, if you take my original example and modify it to look like:

    [Guid("BD95337B-0395-4773-B2FC-2BC812388BAB")]  
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]  
    [ComVisible(true)]  
    public interface IGuidGetter {  
       string GetSessionID();  
    }  
      
    [Guid("6AAFF9A3-875D-4955-AE09-634A88D9EC56")]  
    [ClassInterface(ClassInterfaceType.None)]  
    [ComVisible(true)]  
    [ProgId("JTPApp.GuidGetter")]  
    public class GuidGetter : IGuidGetter {  
       public string GetSessionID() {  
          return Guid.NewGuid.ToString();  
       }  
    }  

    Then, in VB6, I'm able to get the Guid, and assign it to my stored procedure call, with a minor tweak:
    Dim sqlCommand As New ADODB.Command 
    With sqlCommand 
       .ActiveConnection = dbConn 
       .CommandText = "A_Stored_Procedure" 
       .CommandType = adCmdStoredProc 
       .Parameters.Refresh 
               
       Dim tmp As String 
       tmp = g_UserInfo.GetSessionID() 
       .Parameters("@sessionID").Value = "{" & tmp & "}" 
               
       .Execute , , adExecuteNoRecords 
    End With 
    Set sqlCommand = Nothing 

    The only tweak was, in order for SQL Server to be able to convert the string-Guid to a 'uniqueidentifier' Guid, the string needed to be encapsulated in curly-braces.

    I hope this helps someone...
    Thursday, October 16, 2008 3:12 PM