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

 

 

Migrating the CommonDialog print dialog

2. March 2012 15:06 by Mrojas in Printing, VB6 Migration  //  Tags: , , , ,   //   Comments (0)

In VB6 you could use the commondialog to show several of the 
standard operating system dialogs. For example show the a 
the print setup dialog box or the print dialog box.

VB6 Example showing the Print Setup Dialog Box

cdlgprint.Flags = cdlPDPrintSetup

    cdlgprint.ShowPrinter

In .NET you have 4 kinds of Priting Dialogs:

PageSetupDialog

PrintDialog

PrintDocument

PrintPreviewDialog

 

So during a migration remember that if your were using the the CommonDialog.Flags 

property to show the PrintSetupDialog you will have to change that for something like

 

var cdlgprint_ForPrintSetup  = new PrintSetupDialog();

 cdlgprint_ForPrintSetup.Print()

BEWARE of FileSystem.FilePutObject

29. February 2012 15:45 by Mrojas in File, VB6 Migration  //  Tags: , , , , , , ,   //   Comments (0)

When you are migrating from VB6 to VB.NET most of the Put statements will be migrated to 

FileSystem.FilePutObject http://msdn.microsoft.com/en-us/library/z07he9as.aspx

The problem with this is that if you need that the files generated by your application to be compatible byte per byte
with your VB6 generated files you must put special attention.

Take a look at this example: 

Module Module1

    Sub Main()
              Dim str = "TEXTDATA"
              FileSystem.FileOpen(1, "c:\temp\file1.dat", OpenMode.Random, OpenAccess.Write, , 30)
              FileSystem.FilePutObject(1, str, 2)
              FileSystem.FileClose(1)
    End Sub

End Module

 

Module Module1

    Sub Main()
              Dim str = "TEXTDATA TEXTDATA TEXTDATA"
              FileSystem.FileOpen(1, "c:\temp\file1.dat", OpenMode.Random, OpenAccess.Write, , 30)
              FileSystem.FilePut(1, str, 2)
              FileSystem.FileClose(1)
    End Sub

End Module

 

The difference might seem minimal but it can be hard to detect.

The different byte is caused by using the FilePutObject instead of FilePut method


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)

Running Object Table and .NET

30. September 2011 10:27 by Mrojas in General  //  Tags: , , , , , , , , ,   //   Comments (0)

What is the ROT?

“Using ROT (Running Object Table) is a great way to establish interprocess communication between two windows applications. From a purely logical aspect, one application registers a pointer to an instance of a class in the ROT, the other one gets a pointer pointing to the same instance of the registered class and therefore can use the same instance of the class via this pointer. The class that is registered has to be a COM class, otherwise it can be written in any language. The application that will retrieve the pointer from the ROT can be written in any language that can use COM, as ROT gives a pointer to a COM interface.”

Can it be implemented in .NET?

Sure a .NET application can be exposed thru COM and then its pointer can be gotten and consumed by other applications querying the ROT.

And excelent example can be found here: http://www.codeproject.com/KB/COM/ROTStuff.aspx

As always it has its caveats. Be careful.

Obvious replacement?

Well if what you want is (Interprocess Communication) IPC,there are several options in .NET :

* Classical .NET remoting which is very simple and stable to

* Named Pipes see an example here http://bartdesmet.net/blogs/bart/archive/2007/04/12/getting-started-with-named-pipes.aspx

* or WCF with Named Pipes, an example here http://www.codeproject.com/KB/WCF/WCF_CommOptions_part1.aspx

WCF can be an interesting option specially if we were doing things like DCOM and Remote monikers.

VB6 Windows Service

2. June 2011 04:30 by Mrojas in General  //  Tags: , , , , , , , ,   //   Comments (0)

How do you write a Windows Service in VB6?

Althought this is not a recommend practice due to the stability issues and VB6 support, if for
any reason you need to do this I provide a guide of how to do that in VB6 and how do the same thing
in VB.NET and C#.

Using the NTSVC.ocx

This is an OCX implement by Mauricio Ordonez some time ago.
It is very simple to use. You just drop it on a form and add some code to your VB6.

Example: VB6 Form with the NTSVC.ocx control

Private Sub Form_Load()
Me.Visible = False
    Dim strDisplayName As String

On Error GoTo Err_Load

    strDisplayName = NTService1.DisplayName
   
    If Command = "-install" Then
        ' Enable interaction with desktop.
        NTService1.Interactive = True
       
        If NTService1.Install Then
MsgBox strDisplayName & " installed successfully"
        Else
            MsgBox strDisplayName & " failed to install"
        End If
        End
   
    ElseIf Command = "-uninstall" Then
        If NTService1.Uninstall Then
            MsgBox strDisplayName & " uninstalled successfully"
        Else
            MsgBox strDisplayName & " failed to uninstall"
        End If
        End
   
    ElseIf Command = "-debug" Then
        NTService1.Debug = True
   
    ElseIf Command <> "" Then
        MsgBox "Invalid command option"
        End
   
    End If
   
    ' Connect service to Win32 services controller.
    NTService1.StartService
   
