Calling Delphi 6 DLL from Delphi 2010

We have a Delphi 6 dll that has a PChar passed to it.  The dll function then does a search and returns a PCHar.  This works good using Delphi 6 to call and recieve the dll 

result.  We have upgraded our calling program to Delphi 2010 and most of the time, the value is passed back just fine.  However, we do have times when we get an access 

violation, when calling the dll.  The dll does process the request.   Below is the code.  Has anyone had a similar issue using an older version Delphi Dll with Delphi 2010?  I am sure that Unicode has something to do with it, but not sure how to fix this.  

TIA


 This is the function in the dll.

function ContactSearch(aCriteria: PChar):PChar; stdcall;
var
 lCriteria: String;
begin
lCriteria := aCriteria;
lCriteria := Verify(lCriteria, 'Contact');
Result := PChar(lCriteria);
end;

 

Delphi 2010 calling code

   var
    lRequest: AnsiString;
    dllResult: String;

         dllHandle := LoadLibrary('Search.dll') ;
          if dllHandle <> 0 then
          begin
            @FuncContactSearch := GetProcAddress(dllHandle, 'ContactSearch') ;
            if Assigned (FuncContactSearch) then
             begin
               dllResult := PAnsiChar(FuncContactSearch(PChar(lRequest)));  <---  This populates dllResult with the string that is returned.  This is also where I get a memory error   

             sometimes and it does not populate dllResult  

            // dllResult := FuncContactySearch(PChar(lRequest)) ; //call the function <-- This call would not populate dllResult wi
0
Jan
2/27/2013 11:38:44 PM
embarcadero.delphi.general 4258 articles. 0 followers. Follow

4 Replies
1808 Views

Similar Articles

[PageSpeed] 21

Jan wrote:

> We have a Delphi 6 dll that has a PChar passed to it.

PChar in D6 was an alias for PAnsiChar, but is now an alias for PWideChar 
in D2009+.  You need to adjust your D2010 code accordingly to use PAnsiChar 
directly instead of PChar generically.

> function ContactSearch(aCriteria: PChar):PChar; stdcall;
> var
>   lCriteria: String;
> begin
>   lCriteria := aCriteria;
>   lCriteria := Verify(lCriteria, 'Contact');
>   Result := PChar(lCriteria);
> end;

This kind of code is very dangerous, even in D6.  You are returning a PAnsiChar 
to an AnsiString that gets freed when the function exits, thus returning 
a pointer to invalid memory.  The fact that it works at all simply means 
that the caller is using the pointer before the Memory Manager has a chance 
to overwrite the freed data with something else.  Don't rely on that behavior.

If you need the returned data to persist after the function exits, you must 
either:

1) save the result of the search in a global variable inside of the DLL so 
it remains in memory, then you can return a pointer to it:

{code:delphi}
var
  // if the function gets called by multiple threads, you will have to declare
  // this variable as 'threadvar' so each thread gets its own copy of the 
variable...
  CriteriaResult: String;

function ContactSearch(aCriteria: PChar):PChar; stdcall;
var
  lCriteria: String;
begin
  lCriteria := aCriteria;
  CriteriaResult := Verify(lCriteria, 'Contact');
  Result := PChar(CriteriaResult);
end;
{code}

2) dynamically allocate a memory block to return, and then require the caller 
to pass it back to the DLL when finished using it so it can be freed correctly:

{code:delphi}
function ContactSearch(aCriteria: PChar):PChar; stdcall;
var
  lCriteria: String;
begin
  lCriteria := aCriteria;
  lCriteria := Verify(lCriteria, 'Contact');
  Result := StrNew(PChar(lCriteria));
end;

procedure FreeContactSearchResult(aResult: PChar); stdcall;
begin
  StrDispose(aResult);
end;
{code}

{code:delphi}
var
  dllResult: PAnsiChar;

dllResult := ContactSearch(...);
if dllResult <> nil then
begin
  // use dllResult as needed...
  FreeContactSearchResult(dllResult);
end;
{code}

3) have the caller pass in a block of memory for the DLL to fill in, that 
way the caller decides how to manage the memory:

{code}
function ContactSearch(aCriteria: PChar; aResultBuffer: PChar; aBufferSize: 
Cardinal): Cardinal; stdcall;
var
  lCriteria: String;
  lResultLen: Cardinal
begin
  lCriteria := aCriteria;
  lCriteria := Verify(lCriteria, 'Contact');
  lResultLen := Min(aBufferSize, Length(lCriteria));
  StrPLCopy(aResultBuffer, lCriteria, aBufferSize);
  Result := lResultLen;
