VB6 Types (Structs) with Fixed Length Strings

24. February 2013 12:06 by Mrojas in C#, COM Interop  //  Tags: , , , , , , , , ,   //   Comments (0)

 

In VB6 the communication with backend services usually involves the definition of Types (or Structs) using fixed length strings.

VB6 provided language support for defining these data types. 

For example:
Public Type HostData     
	UserName       As String * 8
	PassWord       As String * 8
	FullName       As String * 50
End Type
Figure 1 Example of VB6 Type with Fixed Length Strings
There are some ways to model this structures in .NET using the FixedLengthString defined in Microsoft.VisualBasic.Compatibity. Here I will present another approach. This approach uses character arrays (char[]) to model this structures. Ok. Let’s get down to business. To model a vb6 type like the one in Figure 1, we will use this approach:
struct HostData
    {
        [DebuggerDisplay("{s(UserName)}")]
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=8)]
        public char[] UserName;

        [DebuggerDisplay("{s(PassWord)}")]
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
        char[] PassWord;

        [DebuggerDisplay("{s(FullName)}")]
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 50)]
        public char[] FullName;
        /// This method is used to provide a better display of
	 /// character arrays as strings on the debugger.
        string s(char[] array) { return new string(array); }

        public byte[] toByteArray() {    
           return StructsHelper.StructToByteArray(this);
        }

 public static explicit operator HostData(byte[] array)
        {
            return (HostData)StructsHelper.ByteArrayToStructure(array,typeof(HostData));
        }

        /// <summary>
        /// Constructor to initialize the char arrays that are used as fixed length strings
        /// Struct constructors must have at least one parameter.
        /// </summary>
        /// <param name="initFixedLengthStrings">if true will automatically init all fixed length char arrays according to the SizeConst property of the MarshalAs attribute</param>
        public HostData (bool initFixedLengthStrings=false)
        {
            UserName = null; 
            PassWord = null;
            FullName = null;
            if (initFixedLengthStrings)
            {
                StructsHelper.InitFixedStrings(GetType(), __makeref(this));
            }
        }
    }
Figure 2: Code of Example 1 in C#
 
So several tricks are used here, I will describe them:
 

First

All fixed length strings are declared as char[]. A MarshalAs attribute is applied to each field. Like this:
[MarshalAs(UnmanagedType.ByValArray, SizeConst=n)]
Where n is the number of characters in the fixed length strings. Note that character arrays must be initialized. However structs do not allow field initializers. So they will need to be initialized on a constructor.
 

Second

A DebuggerDisplay attribute 
[DebuggerDisplay("{s(<AttributeName>)}")]
is added to each field, just to make the developer experience. That makes that instead of showing this field as a character array it will be shown as a string.
This attribute uses a small helper function used s that just converts the character array to string.
 

Third

A constructor is added. Structs do not accept parameter-less constructors.
This struct receives a Boolean indicating whether you want to initialize the character array fields.
As a requirement character arrays fields should at least be initialized to null. Character arrays could have been initialized here but I opted to create a helper function. Why? Well I think it is better if this arrays are initialized using the SizeConst attribute. So if I want to change their size I do not have to update both the SizeConst and the constructor.
public static void InitFixedStrings(Type type,TypedReference reference)  
        {
            if (type.IsValueType && !type.IsPrimitive && !type.Namespace.StartsWith("System") && !type.IsEnum)
            {//This should be an struct
                foreach (var field in
                    type.GetFields(System.Reflection.BindingFlags.Instance |
                    System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic))
                {
                    
                    if (field.FieldType.IsArray && field.FieldType == typeof(char[]))
                    {
                        var attr = field.GetCustomAttributes(typeof(MarshalAsAttribute),false);
                        if (attr != null && attr.Length > 0)
                        {
                            MarshalAsAttribute maa = (MarshalAsAttribute)attr[0];
                            var constSize = maa.SizeConst;
                            if (constSize != -1)
                            {
                                var newValue = new char[constSize];
                                field.SetValueDirect(reference, newValue);
                            }
                        }
                    }
                }
            }
        }
 

Forth>

