Я создаю DLL в C++, он будет использоваться в проекте Delphi 7.
Этот вопрос связан с этим, когда я представляю две функции Validate
и GetToken
только, что теперь они будут выполнены в C++, а массив строк GetToken
будет отправлен обратно в Delphi.
Проблемы в том, что я не знаю, как создать функцию в dll, которая вернет массив строки в C++, и я не знаю, как он будет храниться для дальнейшего использования в Delphi.
Объявление функции выглядит следующим образом:
function GetToken(Chain:string):Arrayofstring;
Согласно вашему обзору кода, код Delphi ожидает, что функция будет иметь следующую подпись:
function GetToken(Chain: AnsiString): array of AnsiString;
Вы не можете записать такую функцию в C++. C++ не знает, что такое строки Delphi, и он не знает, что такое динамические массивы Delphi. Оба типа должны быть выделены из диспетчера памяти Delphi, к которым у вашей C++ DLL не будет доступа. Кроме того, C++ не знает, как использовать соглашение о register
Delphi.
Интерфейс DLL был разработан плохо. DLL никогда не должны использовать типы, специфичные для языка, если только разработчик не намерен исключать все другие языки. (И в этом случае исключаются даже более поздние версии одного и того же языка, поскольку определение AnsiString
изменено в Delphi 2009, чтобы включить больше метаданных, которые Delphi 7 не будет обрабатывать должным образом.) Самый безопасный вызов для выбора, как правило, является stdcall
. Это то, что все в Windows API использует.
Лучший интерфейс будет использовать типы, общие для всех языков, и это будет диктовать использование управления памятью, доступное универсально. Существует несколько общих способов сделать это. Например:
Строки возвращаются как простые массивы символов с нуль-терминированием - PAnsiChar
в Delphi; char*
в C++. DLL выделяет буферы для строк, а также выделяет буфер для массива этих строк. Когда приложение хоста завершено с использованием массива и строк, оно вызывает другую функцию, экспортированную DLL, в которой DLL освобождает выделенную память. Это модель, используемая, например, FormatMessage
; когда хост-программа завершена с строкой сообщения, она вызывает LocalFree
.
type
PStringArray = ^TStringArray;
TStringArray = array[0..Pred(MaxInt) div SizeOf(PAnsiChar)] of PAnsiChar;
function GetToken(Char: PAnsiChar): PStringArray; stdcall;
procedure FreeStringArray(StringArray: PStringArray); stdcall;
char** __stdcall GetToken(char const* Chain);
void __stdcall FreeStringArray(char** StringArray);
Используйте COM для возврата Safariray объектов BStr. Это похоже на предыдущую технику, но управление памятью определяется COM, а не вашей DLL, поэтому там меньше информации, которая должна быть определена любой из сторон интерфейса.
Передайте функцию обратного вызова в DLL, поэтому вместо того, чтобы возвращать массив строк, DLL просто вызывает функцию один раз для каждой идентифицируемой строки. Тогда вам не нужно определять, как выглядит какой-либо массив, а время жизни каждой строки может быть просто временем вызова обратного вызова - если хост-приложение хочет получить копию, оно может это сделать. Новая подпись функции будет выглядеть примерно так:
type
TTokenCallback = procedure(Token: PAnsiChar); stdcall;
procedure GetToken(Chain: PAnsiChar; ProcessToken: TTokenCallback); stdcall;
typedef void (__stdcall* TokenCallback)(char const* Token);
void __stdcall GetToken(char const* Chain, TokenCallback ProcessToken);
Если вы не тот, кто проектировал интерфейс DLL, то вам нужно опираться на людей, которые это сделали, и изменить их, чтобы быть более доступными для кода, отличного от Delphi. Если вы не можете этого сделать, то последней альтернативой является написать DLL в Delphi, которая обертывает вашу DLL, чтобы массировать параметры во что-то, что понимает каждая сторона.