Modifying array of structures (default marshaling)

Let’s assume you have a user defined structure “EMPLOYEE” in a win32 DLL (let say it “Win32Native.dll”) as shown below.

typedef struct _EMPLOYEE
{
    int Age ;
    int Sex;
    double Salary ;
    char* FirstName ;
    char* LastName ;
} EMPLOYEE;

Define some functions in native dll (let say it “Win32Native.dll”)  as shown below. 

extern "C" __declspec(dllexport) void ModifyArrayOfEmployeeStruct(int nSize, EMPLOYEE* pArray);

Implementing Functions

extern "C" __declspec(dllexport) void ModifyArrayOfEmployeeStruct( int nSize, EMPLOYEE* pArray)
{
    int result = 0;
    EMPLOYEE* pCur = pArray;
    STRSAFE_LPSTR temp1, temp2 ;
    for ( int i = 0; i < nSize; i++ )
    {
        size_t nLen1 = 0;
        size_t nLen2 = 0;
        StringCchLengthA( pCur->FirstName, STRSAFE_MAX_CCH, &nLen1 );
        StringCchLengthA( pCur->LastName, STRSAFE_MAX_CCH, &nLen2 );
       
        nLen1 = sizeof(char) * ( nLen1 + 11 );    //    To accomodate "<Modified>"
        nLen2 = sizeof(char) * ( nLen2 + 11 );    //    To accomodate "<Modified>"
        temp1 = (STRSAFE_LPSTR)CoTaskMemAlloc( nLen1 );
        temp2 = (STRSAFE_LPSTR)CoTaskMemAlloc( nLen2 );

        StringCchCopyA( temp1, nLen1, (STRSAFE_LPCSTR)"<Modified>" );
        StringCbCatA( temp1, nLen1, (STRSAFE_LPCSTR)pCur->FirstName);

        StringCchCopyA( temp2, nLen2, (STRSAFE_LPCSTR)"<Modified>" );
        StringCbCatA( temp2, nLen2, (STRSAFE_LPCSTR)pCur->LastName);
               
        // CoTaskMemFree must be used instead of delete to free memory.
        CoTaskMemFree( pCur->FirstName  );
        CoTaskMemFree( pCur->LastName  );

        pCur->FirstName = (char *)temp1;
        pCur->LastName = (char *)temp2;

        pCur->Age += 1 ;
        pCur->Salary  += 1000.0 ;
        pCur++;
   }
}