In VB6 the common approach is to use the StrConv and CopyMemory functions to copy memory to and from structs and send them as strings or event to copy data between structs of different types. To solve that utility methods have been created:
/// <summary>
        /// Takes a bytearray and uses it to create a struct of the given type
        /// and populate it with the data of the byte array.
        /// NOTE: this method only works withs Structs which have a fixed size
        /// </summary>
        /// <param name="bytearray"> The data that will be used to initialize the struct</param>
        /// <param name="type">The type of the expected struct</param>
        /// <returns>A new struct instance with its fields initialized with the bytes from bytearray</returns>
        public static object ByteArrayToStructure(byte[] bytearray, Type type)
        {
            int len = Marshal.SizeOf(type);
            IntPtr i = Marshal.AllocHGlobal(len);
            Marshal.Copy(bytearray, 0, i, len);
            var obj = Marshal.PtrToStructure(i,type);
            Marshal.FreeHGlobal(i);
            return obj;
        }

/// <summary>
        /// Returns the contents of an struct as a byte array.
        /// It only works with fixed length structs.
        /// </summary>
        /// <param name="obj">the struct that holds the data that will be returned in the byte array</param>
        /// <returns>A byte array with the contents of the struct</returns>
        public static byte[] StructToByteArray(this object obj)
        {

            int len = Marshal.SizeOf(obj);
            byte[] arr = new byte[len];
            IntPtr ptr = Marshal.AllocHGlobal(len);
            Marshal.StructureToPtr(obj, ptr, true);
            Marshal.Copy(ptr, arr, 0, len);
            Marshal.FreeHGlobal(ptr);
            return arr;

        }
With these utility methods you can then use your structs like this:
var hostData = new HostData (true);
var byteArray = UnicodeEncoding.Unicode.GetBytes(new String(' ', Marshal.SizeOf(typeof(HostData))));
hostData = (HostData)byteArray;

var size = Marshal.SizeOf(HostData);
var test = "helloworld";
test = test.PadRight(size, '*');
            
byteArray = UnicodeEncoding.Unicode.GetBytes(test);
hostData = (HostData)byteArray;
 

Fifth

And finally how to you easily get/set data from these structs? Very easy. We will add an extension method:
const string IF_VALUE_NOT_PROVIDED_THEN_RETURN_VALUE = "\0\0internal";
        /// <summary>
        /// This method is used to get/set the values of a char array as an string.
        /// It has been implemented in a way similar to that used in the jquery .val function.
        /// If called without parameters it will return the character array value as an string.
        /// If called with parameters will use the given string to set the character array value.
        /// If the given string is bigger that the character string the value is truncated
        /// </summary>
        /// <param name="array"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string val(this char[] array, String value = IF_VALUE_NOT_PROVIDED_THEN_RETURN_VALUE)
        {
            if (value == IF_VALUE_NOT_PROVIDED_THEN_RETURN_VALUE)
                return new string(array);
            else
            {
                var source = value.ToCharArray();
                Array.Copy(source, array, Math.Min(source.Length, array.Length));
                return value;
            }
        }
With that if you want to set a field you will do something like:
hostData.UserName.val(“Mauricio”)
And if you want to get the contents of the field you will do something like:
String username = hostData.UserName.val();
Well that’s all. Hope this helps

StructsHelpers.cs (5.93 kb)

VB6 Interop of Function with Array of User Defined Type (UDT)

27. January 2013 03:33 by Mrojas in COM Interop  //  Tags: , , , , , , , , ,   //   Comments (0)

Well tonight while I was deleting some spam comments from my blog and watching Dr. Who with my wife, I found a rather interesting comment.

So the story was:

First there is a VB6 DLL that had a class called Class1 with code like the following:

public type emprecord 
name as string 
end type 

Public Sub Fn(T()as emprecord) 
 MsgBox "The silence is comming said Prisoner 0"
End Sub

When this little dll was called from a VB.NET big brother 

Dim test as new prj.class1   
Dim em(0) as prj.emprecord 'able to create it no problem 
em(0).name="hello" 
test.fn(em)  ' here gives error


An error ocurred... well this is not very document issue with the TLBIMP tool which creates the interop assemblies. See StackOverflow Answer. The workaround is to right click on your type library, select properties, and change Embed interop Types to false.

After that you will be able to call your function.




Problem using ActiveX controls with VS2010

