Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I return a PChar from a DLL function to a VB6 application without risking crashes or memory leaks?

I have to create a DLL which is used by a VB6 application. This DLL has to provide several functions, some of them must return strings.

This is the VB6 declaration:

Declare Function MyProc Lib "mylib.dll" (ByVal Param As String) As String

And this the Delphi implementation stub in mylib.dll:

function MyProc(AParam: PChar): PChar; stdcall;
var
  ReturnValue: string;
begin
  ReturnValue := GetReturnValue(AParam);
  Result := ???;
end;

What do I have to return here? Who will free the memory of the returnd PChar string?

EDIT: I'm asking about Delphi 2005 (PChar = PAnsiChar)

like image 445
Daniel Rikowski Avatar asked Nov 09 '09 08:11

Daniel Rikowski


4 Answers

You need to craft a BSTR instead. VB6 strings are actually BSTRs. Call SysAllocString() on the Delphi side and return the BSTR to the VB6 side. The VB6 side will have to call SysFreeString() to free the string - it will do it automatically.

If PChar corresponds to an ANSI string (your case) you have to manually convert it to Unicode - use MultiByteToWideChar() for that. See this answer for how to better use SysAllocStringLen() and MultiByteToWideChar() together.

like image 76
sharptooth Avatar answered Nov 12 '22 07:11

sharptooth


If you don't want to risk crashes or memory leaks, then craft your API using the Windows API as a model. There, the API functions generally don't allocate their own memory. Instead, the caller passes a buffer and tells the API how big the buffer is. The API fills the buffer up to that limit. See the GetWindowText function, for example. Functions don't return pointers, unless they're pointers to things the caller already provided. Instead, the caller provides everything itself, and the function just uses whatever it's given. You almost never see an output buffer parameter that isn't accompanied by another parameter telling the buffer's size.

A further enhancement you can make to that technique is to allow the function to tell the caller how big the buffer needs to be. When the input pointer is a null pointer, then the function can return how many bytes the caller needs to provide. The caller will call the function twice.

You don't need to derive your API from scratch. Use already-working APIs as examples for how to expose your own.

like image 23
Rob Kennedy Avatar answered Nov 12 '22 05:11

Rob Kennedy


Combining Sharptooth and Lars D's answer; aren't widestrings already allocated via windows and BSTR?

like image 2
Marco van de Voort Avatar answered Nov 12 '22 06:11

Marco van de Voort


I'm not familiar with Dephi, but here are the two main options when using strings with a non-COM DLL and VB6.

Option 1. Use "ANSI" strings.

'DLL routine expecting to be passed pointers to ANSI strings '
'VB6 will allocate and deallocate the strings '
'Its vital that VB6 allocates sufficient space for the return string '
Declare Sub MyProc Lib "mylib.dll" (ByVal Param As String, _ 
  ByVal OutVal As String) 

Function DoMyProc(ByVal Param As String) As String
  Dim sResult As String
  sResult = Space$(255)  ' create 255 bytes of space for the return string '
  Call MyProc(Param, sResult) 
  DoMyProc = sResult
End Function

Option two. Use BSTRs.

'DLL routine expecting to be passed two BSTRs. It will modify the second one. '
'VB6 "owns" both BSTRs and will deallocate them when it has finished with them. '
Declare Sub MyProc(ByVal lpParam As Long, ByVal lpOutVal As Long)

Function DoMyProc(ByVal Param As String) As String
  Dim sResult As String
  Call MyProc(StrPtr(Param), StrPtr(sResult)) 
  DoMyProc = sResult
End Function

I'd also suggest looking at the Microsoft advice on writing C DLLs to be called from VB. Originally released with VB5 but still relevant to VB6.

like image 2
MarkJ Avatar answered Nov 12 '22 06:11

MarkJ