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)

HTML5 lets you deploy your applications everywhere

Some VB6 and Windows Forms applications are still great but probably you need them to reach a bigger audience. 

There are now amazing devices with internet access, but all of then different in hardware and software combinations. 

HTML and in particular HTML5 five technologies provide an unifying set of technologies that are supported by the main industry vendors. HTML5 browsers are available on tables, cell phones, linux/mac/windows PCs. 

Mobilize.NET tools automate this complicated process. See http://mobilize.net/html5/

Embedding Windows Forms Applications in a WebBrowser using XBAP

In this post I will add references to other pages which do a great work on explaining how to use this approach to host a Windows Forms App inside a WebBrowser. 

The technique described here is a deployment solution to ease distribution of applications modernized from legacy technologies to Windows Forms, maybe using the Artinsoft\Mobilize.NET tools ;) 

NOTE: "This workaround of .NET Winforms to WPF and then hosting it in a browser isn't truly moving a Windows based application to a web based application. e.g. database calls will be made from inside the browser on the users PC to the database, not via the IIS server. Therefore this functionality is most useful if you have connected your Winforms UI to you back-end code using web services, then you can have a somewhat web based application."  

Adam Berent gives step by step instrutions:

 

 

 

 

 

 

Figure 1: Example of Windows Forms applications running inside FireFox browser using an XBAP wrapper.

 

Some details that are missing in this article are related to how to publish the application on IIS, and these can described as: 

1. Create a test Certificate within Visual Studio and sign it against the project.  

The following links provides information on how to sing the xbap with your own certificate. 

2. Within VS publish the application. 

3. Setup the MIME encoding on the IIS server. See http://msdn.microsoft.com/en-us/library/ms752346.aspx 
 

4. Install the certificate on the Client PCs 

The certificate must be added in the trusted publisher and in the trusted root authority. If this is not done correctly you get the error 'Trust not granted' 

Silverlight as a mechanism for simplification of application deployment

 

I think Silverlight is a great technology that can be use to create great applications, or as a migration target from Visual Basic 6, Powerbuilder or Windows Forms apps which can take advantage of a very simplified deployment approach.

And use  Artinsoft/ Mobilize automated solutions (see http://www.artinsoft.com/visual-basic-6-or-csharp-to-the-web.aspx ) 

Deployment is as simple as just publish your SL application on your intranet and your clients just need to press F5 to have the latest SL version. 


Silverlight provides a good balance between ease of development and platform features(XAML and .NET and Visual Studio). 

Silverlight can run cross-platform. (On Windows and Mac using the official Microsoft implementation and on Linux using Moonlight. 

 

 

Support For Silverlight 4

 

 

Support For Silverlight 5

  

Also Silverlight is a good intermediate point if you plan to modernize your application even further to Windows 8 but not just yet. You will find several resources for porting your Silverlight app to Windows 8 like http://msdn.microsoft.com/en-us/library/windows/apps/xaml/br229571.aspx.  

 

 

 

NOTE: Moonlight provides support for 32 and 64 bit Linux. It supports Silverlight 2.0, and some features of silverlight 3 and 4, but is not currently an active project.  

 

Using ClickOnce deployment over the web to simplify Windows Form Application deployment

 

Artinsoft\Mobilize.Net helps legacy application to be modernized using the Windows Forms technology. Doing this upgrade revitalizes your application code and allows you to take advantage of the new platforms features like ClickOnce deployment. 

What is ClickOnce deployment?   

 
"ClickOnce is a deployment technology that enables you to create self-updating Windows-based applications that can be installed and run with minimal user interaction. Visual Studio provides full support for publishing and updating applications deployed with ClickOnce technology if you have developed your projects with Visual Basic and Visual C#.

ClickOnce deployment overcomes three major issues in deployment: 

  •          Difficulties in updating applications. With Microsoft Windows Installer deployment, whenever an application is updated, the user can install an update, an msp file, and apply it to the installed product; with ClickOnce deployment, you can provide updates automatically. Only those parts of the application that have changed are downloaded, and then the full, updated application is reinstalled from a new side-by-side folder. 

  •          Impact to the user's computer. With Windows Installer deployment, applications often rely on shared components, with the potential for versioning conflicts; with ClickOnce deployment, each application is self-contained and cannot interfere with other applications. 

  •          Security permissions. Windows Installer deployment requires administrative permissions and allows only limited user installation; ClickOnce deployment enables non-administrative users to install and grants only those Code Access Security permissions necessary for the application. 

In the past, these issues sometimes caused developers to decide to create Web applications instead of Windows-based applications, sacrificing a rich user interface for ease of installation. By using applications deployed using ClickOnce, you can have the best of both technologies."


Click Once Deployment Strategies 

 

There are 3 deployment strategies: 

  • Install from Web or a Network Share 
  • Install from a CD 
  • Start from the Web or Network share 


Quick Overview 


 

For a quick overview of how to use the ClickOnce deployment take a look at Shahar Gvirtz's post http://weblogs.asp.net/shahar/archive/2008/01/29/how-to-use-clickonce-to-deploy-your-applications.aspx

 

Problem uploading large files in ASP.NET

12. October 2012 10:35 by Mrojas in ASP.NET, C#, Cloud, General, HTML5, SQL Server, VB6 Migration  //  Tags: , , , ,   //   Comments (0)

If you try to upload large files you might get an exception like

HttpException: Maximum request lenght exceeded.

This problem occurs because the default value for the maxRequestLength parameter in the section 

of the machine.config or Web.config file is 4096 (4M).

So any file with a size bigger will fail.

However I think that the max size that you can write here is 2G 2097151

Some info can be found here: http://support.microsoft.com/default.aspx?scid=kb;EN-US;295626

 

So to change that for 512mb use something like:

 

<configuration>

    <system.web>

        <httpRuntime maxRequestLength="524288" /> 

    </system.web>

</configuration>

Office Interop and Call was rejected by callee

28. September 2012 13:14 by Mrojas in ActiveX, C#, Memory, VB6 Migration, WinForms  //  Tags: , ,   //   Comments (0)

This issue is mostly cause by timing issues when calling a multi-threaded app.

Some workarounds:

 

1. Set the application visible property to false. For example if using Word make word visible property false at the start of the method and set it back to true at the end.

This will delay some GUI changes avoiding timing issues.

 

2. Insert some Thread.Sleep calls (yes this is ugly)

 

3. Register and IOleMessageFilter. I have copied an implementation from the MSDN

 

Just copy this class in your code.

At the start of the method call 

 

            MessageFilter.Register();
            // This registers the IOleMessageFilter to handle any threading 
            // errors.

 

And at the end

MessageFilter.Revoke();

public class MessageFilter : IOleMessageFilter
{

        //
        // Class containing the IOleMessageFilter
        // thread error-handling functions.

        // Start the filter.
        public static void Register()
        {
            IOleMessageFilter newFilter = new MessageFilter(); 
            IOleMessageFilter oldFilter = null; 
            CoRegisterMessageFilter(newFilter, out oldFilter);
        }


        // Done with the filter, close it.
        public static void Revoke()
        {
            IOleMessageFilter oldFilter = null; 
            CoRegisterMessageFilter(null, out oldFilter);
        }


        //
        // IOleMessageFilter functions.
        // Handle incoming thread requests.
        int IOleMessageFilter.HandleInComingCall(int dwCallType, 
        System.IntPtr hTaskCaller, int dwTickCount, System.IntPtr lpInterfaceInfo) 
        {

            //Return the flag SERVERCALL_ISHANDLED.
            return 0;
        }


        // Thread call was rejected, so try again.
        int IOleMessageFilter.RetryRejectedCall(System.IntPtr 
        hTaskCallee, int dwTickCount, int dwRejectType)
        {

            if (dwRejectType == 2)
            // flag = SERVERCALL_RETRYLATER.
            {
                // Retry the thread call immediately if return >=0 & 
                // <100.
                return 99;
            }
            // Too busy; cancel call.
            return -1;
        }


        int IOleMessageFilter.MessagePending(System.IntPtr hTaskCallee, 
        int dwTickCount, int dwPendingType)
        {
            //Return the flag PENDINGMSG_WAITDEFPROCESS.
            return 2; 
        }


        // Implement the IOleMessageFilter interface.
        [DllImport("Ole32.dll")]
        private static extern int 
          CoRegisterMessageFilter(IOleMessageFilter newFilter, out 
          IOleMessageFilter oldFilter);
    }



    [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), 
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    interface IOleMessageFilter 
    {
        [PreserveSig]
        int HandleInComingCall( 
        int dwCallType, 
        IntPtr hTaskCaller, 
        int dwTickCount, 
        IntPtr lpInterfaceInfo);

        [PreserveSig]
        int RetryRejectedCall( 
        IntPtr hTaskCallee, 
        int dwTickCount,
        int dwRejectType);


        [PreserveSig]
        int MessagePending( 
            IntPtr hTaskCallee, 
            int dwTickCount,
            int dwPendingType);
    }

 

GetUserDefaultLCID in .NET with no API calls

13. September 2012 09:43 by Mrojas in C#, Portable  //  Tags: , ,   //   Comments (0)

I was looking for a pure .NET equivalent of the Windows API function

GetUserDefaultLCID because I want my code to be free of pinvoke references

to avoid posible issues in 64 bits.

 

However after a lot of digging I found that there is not direct equivalent.

You could use Thread.CurrentThread.CurrentCulture.LCID but it is not exactly the same.

So I found this great post in stackoverflow:

 

 

"

For some background information have a look here:

 

http://blogs.msdn.com/b/michkap/archive/2010/03/19/9980203.aspx

 

So it seems this problem manifests itself on Vista as well as Windows 7. It occurs because Microsoft seems to be in the process of deprecating the Locale ID in favor of the Locale Name.

 

To summarize: The relevant API calls all operate on registry values that can be found at HKCU\Control Panel\International. The value "Locale" is maintained for backward compatibility reasons and under normal circumstances is kept in synch with its newer counterpart called "LocaleName". This synch process however doesn't work under some circumstances.

 

Anyway, the GetThreadLocale API call gets its return value from the "Locale" registry entry mentioned above, while the others (GetUserDefaultLCID, GetSystemDefaultLCID, etc) use the "LocaleName" registry entry.

 

Hence the confusion.

 

BTW, the solution mentioned by JP in a previous post should probably be extended to

 

initialization

  SetThreadLocale(GetUserDefaultLCID);

  GetFormatSettings;

because (if i'm reading it correctly!) according to the docco the GetUserDefaultLCID call will account for user customizations.

 

After a bit more research, Vista is not affected at all. I've got some more detail too ...

 

The relevant API calls all operate on registry values that can be found at HKCU\Control Panel\International. The value " Locale " is maintained for backward compatibility reasons and under normal circumstances is kept in synch with its newer counterpart called " LocaleName ". Under Windows 7 at least, this synch process however doesn't work where processes are being run as another user (i.e. RunAs or Impersonation). This seems to be the case during installation, where the installer is launched from an existing windows session. It does however seem to work correctly if you've booted from the install CD.

 

GetThreadLocale gets its value from Thread Information Block or Thread Environment Block (TIB or TEB) See: http://en.wikipedia.org/wiki/Thread_Environment_Block For both Vista and Windows 7, the TIB is initialised with the HKCU\Control Panel\International\Locale registry entry at logon. This becomes the default Locale for all threads created during the session. Changing this registry value during a session has no effect on the value returned by the GetThreadLocale API call. The user must log out and log in again to see a change. This is the API call that Delphi uses as the basis to initialize all its locale format strings ( See SysUtils.GetFormatSettings method), from which all date fields are formatted.

 

GetUserDefaultLCID: in Vista, bases its return value on the HKCU\Control Panel\International\Locale registry entry. In Windows 7, bases its return value on the HKCU\Control Panel\International\LocaleName registry entry. The respective registry entry can be changed during a session and the result is immediately reflected in this API call return value.

 

SetThreadLocale updates the TIB to reflect the locale provided in the parameter to this call. Note that this only ever effects the thread the API call is executed from. The API calls SetThreadLocale(LOCALE_USER_DEFAULT) and SetThreadLocale(GetUserDefaultLCID) are functionally equivalent. They both derive the source locale as described in the GetUserDefaultLCID API call above."

 

With that information you could create a function to get that value from the Registry using just .NET framework calls. I do not love that approach either but at least is another alternative

Free Alternatives for .NET Reflector

6. October 2011 06:26 by Mrojas in C#  //  Tags: , ,   //   Comments (0)

As I use to start stories to my kids when they go to bed:

“A long long time ago when I was a kid there existed a tool called
.NET Reflector, by a great wizard called Lutz Roeder. This wizard created
a magical tool that allowed you to discover what where the dark forces of the
Framework that produced weird behavior. And he provided it to the code warrior so
they could find their path”

Well .NET Reflector still exists and is a great tool, but is no longer free. You can no longer
just download it and enjoy its bliss : S

But is everything loss. Should I sell my sword and horse to be able to use the .NET reflector Oracle jQuery15203662828153464943_1359277348820?

No. There are now good free alternatives.

First one is call ILSpy(http://wiki.sharpdevelop.net/ILSpy.ashx). It looks almost the same as .NET reflector. Is not the same but is very good

And recently JetBrains a clan of powerful code warlocks, has just release another magical jewel,
they called it dotPeek (http://www.jetbrains.com/decompiler/):

IsMissing migration in VB.NET or C#

Recently  we added some support for migrating the IsMissing function to VB.NEt or C#

The thing is. In VB6 the IsMissing Function is TRUE only if you have something like:

Public Sub Foo(Optional str)

 

Where you dont specify the variable type, or if you have

 

Public Sub Foo(Optional str as Variant)

 

And is IsMissing is FALSE for any other case. Including Optional variables whose definition type is not Variant.

 

So let's see some examples to illustrate the idea:
 Example 1:

Public Sub Foo(str, a As Integer, b As Integer, Optional c As Integer)
    MsgBox (str & "Foo Is missing a " & IsMissing(a))
    MsgBox (str & "Foo Is missing b " & IsMissing(b))
    MsgBox (str & "Foo Is missing c " & IsMissing(c))
End Sub

 

It occurs that IsMissing is really FALSE in all cases. So it is equivalent to:

 

Public Sub Foo(str, a As Integer, b As Integer, Optional c As Integer)
    MsgBox (str & "Foo Is missing a " & false)
    MsgBox (str & "Foo Is missing b " & false)
    MsgBox (str & "Foo Is missing c " & false)
End Sub

 

 

Example 2:

 

Public Sub Goo(str, a As Integer, b As Integer, Optional c As Object, Optional d As Byte, Optional e)
    MsgBox (str & "Goo Is missing a" & IsMissing(a))
    MsgBox (str & "Goo Is missing b" & IsMissing(b))
    MsgBox (str & "Goo Is missing c" & IsMissing(c))
    MsgBox (str & "Goo Is missing d" & IsMissing(d))
    MsgBox (str & "Goo Is missing e" & IsMissing(e))
End Sub

 

All cases EXCEPT "e" are equivalent to FALSE

 

Public Sub Goo(str, a As Integer, b As Integer, Optional c As Object, Optional d As Byte, Optional e)
    MsgBox (str & "Goo Is missing a" & false)
    MsgBox (str & "Goo Is missing b" & false)
    MsgBox (str & "Goo Is missing c" &false)
    MsgBox (str & "Goo Is missing d" & false)
    MsgBox (str & "Goo Is missing e" & IsMissing(e))
End Sub

 

So if you are migrating your VB6 Code to C# put attention to these little details it can save you a lot of time.And remember that this is just one feature of VBCompanion tool ;)