end;
{code}

{code:delphi}
var
  dllResult: array[0..255] of AnsiChar;
  dllResultLen: Cardinal;
begin
  dllResultLen := ContactSearch(..., dllResult, 256);
  // use dllResult up to dllResultLen AnsiChars as needed...
{code}

{code:delphi}
var
  dllResult: AnsiString;
  dllResultLen: Cardinal;
begin
  SetLength(dllResult, 256);
  SetLength(dllResult, ContactSearch(..., PAnsiChar(dllResult), 256));
  // use dllResult as needed...
{code}

> I am sure that Unicode has something to do with it, but not sure how to 
fix this. 

It is OK to assign a PAnsiChar to a UnicodeString. The RTL will perform an 
Ansi->Unicode conversion for you.  However, you are casting an AnsiString 
to a PWideChar, which should not even compile let alone work at runtime. 
 Not taking the memory management issue into account, your D2010 code would 
need to look more like this:

{code:delphi}
var
  lRequest: AnsiString;
  dllResult: PAnsiChar;
  FuncContactSearch: function(aCriteria: PAnsiChar): PAnsiChar; stdcall;
begin
  dllHandle := LoadLibrary('Search.dll') ;
  if dllHandle <> 0 then
  begin
    @FuncContactSearch := GetProcAddress(dllHandle, 'ContactSearch') ;
    if Assigned (FuncContactSearch) then
    begin
      dllResult := FuncContactSearch(PAnsiChar(lRequest));
      // use dllResult as needed...
    end;
  end;
end;
{code}

But you do need to take the memory management issue into account, so you 
have to fix your DLL first and then adjust the D2010 code if needed.

--
Remy Lebeau (TeamB)
0
Remy
2/28/2013 12:30:22 AM
Remy:

Would your suggestion require the use of the Borland Memory Manager.  That is one thing the client wants to stay away from?  The Delphi exe is on a network server and we were going to put the dll in the same folder, with the exe.  When multiple clients launch the exe, do we have to use threads, to make sure the client that called the dll frees their copy?  Since I posted this we did change the PChars to PAnsiChar, in the calling program and dll, it did work better.  We still have a few searches that give 
memory errors.  I will give the code below a try and thank you for the samples and advice.

