Использование stuct массивов и строк Delphi в C#


Я пытался вызвать метод, который был создан в Delphi следующим образом:

 function _Func1(arrParams: array of TParams): Integer;stdcall;    

 type 
   TParams = record
   Type: int;
   Name: string;
   Amount : Real;
 end;

Мой код:

[DllImport("some.dll", EntryPoint = "_Func1", CallingConvention = CallingConvention.StdCall)]
public static extern int Func(
  [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct)] TParams[] arrParams)

А структура такова:

[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TParams
{
  public int Type;
  [MarshalAs(UnmanagedType.AnsiBStr)]
  public string Name;
  public double Amount;
}

Когда я вызываю этот метод, я получаю ошибку: Не удается маршалировать поле "Name" типа "TParams": недопустимая комбинация управляемого/неуправляемого типа (строковые поля должны быть сопряжены с LPStr, LPWStr, BStr или ByValTStr).

Однако ни одна из этих комбинаций не работает, так как строки Delphi имеют префикс длина и это Ansi наверняка (я пробовал его с другими строковыми параметрами). Есть ли у кого-нибудь ключ, как решить эту проблему?

2 4

2 ответа:

Есть две основные проблемы с этим, использование открытых массивов и использование Delphi string.

Открытые массивы

Открытые массивы Delphi реализуются путем передачи указателя на первый элемент массива и дополнительного параметра, указывающего индекс последнего элемента, high в терминологии Delphi. Для получения дополнительной информации смотрите этот ответ.

Delphi strings

Маршаллер C# не может взаимодействовать со строкой Delphi. Delphi строки являются частными типы, только для внутреннего использования в модуле Delphi. Вместо этого следует использовать строку с нулевым окончанием, PAnsiChar.


Собрав все это вместе, вы можете написать это так:

Delphi

type 
  TParams = record
    _Type: Integer;//Type is a reserved word in Delphi
    Name: PAnsiChar;
    Amount: Double;
  end;

function Func(const arrParams: array of TParams): Integer; stdcall;

C#

[StructLayoutAttribute(LayoutKind.Sequential)]
public struct TParams
{
  public int Type;
  public string Name;
  public double Amount;
}

[DllImport("some.dll")]
public static extern int Func(TParams[] arrParams, int high);

TParams[] params = new TParams[len];
...populate params
int retval = Func(params, params.Length-1);

Чтобы похвалить ответ Дэвида, выможете маршировать к строке Delphi, но это уродливо. В C# вы должны заменить все строки в структуре на IntPtr.

private static IntPtr AllocDelphiString(string str)
{
    byte[] unicodeData = Encoding.Unicode.GetBytes(str);
    int bufferSize = unicodeData.Length + 6;

    IntPtr hMem = Marshal.AllocHGlobal(bufferSize);

    Marshal.WriteInt32(hMem, 0, unicodeData.Length); // prepended length value

    for (int i = 0; i < unicodeData.Length; i++)
        Marshal.WriteByte(hMem, i + 4, unicodeData[i]);

    Marshal.WriteInt16(hMem, bufferSize - 2, 0); // null-terminate

    return new IntPtr(hMem.ToInt64() + 4);
}

Это можно напрямую отправить в Delphi, где оно будет правильно прочитано как строка.

Помните, что выдолжны освободить эту строку, когда закончите с ней. Однако GlobalFree() нельзя вызвать непосредственно по указателю на строку, поскольку он не указывает на начало выделения. Тебе придется это сделать. приведите этот указатель к длинному, затем вычитайте 4, а затем снова приведите его к указателю. Это компенсирует префикс длины.