2

I'm becoming crazy.

I'm a beginner and I want to make my first DLL. I've followed this guide:

http://www.tutorialspoint.com/dll/dll_delphi_example.htm

I want to set a text information about a program version and read it when I want, so display it to the user through main application. This is just an example to keep confidence with DLLs, I already know that there are many other way to achieve this.

Now I'm trying to read the variable "versione" from a DLL like this:

library Clientdll;


 uses SysUtils, Classes, Dialogs;

{$R *.res}


function Versione(var messaggio, versione: String):string; export; stdcall;
begin
  versione:='Nessun dato ricavato. Valore di chiamata alla DLL errato!';
  if messaggio='chiama' then  versione:='v4.0.0 build 31';
end;

exports versione;

begin
end.

In the main application I've write this:

[...]

implementation

uses unit2;

{$R *.dfm}
function Versione(var messaggio, versione:string):string; stdcall; external 'Clientdll.dll'

[...]

Now I said 'OK, I've just to call the DLL and that's all...'. So:

procedure TForm1.Button1Click(Sender: TObject);
var x, y:string;
begin
 x:='chiama';
 Versione(x,y);
 showmessage(y);
end;

I can read v4.0.0 build 31 in the dialog box, but when I press OK I've receive this error:

"Invalid Pointer Operation".

Any ideas?

I've try to google it, but my english is poor and some answers are difficult to understand, also with translation tools!

Ken White
  • 123,280
  • 14
  • 225
  • 444
Drift89
  • 69
  • 1
  • 1
  • 6
  • 1
    If you use `File->New->Other->DLL wizard` to create your DLL shell, the IDE inserts a very important comment: "{ Important note about DLL memory management: **ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL**... **To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters.** }" Don't use **string** parameters. – Ken White Feb 15 '14 at 16:36
  • You're right. Thanks! But now I receive an exception when I try to exit from the application: Runtime error 217 at 0041470C – Drift89 Feb 15 '14 at 17:03

1 Answers1

6

Don't use String as the parameter type. This is clearly explained in the comment that the IDE generates when you use File->New->Other->Delphi Projects->DLL Wizard to create a new DLL:

{ Important note about DLL memory management: ShareMem must be the first unit in your library's USES clause AND your project's (select Project-View Source) USES clause if your DLL exports any procedures or functions that pass strings as parameters or function results. This applies to all strings passed to and from your DLL--even those that are nested in records and classes. ShareMem is the interface unit to the BORLNDMM.DLL shared memory manager, which must be deployed along with your DLL. To avoid using BORLNDMM.DLL, pass string information using PChar or ShortString parameters. }

In addition, using Delphi strings means that your DLL functions are not callable from other languages such as C.

You should also expect the calling application to provide you with the memory in which to place the results (and a length parameter that tells you how large that memory buffer is, too).

Here's a minimal (quite useless) example of a Delphi dll with a single function, along with a test application that calls it. (The DLL is, as I said, quite meaningless. Any actual DLL should be designed to put the functional code in its own unit and not in the project file.)

The sample DLL source:

library SimpleTest;

{ Important note about DLL memory management: ShareMem must be the
  first unit in your library's USES clause AND your project's (select
  Project-View Source) USES clause if your DLL exports any procedures or
  functions that pass strings as parameters or function results. This
  applies to all strings passed to and from your DLL--even those that
  are nested in records and classes. ShareMem is the interface unit to
  the BORLNDMM.DLL shared memory manager, which must be deployed along
  with your DLL. To avoid using BORLNDMM.DLL, pass string information
  using PChar or ShortString parameters. }

uses
  SysUtils,
  Classes;

{$R *.res}

// Parameters:
//   arg:    Argument that indicates whether this is a test or
//           something else, so we know which value to return
//   Buffer: The space in which to place the result
//   Len:    The length of the buffer provided
function TestDLL(const arg: PChar; const Buffer: PChar; 
  const Len: Integer): Boolean; stdcall;
begin
  // Make sure we use the Len parameter, so we don't overflow
  // the memory we were given. StrLCopy will copy a maximum of
  // Len characters, even if the length of the string provided
  // as the 'source' parameter is longer.
  if arg = 'Test' then
    StrLCopy(Buffer, 'Test result', Len)   
  else
    StrLCopy(Buffer, 'Non-test result', Len);
  Result := True;
end;

exports
  TestDll;

begin

end.

The form for the test application that calls it:

unit DLLTestForm;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form4: TForm4;

implementation

{$R *.dfm}

function TestDLL(const arg: PChar; const Buffer: PChar; const Len: Integer): Boolean; stdcall;
  external 'SimpleTest.dll';

procedure TForm4.Button1Click(Sender: TObject);
var
  Parm1: String;
  Parm2: String;
  BuffLen: Integer;
begin
  Parm1 := 'Test';
  // Length of buffer (including null terminator) for DLL call
  // Chosen arbitrarily - I know the DLL won't return more than 15 + the
  // null. I'm pretending I don't, though, and allowing extra space. The
  // DLL won't return more than 30 characters, even if it has more to say,
  // because it uses StrLCopy to limit the result to Len characters.
  BuffLen := 30;

  // Allocate space for return value
  SetLength(Parm2, BuffLen);

  // Call the DLL with `Test` arg
  if TestDLL(PChar(Parm1), PChar(Parm2), BuffLen) then
    ShowMessage(Parm2);

  // Call the DLL with a different parameter value
  Parm1 := 'Other';
  if TestDLL(PChar(Parm1), PChar(Parm2), BuffLen) then
    ShowMessage(Parm2);
end;

end.
Ken White
  • 123,280
  • 14
  • 225
  • 444
  • Thanks for your help. I've tried your code and it works fine. I'm at the beginning with this devil's DDLs :D – Drift89 Feb 15 '14 at 17:34
  • This is a good answer. If you want the cheap and easy way out you can use WideString and take advantage of the shared COM heap. But don't use WideString as a function return value. Has to be out or var parameter. – David Heffernan Feb 15 '14 at 17:56
  • @DavidHeffernan Why can't `WideString` be used as a function return value? – iMan Biglari Feb 15 '14 at 18:57
  • @iMan http://stackoverflow.com/questions/9349530/why-can-a-widestring-not-be-used-as-a-function-return-value-for-interop – David Heffernan Feb 15 '14 at 19:10