0
votes

I have a DLL written in Delphi 2007 that I can't, for various reason, convert to being compiled in XE6.

I have written a wee test app in XE6 that calls the DLL, and that's all fine, but I need to pass string data from the DLL to the exe, and I am using a PCHAR to do this. However, consuming the PCHAR in the XE6 exe is proving to be a bit of a pain.

I read somewhere that in D2007 a PCHAR is actually a PANSICHAR, so I tried using a PANSICHAR and I make a call to the DLL but it just returns an empty string!

I have tried various other types like PWIDECHAR, String, WideString, ShortString and PCHAR. PCHAR does return something that looks like a Wide String, but I am not sure if it actually is because it didn't cast when I tried it :-)

So I am wondering what I am doing wrong? Am I passing the wrong type from the D2007 DLL? Should I be doing the consumption of the data differently in XE6?

-- EDIT -- Ok, I didnt get any results based on Remy's idea, so I have included my code this time and I am sure the solution will be obvious to someone. I appreciate the help :-)

This is the code for the DLL which is written in D2007:

library mydll;

  uses
    SysUtils

type
  TOnCommandProc = procedure(sMessage:PAnsiChar);
stdcall;

var
  FOnCommandProc: TOnCommandProc = nil;

procedure SetOnCommandProc(CallbackProc: TOnCommandProc);
stdcall;
begin
  FOnCommandProc := CallbackProc;
end;

procedure OnEventMessage(Data: String);
var
 BuffSize: Integer;
 sOut: string;
 oData : PAnsiChar;
begin

  sOut:=Data;
  BuffSize:=SizeOf(Char)*(Length(sOut)+1);
  getmem(oData, BuffSize);
  FillChar(oData^,BuffSize,0);

  if (Length(sOut)>0) then
  begin
    Move(sOut[1], PAnsiChar(oData)^, BuffSize);
    FOnCommandProc(oData);
  end;
end;

procedure TryTheEvent;
begin
  OnEventMessage('Hello');
end;

exports
  SetOnCommandProc name 'SetOnCommandProc',
  TryTheEvent name 'TryTheEvent';
end.

This is the calling EXE code written in Delphi XE6:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TOnCommandProc = procedure(sMessage:PAnsiChar); stdcall;

  procedure SetOnCommandProc(CallbackProc: TOnCommandProc; sMessage:PAnsiChar); stdcall; external 'mydll.dll';
  procedure TryTheEvent; stdcall; external 'mydll.dll';

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    class procedure MyOnCommandProc(sMessage:PAnsiChar); stdcall; static;
    procedure OnCommand(sMessage : PAnsiChar);

  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  TryTheEvent;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetOnCommandProc(@MyOnCommandProc,0);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  SetOnCommandProc(nil,0);
end;

class procedure TForm1.MyOnCommandProc(sMessage:PAnsiChar); stdcall;
begin
  TForm1(sMessage).OnCommand(sMessage);
end;

procedure TForm1.OnCommand(sMessage :PAnsiChar);
begin
  showmessage(sMessage);
end;

end.
2
You need to show your codeDavid Heffernan
"What am I doing wrong?" Expecting us to help you with your code when you don't include your code. It's hard to see what's wrong when there's nothing to examine.Ken White
Your implementation of OnEventMessage is needlessly complex. It can be simply begin FOnCommandProc(PChar(Data)); end; Your question is really east to answer but I cannot do so because it is closed because you would not supply code.David Heffernan
There are multiple problems with your code. The one that is burning you is a function declaration mismatch. The DLL implementaton of SetOnCommandProc has one parameter. You import a function with two parameters. On top of that the cast of sMessage to TForm1 is quite wrong. The question actually has nothing at all to do with ANSI/Unicode.David Heffernan
Thanks for the extra pair of eyes David!! I must have inadvertently removed the missing parameter at some stage of frustration:-) However, can you offer more to your comment "On top of that the cast of sMessage to TForm1 is quite wrong" as I am missing something.. Jeremyuser3597837

2 Answers

0
votes

I can see a number of problems with your code. Specifically:

  • SetOnCommandProc has a single parameter. You declare it in the EXE with two.
  • You implement TryTheEvent with the register calling convention in the DLL, but then declare it as stdcall in the EXE.
  • The implementation of OnEventMessage is needlessly complex and in fact leaks.
  • The EXE code performs an erroneous cast TForm1(sMessage). A PAnsiChar is never an object reference.
  • You use the @ operator to obtain a function pointer. This suppresses type checking.

