Использование Delphi DLL с динамическим массивом из C # - PullRequest
3 голосов
/ 13 ноября 2011

У меня есть Delphi DLL, которая содержит следующие типы:

type
  TStepModeType = (smSingle, smMultiStep);

  TParameter = record
    Number: Integer;
  end;

  TStruct = record
    ModType: PAnsiChar;
    ModTypeRev: Integer;
    ModTypeID: Integer;
    RecipeName: PAnsiChar;
    RecipeID: Double;
    RootParamCount: Integer;
    StepMode: TStepModeType;
    ParamCount: Integer;
    Parameters: array of TParameter;
  end;

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

enum stepModeType
{
    Single,
    MultiStep
}

[StructLayout(LayoutKind.Sequential)]
struct parameter
{
    public int Number;
}

[StructLayout(LayoutKind.Sequential)]
struct recipe
{
    public string modType;
    public int modTypeRev;
    public int modTypeId;
    public string recipeName;
    public double recipeId;
    public int rootParamCount;
    public stepModeType stepMode;
    public int paramCount;
    public IntPtr parameters;
}

У меня все было хорошо, пока я не столкнулся с динамическим массивом (Параметры: массив TParameter) в коде Delphi. Я понимаю, что динамические массивы являются только конструкцией Delphi, поэтому я решил использовать IntPtr в своем коде на C # в надежде просто получить указатель на массив и извлечь содержимое. К сожалению, я новичок в этом взаимодействии, и я не уверен, что делать с IntPtr.

Допустим, DLL-библиотека Delphi заполняет динамический массив двумя элементами параметров. Может ли кто-нибудь показать мне код C #, который будет извлекать эти 2 параметра из массива, как только он будет передан обратно из Delphi DLL в мое вызывающее приложение C #?

ОБНОВЛЕНИЕ: Как оказалось, код Delphi, который мне дали, был упрощенной версией. Один из наших разработчиков на Delphi подумал, что будет легче начать работу с упрощенной версией, чем с реальной версией, которая значительно сложнее и содержит динамические массивы динамических массивов динамических массивов. Во всяком случае, я сейчас полностью над моей головой. Я только знаю достаточно о Delphi, чтобы быть опасным. Ниже приведен код для реальных структур в коде Delphi. Будем весьма благодарны за любые дальнейшие указания о том, как обращаться с этими структурами из моего C # вызывающего приложения. Это может быть даже невозможно при вложенности динамических массивов, таких как они.

type
  TStepModeType = (smSingle, smMultiStep);

  TParamValue = record
    strVal: String;
    fVal: Double;
    Changed: Boolean;
  end;

  TSteps = array of TParamValue;

  TRule = record
    Value: String;
    TargetEnabled: Boolean;
  end;

  TParamInfo = record
    Caption: String;
    Units: String;
    RuleCount: Integer;
    Rules: array of TRule;
  end;

  TParameter = record
    Info: TParamInfo;
    Steps: TSteps;
  end;

  TStruct = record
    ModType: PAnsiChar;
    ModTypeRev: Integer;
    ModTypeID: Integer;
    RecipeName: PAnsiChar;
    RecipeID: Double;
    RootParamCount: Integer;
    StepMode: TStepModeType;
    ParamCount: Integer;
    Parameters: array of TParameter;
  end;

Ответы [ 3 ]

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

Я предполагаю, что у DLL есть функция, которая освобождает структуру recipe. Это то, что вы не можете надеяться сделать из C #. Подробнее об этом позже.

Динамический массив Delphi не является допустимым типом взаимодействия. Он действительно должен использоваться только внутри кода Delphi, скомпилированного с одной версией компилятора. Публичное раскрытие информации похоже на экспорт классов C ++ из DLL.

В идеальном мире вы бы переработали код Delphi так, чтобы он экспортировал массив, используя правильный тип взаимодействия. Тем не менее, в этом случае вам будет относительно просто выполнить сортировку без изменения кода Delphi.

Динамические массивы Delphi были введены еще в Delphi 4, и с тех пор их реализация осталась неизменной. Переменная динамического массива array of T фактически является указателем на первый элемент. Элементы располагаются последовательно в памяти. Переменная динамического массива также поддерживает (при отрицательных смещениях) счетчик ссылок и размер массива. Вы можете спокойно их игнорировать, поскольку вы не изменяете динамический массив и не нуждаетесь в определении его размера.

Использование IntPtr для поля Parameters идеально. Поскольку TParameter содержит только одно 32-разрядное целое число, вы можете использовать Marshal.Copy, чтобы скопировать его прямо в массив int[].

Итак, когда DLL-библиотека Delphi возвращается, вы можете выполнить последний этап сортировки, используя Marshal.Copy.

if (theRecipe.paramCount>0)
{
    int[] parameters = new int[theRecipe.paramCount];
    Marshal.Copy(theRecipe.parameters, parameters, 0, theRecipe.paramCount);
    ... do something with parameters
}

Это касается динамического массива, но, как это происходит, у вас есть другая проблема с вашим кодом в его нынешнем виде. Вы объявляете две строки как string в структуре C #. Это означает, что маршаллер будет нести ответственность за освобождение памяти, возвращаемой DLL-библиотекой Delphi в двух полях PAnsiChar. Это будет сделано путем вызова CoTaskMemFree. Я вполне уверен, что это не будет соответствовать распределению полей PAnsiChar, сделанных в коде Delphi.

Как указано выше, я ожидаю, что контракт для этого интерфейса заключается в том, что вы вызываете дополнительную функцию DLL для освобождения памяти кучи, на которую ссылается структура recipe. То есть две строки и динамический массив.

Для решения этой проблемы из C # вам нужно убедиться, что маршаллер не пытается освободить поля PAnsiChar. Вы можете добиться этого, объявив их как IntPtr в структуре C #. Затем вызовите Marshal.PtrToStringAnsi для преобразования в строку C #.


Мне пришлось сделать несколько предположений о контракте между кодом Delphi и кодом C #, чтобы написать выше. Если какое-либо из моих предположений неверно, обновите вопрос, и я постараюсь, чтобы этот ответ соответствовал! Надеюсь, это поможет.

0 голосов
/ 13 ноября 2011

Два варианта: либо вы выясните, как именно хранятся динамические массивы, и сопоставите его со стороной c #, либо, что еще лучше, создадите набор базовых методов на стороне Delphi, которые можно вызывать со стороны c # для манипулирования массивом и записью например, getItem, setItem и т. д. Обычно это делается, когда в языковом барьере есть несовместимые типы. Я бы использовал более поздний подход, потому что вы не знаете, может ли когда-нибудь в будущем измениться структура памяти динамического массива.

Кстати, почему вы определили TParameter как запись, вы могли бы использовать TParameter = integer?

Я нашел эту ссылку, в которой есть, что сказать о структуре динамических массивов Delphi:

http://www.programmersheaven.com/mb/delphikylix/262971/262971/dynamic-array-memory-storage/

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

Динамическая структура массива

0 голосов
/ 13 ноября 2011

Жаргон путаница, я подозреваю, моя первая мысль была просто.

открытый параметр [] параметры;

...