> {quote:title=Remy Lebeau (TeamB) wrote:}{quote}
> Jan wrote:
> 
> > We have a Delphi 6 dll that has a PChar passed to it.
> 
> PChar in D6 was an alias for PAnsiChar, but is now an alias for PWideChar 
> in D2009+.  You need to adjust your D2010 code accordingly to use PAnsiChar 
> directly instead of PChar generically.
> 
> > function ContactSearch(aCriteria: PChar):PChar; stdcall;
> > var
> >   lCriteria: String;
> > begin
> >   lCriteria := aCriteria;
> >   lCriteria := Verify(lCriteria, 'Contact');
> >   Result := PChar(lCriteria);
> > end;
> 
> This kind of code is very dangerous, even in D6.  You are returning a PAnsiChar 
> to an AnsiString that gets freed when the function exits, thus returning 
> a pointer to invalid memory.  The fact that it works at all simply means 
> that the caller is using the pointer before the Memory Manager has a chance 
> to overwrite the freed data with something else.  Don't rely on that behavior.
> 
> If you need the returned data to persist after the function exits, you must 
> either:
> 
> 1) save the result of the search in a global variable inside of the DLL so 
> it remains in memory, then you can return a pointer to it:
> 
> {code:delphi}
> var
>   // if the function gets called by multiple threads, you will have to declare
>   // this variable as 'threadvar' so each thread gets its own copy of the 
> variable...
>   CriteriaResult: String;
> 
> function ContactSearch(aCriteria: PChar):PChar; stdcall;
> var
>   lCriteria: String;
> begin
>   lCriteria := aCriteria;
>   CriteriaResult := Verify(lCriteria, 'Contact');
>   Result := PChar(CriteriaResult);
> end;
> {code}
> 
> 2) dynamically allocate a memory block to return, and then require the caller 
> to pass it back to the DLL when finished using it so it can be freed correctly:
> 
> {code:delphi}
> function ContactSearch(aCriteria: PChar):PChar; stdcall;
> var
>   lCriteria: String;
> begin
>   lCriteria := aCriteria;
>   lCriteria := Verify(lCriteria, 'Contact');
>   Result := StrNew(PChar(lCriteria));
> end;
> 
> procedure FreeContactSearchResult(aResult: PChar); stdcall;
> begin
>   StrDispose(aResult);
> end;
> {code}
> 
> {code:delphi}
> var
>   dllResult: PAnsiChar;
> 
> dllResult := ContactSearch(...);
> if dllResult <> nil then
> begin
>   // use dllResult as needed...
>   FreeContactSearchResult(dllResult);
> end;
> {code}
> 
> 3) have the caller pass in a block of memory for the DLL to fill in, that 
> way the caller decides how to manage the memory:
> 
> {code}
> function ContactSearch(aCriteria: PChar; aResultBuffer: PChar; aBufferSize: 
> Cardinal): Cardinal; stdcall;
> var
>   lCriteria: String;
>   lResultLen: Cardinal
> begin
>   lCriteria := aCriteria;
>   lCriteria := Verify(lCriteria, 'Contact');
>   lResultLen := Min(aBufferSize, Length(lCriteria));
>   StrPLCopy(aResultBuffer, lCriteria, aBufferSize);
>   Result := lResultLen;
> end;
> {code}
> 
> {code:delphi}
> var
>   dllResult: array[0..255] of AnsiChar;
>   dllResultLen: Cardinal;
> begin
>   dllResultLen := ContactSearch(..., dllResult, 256);
>   // use dllResult up to dllResultLen AnsiChars as needed...
> {code}
> 
> {code:delphi}
> var
>   dllResult: AnsiString;
>   dllResultLen: Cardinal;
> begin
>   SetLength(dllResult, 256);
>   SetLength(dllResult, ContactSearch(..., PAnsiChar(dllResult), 256));
>   // use dllResult as needed...
> {code}
> 
> > I am sure that Unicode has something to do with it, but not sure how to 
> fix this. 
> 
> It is OK to assign a PAnsiChar to a UnicodeString. The RTL will perform an 
> Ansi->Unicode conversion for you.  However, you are casting an AnsiString 
> to a PWideChar, which should not even compile let alone work at runtime. 
>  Not taking the memory management issue into account, your D2010 code would 
> need to look more like this:
> 
> {code:delphi}
> var
>   lRequest: AnsiString;
>   dllResult: PAnsiChar;
>   FuncContactSearch: function(aCriteria: PAnsiChar): PAnsiChar; stdcall;
> begin
>   dllHandle := LoadLibrary('Search.dll') ;
>   if dllHandle <> 0 then
>   begin
>     @FuncContactSearch := GetProcAddress(dllHandle, 'ContactSearch') ;
>     if Assigned (FuncContactSearch) then
>     begin
>       dllResult := FuncContactSearch(PAnsiChar(lRequest));
>       // use dllResult as needed...
>     end;
>   end;
> end;
> {code}
> 
> But you do need to take the memory management issue into account, so you 
> have to fix your DLL first and then adjust the D2010 code if needed.
> 
> --
> Remy Lebeau (TeamB)
0
Jan
2/28/2013 1:34:58 AM
Hello Jan,

> Remy:
> 
> Would your suggestion require the use of the Borland Memory Manager.
> That is one thing the client wants to stay away from?

D6 uses the Borland MM by default.  D2010 uses FastMM instead.  You can download 
the full version of FastMM from http://fastmm.sourceforge.net if you want 
to use it in your D6 code.

In any case, any memory allocated by the DLL has to be freed by the DLL, 
and any memory allocated by the app has to be freed by the app.  This ensures 
the same memory manager, whatever it happens to be, is used to allocate a 
free a given block of memory.

> The Delphi exe is on a network server and we were going to put the dll 
in the same
> folder, with the exe.

Do you have to use the DLL version of the Memory Manager at all?  In most 
situations, it is desirable to static-link the MM directly into apps/DLLs 
instead.  The only time where you have to use a external MM is if you are 
using Runtime Packages, so they all share the same MM.

--
Remy Lebeau (TeamB)
0
Remy
2/28/2013 3:21:50 AM
On Wed, 27 Feb 2013 17:34:58 -0800, Jan B <> wrote:

>Remy:
>
>Would your suggestion require the use of the Borland Memory Manager.  That is one thing the client wants to stay away from?  The Delphi exe is on a network server and we were going to put the dll in the same folder, with the exe.  When multiple clients launch the exe, do we have to use threads, to make sure the client that called the dll frees their copy?  Since I posted this we did change the PChars to PAnsiChar, in the calling program and dll, it did work better.  We still have a few searches that give
 
>memory errors.  I will give the code below a try and thank you for the samples and advice.

What you are doing is certain to on occasion fail.  The only question
is the frequency of such failures.

Furthermore, bugs like this are extremely hard to find while
debugging.
0
Loren
2/28/2013 5:04:15 AM
Reply: