Подтвердить что ты не робот

Изменение среды P/Invoke в .NET 4.0?

Я начал обновлять приложение .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 (С#), я изменил их, чтобы быть более явным, на всякий случай.

---------------------------- Изменить ------------------ -------------------

Я уже писал ранее о 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.
4b9b3361

Ответ 1

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

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

Ответ 2

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

Ответ 3

Boolean является однобайтным типом на Delphi. Поэтому их изменение должно быть с одним байтовым типом

Ответ 4

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

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