Предварительно распределить память между HostApp и DLL - PullRequest
6 голосов
/ 12 апреля 2010

У меня есть DLL, которая обеспечивала функцию декодирования следующим образом:

function MyDecode (Source: PChar; SourceLen: Integer; var Dest: PChar; DestLen: Integer): Boolean; stdcall; 

Вызов HostApp "MyDecode" и передача в параметры Source, SourceLen и Dest, DLL возвращает декодированные Dest и DestLen,Проблема в том, что HostApp невозможно узнать декодированную длину Dest, и поэтому не будет знать, как предварительно выделить память Dest.

Я знаю, что можно разделить «MyDecode» на две функции:

function GetDecodeLen (Source: PChar; SourceLen: Integer): Integer; stdcall;  // Return the Dest's length
function MyDecodeLen (Source: PChar; SourceLen: Integer; var Dest: PChar): Boolean; stdcall; 

Но, мой процесс декодирования очень сложен, поэтому, если разделение на две функции повлияет на эффективность.

Есть ли лучшее решение?


Да, Александр, это может быть хорошим решением.Код HostApp:

//... 
MyDecode(....) 
try 
  // Use or copy Dest data 
finally 
  FreeDecodeResult(...) 
end;

Код DLL:

function MyDecode(...): Boolean;
begin
  // time-consuming calculate

  // Allocate memory
  GetMem(Dest, Size);   
  // or New()?
  // or HeapAlloc()?
end;

procedure FreeDecodeResult(Dest: PChar);
begin
  FreeMem(Dest);
  // or Dispose(Dest); ?
  // or HeapFree(Dest); ?
end;

Возможно, мне следует изменить тип Dest на Pointer.

Какой метод выделения памяти лучше?GetMem / New или HeapAlloc?

Ответы [ 4 ]

8 голосов
/ 12 апреля 2010

Вы можете разделить «MyDecode» на две подпрограммы другим способом:

function  MyDecode(Source: PChar; SourceLen: Integer; out Dest: PChar; out DestSize: Integer): Boolean; stdcall;
procedure FreeDecodeResult(Dest: PChar); stdcall;

т.е. - вы выделяете память в MyDecode вместо того, чтобы просить вызывающего сделать это.

5 голосов
/ 12 апреля 2010

Вы можете использовать ту же технику, что и большинство API Windows, то есть, если ваш буфер недостаточно велик, функция возвращает размер необходимого буфера. Таким образом, вы можете выделить буфер нужного размера из вызывающей функции.

function MyDecode (Source: PChar; SourceLen: Integer; Dest: PChar; var Len: Integer): Boolean; stdcall;

procedure SomeProc;
var iSourceLen, iLenNeeded : Integer;
    pSource, pDest : Pointer;
begin
  MyDecode(pSource, iSourceLen, nil, iLenNeeded);
  GetMem(pDest,iLenNeeded);
  try
    MyDecode(pSource, iSourceLen,pDest, iLenNeeded);
  finally
    FreeMem(pDest);
  end;
end;

РЕДАКТИРОВАТЬ : в соответствии с предложением mghie. Поскольку параметром является PCHAR, предполагается, что iLenNeeded, возвращаемый MyDecode, будет числом TCHAR, требуемым в соответствии со стандартом Windows API (в основном?).

function SomeProc(sEncode : String) : string;
var iLenNeeded : Integer;
begin
  MyDecode(PChar(sEncode), Length(sEncode), nil, iLenNeeded);
  SetLength(Result, iLenNeeded);  //if iLenNeeded include a null-terminating character, you can use (iLenNeeded - 1) instead
  if not MyDecode(PChar(sEncode), Length(sEncode), PChar(Result), iLenNeeded) then
    SetLength(Result, 0);
end;
2 голосов
/ 12 апреля 2010

Другой вариант - передать в dll указатель на функцию для выделения памяти. DLL вызывает эту функцию, когда ей нужна память и, поскольку память выделяется с помощью диспетчера памяти приложения, приложение может просто освободить ее.

К сожалению, это на самом деле не решает вашу проблему, а только перемещает ее в dll, которая затем должна выяснить, сколько памяти ей нужно. Возможно, вы могли бы использовать несколько буферов, хранящихся в связанном списке, поэтому каждый раз, когда функция декодирования исчерпывает память, она просто выделяет другой буфер.

1 голос
/ 13 апреля 2010

Я не уверен, подойдет ли вам это, но (в этом конкретном примере) вы можете использовать WideString:

function MyDecode(Source: PChar; SourceLen: Integer; out Dest: WideString): Boolean; stdcall;

Или:

function MyDecode(Source: PChar; SourceLen: Integer): WideString; stdcall;

Используя WideString, вы вообще можете избежать проблем с выделением памяти.

Почему это будет работать? Потому что WideString является псевдонимом для системного типа BSTR. И у BSTR есть специальное правило: его память должна выделяться через специальный менеджер системной памяти. То есть когда вы работаете с WideString, Delphi вызывает этот диспетчер системной памяти, а не свой собственный. Поскольку диспетчер системной памяти доступен из каждого модуля (и он одинаков для каждого модуля) - это означает, что и вызывающий (exe), и вызываемый (DLL) будут использовать один и тот же диспетчер памяти, что позволит им без проблем передавать данные.

Итак, вы можете использовать WideString и просто получать результаты, не беспокоясь о памяти. Просто отметьте, что символы в WideString являются Unicode - то есть 2 байта. Если вы используете D2007 и ниже, у вас будут небольшие издержки при преобразовании ANSI <-> юникода. Это (как правило) не является проблемой, поскольку типичное приложение выполняет массу вызовов WinAPI - и каждый вызов WinAPI означает одно и то же преобразование Unix-кода в ANSI <-> (поскольку вы вызываете A-функции).

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