I find myself in the <finger-quote>interesting</finger-quote> situation of having to interop a large portion of .NET code back into an older VB6 code base. As it turns out, I need to pass a System.Guid to VB6. Before exposing a property that returned a System.Guid I made sure that System.Guid was COM-Visible - it is:
[ComVisible(true)]
public struct Guid : IFormattable, IComparable, IComparable<Guid>, IEquatable<Guid> {
// remainder of metadata snipped for brevity
}
But let's get this point across right from the beginning: 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:
But you are unable to actually make use of 'myGuid' variable. There is no intellisense pop-up when you type 'myGuid.', there's no way to create one or populate a declared mscorlib.Guid
I'm not entirely sure what the problem is, but I have to assume it has to do with Guid being a struct. Regardless of the reason for the problem, I still needed access to a System.Guid in VB6. Thankfully, all my problem requires is to fetch the Guid from a .NET object, and then pass that Guid to a stored procedure that's expecting a SQL Server "uniqueidentifier" data type.
I started searching. Talk about a tough problem to google for! There's a lot of really good hits regarding Guid's an creating COM-visible classes/interfaces, but I found very little info on actually marshalling a System.Guid back-n-forth to/from .NET. Thankfully, I was able to find this post where someone is experiencing the exact same problem. Unfortunately, there was no good answer. Someone did post a reply which didn't answer the original poster's question, but does include a link to an Adam Nathan blog post discussing customizing marshalling, which uses a System.Guid as an example. It's informative, but it doesn't offer any assistance with the problem.
For verbosity (and the morbidly curious,) let's look at a simple example illustrating the problem. Here's an interface and a class that implements that interface, all exposed to COM. (This is in C#, but can just as easily be written in VB.NET.)
[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, we compile it, regasm it, and add the registered type lib as a reference to the VB6 project. We can now do something like:
Dim myGuid As mscorlib.Guid
myGuid = guidGetter.GetSessionID()
Unfortunately, Line 4 will fail to compile, giving you a "Variable uses an automation type not supported in Visual Basic" error message.
Decorating the .NET interface and/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 a VB6 standpoint. You always get the "Variable uses an automation type not supported in Visual Basic" error.
The really aggravating 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!
With this in mind, I tried returning the Guid as an object, but figured it would have problems because I would be boxing a value type in a reference type and then expecting VB6 to know how to properly unbox it. VB6 was able to retrieve the Guid into a Variant, but upon trying to set it as a parameter to the stored procedure, I received an AccessViolationException in my .NET code. Not pretty.
Thankfully, my work-around to the problem turns out to be pretty simple: Return the Guid as a string. So, to follow through with the example code I started above, if you change it to look like:
[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();
}
}
It will work. You get the Guid, as a string, in VB6. There's only one minor catch to getting SQL Server to be able to convert that string into a unique identifier, it must be enclosed in curly braces, like so:
- 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
On line 10, you can see that I wrapped it in curly-braces. So while this works for my scenario, there's no way, that I've figured out, to easily interop a System.Guid value back and forth between .NET and VB6.
Anybody have useful insight in how to get the back-n-forth marshalling to work? I can't imagine I'm the only one who's ever needed to do this.
No comments:
Post a Comment