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), однако есть много ресурсов, которые могут помочь вам перенести ваше приложение.
White Paper: Delphi and Unicode (from Marco Cantù)
- Delphi Conversion Unicode Проблемы
- "Глобализация ваших приложений Delphi" - Delphi Unicode Resources
- Компиляция ресурсов для перехода на Delphi 2009/2010 Unicode