title: Did P/Invoke environment change in .NET 4.0?
body: <p>I've started upgrading a .NET 2.0 WinForms application to .NET 4.0.  Well, OK, the upgrade process was just a matter of switching platform target, but making it actually work.  I assumed that's all there would be to it.</p>
  
  <p>But it seems that something drastically changed in .NET 4.0 regarding interop.  Using DllImport(), the application embeds a couple Delphi dlls.  When the application targets .NET 2.0, everything works normally.  But when I changed it to target .NET 4.0, stuff starts going haywire, like something is corrupting memory.</p>
  
  <p>For example, it replaces single digits with "0" in strange places.  Data passed in an IStream gets 8 characters replaced with (Hex) 00 00 00 00 00 00 00 80, but only about 70% of the time.  Two consecutive calls to retrieve the same value return different results (retrieving a value from a cache in memory, succeeds the first time, fails the second time).  Strings being sent to a log are showing up truncated.</p>
  
  <p>I've tried lots of stuff trying to make calling conventions more explicit, none of it has any effect.  All strings are handled as [MarshalAs(UnmanagedType.LPWStr)] on the .NET side and PWChar on the Delphi side.</p>
  
  <p>What changed in .NET 4.0 that would break P/Invoke like this?</p>
  
  <p>----------------------------Edit-------------------------------------</p>
  
  <p>Here's the simplest example.  It generates a PDF which sometimes works correctly, but more frequently ends up corrupt (and works correctly in .NET 2.0):</p>
  
  <pre><code>[DllImport(DLLName)]
  public static extern void SetDBParameters(
      [MarshalAs(UnmanagedType.LPWStr)] string Server,
      [MarshalAs(UnmanagedType.LPWStr)] string Database,
      [MarshalAs(UnmanagedType.LPWStr)] string User,
      [MarshalAs(UnmanagedType.LPWStr)] string Password,
      short IntegratedSecurity);
  
  procedure SetDBParameters(Server, Database, User, Password: PWChar;
      IntegratedSecurity: WordBool); stdcall;
  
  
  [DllImport(DLLName)]
  public static extern short GeneratePDF(
      [MarshalAs(UnmanagedType.LPWStr)] string Param1,
      [MarshalAs(UnmanagedType.LPWStr)] string Param2,
      [MarshalAs(UnmanagedType.LPWStr)] string Param3,
      [MarshalAs(UnmanagedType.LPWStr)] string Param4,
      out IStream PDFData);
  
  function GeneratePDF(Param1, Param2, Param3, Param4: PWChar;
      out PDFData: IStream): WordBool; stdcall;
  
  private byte[] ReadIStream(IStream Stream)
  {
      if (Stream == null)
          return null;
      System.Runtime.InteropServices.ComTypes.STATSTG streamstats;
      Stream.Stat(out streamstats, 0);
      Stream.Seek(0, 0, IntPtr.Zero);
      if (streamstats.cbSize &lt;= 0)
          return null;
      byte[] result = new byte[streamstats.cbSize];
      Stream.Read(result, (int)streamstats.cbSize, IntPtr.Zero);
      return result;
  }
  </code></pre>
  
  <p>WordBool and short were originally boolean (Delphi) and bool (C#), I changed them to be more explicit, just in case.</p>
  
  <p>----------------------------Edit-------------------------------------</p>
  
  <p>The stuff I wrote earlier about WinForms appears to have turned out to be not completely relevant, I've recreated one of the issues without any UI.  The following program generates 0,1,2,3,4,5,6,7,8,9 under 2.0/3.5, but 0,-1,-1,-1,-1,-1,-1,-1,-1 under 4.0.</p>
  
  <pre><code>using System;
  using System.Runtime.InteropServices;
  
  namespace TestNet4interop
  {
      static class Program
      {
          [DllImport("TestSimpleLibrary.dll", PreserveSig=true, CallingConvention = CallingConvention.StdCall)]
          public static extern void AddToList(long value);
  
          [DllImport("TestSimpleLibrary.dll", PreserveSig=true, CallingConvention = CallingConvention.StdCall)]
          public static extern int GetFromList(long value);
  
          static void Main()
          {
              for (long i = 0; i &lt; 10; i++)
              {
                  AddToList(i);
                  Console.WriteLine(GetFromList(i));
              }
          }
      }
  }
  </code></pre>
  
  <p>And the Delphi side (compiled with Delphi 2007):</p>
  
  <pre><code>library TestSimpleLibrary;
  
  uses
    SysUtils,
    Classes;
  
  {$R *.res}
  
  var
     List: TStringList;
  
  procedure AddToList(value: int64); stdcall;
  begin
     List.Add(IntToStr(value));
  end;
  
  function GetFromList(value: int64): integer; stdcall;
  begin
     result := List.IndexOf(IntToStr(value));
  end;
  
  exports
     AddToList,
     GetFromList;
  
  begin
     List := TStringList.Create;
  end.
  </code></pre>