Элегантный способ решения этой проблемы (Проблема Unicode-PAnsiString) - PullRequest
2 голосов
/ 21 сентября 2009

Рассмотрим следующий сценарий:

type 
PStructureForSomeCDLL = ^TStructureForSomeCDLL;
TStructureForSomeCDLL = record 
  pName: PAnsiChar;
end

function FillStructureForDLL: PStructureForSomeDLL;
begin
  New(Result);
  // Result.pName := PAnsiChar(SomeObject.SomeString);  // Old D7 code working all right
  Result.pName := Utf8ToAnsi(UTF8Encode(SomeObject.SomeString));  // New problematic unicode version
end;

...code to pass FillStructureForDLL to DLL...

Проблема в версии Unicode заключается в том, что при преобразовании строки теперь возвращается новая строка в стеке, которая возвращается в конце вызова FillStructureForDLL, оставляя DLL с поврежденными данными. В старом коде D7 не было промежуточных функций преобразования и, следовательно, нет проблем.

Мое текущее решение - это функция конвертера, подобная приведенной ниже, что слишком много для IMO. Есть ли более элегантный способ достижения того же результата?

var gKeepStrings: array of AnsiString;

{ Convert the given Unicode value S to ANSI and increase the ref. count 
  of it so that returned pointer stays valid }
function ConvertToPAnsiChar(const S: string): PAnsiChar;
var temp: AnsiString;
begin
  SetLength(gKeepStrings, Length(gKeepStrings) + 1);
  temp := Utf8ToAnsi(UTF8Encode(S));
  gKeepStrings[High(gKeepStrings)] := temp; // keeps the resulting pointer valid 
                                            // by incresing the ref. count of temp.
  Result := PAnsiChar(temp);
end;

Ответы [ 4 ]

3 голосов
/ 21 сентября 2009

Одним из способов может быть решение проблемы до того, как она станет проблемой, под которой я подразумеваю адаптацию класса SomeObject для поддержки зашифрованной ANSI-версии SomeString (ANSISomeString?) Для вас вместе с исходной SomeString сохраняя два в шаге в «сеттере» для свойства SomeString (используя то же преобразование UTF8> ANSI, которое вы уже делаете).

В версиях компилятора, отличных от Юникода, ANSISomeString делает просто «копию» строки SomeString, которая, конечно, не будет копией, а просто дополнительным счетчиком ссылок на SomeString. В версии Unicode он ссылается на отдельную кодировку ANSI с тем же «временем жизни», что и исходная SomeString.

procedure TSomeObjectClass.SetSomeString(const aValue: String);
begin
  fSomeString := aValue;

{$ifdef UNICODE}
  fANSISomeString := Utf8ToAnsi(UTF8Encode(aValue));
{$else}
  fANSISomeString := fSomeString;
{$endif}
end;

В вашей функции FillStructure ... просто измените свой код, чтобы он ссылался на свойство ANSISomeString - тогда это полностью не зависит от того, компилируется ли для Unicode или нет.

function FillStructureForDLL: PStructureForSomeDLL;
begin
  New(Result);
  result.pName := PANSIChar(SomeObject.ANSISomeString);
end;
2 голосов
/ 21 сентября 2009

Надеюсь, у вас уже есть код в вашем приложении для правильного удаления всех динамически распределенных записей, которые вы New() в FillStructureForDLL(). Я считаю этот код очень сомнительным, но давайте предположим, что это сокращенный код только для демонстрации проблемы. В любом случае, DLL, которой вы передаете экземпляр записи, не заботится о том, насколько велик кусок памяти, он все равно получит только указатель на нее. Таким образом, вы можете увеличить размер записи, чтобы освободить место для строки Pascal, которая теперь является временным экземпляром в стеке в версии Unicode:

type 
  PStructureForSomeCDLL = ^TStructureForSomeCDLL;
  TStructureForSomeCDLL = record 
    pName: PAnsiChar;
    // ... other parts of the record
    pNameBuffer: string;
  end;

И функция:

function FillStructureForDLL: PStructureForSomeDLL;
begin
  New(Result);
  // there may be a bug here, can't test on the Mac... idea should be clear
  Result.pNameBuffer := Utf8ToAnsi(UTF8Encode(SomeObject.SomeString));
  Result.pName := Result.pNameBuffer;
end;

Кстати: у вас даже не было бы этой проблемы, если бы запись, передаваемая в DLL, была переменной стека в процедуре или функции, которая вызывает функцию DLL. В этом случае временные строковые буферы будут необходимы только в версии Unicode, если нужно передать более одного PAnsiChar (в противном случае вызовы преобразования будут повторно использовать временную строку). Попробуйте изменить код соответствующим образом.

Изменить:

Вы пишете в комментарии:

Это было бы лучшим решением, если бы была возможна модификация структур DLL.

Вы уверены, что не можете использовать это решение? Дело в том, что из POV DLL структура не изменяется вообще. Возможно, я не прояснил себя, но DLL будет не заботиться о том, является ли переданная ей структура именно такой, какой она была объявлена ​​. Будет передан указатель на структуру, и этот указатель должен указывать на блок памяти, который по крайней мере такой же большой, как структура, и должен иметь ту же структуру памяти. Однако это может быть блок памяти, который на больше , чем исходная структура, и содержит дополнительные данные.

На самом деле это используется во многих местах в Windows API. Вы когда-нибудь задумывались, почему в Windows API есть структуры, которые в первую очередь содержат порядковое значение, дающее размер структуры? Это ключ к развитию API, сохраняя обратную совместимость. Всякий раз, когда для работы функции API требуется новая информация, она просто добавляется к существующей структуре, и объявляется новая версия структуры. Обратите внимание, что макет памяти старых версий структуры сохраняется. Старые клиенты DLL могут по-прежнему вызывать новую функцию, которая будет использовать член структуры размера, чтобы определить, какая версия API вызывается.

В вашем случае не существует разных версий структуры, если речь идет о DLL. Однако вы можете объявить его большим для своего приложения, чем он есть на самом деле, при условии, что структура реальной структуры памяти сохранена, а дополнительные данные только добавлены . Единственный случай, когда это не сработает, - это когда последняя часть структуры была записью с переменным размером, примерно как структура Windows BITMAP - фиксированный заголовок и динамические данные. Однако ваша запись выглядит так, как будто она имеет фиксированную длину.

2 голосов
/ 21 сентября 2009

Есть как минимум три способа сделать это.

  1. Вы можете изменить класс SomeObject определение использовать AnsiString вместо строки .
  2. Вы могли бы использовать систему преобразования, чтобы держать ссылки, как в вашем примере.
  3. Вы можете инициализировать result.pname с GetMem и скопируйте результат преобразование в result.pname^ с Move. Просто не забудьте FreeMem это когда вы закончите.

К сожалению, ни один из них не является идеальным решением. Так что взгляните на варианты и решите, какой из них лучше для вас.

0 голосов
/ 21 сентября 2009

PChar (AnsiString (SomeObject.SomeString)) не будет работать?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...