Simplifying the use of IPP in WPF, Visual Studio and Managed code.

Introduction

The IPP 7.0 DLLs now have the same names for both the 32 and 64 bit version.  Under the previous versions, the names were different; e.g., ippcore-6-1.dll for the 32 bits and ippcoreem64t-6-1.dll for 64 bit.  Now the name is ippcore-7.0.dll for both versions.  The change is mostly good but exacerbates a prickly problem for development under managed code and VS2010.

Like it predecessors, VS2010 is a 32 bit program and any DLL loaded for display purposes, such as in WPF or WinForms must be able to run in 32 bit format even if the solution platform is set to 64 bits.  Because of this, Microsoft recommends that the user components are compiled for Any Cpu.

This of course works seamless unless the WPF converters or component depends upon the IPP DLLs, or any non-managed DLL, as do many of mine.  In that case Visual Studio displays the dreaded unhandled exception and the GUI display goes away.

There is a second problem of where to put the 7.0 DLLs when creating both 32 and 64 bit programs.  Before version 7, one just needed to have both sets in the path and the correct set would be picked up automatically.  Under 7.0, you must put the DLLs in the binary output directory, or at least put one set in the output directory and create a path to the other set.  If you have many sandboxes with many different output directories, and switch between 32 and 64 bit development, this becomes a major pain.

Finally, the third problem is testing.  Testing often does shallow copies so the DLLs must be in a path or listed in the deployment manifest.  Depending upon the testing framework used, some testing is done in 32 bits (MSTest) and other tests in 64 bit (NUnit).

In general, although development can be done when the WPF converters and components reference the IPP DLLs, it becomes quite a challenge to keep everything straight. 

Luckily there is a solution that allows the choice of the proper DLL at runtime that requires quite very minimal changes in the distributed code.   Moreover, it still allows a distribution package to be built with no source code changes.

The DllImport problem

Fundamentally the issue is how the DllImport works in the Managed/Unmanaged interface. 

        internal const string libname = "ippcore-7.0.dll";
        [SuppressUnmanagedCodeSecurityAttribute()]
        [DllImport(Ipp.Managed.core.libname)]
        public static extern void ippAlignPtr(char* ptr, int alignBytes);

Looking at this code, the solution to loading different at run time appears quite simple.  Just make a static constructor for all the classes that changes the libname at runtime.   Since static constructors are run before anything public function, the correct DLL could be used.

Something like

    unsafe public class core
 {
      static core()
      {
          if (IntPtr.Size == 8)
          {
               libname = (path to x64 ipcore-7.0.dll)                
          }
          else
          {
              libname = (path to x86 ipcore-7.0.dll)                
          }
 

This fails because DllImport must take a const string, not even a readonly string.  Thus one cannot set the libname at run time using this method.  The next approach, which is to change the PATH dynamically in order to point to the correct directory, I could not get to work.

However, all is not lost.  DllImport appears first to check to see if the library is already loaded and if so it doesn't load it again.  We can use this fact to preload the library using the windows api.

        [DllImport("kernel32", SetLastError = true)]
 private static extern IntPtr LoadLibrary(string fileName)
        unsafe public class core
        {
           static core()
           {
            if (IntPtr.Size == 8)
          {
                 LoadLibrary(from the 32 bit path
         }
            else
          {
                 LoadLibrary(from the 32 bit path);                
      }
           }
 

This is not quite enough as the Intel DLLs depend upon libiomp5md.dll so we will need to use LoadLibrary on this library first.   This gives us a handle on how to solve the three problems.


Minimizing the changes to the distributed code

But this solution has its own issues: every interface class that Intel distributes must be edited to add the static constructor and every upgrade requires a reediting since one cannot be sure the order the IPP functions will be called.

Microsoft had a similar problem with auto generated code and introduced an idea of a partial class so that modifications may be made to the class that is not lost when the class needs to be regenerated.  The Intel generated C# interface files still need to be modified but only by one word.  The static constructors all live in a separate file.   For example, we change

        unsafe public class core
{

to

        public unsafe partial class core
        { 

(A side note, Microsoft's Style Cop recommendation is to put the public before the unsafe, not after.)

Putting this together

Now we can build a loader class and a partial class for each DLL we need to load.

public static class IppLoader
{
   /// <summary>
   /// Preloads a DLL from either 32 or 64
   /// </summary>
   /// <param name="dllName">Name of the DLL without a path</param>
   /// <remarks></remarks>
   public static void PreloadDll(string dllName)
   {
      // Find the default install, we could do this with the resistery 
      const string basePath = @"C:\Program Files (x86)\Intel\ComposerXE-2011\redist";

      string dllPath;
      string libPath;
      // Find if we are running under 32 or 64 bit code
      if (IntPtr.Size == 8)
      {
         dllPath = Path.Combine(basePath, @"intel64\ipp", dllName);
         libPath = Path.Combine(basePath, @"intel64\Compiler\libiomp5md.dll");
      }
      else
      {
         dllPath = Path.Combine(basePath, @"ia32\ipp", dllName);
      libPath = Path.Combine(basePath, @"ia32\Compiler\libiomp5md.dll");
      }
      // We could optimize the loads here so the lib is not tried many times
      if (File.Exists(libPath))
      {
          LoadLibrary(libPath);
      }
 
      // We could optimize the loads here so the lib is not tried many times
      if (File.Exists(libPath))
      {
          LoadLibrary(dllPath);
      }
  }
}
 
An example of the partial class  
    /// <summary>
    /// The static constructor loads the files before anything else is done
    /// </summary>
    /// <remarks></remarks>
    public unsafe partial class sp
    {
        /// <summary>
        /// Initializes static member of the <see cref="sp"/> class.
        /// </summary>
        static sp()
        {
            IppLoader.PreloadDll(Ipp.Managed.sp.libname);
        }
    }

 

Installation

When an installer is used to distribute the programs, one needs only to distribute the IPP libraries in the binary directories then the LoadLibary is never called and the correct DLLs found.

Per informazioni più dettagliate sulle ottimizzazioni basate su compilatore, vedere il nostro Avviso sull'ottimizzazione.