tags:

views:

277

answers:

4

I have a DLL which provided a decoding function, as follows:

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

The HostApp call "MyDecode", and transfer into the Source, SourceLen and Dest parameters, the DLL returns decoded Dest and DestLen. The problem is: The HostApp impossible to know decoded Dest length, and therefore would not know how to pre-allocated Dest's memory.

I know that can split "MyDecode" into two functions:

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

But, My decoding process is very complicated, so if split into two functions will affect the efficiency.

Is there a better solution?


Yes Alexander, this may be a good solution. HostApp code:

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

DLL code:

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;

Maybe I should change Dest's type to Pointer.

Which is a better allocate memory method? GetMem/New, or HeapAlloc?

+7  A: 

You can split "MyDecode" into two routines by other way:

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

I.e. - you allocate memory in MyDecode instead of asking a caller to do that.

Alexander
Note, that you may not implement FreeDecodeResult, if you'll use some common allocator for both caller and callee. For example, if you allocate memory via LocalAlloc instead of GetMem. Then the caller should call LocalFree instead of FreeDecodeResult.
Alexander
+4  A: 

You could use the same technique that most Windows API use, that is, if your buffer is not large enough, the function returns the size of the buffer needed. That way, you can allocate a buffer of the right size from the calling function.

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;

EDIT: As suggested by mghie. Since parameter is PCHAR, it will be assumed the iLenNeeded returned by MyDecode will be the number of TCHAR required as is (mostly?) standard by the 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;
Ken Bourassa
+1, but this has some potential for confusion, as `Len` could mean either buffer length or string length. To be on the safe side I would not use `GetMem()` but `SetLength()` on a string, this would take care of the trailing null character. Regarding efficiency: If the DLL caches the decoded data in a `threadvar` it need not be any less efficient.
mghie
Depending on the exact implementation of MyDecode, using pchar might not be the best idea either... But I'll edit it and show an alternate use.
Ken Bourassa
Yes, I known this is the Windows API solution.It may be a best solution if the dll funtion is simple.But my "MyDecode" is a time-consuming funtion, so I don't want call it twice.
Leo
as mghie mentionned, the operation result can be cached so that on subsequent calls it only need to copy the data to the application supplied buffer. Whether you use threadvar or another mechanism, the overhead remain small.
Ken Bourassa
@Ken: I removed that comment, as the long string solution takes care of the trailing null byte, so it will work correctly with both meanings of `Len`. The buffer may just be a little too big.
mghie
The comment was pertinent... Length('Hello world') <> Length('Hello world'#0). Long string are implicitly null-terminated, so if I need a buffer for a null-terminated string, I always use (Len - 1) to have a string of the proper size. If you really prefer, you can call Setlength(Result, Length(Result) - 1) at the end of the function... But IMO, it's a useless reallocation.
Ken Bourassa
@Ken: If the parameters are declared as `PChar` then allowing for 'Hello world'#0 (the null being part of the data) makes for an insane API. It breaks all assumptions when using the DLL from C-like languages. This is exactly why I wrote my first comment about buffer size vs. data length. Your comment IMHO only complicates stuff for readers of the code.
mghie
Actually, if not using Len - 1, you'll end up with 'Hello world'#0. Not because of the PChar declaration, but because of how long string are working themselves. For exemple, calling SetLenght(S,10) actually reserve 11 characters, 10 for the text, and 1 for the termination null character. If the API ask for 10 character including the null character, the resulting string will have a null being part of the resulting string.i.e. S[Length(S)] will be null. All Windows API returning a null-terminated string will give you a similar result.
Ken Bourassa
All such Windows API calls will return the string length excluding the null delimiter. The returned character data will never contain embedded nulls, unless this is a double-null terminated list of null-terminated strings. That's exactly because it is a delimiter, not data. Declaring a parameter as `PChar` implies this. If there can be embedded nulls it shouldn't be a `PChar` to begin with. Why would this `MyDecode()` API be any different?
mghie
After digging a bit around a few Windows API, it seems to be largely dependent on specific implementation and not really standard.ExpandEnvironmentStrings and LCMapString returns the number of TChar including Null. GetEnvironmentVariable returns the count excluding it. I'm going to update the comment to more accurately reflect this.
Ken Bourassa
+2  A: 

Another option is to pass into the dll a function pointer to allocate memory. The dll calls this function when it needs memory and since the memory is allocated using the application's memory manager the application can just free it.

Unfortunately this does not really solve your problem but only moves it to the dll which must then figure out how much memory it needs. Maybe you could use multiple buffers stored in a linked list so every time the decode function runs out of memory it just allocates another buffer.

dummzeuch
+1  A: 

I'm not sure if this will suit you, but (in this particular example) you can use WideString:

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

Or:

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

By using WideString you can avoid memory allocation problems at all.

Why this will work? Because WideString is alias for system's type BSTR. And BSTR have special rule: its memory must be allocated via specific system memory manager. I.e. when you work with WideString, Delphi calls this system memory manager and not its own. Since system memory manager is accessible from each module (and it is the same for each module) - this means that both caller (exe) and callee (DLL) will use the same memory manager, thus allowing them to pass data without problems.

So, you can use WideString and just produce results without worrying about memory. Just note, that charaters in WideString are unicode - i.e. 2 bytes. You will have a little overhead with converting ANSI<->unicode, if you're using D2007 and below. This (usually) is not a problem, since typical application makes a tons of WinAPI calls - and each WinAPI call means the same ANSI<->unicode convertion (since you're calling A-functions).

Alexander
would that work even if hostapp is not a delphi program?
PA
Yes, since WideString is BSTR, non-Delphi application will need to use BSTR, which is system type (actually, a COM type). See here: http://msdn.microsoft.com/en-us/library/ms221069(VS.100).aspxExample of using BSTR from C++: http://msdn.microsoft.com/en-us/library/xda6xzx7(VS.100).aspx
Alexander