Posts
7
Comments
2
Trackbacks
0
Thursday, July 09, 2009
Unmanaged arrays in C# structs
I am creating a Memory Mapped File (MMF) that will act like a circular buffer because it will store "live" 100msec data from a PLC.  The controls engineer wanted to send the data to me as tag arrays.  So, to mimic these tag arrays, I decided to create a structure the reflects these tag arrays. 

There are ten arrays in the structure. 

The first array is an array of two short integers (16-bits) that contain a write and read pointer.
The next three array is an array of 1024 bytes, an array of 1024 short integers and an array of 1024 floats.  This set of three arrays is repeated three times.

A structLayout attribute is used to place these arrays in sequential order and I specify the size so that I can reserve that amount of space on the disk for the MMF when it is created.  Each array is marshalled as an  UnmanagedType.ByValArray. 

Here is the structure as I first created it:

[StructLayout(LayoutKind.Sequential, Pack = 1, Size = 150532)]
public struct strL1CmnRec
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 2, ArraySubType = UnmanagedType.I2)] public short[] shtL1Ptrs;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = UnmanagedType.U1)] public byte[] byEntryBool;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = UnmanagedType.I2)] public short[] shtEntryInts;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = UnmanagedType.R4)] public float[] sngEntryReals;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = UnmanagedType.U1)] public byte[] byProcBool;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = UnmanagedType.I2)] public short[] shtProcInts;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = UnmanagedType.R4)] public float[] sngProcReals;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = UnmanagedType.U1)] public byte[] byDelvBool;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = UnmanagedType.I2)] public short[] shtDelvInts;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024, ArraySubType = UnmanagedType.R4)] public float[] sngDelvReals;
}

To access this structure and its arrays, I declared this variable:

strL1CmnRec L1CRec;

I then attempted to write into one the arrays as follows:

L1CRec.shtL1Ptrs[0] = i;
L1CRec.shtL1Ptrs[1] = 0;

And, when i was attempted to be placed into shtL1Ptrs[0], I received a runtime error because shtL1Ptrs[0] was null!

What to do? What to do?


It turns out that in a C# struct, the arrays are declared but its memory is not allocated. So I wrote a create method to allocate memory
for each array in the struct. The create method is part of the C# struct.

public static strL1CmnRec create()
{
return new strL1CmnRec
{
shtL1Ptrs = new short[2],
byEntryBool = new byte[1024],
shtEntryInts = new short[1024],
sngEntryReals = new float[1024],
byProcBool = new byte[1024],
shtProcInts = new short[1024],
sngProcReals = new float[1024],
byDelvBool = new byte[1024],
shtDelvInts = new short[1024],
sngDelvReals = new float[1024]
};
}


So, now to access these arrays, I first declared the variable:

strL1CmnRec L1CRec;

then I call the create function.  I place this in the constructor of the class that is using this structure:

L1CRec = strL1CmnRec.create();
I can now successfully access the arrays in the struct. 

 
posted @ Thursday, July 09, 2009 5:13 PM | Feedback (1)
Friday, July 03, 2009
C# Constants & Global Variables
Moving from C++ to C#, I discovered that constants in the way I was using them are not supported; however, they can be emulated.  they are now defined in their own class as static variables; I go a step further by making them private and only available through a property. 

Before I show a code example, let's explore static further.  When a variable is declared as static, the variable is essentially global.  All instances of the class share the same static variable.  A static variable is initialized to zero unless an explicit initializer is specified.

FIrst, let's look at the class that defines the constants:

namespace Globals
{
    public class clsGlobalDefs
    {
        private static string _c1 = "Constant 1";
        private static string _c2= "This is a constant string";

        public string c1
        {
            get { return _c1; }
        }

        public string c2
        {
            get { return _c2; }
        }

        public clsGlobalDefs()
        {
 
        }
}

A few notes on the above class:

  1. I like placing the constants in their own namespace and their own DLL.  This allows me to use the same constants across multiple projects.
  2. Note the use of the static qualifier
  3. Note the use of properties
To use class clsGlobalDefs:

