Использование 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 ответа:
Есть две основные проблемы с этим, использование открытых массивов и использование 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, а затем снова приведите его к указателю. Это компенсирует префикс длины.