Point of Interest

  • CoTaskMemAlloc is used to allocated the memory required.
  • CoTaskMemFree is used to free any previously allocated buffer, if null is passed then, CoTaskMemFree is not called.
  • StringCchCopyA copies an ANSI string to a string buffer.
  • StringCbCatA Appends an ANSI string to a string buffer.

    If you want to use a heap that is shared between native and managed, it is more common to use the COM heap.

  • On the native side use CoTaskMemAlloc() and CoTaskMemFree().

     

    Writing the client code (the managed part)

    one can simple create a console base application which can use this dll. let’s name it MarshallingTest.

    see the code snippet below.

    using System;
    using System.Runtime.InteropServices;
    using System.Text;

    namespace MarshallingTest
    {

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        public struct Employee
        {
            public int Age;
            public int Sex;
            public double Salary;
            public String FirstName;
            public String LastName;
        }
        class Program
        {
            [DllImport("Win32Native.dll")]

            public static extern int ModifyArrayOfEmployeeStruct(

                                                        int nSize,

                                                        [In, Out] Employee[] empArr);
            static void Main(string[] args)
            {
                int nCnt = 5; // Number of Items in Structure Array

                Employee[] emp = new Employee[nCnt];
                emp[0].FirstName = "Ramesh"; emp[0].LastName = "Sharma"; emp[0].Age = 42; emp[0].Salary = 40000; emp[0].Sex = 0;
                emp[1].FirstName = "Shalini"; emp[1].LastName = "Verma"; emp[1].Age = 30; emp[1].Salary = 25000; emp[1].Sex = 1;
                emp[2].FirstName = "Ramesh"; emp[2].LastName = "Sharma"; emp[2].Age = 51; emp[2].Salary = 35000; emp[2].Sex = 0;
                emp[3].FirstName = "Aarushi"; emp[3].LastName = "Shukla"; emp[3].Age = 25; emp[3].Salary = 20000; emp[3].Sex = 0;
                emp[4].FirstName = "Malini"; emp[4].LastName = "Kapoor"; emp[4].Age = 33; emp[4].Salary = 30000; emp[4].Sex = 1;

                Console.WriteLine("\nEmployee Array Before Call");
                for (int nI = 0; nI < nCnt; nI++)
                {
                    StringBuilder sb = new StringBuilder( "First Name=[" );
                    sb.Append(emp[nI].FirstName);
                    sb.Append("]  Last Name=[");
                    sb.Append(emp[nI].LastName );
                    sb.Append("]  Age=[");
                    sb.Append(emp[nI].Age .ToString ());
                    sb.Append("]  Salary=[");
                    sb.Append(emp[nI].Salary.ToString ("F2"));
                    sb.Append("]  Sex=[");
                    sb.Append(((emp[nI].Sex == 0) ? "Male" : "Female"));
                    sb.Append("]");
                    Console.WriteLine(sb.ToString ());
                }

                // Call the Function.

                ModifyArrayOfEmployeeStruct(emp.Length, emp);

                Console.WriteLine("\nEmployee Array After Call");
                for (int nI = 0; nI < nCnt; nI++)
                {
                    StringBuilder sb = new StringBuilder("First Name=[");
                    sb.Append(emp[nI].FirstName);
                    sb.Append("]  Last Name=[");
                    sb.Append(emp[nI].LastName);
                    sb.Append("]  Age=[");
                    sb.Append(emp[nI].Age.ToString());
                    sb.Append("]  Salary=[");
                    sb.Append(emp[nI].Salary.ToString("F2"));
                    sb.Append("]  Sex=[");
                    sb.Append(((emp[nI].Sex == 0) ? "Male" : "Female"));
                    sb.Append("]");
                    Console.WriteLine(sb.ToString());
                }

            }
        }
    }

    Point of Interest

  • namespace System.Runtime.InteropServices;  defines the declarations necessary for Interop operations, like DllImport,
  • DllImport defines the DLL entry point.

    compile and execute you will get following output.

    image

  • Modifying array of string values (default Marshaling)

    Define some functions in native dll (let say it “Win32Native.dll”)  as shown below. this function receives array of char pointers (string).

    extern "C" __declspec(dllexport) void ModifyStringArrayValues( int nCount, char* ppStrArray[] )

    Implementing Functions

    extern "C" __declspec(dllexport) void ModifyStringArrayValues( int nCount, char* ppStrArray[] )
    {
        size_t cchDest = 40;
        const size_t alloc_size = sizeof(char) * 40;
        for ( int nI = 0; nI < nCount; nI++ )
        {
            char *pszFormat = "<<from DLL>> [Modified String %2d]";
            STRSAFE_LPSTR temp = (STRSAFE_LPSTR)CoTaskMemAlloc( alloc_size );
            StringCchPrintfA(temp, cchDest, pszFormat, nI);
            CoTaskMemFree( ppStrArray[nI] );
            ppStrArray[nI] = (char *) temp;
       }
    }

    Point of Interest

    • CoTaskMemAlloc is used to allocated the memory required.
    • CoTaskMemFree is used to free any previously allocated buffer, if null is passed then, CoTaskMemFree is not called.
    • StringCchPrintfA printf used to print a formatted output to a string buffer.

    If you want to use a heap that is shared between native and managed, it is more common to use the COM heap.

    Writing the client code (the managed part)

    one can simple create a console base application which can use this dll. let’s name it MarshallingTest.

    see the code snippet below.

    using System;
    using System.Runtime.InteropServices;
    using System.Text;

    namespace MarshallingTest
    {
        class Program
        {
             [DllImport("Win32Native.dll")]
             public static extern double ModifyStringArrayValues(int nSize, [In, Out] String[] strArr);
        
            static void Main(string[] args)
            {
                int nSize = 5;
                string[] arrStr = new string[nSize];
                for (int nI = 0; nI < nSize; nI++) arrStr[nI] = "String " + (nI + 1).ToString();

               
                Console.WriteLine("String Values in Array, before calling function");
                for (int i = 0; i < nSize; i++)
                {
                    Console.WriteLine(string.Format("Array[{0:D2}] : {1}", i, arrStr[i]));
                }

                ModifyStringArrayValues(nSize, arrStr);

                Console.WriteLine("String Values in Array, after calling function");
                for (int i = 0; i < nSize; i++)
                {
                    Console.WriteLine(string.Format("Array[{0:D2}] : {1}", i, arrStr[i]));
                }
            }
        }
    }

    Point of Interest

    • namespace System.Runtime.InteropServices;  defines the declarations necessary for Interop operations, like DllImport,
    • DllImport defines the DLL entry point.

    compile and execute you will get following output.

    image

    Modifying array of numeric types (default Marshaling)

    Define some functions in native dll (let say it “Win32Native.dll”)  as shown below. These 2 functions receives integer buffer, and double buffer respectively.

    extern "C" __declspec(dllexport) int ModifyIntegerArrayValues ( int nSize, int* pArray)

    extern "C" __declspec(dllexport) double ModifyDoubleArrayValues ( int nSize, double* pArray)

    Implementing Functions

    extern "C" __declspec(dllexport) int ModifyIntegerArrayValues ( int nSize, int* pArray)
    {
        int nResult = 0 ;
        for ( int i = 0; i < nSize; i++ )
        {
            nResult += pArray[ i ];
            pArray[i] += 100;
        }
        return nResult;
    }

    extern "C" __declspec(dllexport) double ModifyDoubleArrayValues ( int nSize, double* pArray)
    {
        double dblResult = 0 ;
        for ( int i = 0; i < nSize; i++ )
        {
            dblResult += pArray[ i ];
            pArray[i] += 200 ;
        }
        return dblResult ;
    }

    Point of Interest

    • CoTaskMemAlloc is used to allocated the memory required.
    • CoTaskMemFree is used to free any previously allocated buffer, if null is passed then, CoTaskMemFree is not called.

    If you want to use a heap that is shared between native and managed, it is more common to use the COM heap.

    Writing the client code (the managed part)

    one can simple create a console base application which can use this dll. let’s name it MarshallingTest.

    see the code snippet below.

    using System;
    using System.Runtime.InteropServices;
    using System.Text;

    namespace MarshallingTest
    {
        class Program
        {
             [DllImport("Win32Native.dll")]
            public static extern void ModifyIntegerArrayValues(int nCount, [In, Out] int[] arrStr);

             [DllImport("Win32Native.dll")]
             public static extern int ModifyDoubleArrayValues(int nSize, [In, Out] double[] nArr);
        
            static void Main(string[] args)
            {
                int nSize = 5 ;
                int[] arrInt = new int[nSize];
                for (int nI = 0; nI < nSize; nI++) arrInt[nI] = (nI + 1) * 10;

                Console.WriteLine("\nValues of Integer array, before calling function");
                for (int i = 0; i < nSize; i++)
                {
                    Console.WriteLine(string.Format("Array[{0:D2}] : {1:D3}", i, arrInt[i]));
                }

                ModifyIntegerArrayValues(nSize, arrInt);

                Console.WriteLine("\nValues of Integer array, after calling function");
                for (int i = 0; i < nSize; i++)
                {
                    Console.WriteLine(string.Format("Array[{0:D2}] : {1:D3}", i, arrInt[i]));
                }

                double [] arrDbl= new double [nSize];
                for (int nI = 0; nI < nSize; nI++) arrDbl[nI] = (nI + 1) * 5.0 ;

                Console.WriteLine("\nValues of Double array, before calling function");
                for (int i = 0; i < nSize; i++)
                {
                    Console.WriteLine(string.Format("Array[{0:D2}] : {1:F2}", i, arrDbl[i]));
                }

                ModifyDoubleArrayValues(nSize, arrDbl);

                Console.WriteLine("\nValues of double array, after calling function");
                for (int i = 0; i < nSize; i++)
                {
                    Console.WriteLine(string.Format("Array[{0:D2}] : {1:F2}", i, arrDbl[i]));
                }
            }
        }
    }

    Point of Interest
    • namespace System.Runtime.InteropServices;  defines the declarations necessary for Interop operations, like DllImport,
    • DllImport defines the DLL entry point.
    • Marshal.Copy function used to copy buffer from managed buffer to unmanaged buffer and vice versa.
    • Marshal.FreeCoTaskMem frees the memory allocated by native DLL.

    compile and execute you will get following output.

    image

    Fetching buffer of numeric types from a Win32 DLL

    Define some functions in native dll (let say it “Win32Native.dll”)  as shown below. These 3 functions receives integer buffer, and double buffer respectively.

    extern "C" __declspec(dllexport) int FetchIntegerArray ( int nNewSize, int** ppnArray );

    extern "C" __declspec(dllexport) double  FetchDoubleArray ( int nNewSize, double ** ppnArray );

    Implementing Functions

    extern "C" __declspec(dllexport) int FetchIntegerArray ( int nNewSize, int** ppnArray )
    {
          int result = 0;
          //    CoTaskMemAlloc must be used because code on the managed side will call
          //    Marshal.FreeCoTaskMem to free this memory.
          int* newArray = (int*)CoTaskMemAlloc( sizeof(int) * nNewSize);
          for ( int j = 0; j < nNewSize ; j++ )
          {
               newArray[j] = ( j + 1 ) * 10 ;
               result += newArray[j];
           }

          if ( *ppnArray != NULL ) CoTaskMemFree( *ppnArray );
          *ppnArray = newArray;
          return result;
    }

    extern "C" __declspec(dllexport) double  FetchDoubleArray ( int nNewSize, double ** ppnArray )
    {
         double result = 0;
         //    CoTaskMemAlloc must be used because code on the managed side will call
         //    Marshal.FreeCoTaskMem to free this memory.
         double* newArray = (double*)CoTaskMemAlloc( sizeof(double) * nNewSize );
         for ( int j = 0; j < nNewSize ; j++ )
         {
             newArray[j] = 10 + ( j+1 ) * 30 ;
             result += newArray[j];
         }

         if ( *ppnArray != NULL ) CoTaskMemFree( *ppnArray );
         *ppnArray = newArray;
         return result;
    }

    Point of Interest

    • CoTaskMemAlloc is used to allocated the memory required.
    • CoTaskMemFree is used to free any previously allocated buffer, if null is passed then, CoTaskMemFree is not called.

    If you want to use a heap that is shared between native and managed, it is more common to use the COM heap.

    Writing the client code (the managed part)

    one can simple create a console base application which can use this dll. let’s name it MarshallingTest.

    see the code snippet below.

    using System;
    using System.Runtime.InteropServices;
    using System.Text;

    namespace MarshallingTest
    {
       class Program
       {
           [DllImport("Win32Native.dll")]
           public static extern int FetchIntegerArray(int nSize, ref IntPtr arrInt);

           [DllImport("Win32Native.dll")]
           public static extern double FetchDoubleArray(int nSize, ref IntPtr arrInt);

           static void Main(string[] args)
           {
               int nSize = 10;
               IntPtr ptrArr = IntPtr.Zero;

               int nSum = FetchIntegerArray(nSize, ref ptrArr);
               int [] arrInt = new int [nSize];
               Marshal.Copy(ptrArr, arrInt, 0, nSize);

               Console.WriteLine("\nReturned Integer Buffer\n");

               for (int i = 0; i < nSize; i++)
               {
                   Console.Write("{0:}  ", arrInt[i]);
               }

               Console.Write("\nSum of Integer Buffer : {0}\n", nSum );
               Marshal.FreeCoTaskMem(ptrArr);

               ptrArr = IntPtr.Zero;
              double dblSum = FetchDoubleArray(nSize, ref ptrArr);
              double[] arrDbl = new double[nSize];
              Marshal.Copy(ptrArr, arrDbl, 0, nSize);

              Console.WriteLine("\nReturned Double Buffer\n");
              for (int i = 0; i < nSize; i++)
              {
                  Console.Write("{0:F2}  ", arrDbl[i]);
              }

              Console.Write("\nSum of Double Double Buffer : {0}\n", dblSum);
              Marshal.FreeCoTaskMem(ptrArr);
          }
       }
    }

    Point of Interest

    • namespace System.Runtime.InteropServices;  defines the declarations necessary for Interop operations, like DllImport,
    • DllImport defines the DLL entry point.
    • Marshal.Copy function used to copy buffer from managed buffer to unmanaged buffer and vice versa.
    • Marshal.FreeCoTaskMem frees the memory allocated by native DLL.

    compile and execute you will get following output.

    image

    Fetching a byte buffer from a Win32 DLL

     

    Define  a function in native dll (let say it “Win32Native.dll”)  as shown below.

    extern "C" __declspec(dllexport) int FetchByteArray ( int nSize, byte** ppnArray )
    {
        int result = 0;
        //    CoTaskMemAlloc must be used instead of the new operator because code on the managed side will call
        //    Marshal.FreeCoTaskMem to free this memory.
        byte* newArray = (byte*)CoTaskMemAlloc( sizeof(byte) * nSize );
        for ( int j = 0; j < nNewSize ; j++ )
        {
            newArray[j] = ( j+1 ) % 255 ;
            result += newArray[j];
        }

        // release the previous buffer, if any allocated.
        if ( *ppnArray != NULL ) CoTaskMemFree( *ppnArray );
        *ppnArray = newArray;
        return result;
    }

    Point of Interest
    • CoTaskMemAlloc is used to allocated the memory required.
    • CoTaskMemFree is used to free any previously allocated buffer, if null is passed then, CoTaskMemFree is not called.

    If you want to use a heap that is shared between native and managed, it is more common to use the COM heap.

    Writing the client code (the managed part)

    one can simple create a console base application which can use this dll. let’s name it MarshallingTest.

    see the code snippet below.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Text;

    namespace MarshallingTest
    {
        class Program
        {
            [DllImport("Win32Native.dll")]
            public static extern int FetchByteArray(int nSize, ref IntPtr arrInt);
        
            static void Main(string[] args)
            {
                int nSize = 10;
                IntPtr ptrArr = IntPtr.Zero;

                int nSum = FetchByteArray(nSize, ref ptrArr);
                byte[] arrByte = new byte[nSize];
                Marshal.Copy(ptrArr, arrByte, 0, nSize);

                Console.WriteLine("\nReturned Buffer\n");

                for (int i = 0; i < nSize; i++)
                {
                    Console.Write ( "{0:D2}  ", arrByte[i] );
                }
               
                Console.Write("\nSum of Buffer : {0}\n", nSum );
                Marshal.FreeCoTaskMem(ptrArr);
            }
        }
    }

    Point of Interest
    • namespace System.Runtime.InteropServices;  defines the declarations necessary for Interop operations, like DllImport,
    • DllImport defines the DLL entry point.
    • Marshal.Copy function used to copy buffer from managed buffer to unmanaged buffer and vice versa.
    • Marshal.FreeCoTaskMem frees the memory allocated by native DLL.

    compile and execute you will get following output.

    image