  1. Reference clsGlobalDefs through a using statement : using Globals
  2. Instantiate clsGlobalDefs: clsGlobalDefs GlobalDefs = new clsGlobalDefs();
  3. Access the constant through the class' property:
String s;
s = GlobalDefs.c1;

-or-

s = "This is constant #1: " + GlobalDefs.c1


posted @ Friday, July 03, 2009 8:16 AM | Feedback (0)
Tuesday, May 26, 2009
C# - Checking for NULL

If the column you are reading has the potential for a NULL value, then you must use the DBNull object:


DataRowView dRowView = m_SchedCommentsBindingSource[0] as DataRowView;

dsL2DB.SchedulesRow SchedRow = dRowView.Row as dsL2DB.SchedulesRow;

sDeleteComment = (SchedRow["DeleteComment"] == DBNull.Value ? "No Delete Comment" : SchedRow.DeleteComment);

 

More generically:

 

If (ds.Rows[0]["ColumnName"] == DBNull.Value)

{

// Do something for NULL value

}


posted @ Tuesday, May 26, 2009 5:04 PM | Feedback (0)
Thursday, May 14, 2009
Powershell output
I'm beginning to write simple scripts in powershell.  One of the things items that really isn't straight forward is the write-output command.  At first glance, one would think that this would simply output a string.  Well, there is a trick I found.

PS>write-output xxxxxxxx yyyyyyyyy

really outputs

xxxxxxxx
yyyyyyyyy


PS>write-output "xxxxxxxx yyyyyyyyy"

really outputs

xxxxxxxx yyyyyyyyy

PS> $A = 10
PS>$B = 20
PS>write-output "A = $A   B = $B"

really outputs

A = 10  B = 20
posted @ Thursday, May 14, 2009 3:30 PM | Feedback (0)
Monday, April 13, 2009
C# string formatting
In C++, one uses the sprintf function to build a formatted string like this:

char szOutput[256];
sprintf(szOutput, "At loop position %d.\n", i);



The C# equivalent is the String.Format method [string.format(string, object)].


TrkCmnRec.szMillOrderNbr = String.Format("MO #{0}", i);

Each placeholder in the string is numeric, so if we want to have a string with three placeholders, we would use {0}, {1}, {2},...{n} as shown in this example:

String sA = "Test string"
int i = 10;
Single f = 45.0
String s = String.Format("String = {0}, int = {1}, Single
= {2}", sA, i, f};

String, numeric and date data types have their own formatting specifiers.


Numeric Format Specifiers
Specifier Description Example C#
c Currency; specify the number of decimal places  $12,345.00 string.Format("Currency: {0:c}", iNbr)
d Whole numbers; specifies the minimum number of digits - zeroes will be used to pad the result  12345 string.Format("Whole: {0:d}", iNbr)
e Scientific notation; specifies the number of decimal places  1.2345e+004 string.Format("Exponential: {0:e}", iNbr)
f Fixed-point; specifies the number of decimal places  12345.00 string.Format("Fixed: {0:f3}", iNbr)
n Fixed-point with comma separators; specifies the number of decimal places  12,345.00 string.Format("Fixed formatted: {0:n3}", iNbr)
p percentage; specifies the number of decimal places  1,234,500.00% string.Format("Percentage: {0:p2}", iNbr)
x Hexadecimal  3039 string.Format("Hexadecimal: {0:x}", iNbr)

posted @ Monday, April 13, 2009 3:56 PM | Feedback (0)
Tuesday, April 07, 2009
Using win32 API in C#
When I was working in VC++, it was relatively easy to include a win32 API function.  All we did was include the header file and then made a call to a function like so:
 
        #include <Mailbox.h>
 
                CMailbox::MbxStatus CMailbox::iCreateMbx() {
        MbxStatus iStatus = mbxSuccess;

        switch ( m_iType ) {
        case mbxReceiver:
        case mbxBoth:
                // for receive type mailboxes we need to create a
                // space for the reception of data
                /* if ( m_hRecvMbx ) CloseHandle( m_hRecvMbx ); */
                m_hRecvMbx = ::CreateMailslot(
                        m_strInName,
                        0,
                        MAILSLOT_WAIT_FOREVER,
                        &( SECURITY_ATTRIBUTES )m_saInfo // previously was NULL
                );

                // if the mailslot already exists, return error
                if ( m_hRecvMbx == INVALID_HANDLE_VALUE ) {
                        m_hRecvMbx = NULL;
                        iStatus = mbxCreateFailure;
                }
                /* if ( m_iType == mbxReceiver ) */ break;

        case mbxSender:
                // for send type mailboxes we need to obtain a
                // target to send to
                iStatus = iSetReceiver( m_strOutName );
                break;
        }

        return iStatus;
            }
 
 
But, now, we have to use interop services because the DLL functions are considered to be in unmanaged code, which does not execute under the control of the CLR.
 
The list of DLL functions are listed in the MSDN in the Platform SDK as System Services.
 
Use DLLImport to specify  an entry point into the DLL for the win32 function that is to be called.  The DLLImport Attribute has four fields: EntryPoint, CharSet, CallingConvention, SetLastError.  From the MSDN library, these are defined as such:
 

DllImportAttribute field

Description

EntryPoint

Specifies the DLL entry point to be called. The default entry point name is the name of the managed method.

CharSet

Controls the name mangling and the way that String parameters should be marshaled. The .NET Compact Framework only supports CharSet..::.Unicode and CharSet..::.Auto. CharSet..::.Auto equates to CharSet..::.Unicode on Windows CE. The default marshaling on the .NET Compact Framework is CharSet..::.Unicode, unlike the .NET Framework that defaults to CharSet..::.Ansi.

Because the .NET Compact Framework does not support the DllImportAttribute..::.ExactSpelling field, the common language runtime automatically searches for an entry point according to the values specified by CharSet.

CallingConvention

Specifies the calling-convention values used in passing method arguments. The default is CallingConvention..::.Winapi, which corresponds to __cdecl on the Windows CE platform.

SetLastError

Enables the caller to use the GetLastWin32Error method to determine whether an error occurred while executing the platform invoke method. In Visual Basic 2005, the default is true; in C#, the default is false.

 
Here's an example of using the DLLImport attribute:
 
        [DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true)]
        public static extern IntPtr CreateFile (
            String lpFileName, int dwDesiredAccess, int dwShareMode,
            ref SECURITY_ATTRIBUTES lpSecurityAttributes, int dwCreationDisposition,
            int dwFlagsAndAttributes, IntPtr hTemplateFile );
 
I want to make a few notes about the way a win32 function is defined:
 