It is the first of these points that is giving you the most problems. But you should fix them all.

I would have the DLL like this:

library mydll;

type
  TOnCommandProc = procedure(sMessage: PAnsiChar); stdcall;

var
  FOnCommandProc: TOnCommandProc = nil;

procedure SetOnCommandProc(CallbackProc: TOnCommandProc); stdcall;
begin
  FOnCommandProc := CallbackProc;
end;

procedure OnEventMessage(Data: AnsiString);
begin
  if Assigned(FOnCommandProc) then
    FOnCommandProc(PAnsiChar(Data));
end;

procedure TryTheEvent; stdcall;
begin
  OnEventMessage('Hello');
end;

exports
  SetOnCommandProc, TryTheEvent;

end.

And the EXE like this:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    class procedure MyOnCommandProc(sMessage: PAnsiChar); stdcall; static;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

type
  TOnCommandProc = procedure(sMessage: PAnsiChar); stdcall;

procedure SetOnCommandProc(CallbackProc: TOnCommandProc); stdcall; external 'mydll.dll';
procedure TryTheEvent; stdcall; external 'mydll.dll';

procedure TForm1.Button1Click(Sender: TObject);
begin
  TryTheEvent;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetOnCommandProc(MyOnCommandProc);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  SetOnCommandProc(nil);
end;

class procedure TForm1.MyOnCommandProc(sMessage: PAnsiChar);
begin
  ShowMessage(sMessage);
end;

end.

Note that I've removed the instance method OnCommand altogether. If you do need to call an instance method then you need to pass an instance as well as a callback function. So you might pass the instance reference to the DLL, typed as a Pointer, and then pass that instance reference back in the callback.

3
votes

In D2007 and earlier, String is an alias for AnsiString and PChar is an alias for PAnsiChar.

In D2009 and later, String is an alias for UnicodeString and PChar is an alias for PWideChar.

If you want an XE6 project to call into a D2007 DLL, you will have to declare the DLL types/parameters using PAnsiChar instead of PChar. And be sure to use AnsiString or other AnsiChar-based memory allocation when passing PAnsiChar pointers into the DLL. If you are still having trouble, then please update your question to show the actual code that is not working.

Edit: Based on the code you have now shown, try this code instead:

D2007 DLL:

library mydll;

uses
  SysUtils;

type
  TOnCommandProc = procedure(sMessage: PChar; pUserData: Pointer); stdcall;

var
  FOnCommandProc: TOnCommandProc = nil;
  FOnCommandUserData: Pointer = nil;

procedure SetOnCommandProc(CallbackProc: TOnCommandProc; UserData: Pointer); stdcall;
begin
  FOnCommandProc := CallbackProc;
  FOnCommandUserData := UserData;
end;

procedure OnEventMessage(Data: String);
begin
  if (Data <> '') and Assigned(FOnCommandProc) then
  begin
    FOnCommandProc(PChar(Data), FOnCommandUserData);
  end;
end;

procedure TryTheEvent; stdcall;
begin
  OnEventMessage('Hello');
end;

exports
  SetOnCommandProc name 'SetOnCommandProc',
  TryTheEvent name 'TryTheEvent';
end.

XE6 app:

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  TOnCommandProc = procedure(sMessage: PAnsiChar; pUserData: Pointer); stdcall;

  procedure SetOnCommandProc(CallbackProc: TOnCommandProc; UserData: Pointer); stdcall; external 'mydll.dll';
  procedure TryTheEvent; stdcall; external 'mydll.dll';

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
    class procedure MyOnCommandProc(sMessage: PAnsiChar: pUserData: Pointer); stdcall; static;
    procedure OnCommand(sMessage : PAnsiChar);

  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  TryTheEvent;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  SetOnCommandProc(@MyOnCommandProc, Self);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  SetOnCommandProc(nil, nil);
end;

class procedure TForm1.MyOnCommandProc(sMessage: PAnsiChar; pUserData: Pointer); stdcall;
begin
  TForm1(pUserData).OnCommand(sMessage);
end;

procedure TForm1.OnCommand(sMessage: PAnsiChar);
begin
  ShowMessage(sMessage);
end;

end.