11. May 2012 11:56 by Mrojas in ActiveX, COM Interop, VB6 Migration, Visual Studio, WinForms  //  Tags: , , , , , , , ,   //   Comments (0)

 

Sometimes during migrations from VB6 to VS2010 we have found issues when you tried to add an ActiveX control with the VS2010 winforms designer. The issue is only present in VS2010 not on previous versions.

You usually will see an error in the added Interop references, and messages like a missing VBA or StdLib library.

The error has been reported several times so please vote on Connect to make sure MS will consider fixing it.

 

https://connect.microsoft.com/VisualStudio/feedback/details/557722/errors-utilizing-activex-controls-in-vs-2010

http://connect.microsoft.com/VisualStudio/feedback/details/568769/aximp-error-with-vb6-activex-control

 

And possible workarounds are running the Aximp manually from the command line and the add the references. You will then need to add the control by hand in your forms. Do not use the designer to add the component, this will try to regenerate the references and reproduice the issue.

Sending Messages between EXE files

13. March 2012 10:37 by Mrojas in COM Interop, General, IPC, VB6 Migration  //  Tags: , , , ,   //   Comments (0)

Today someone asked what is a way to send messages between two .NET exes.

mmm Well. Interesting question. There are several approaches.

 

1. .NET Remoting

.NET remoting is not a new technology but is a core part of the

.NET framework and it's always available:

 

For an example see this code from: http://www.codeproject.com/Articles/62813/NET-Remoting-Events-Explained

 

2. Named Pipes

This is vb.net example: http://support.microsoft.com/kb/871044

 

3. WCF

WCF is a great option even if you have legacy VB6 code you can use the SOAP Client to communicate

with the service:

This link http://www.aspfree.com/c/a/VB.NET/Calling-a-Web-Service-using-VB6-with-SOAP-30/

shows an example calling a Coldfusion Service but use it as a base for calling 

a WCF service

You can also integrate WCF with COM+ http://msdn.microsoft.com/en-us/library/bb735856.aspx

 

4. Windows Messages

There is a nice project that wrap it all up for you so you can use this solution:

http://www.codeproject.com/Articles/17606/NET-Interprocess-Communication

 

 

STAThread and Memory Issues

15. February 2012 15:39 by Mrojas in COM Interop, Memory  //  Tags: , , , , , ,   //   Comments (0)


VB6 Application where STAThread. And that is the reason that Winforms applications are
by default STAThread. Using MTAThread causes problems with some ActiveX Controls.

However STAThread has a nasty implication
see: http://social.msdn.microsoft.com/Forums/en/clr/thread/835db88e-db51-4f83-bd4f-a10d126effa6
 
"inside a STA thread, the finalizer thread must reenter the STA thread in order to finalize the component.
If the STA is blocked and isn't pumping, the finalizer has to wait in line until it does"

This can then cause leaks of components affecting the memory use.


"To get around this issue, you have some options (from best to worst), e.g.:"

1) Create your components in an MTA. ... Unless you have an explicit reason to use an STA, you shouldn't. I realize that Visual Studio adds these to some entrypoints automatically for you.
For example, most GUI applications have to start life inside an STA, e.g. WinForms, but Console applications"  or services "certainly do not."

"2) Deterministically release your resources. If you are using components which implement IDisposable, wrap them in
a C# 'using' statement or call Dispose() on them explicitly when you're done.
RCW's done have Dispose on them. You can consider doing a Marshal.ReleaseComObject on them directly,
but realize that this can cause problems if you're not really done using the COM object."


"3) Use another form of blocking to prevent the primary thread from exiting."

"Chris Brumme writes about this" (COM Apartments)
" at http://blogs.msdn.com/cbrumme/archive/2004/02/02/66219.aspx; caution: that's a fairly lengthy post"

Add Return for COM Interop marshaling to Short in Vb.NET

3. February 2012 14:10 by Mrojas in COM Interop  //  Tags: , , , , , , , ,   //   Comments (0)

In VB.NET if you want to make your interfaces available thru COM and make sure that its parameters are of a certain type you have to use the MarshalAs attribute. For the return type it is a little tricky because it has to be added after the As Keyword.

 