   1. Use the extern keywprd as it defines a function as external, meaning it is implemented outside the C# code.
   2. Verify that the function definition matches its signature found in the DLL
   3. Some functions return a handle or pointer.  In this case use the IntPtr type since it represents a pointer/handle in the .Net world.
   4. On a personal note, I like putting these definitions in its own class.
 
Once a win32 function is defined, we use it as follows:
 
hFile = Win32Api.CreateFile (  
                fileName,                       // Filename
                desiredAccess,                  // Open Read/Write
                desiredShare,                   // Share Reading
                ref security_attributes,        // NULL Security new 15-May-2007
                OPEN_ALWAYS,                    // Creation attrib
                desiredAttrib,                  // File Attributes
                IntPtr.Zero  );                 // NULL  no template
 
posted @ Tuesday, April 07, 2009 9:51 AM | Feedback (0)
Sunday, April 05, 2009
Adding items to a listview in C#

I created a listview with two columns as part of a text C# solution to test the use of structures. This list was created with the IDE. When creating a list where you want to show multiple columns, set the listview's view property to Details. I use the IDE to define the columns by selecting columns from the properties list.

To fill in the listview before displaying it, I programmatically add the items in the form's LOAD event.

BTW, I'm moving from VB.Net to C# and adding events to the form is different in C# from VB .Net. Rather than selecting the event from a drop-down list (as in VB), the event is pro grammatically defined as shown here:

this.Load += new EventHandler(frmL1Cmn_Load);
this.Activated += new EventHandler(frmL1Cmn_Activated);

Here's the generated callback

void frmL1Cmn_Activated(object sender, EventArgs e)
{
     throw new NotImplementedException();
}

But, to add this eventhandler is much different than VB. After enter +=, let the IDE instruct you to enter the TAB. It is at this point that the TAB key is depressed. Then, let the IDE prompt you one more time to hit the TAB key to generate the callback function. Select the TAB key a 2nd time and the callback function is generated.

Coming back from this tangent, here's the code in the form's load callback function to populate the listview.

void frmL1Cmn_Load(object sender, EventArgs e)
{
   try
   {
      // Populate lstReadL1Ints with lngVals
      lstReadL1Ints.BeginUpdate();
      lstReadL1Ints.Items.Clear();
      for (i = 0; i < 50; i++)
     {
           ListViewItem row = new ListViewItem(i.ToString());
           int j = i * 2;
           row.SubItems.Add(l1CmnRec[i].lngVal[j].ToString());
           lstReadL1Ints.Items.Add(row);
     }
     lstReadL1Ints.EndUpdate();
     lstReadL1Ints.Refresh();
  }
  catch (Exception ex)
   {
      StringBuilder sb = new StringBuilder();
      sb.Append("Cannot fill in Int listview. ex = ").Append(ex.Message);
     Console.WriteLine(sb.ToString());
   }
}

posted @ Sunday, April 05, 2009 4:50 PM | Feedback (1)
News