Err_Load:
    ' Error starting service
   
End Sub


Private Sub Timer1_Timer()
MsgBox "hola"
End Sub

 

NOTE: Remember that VB6 is not a supported platform and that even if it is true
that you can still run VB6 code in Windows Vista and Windows 7 MS does not support this
platform anymore.

How can I convert my VB6 service to .NET?

To create a Windows Service in VB.NET follow this steps.

1. First you need to create a Windows Service Project:

a. Open Visual Studio 2010

b. Go to the File\New\Project….

c. Select the Windows Service Template

d. And you just put your code in the OnStart method:

Public Class Service1

    Protected Overrides Sub OnStart(ByVal args() As String)
        ' Add code here to start your service. This method should set things
        ' in motion so your service can do its work.
        ExecuteWindowsServiceCode()
    End Sub

    Protected Overrides Sub OnStop()
        ' Add code here to perform any tear-down necessary to stop your service.
    End Sub

    Private Sub ExecuteWindowsServiceCode()
        'TODO Add Some Code
    End Sub

End Class
 

e. Another typical thing to do in Windows Service is to add a Timer Control, you you can have
your windows service performs some actions every number of seconds. To do that, drag a
Timer Control on your Service component, execute the Start method of the timer control on the OnStart method and
handle the Tick event:

 

 

 

Public Class Service1

    Protected Overrides Sub OnStart(ByVal args() As String)
        ' Add code here to start your service. This method should set things
        ' in motion so your service can do its work.
        Timer1.Start()
        ExecuteWindowsServiceCode()
    End Sub

    Protected Overrides Sub OnStop()
        ' Add code here to perform any tear-down necessary to stop your service.
        Timer1.Stop()
    End Sub

    Private Sub ExecuteWindowsServiceCode()
        'TODO Add Some Code
    End Sub

    Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
        MsgBox("Viva la vida Loca!")
    End Sub
End Class

If you need to recover some of your code from your VB6 project, download our Visual Basic Conversion Tool VBUC tool.

 

How do I Install my Windows Service?

In general you just need to use the command line utility installutil.exe for more details see this other post.

Interop Structures to UnManaged Dlls

For VB6 applications it is common to rely on OS or Kernel API Calls. Some of those APIs might
need you to send data back and for the native API.

Marshalling in .NET can be complicated and bothersome. I have published several posts about
interop. But it usually depends on adding several marshalling attributes and even tricks specially for
fixed strings.

So I decided to provide a more a simpler approach for conversion. In this approach you just need to things:

1. Your VB6 types or structs will be mapped to .NET classes
2. All VB6 type or struct fields will be mapped to public fields
3. An attribute must be used on those fields to indicate the field length, for arrays or strings.
4. Extension methods .AsString() .SetFromString and .GetClassLength will handle all the complexities of setting the struct fields.

Let’s see an example:

Type EmployeeRecord
    FirstName As String * 5
    LastName As String * 5
End Type

That vb6 type will be mapped in .NET following this approach to:

    public class EmployeeRecord 
    {
        [FixedLength(5)]
        public string FirstName = "Mau";
        [FixedLength(5)]
        public string LastName = "Rojas";

    }

You can then simple use that class in .NET

var emp = new EmployeeRecord {FirstName="Mauricio",LastName="Rojas"} ;
var str = emp.AsString();
//This str value will be "MauriRojas" the helper extension methods
// .AsString and .SetFromString will handle setting the internal class fields

All that is very good but how is this used in Marshalling?? Well very simple. Let’s say you have a Dll called foo.dll
with a function foo that receives an EmployeeRecord:

        [DllImport("foo.dll")]
        public static extern int foo(IntPtr Struct);

Then if you want to call that function you will do something like:

            var emp = new EmployeeRecord { FirstName="Ann",LastName="Smith"};
            string str = emp.AsString();
            var ptr = IntPtr.Zero;
            ptr = Marshal.StringToBSTR(str);
            //or 
            ptr = Marshal.StringToHGlobalAnsi(str);
            //or
            ptr = Marshal.StringToHGlobalAuto(str);
            //or
            ptr = Marshal.StringToHGlobalUni(str);

            //And call the native function
            foo(ptr);

If the function modifies the structure and you want to reflect those changes then you will do something like:

str = Marshal.PtrToStringAnsi(ptr,typeof(EmployeeRecord).GetClassLength())
emp.SetFromString(str);

This solution can also be applied for more complex structures. For example:

    public class EmployeeRecord 
    {
        [FixedLength(5)]
        public string FirstName = "Mau";
        [FixedLength(5)]
        public string LastName = "Rojas";

    }

    public class Record1
    {
        public int field1;
        [FixedLength(10)]
        public string field2 = "";
        public EmployeeRecord rec = new EmployeeRecord();
    }

    public class GeneralInfo
    {
        public int field1;
        [ArrayLength(5)]
        [FixedLength(2)]
        public String[] countrycodes = { "cr","es","mx","pa","ni"};
        [FixedLength(2)]
        public EmployeeRecord[] employees;
    }

If you want to try it out this is the link to the CODE