AnsiString возвращает значения из DLL-библиотеки Delphi 2007 в приложении Delphi 2009

У меня есть DLL, скомпилированная с D2007, которая имеет функции, которые возвращают AnsiStrings.

Мое приложение скомпилировано в D2009. Когда он вызывает функции AnsiString, он возвращает мусор.

Я создал небольшое тестовое приложение /dll для экспериментов и обнаружил, что если приложение и dll скомпилированы с одной и той же версией Delphi (2007 или 2009), проблем нет. Но когда один компилируется в 2009 году, а другой 2007, я получаю мусор.

Я пытался включить последнюю версию FastMM в оба проекта, но даже в этом случае приложение 2009 года не может читать AnsiStrings из dll 2007 года.

Есть идеи, что здесь происходит не так? Есть ли способ обойти это?

6 ответов

Внутренняя структура AnsiStrings изменилась в период между Delphi 2007 и Delphi 2009. (Не расстраивайтесь; такая возможность присутствует со дня 1). Строка Delphi 2009 поддерживает число, указывающее, в какой кодовой странице находятся ее данные.

Я рекомендую вам делать то, что делает любая другая DLL на Земле, и передавать символьные буферы, которые может заполнять функция. Вызывающая сторона должна передать указатель буфера и число, указывающее размер буфера. (Убедитесь, что вы точно знаете, измеряете ли вы размер в байтах или символах.) Функция DLL заполняет буфер, записывая не более указанного размера, считая завершающий нулевой символ.

Если вызывающая сторона не знает, сколько байтов должно быть в буфере, у вас есть два варианта:

  • Заставьте DLL вести себя особенно, когда указатель входного буфера нулевой. В этом случае верните требуемый размер, чтобы вызывающий мог выделить столько места и вызвать функцию во второй раз.

  • Сделайте так, чтобы DLL выделяла пространство для себя с помощью заранее определенного метода, доступного для вызывающей стороны, чтобы позже освободить буфер. DLL может либо экспортировать функцию для освобождения выделенных буферов, либо вы можете указать некоторую взаимно доступную функцию API для вызывающей стороны, такую ​​как GlobalFree, Ваша DLL должна использовать соответствующий API распределения, такой как GlobalAlloc, (Не используйте встроенные в Delphi функции выделения памяти, такие как GetMem или же New; нет гарантии, что менеджер памяти звонящего будет знать, как звонить Free или же Dispose, даже если он написан на том же языке, даже если он написан с той же версией Delphi.)

Кроме того, эгоистично писать DLL, которая может использоваться только одним языком. Напишите свои библиотеки DLL в том же стиле, что и Windows API, и вы не ошибетесь.

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

В средстве просмотра справки просмотрите тему (Unicode в RAD Stufio) ms-help://embarcadero.rs2009/devcommon/unicodeinide_xml.html

Возвращая строку Delphi 2007 в Delphi 2009, вы должны получить две проблемы.

Во-первых, кодовая страница, упомянутая Робом. Вы можете установить это, объявив другую AnsiString и вызвав StringCodePage для новой AnsiString. Затем присвойте это старому AnsiString, вызвав SetCodePage. Это должно сработать, но если этого не произойдет, все еще есть надежда.

Вторая проблема - размер элемента, который будет чем-то совершенно безумным. Это должно быть 1, поэтому сделайте это 1. Проблема здесь в том, что нет функции SetElementSize, на которую можно опереться.

Попробуй это:

var
  ElemSizeAddr: PWord; // Need a two-byte type
  BrokenAnsiString: AnsiString; // The patient we are trying to cure
...
  ElemSizeAddr := Pointer(PAnsiChar(BrokenAnsiString) - 10);
  ElemSizeAddr^ := 1; // The size of the element

Это должно сделать это!

Теперь, если вещь StringCodePage/SetCodePage не сработала, вы можете сделать то же, что и выше, изменив строку, в которой мы получаем адрес для вычета 12 вместо 10.

Он взломал все это, поэтому я люблю это.

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

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

Я согласен с Робом и Реми: обычные Dll должны возвращать PAnsiChar вместо AnsiStrings.

Если DLL работает нормально скомпилировано с D2009, почему просто не перестать компилировать ее с D2007 и начать компилировать ее с D2009 раз и навсегда?

Скорее всего, вам просто нужно конвертировать DLL в 2009. По словам Embarcadero, конвертация в 2009 является "простой" и не займет у вас совсем никакого времени.

Ваша DLL не должна возвращать значения AnsiString для начала. Единственный способ, который в первую очередь будет работать правильно, - это если бы DLL и EXE были скомпилированы с помощью модуля ShareMem, и даже тогда, только если они скомпилированы с одной и той же версией Delphi. Диспетчер памяти D2007 несовместим с диспетчером памяти D2009 (или любым другим кросс-версионным использованием диспетчеров памяти), AFAIK.

Как быстрое решение здесь: если ваши фактические данные, которые вы передаете обратно из dll в строке, не превышают 255 символов, вы можете изменить как in-dll, так и интерфейсные описания, чтобы использовать ShortString, которая будет работать независимо от версии 2007/2009, Поскольку вы используете AnsiString уже в 2007 году без идентификатора кодовой страницы, Unicode не доставит вам никаких хлопот.

если вы идете по этому пути, все, что вам нужно сделать, это изменить объявления, как:

function MyStringReturningFunction : ShortString ; external 'MyLibrary.dll';

(и в dll: function MyStringReturningFunction : ShortString; соответственно)

То же самое касается параметров ввода / вывода, конечно:

procedure MyStringTakingAndReturningFunction(s1:ShortString; var s2:ShortString); external 'MyLibrary.dll';

Должно быть проще, чем менять много кода. Но будьте осторожны, как я уже сказал, ваши данные не должны превышать 255 символов, так как это максимальный размер, который может содержать ShortString.

Другие вопросы по тегам