<ComVisible(True)> _
<Guid("15D492C7-CD14-4239-B98D-689F329EEDA4")>
<InterfaceType(ComInterfaceType.InterfaceIsDual)> _
Public Interface MyCOMInterface
	Function FooReturningShort(ByVal data As Integer, <MarshalAs(UnmanagedType.U2)> ByVal shortData As short) As <MarshalAsAttribute(UnmanagedType.U2)> Short
End Interface

How to add helpstring in tlb generate from VB.NET or C# class

25. January 2012 11:34 by Mrojas in COM Interop  //  Tags: , , , , ,   //   Comments (0)

In IDL you can have a helpstring attribute that adds descriptions to your interfaces.
To be able to generated those helpstring tags in a VB.NET  class you should use the Description tag

For example see this example taken from:http://stackoverflow.com/questions/6668500/getting-the-helpstring-attribute-applied-to-c-sharp-properties-exposed-via-com-i

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;



namespace ClassLibrary1 {
    [ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsDual)]
    public interface IFoo {
        int property {
            [Description("prop")]
            get;
            [Description("prop")]
            set;
        }
    }
}

NOTE: remenber that in some cases you have to apply the description attribute to both get and set.

ADITIONAL NODE:

After some test, I found that there are some differences with the helpstring behaviour in VB.NET. For example the helpstring attribute is not generated for methods. More on C#/VB.NET Interop Differences http://mheironimus.blogspot.com/2006/10/vbnet-com-interface-issues.html

Quick replacement for the VB6 OLE Container Control in .NET

23. January 2012 13:09 by Mrojas in COM Interop  //  Tags: , , , , ,   //   Comments (0)

I have developed a very quick replacement for the OLE Container Control that you had in VB6.
I just did it in rush so it just supports basic properties as DisplayType Icon or Content
The content functionality is performed using the Vista feature for content preview. I would have tried using a 
WebBrowser control but in new versions of Office, the default is not showing the document on the Browser and
it might be difficult to change the registry in some vista or Win7 environments.

 The following picture show the OLEContainer inside a Windows Form.

This is the container with the Display set to content:

 

And the container with display set to icon:

 

You can call the CreateLink and you can also use the DoVerb Open.

I have attached the example source code in this post.

ReplaceOLEContainer.zip (100.69 kb)

UPDATE

 NOTE: I friend also sent me a link to this article in CodeProject which is very similar: http://www.codeproject.com/Tips/487566/OLE-container-surrogate-for-NET

 

NOTE: This solution only applies for read-only. If you wan to edit your files, then you need a real ActiveX container. MS used to have a sample OCX called DSOFramer that allows you to do that. Warning: this sample is no longer supported by MS becuase it said to have issues However I have used it in the past and it worked fine in some simple scenarios. I have added the control and its source to this post. There is a commercial product from Edraw http://www.edrawsoft.com/edword.php that is supported by them and has samples for C# and VB.NET

 

 NOTE: Another approach, embed the application in your windows form.

In general what you should do is use the SetParent, SetWindowLong and MoveWindow APIs to embed the application. Something like this:

var filename = openFileDialog1.FileName;
                var officeApplicationProgID = "Excel.Application";
                var officeApplicationType = Type.GetTypeFromProgID(officeApplicationProgID, false);
                dynamic officeApplication = Activator.CreateInstance(officeApplicationType);
                officeApplication.Workbooks.Open(filename);
                int pid = 0;
                GetWindowThreadProcessId(officeApplication.HWnd, out pid);
                officeApplication.Visible = true;
                //officeApplication.Visible = false;
                var process = Process.GetProcessById(pid);
                var panel = new Panel();
                panel.Location = new Point(0, 0);
                panel.Size = new Size(this.Size.Width, this.Size.Height);
                var processHandle = process.MainWindowHandle;
                SetParent(processHandle, panel.Handle);
                SetWindowLong(processHandle, GWL_STYLE, WS_VISIBLE + WS_MAXIMIZE + WS_CHILD);
                MoveWindow(processHandle, 0, 0, panel.Width, panel.Height, true);
                this.mainBody.Controls.Add(panel);
 
Figure: Example of technique of hosting Excel inside a Windows Form Application

I have attached a sample project. (Remember to free your resources, and close the excel App before closing your application, I skipped that from this sample)

DsoFramer.zip (463.42 kb)

ExampleOfEmbeddingExcelInWindowsForm.zip (53.76 kb)