Воссоздание C # `Struct` версии Delphi` Record` - для передачи в DLL в качестве параметра - PullRequest
3 голосов
/ 28 ноября 2011

Я создаю DLL в Delphi, и она должна работать подобно тому, как работает Windows API.Эта DLL имеет только одну экспортированную функцию ...

function DoSomething(var MyRecord: TMyRecord): Integer; stdcall;

... где TMyRecord = мою запись, которую мне нужно будет заново создать в C #.Если я не ошибаюсь, именно так работает стандартный Windows API.Эта запись также содержит ссылку на другой тип записи ...

TMyOtherRecord = record
  SomeDC: HDC;
  SomeOtherInt: Integer;
end;

TMyRecord = record
  SomeInteger: Integer;
  SomeColor: TColor;
  SomeText: PChar;
  SomeTextSize: Integer;
  MyOtherRecord: TMyOtherRecord;
end;

Вопрос часть 1:

Я хотел бы посмотреть, смогу ли яИзбегайте использования PChar, если это вообще возможно.Я не ожидаю, что что-то более 255 символов будет пройдено.Есть ли другой тип, который я могу использовать вместо этого, который не потребует от меня использования size of string?


Вопрос часть 2:

Мне нужно удвоитьпроверьте, правильно ли я объявляю этот класс структуры C #, поскольку он должен полностью соответствовать записи, объявленной в Delphi ...

public struct MyOtherRecord
{
  public IntPtr SomeDC;
  public int SomeOtherInt;
}

public struct MyRecord
{
  public int SomeInteger;
  public Color SomeColor;
  public string SomeText;
  public int SomeTextSize;
  public MyOtherRecord OtherReord = new MyOtherRecord();
}

Часть вопроса 3:

Безопасно ли в этом случае иметь запись внутри записи (или структуру внутри структуры)?Я уверен, что это так, но мне нужно убедиться.

Ответы [ 2 ]

5 голосов
/ 29 ноября 2011

Я собираюсь предположить, что информация поступает из C # в Delphi, а не наоборот, во многом потому, что это облегчает жизнь при написании ответа, а вы не утверждали иначе!

В этом случае объявление функции Delphi должно быть:

function DoSomething(const MyRecord: TMyRecord): Integer; stdcall;

Во-первых, вы не можете ожидать, что маршаллер P / invoke обработает System.Drawing.Color. Объявите цвет как int и используйте ColorTranslator.ToWin32 и ColorTranslator.FromWin32 для обработки преобразования.


Нет ничего страшного с PChar. Вам не нужно поле с длиной строки, поскольку длина неявно указана в PChar из-за нулевого терминатора. Просто объявите поле как string в структуре C #, PChar в записи Delphi и позвольте маршаллеру P / invoke сделать свое волшебство. Не пытайтесь писать содержимое PChar из Delphi. Это закончится слезами. Если вы хотите передать строку обратно в код C #, то есть способы, но я не буду их здесь рассматривать.


Прекрасно иметь встроенные структуры. Не о чем беспокоиться. Не наделяйте их new. Просто относитесь к ним как к типам значений (которые они есть), например int, double и т. Д.


Со временем вам потребуется добавить атрибуты StructLayout и т. Д., Объявить вашу функцию DLL с помощью DllImport и т. Д.


Подводя итог, я бы объявил ваши структуры следующим образом:

Delphi

TMyOtherRecord = record
  SomeDC: HDC;
  SomeOtherInt: Integer;
end;

TMyRecord = record
  SomeInteger: Integer;
  SomeColor: TColor;
  SomeText: PChar;
  MyOtherRecord: TMyOtherRecord;
end;

function DoSomething(const MyRecord: TMyRecord): Integer; stdcall;

C #

[StructLayout(LayoutKind.Sequential)]
public struct MyOtherRecord
{
  public IntPtr SomeDC;
  public int SomeOtherInt;
}

[StructLayout(LayoutKind.Sequential)]
public struct MyRecord
{
  public int SomeInteger;
  public int SomeColor;
  public string SomeText;
  public MyOtherRecord MyOtherRecord;
}

[DllImport("mydll.dll")]
static extern int DoSomething([In] ref MyRecord MyRecord);

Я не пометил string знаком MarshalAs, поскольку по умолчанию он маршалируется как LPSTR, что совпадает с Delphi 7 PChar.

Я только скомпилировал это в своей голове , поэтому может быть несколько морщин.

2 голосов
/ 29 ноября 2011

Если вы не хотите использовать PChar на стороне Delphi, лучше всего использовать массив символов фиксированной длины.Тем не менее, тип PChar специально создан для обработки таких ситуаций: это строка в C-стиле, заканчивающаяся NULL.Для ясности в вашем определении C # вы можете использовать атрибут MarshalAs, чтобы точно указать, какую «строку» вы ожидаете на сайте вызова.Значение по умолчанию для строки внутри структуры зависит от того, какую версию Framework вы используете: Compact Framework поддерживает только строки Unicode (LPWSTR), в противном случае это будет LPSTR.Поскольку существует 7 различных вариантов маршалинга строк, я всегда указываю тот, который мне нужен, даже если это значение по умолчанию, но я думаю, что в вашем случае это необязательно.

Кроме того, как отмечалось выше, тип C # Color не являетсятакой же, как тип Delphi TColor.TColor - это просто целое число в странном смешанном формате, в то время как Color имеет множество дополнительных свойств, помимо цветов RGB.У вас есть несколько вариантов: определить новую структуру C #, соответствующую определению TColor, или просто использовать int и создавать значения вручную.Вот лучшее описание структуры TColor .

Наконец, для типов значений, таких как struct, вам на самом деле не нужно создавать их экземпляры с помощью new;если вы просто объявляете переменную типа структуры, пространство выделяется для вас.Единственное преимущество использования new для создания экземпляра структуры состоит в том, что ваш конструктор будет работать (у вас его нет), и все поля будут инициализированы до значений по умолчанию.Если вы все равно планируете заполнить все поля, вам не нужны только накладные расходы.

В целом, это то, что я, вероятно, использовал бы:

public struct MyOtherRecord
{
  public IntPtr SomeDC;
  public int SomeOtherInt;
}

public struct MyRecord
{
  public int SomeInteger;
  public int SomeColor;
  [MarshalAs(UnmanagedType.LPSTR)] public string SomeText;
  public int SomeTextSize;
  public MyOtherRecord OtherRecord;
}

Одна вещь, которой я не являюсьуверен, это выравнивание записи.Мне кажется, я помню, что читал, что Delphi «в теории» по умолчанию использовал 8-байтовое выравнивание, но «на практике» он выравнивал поля в зависимости от их типа;это контролируется директивой $ {A}.В C #, если вы не используете явный [StructLayout], ваши поля получат выравнивание на основе их размера.Все в ваших записях является целочисленным значением, поэтому вы должны быть в безопасности, но если вы видите, что выглядит как повреждение данных, проверьте значения «sizeof» для структур Delphi и C # и убедитесь, что они одинаковы.

Если нет, вы можете использовать атрибуты [StructLayout (LayoutKind.Explicit)] и [FieldOffset], чтобы точно указать выравнивание вашей структуры C #.

ОБНОВЛЕНИЕ:

Благодаря@David Heffernan за указание на то, что PChar в Delphi 7 - это LPSTR (в этом случае мое личное предпочтение будет заключаться в том, чтобы использовать PWideChar в Delphi, поскольку .NET CF не поддерживает ANSI, а Windows в любом случае использует UTF-16, нокакой бы ни работал.) Ответ обновлен для соответствия.

...