Reputation: 23
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.
Upvotes: 0
Views: 1592
Reputation: 613491
I can see a number of problems with your code. Specifically:
SetOnCommandProc
has a single parameter. You declare it in the EXE with two.TryTheEvent
with the register
calling convention in the DLL, but then declare it as stdcall
in the EXE.OnEventMessage
is needlessly complex and in fact leaks.TForm1(sMessage)
. A PAnsiChar
is never an object reference.@
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.
Upvotes: 0
Reputation: 598134
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.
Upvotes: 3