Изменилась ли среда P / Invoke в .NET 4.0? - PullRequest
13 голосов
/ 03 декабря 2010

Я начал обновление приложения .NET 2.0 WinForms до .NET 4.0. Ну, ладно, процесс обновления был просто вопросом переключения целевой платформы, но заставлял ее работать на самом деле. Я предполагал, что это все, что будет.

Но, похоже, что-то кардинально изменилось в .NET 4.0 в отношении взаимодействия. Используя DllImport (), приложение встраивает пару DLL Delphi. Когда приложение предназначено для .NET 2.0, все работает нормально. Но когда я изменил его на целевую .NET 4.0, все начинает ухудшаться, как будто что-то портит память.

Например, он заменяет однозначные числа на «0» в незнакомых местах. Данные, передаваемые в IStream, заменяются 8 символами на (Hex) 00 00 00 00 00 00 00 80, но только в 70% случаев. Два последовательных вызова для получения одного и того же значения возвращают разные результаты (получение значения из кеша в памяти, успешно в первый раз, во втором случае неудача). Строки, отправляемые в журнал, отображаются усеченными.

Я пробовал много вещей, пытаясь сделать соглашения о вызовах более явными, ни одно из них не имеет никакого эффекта. Все строки обрабатываются как [MarshalAs (UnmanagedType.LPWStr)] на стороне .NET и PWChar на стороне Delphi.

Что изменилось в .NET 4.0, что сломало бы P / Invoke следующим образом?

---------------------------- Редактировать ------------------ -------------------

Вот самый простой пример. Он генерирует PDF, который иногда работает правильно, но чаще оказывается поврежденным (и работает правильно в .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 и short изначально были булевыми (Delphi) и bool (C #), я изменил их на более явные, на всякий случай.

---------------------------- Редактировать ------------------ -------------------

То, что я писал ранее о WinForms, похоже, оказалось не совсем актуальным, я воссоздал одну из проблем без какого-либо пользовательского интерфейса. Следующая программа генерирует 0,1,2,3,4,5,6,7,8,9 в 2.0 / 3.5, но 0, -1, -1, -1, -1, -1, -1, - 1, -1 до 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));
            }
        }
    }
}

И сторона Delphi (скомпилировано с 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.

Ответы [ 4 ]

6 голосов
/ 09 декабря 2010

Кажется, это ошибка в отладчике Visual Studio 2010.Кажется, это измученная память, которая ей не принадлежит.Все проблемы, которые я наблюдал (все из которых могут быть надежно воспроизведены), полностью исчезают, если я запускаю приложение напрямую, а не через Visual Studio 2010.

Ошибка фактически в Managed Debug Assistant.Если вы отключите его полностью (установите HKLM \ Software \ Microsoft.NETFramework \ MDA = "0"), проблема исчезнет.Но, конечно, при этом вы теряете некоторые возможности отладки.

3 голосов
/ 25 января 2012

Появляется, что это проблема со свойством Calling Convention в атрибуте DllImport. Должен быть Cdecl, а не стандартный StdCall. У меня была эта проблема при миграции с 2.0 на 4.0 и запуске в VS2010. Смотрите статью здесь. http://codenition.blogspot.com/2010/05/pinvokestackimbalance-in-net-40i-beg.html

0 голосов
/ 13 декабря 2010

Я вижу похожую проблему с dll Delphi: social_msdn Я заметил, что моя библиотека, скомпилированная с FreePascal (вместо Delphi), работает без проблем даже в VS2010.Поэтому я не знаю, является ли причиной неполадки Delphi, отладчик .NET4 или их комбинация.

Есть некоторые свидетельства того, что память, выделенная во время запуска dll (например, в разделе инициализации), являетсяповреждена память.

0 голосов
/ 03 декабря 2010

Boolean - это однобайтовый тип в Delphi.Таким образом, их изменение должно быть с однобайтовым типом

...