2010-12-03 52 views
13

J'ai commencé à mettre à niveau une application .NET 2.0 WinForms vers .NET 4.0. Eh bien, OK, le processus de mise à niveau était juste une question de changement de cible de la plate-forme, mais le faire fonctionner réellement. J'ai supposé que tout serait là.L'environnement P/Invoke at-il été modifié dans .NET 4.0?

Mais il semble que quelque chose a radicalement changé dans .NET 4.0 en ce qui concerne Interop. En utilisant DllImport(), l'application embarque quelques DLL Delphi. Lorsque l'application cible .NET 2.0, tout fonctionne normalement. Mais quand je l'ai changé pour cibler .NET 4.0, les choses commencent à se détraquer, comme si quelque chose corrompait la mémoire.

Par exemple, il remplace un chiffre « 0 » dans des endroits étranges. Les données transmises dans un flux IStream sont remplacées par 8 caractères (Hex) 00 00 00 00 00 00 00 80, mais seulement 70% du temps. Deux appels consécutifs pour récupérer la même valeur renvoient des résultats différents (récupérer une valeur d'un cache en mémoire, réussir la première fois, échouer la deuxième fois). Les chaînes envoyées à un journal apparaissent tronquées.

J'ai essayé beaucoup de choses en essayant de rendre plus explicites les conventions d'appel, rien de tout cela n'a aucun effet. Toutes les chaînes sont traitées comme [MarshalAs (UnmanagedType.LPWStr)] sur le côté .NET et PWChar sur le côté Delphi.

Ce qui a changé dans .NET 4.0 qui briserait P/Invoke comme ça?

---------------------------- Modifier ----------------- --------------------

Voici l'exemple le plus simple. Il génère un fichier PDF qui fonctionne parfois correctement, mais plus souvent finit par corrompre (et fonctionne correctement dans .NET 2.0):

[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 <= 0) 
     return null; 
    byte[] result = new byte[streamstats.cbSize]; 
    Stream.Read(result, (int)streamstats.cbSize, IntPtr.Zero); 
    return result; 
} 

WordBool court et étaient à l'origine booléenne (Delphi) et bool (C#), je les ai changé être plus explicite, juste au cas où.

---------------------------- Modifier ----------------- --------------------

les choses que je l'ai déjà écrit au sujet WinForms semble avoir avéré être pas tout à fait pertinente, je l'ai recréé l'une des questions sans interface utilisateur. Le programme suivant génère 0,1,2,3,4,5,6,7,8,9 sous 2,0/3,5, mais 0, -1, -1, -1, -1, -1, -1, - 1, -1 sous 4.0.

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 < 10; i++) 
      { 
       AddToList(i); 
       Console.WriteLine(GetFromList(i)); 
      } 
     } 
    } 
} 

et le côté Delphi (compilé avec Delphi 2007):

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. 
+0

Pouvez-vous nous montrer la signature PInvoke? – JaredPar

+0

Cet article http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/922fa431-5426-48c6-8949-538dfbb2f266 suggère que marshalling a bien changé en 4.0, mais ne fournit pas une liste complète de ce qui a changé. –

+0

Peut-être intéressant de savoir: [Visual Studio 2010 SP1] (http://support.microsoft.com/kb/983509) ** ne ** corrige pas le problème, nous venons de l'essayer ici. –

Répondre

6

Cela semble être un bogue dans le débogueur Visual Studio 2010. Cela semble être de la mémoire qui ne lui appartient pas. Tous les problèmes que j'ai observés (qui peuvent tous être reproduits de manière fiable) disparaissent complètement si j'exécute l'application directement, au lieu de Visual Studio 2010.

Le bogue est réellement dans l'Assistant Débogage géré. Si vous le désactivez complètement (définissez HKLM \ Software \ Microsoft.NETFramework \ MDA = "0"), le problème disparaît. Mais bien sûr, vous perdez une capacité de débogage en le faisant.

0

booléen est un type d'un octet sur Delphi. Donc les changer doit être avec un type d'un octet

+0

Je n'étais pas sûr de ce que la taille de chaque côté suppose, donc je l'ai changé en WordBool et court (les deux 16 bits) partout où il est apparu. Mais cela n'a eu aucun effet. –

0

Je vois un problème similaire avec une dll Delphi: social_msdn
J'ai remarqué que ma bibliothèque compilé avec FreePascal (au lieu de Delphi) fonctionne même dans VS2010 sans aucun problème.Par conséquent, je ne sais pas si Delphi, le débogueur .NET4 ou la combinaison est la raison du problème.

Il existe certaines preuves que la mémoire allouée pendant le démarrage dll (par exemple dans la section d'initialisation) est affectée par la corruption de la mémoire.

+0

J'ai vu des problèmes distinctement déconnectés du démarrage ... à moins que ce soit en quelque sorte corrompre le gestionnaire de mémoire Delphi au point de réaffecter la même mémoire pour des choses différentes. Il semble se comporter de manière déterministe mais imprévisible. –

3

Apparaît qu'il s'agit d'un problème lié à la propriété Calling Convention dans l'attribut DllImport. Devrait être Cdecl pas le défaut StdCall. J'ai eu ce problème lors de la migration de 2.0 à 4.0 et fonctionnant dans VS2010. Voir l'article ici. http://codenition.blogspot.com/2010/05/pinvokestackimbalance-in-net-40i-beg.html

+0

Le problème que vous décrivez est différent. Lorsque j'ai testé l'exemple de code ci-dessus avec Cdecl au lieu de StdCall, j'ai obtenu des exceptions de déséquilibre de pile. Mais avec les deux côtés déclaré StdCall, il ne déséquilibre pas la pile, il corrompt juste les internes de Delphi. Dans votre cas, c'est le MDA qui a permis de traquer un problème légitime, dans le cas de Delphi, le MDA cause réellement le problème. –

+0

Mais si quelqu'un regarde juste le titre de ma question originale, votre réponse ici est tout à fait pertinente, P/Invoke a changé en 4.0. Cela n'avait rien à voir avec mon problème. –