Delphi 7 вызывает DelphiXE2 DLL, получая поврежденные строки

У меня есть приложение Delphi 7, которому нужно вызывать API-интерфейс SOAP, который является слишком новым для доступных импортеров SOAP. Я убедился, что D7 не может вызвать SOAP API без особых усилий, чтобы окупиться. Но у меня также есть Delphi XE2, и он может импортировать SOAP и вызывать его довольно счастливо. Итак, я написал простую оболочку dll в XE2, которая предоставляет необходимые части интерфейса мыла. Я могу вызвать DLL из программы XE.

В Delphi7 я взял файл импорта SOAP API из XE, вычеркнул определения {$SCOPED_ENUMS ON} и раздел инициализации, который вызывает недоступные оболочки SOAP, плюс изменил строку на widestring. Это компилируется. Я использую FastMM с включенным ShareMM, чтобы заставить передачу строк работать и не делать все как stdcall.

Причина, по которой я пытаюсь сделать это таким образом, заключается в том, что если это сработает, то это сделает код SOAP очень простым для кодирования и обслуживания, поскольку 90% кода генерируется импортером XE2 SOAP, и это будет означать, что когда мы Переместите приложение D7 в современный Delphi, код в основном останется неизменным.

Но когда я запускаю его, я получаю странные строки (и последующие нарушения доступа). У меня есть простые функции, которые не используют код SOAP, чтобы сделать проблему более очевидной.

Передача самой широкой строки из Delphi7 exe в DelphiXE2 dll, длина строки удваивается (согласно функции Length()), но нет соответствующего преобразования данных. Таким образом, самая широкая строка "123" в D7 становится "1234...." в XE2, где.... это любой мусор, который оказывается в стеке. Рассматриваемые как байтовые массивы, оба имеют половину нулевых байтов, как и ожидалось.

Передав обратно самую широкую строку из DLL XE2 в D7, я получаю зеркальный эффект - длина строки уменьшается вдвое, а строки просто усекаются ("1234" становится "12").

Я вставляю код, потому что знаю, что ты его попросишь.

В Delphi XE2 я экспортирую эти функции:

// testing
function GetString(s:string):string; export;
function AddToString(s:string):string; export;

implementation

function GetString(s:string):string;
begin
  Result := '0987654321';
end;

function AddToString(s:string):string;
begin
  Result := s + '| ' + IntToStr(length(s)) + ' there is more';
end;

В Delphi 7:

function GetString(s:widestring):widestring; external 'SMSShim.dll';
function AddToString(s:widestring):widestring; external 'SMSShim.dll';

procedure TForm1.btnTestGetClick(Sender: TObject);
var
  s: widestring;
begin
  s := widestring('1234');
  Memo1.Lines.Add(' GetString: ' + GetString(s));
end;

procedure TForm1.btnTestAddClick(Sender: TObject);
var
  s: widestring;
begin
  s := widestring('1234567890');
  Memo1.Lines.Add(' AddToString: ' + AddToString('1234567890'));
end;

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

Досадно, если я объявляю импорт в delphi7 как строки, я получаю правильную длину, но неверные данные. При объявлении, как показано, я получаю действительные данные, неправильную длину и нарушения доступа при попытке вернуться.

Создание всего этого stdcall не меняет поведение.

Очевидное решение - просто написать простые функции-обертки, которые предоставляют именно ту функциональность, которая мне нужна прямо сейчас. Я могу это сделать, но я бы предпочел хитрый способ.

2 ответа

Решение

Рассматриваемая DLL экспортирует функции, которые ожидают получить UnicodeString параметры. (Как вы знаете, string тип стал псевдонимом для UnicodeString в Delphi 2009.) Приложение Delphi 7 не может использовать эту DLL; библиотека времени выполнения не знает, как работать с этим типом, потому что она не существовала в 2002 году, когда была опубликована Delphi 7.

Хотя размер символа для UnicodeString совместим с WideStringони не одинаковые типы. UnicodeString структурирован как новый AnsiString, поэтому он имеет поле длины, счетчик ссылок, размер символа и кодовую страницу. WideString имеет поле длины, но любые другие метаданные, которые он несет, недокументированы. WideString это просто способ Delphi разоблачить COM BSTR тип.

Общее правило - никогда не экспортировать функции DLL, которые не могут быть использованы C.1 В частности, это означает использование только C-совместимых типов для любых параметров функции и возвращаемых типов, поэтому string нет, но WideString безопасно из-за его BSTR корнеплоды.

Измените DLL для использования WideString по своим параметрам вместо string,


1 Поддержание совместимости с C также означает использование соглашений о вызовах, которые поддерживает C. Delphi по умолчанию register Соглашение о вызовах не поддерживается в Microsoft C, поэтому используйте cdecl или же stdcall вместо этого, как вы видели в каждой Windows DLL, которую вы когда-либо использовали.

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

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