I have a DLL that was provided by a 3rd party company and when called from Delphi 2007, it worked perfectly fine. The following code is a sample of how the DLL was used in Delphi 2007:
Procedure XC_eXpressLink(hHandle: Hwnd; Parameters: pChar; Result: pChar); stdcall; external 'XCClient.dll';
Here is how the procedure was called:
procedure TForm1.Button1Click(Sender: TObject);
var Result: array[0..2000] of char;
sParams: String;
begin
sParams := RemoveCRLF(memoParameters.Text); //Remove TMemo CR/LF
XC_eXpressLink(Handle, pChar(sParams), Result);
memoResults.Text := String(Result);
end;
I'm not sure what the DLL was compiled in, but I'm assuming it is expecting ansi and not unicode. After converting the code to ansi in Delphi XE5, the code is now as follows:
Procedure XC_eXpressLink(hHandle: Hwnd; Parameters: pAnsiChar; Result: pAnsiChar); stdcall; external 'XCClient.dll';
and
procedure TForm1.Button1Click(Sender: TObject);
var Result: array[0..2000] of Ansichar;
sParams: AnsiString;
begin
sParams := RemoveCRLF(memoParameters.Text); //Remove TMemo CR/LF
XC_eXpressLink(Handle, pAnsiChar(sParams), Result);
memoResults.Text := AnsiString(Result);
end;
memoParameters is a TMemo on the form which provides the parameters for the dll procedure. The RemoveCRLF is a function that removes any carriage returns and line feeds from memoParameters. MemoResults is another TMemo on the form that provides the return results of the dll procedure.
I'm getting access violations when the changed code is run in Delphi XE5. Since I changed all the parameters to use ansi, shouldn't the dll be getting the same parameter format as before? Am I doing something wrong? Will I be able to get this older compiled DLL to work in Delphi XE5?
I contacted the company, OpenEdge, which supplies the dll for X-Charge (for credit card integration). To resolve the problem, the Handle must have a value of 0 and you have to add /IGNOREHANDLEPARAMETER to the parameters list that is sent to the dll. Note, this parameter will only work with the full version XC8.1.1.6.exe installation or later.
procedure TForm1.Button1Click(Sender: TObject);
var Result: array[0..2000] of Ansichar;
sParams: AnsiString;
begin
sParams := RemoveCRLF(memoParameters.Text); //Remove TMemo CR/LF
XC_eXpressLink(0, pAnsiChar(sParams), Result);
memoResults.Text := AnsiString(Result);
end;
Related
There's an overloaded version of the Execute function of the TDBXCallback calls in Data.DBXJSon that looks like this
function Execute(Arg: TObject): TObject; overload; virtual; abstract;
Which in my Datasnap client, I've implemented like this:
type
ServerChannelCallBack = class(TDBXCallback)
public
function Execute(const Arg: TJSONValue): TJSONValue; overload; override; // this works!
function Execute(Arg: TObject): TObject; overload; override; // this doesn't
end;
function ServerChannelCallBack.Execute(Arg: TObject): TObject;
var
i: Integer;
begin
Result := TObject.Create; // is this correct?
try
if Arg is TStringList then
begin
FormClient.QueueLogMsg('ServerChannel', 'Got TStringList');
for i := 0 to TStrings(Arg).Count - 1 do
FormClient.QueueLogMsg('ServerChannel', TStringList(Arg)[i]);
end;
finally
end;
end;
This is called from the Datasnap server like this:
procedure TFormServer.Button2Click(Sender: TObject);
var
sr: TStringList;
begin
sr := TStringList.Create;
try
sr.Add('one');
sr.Add('two');
ServerContainer2.DSServer1.BroadcastObject('SERVERCHANNEL', sr);
finally
// sr
end;
end;
This is following on from an example in the video presented by Matt DeLong
Heavyweight Callbacks with DataSnap - Part 1: Thick Client
The callback works perfectly, but only exactly once! On the second call from the server (Button2Click), I get an AV in the client. It might be a bug in the DBX code. I don't know. I can't trace in there. Or perhaps I have initialized the Result from the ServerChannelCallBack.Execute incorrectly. Any assistance is appreciated.
UPDATE
The callback is registered on the client like this:
TFormClient = class(TForm)
CMServerChannel: TDSClientCallbackChannelManager;
...
private
ServerChannelCBID: string;
...
procedure TFormClient.FormCreate(Sender: TObject);
begin
ServerChannelCBID := DateTimeToStr(now);
CMServerChannel.RegisterCallback(
ServerChannelCBID,
ServerChannelCallback.Create
);
...
I'm basing this answer on the DataSnap Server + Client projects which can be downloaded from inside Delphi Seattle using `File | Open from version control'
https://radstudiodemos.svn.sourceforge.net/svnroot/radstudiodemos/branches/RadStudio_XE/Delphi/DataSnap/CallbackChannels
that's mentioned here: http://edn.embarcadero.com/article/41374.
The forms in both the server and client require a slight correction to get them to compile, name to add JSon to their Uses list.
On the server form, I've added the following:
procedure TForm3.Button1Click(Sender: TObject);
var
sr: TStringList;
begin
Inc(CallbackCount); // A form variable
sr := TStringList.Create;
try
sr.Add('Callback: ' + IntToStr(CallbackCount));
sr.Add('two');
ServerContainer1.DSServer1.BroadcastObject('ChannelOne', sr);
finally
// No need for sr.free
end;
end;
(I'm using ChannelOne for consistency with the client)
and on the client I have:
function TCallbackClient.Execute(Arg: TObject): TObject;
var
i: Integer;
begin
// Result := TObject.Create; // is this correct?
Result := TJSONTrue.Create;
try
if Arg is TStringList then
begin
QueueLogValue('Server: Got TStringList');
for i := 0 to TStrings(Arg).Count - 1 do
QueueLogValue('Server:' + TStringList(Arg)[i]);
end;
finally
end;
end;
With those variations from the code you've shown in your q, the server and client run fine, and I can click the server button as many times as I like and neither the server nor any of the clients get "stuck". So I think your problem must be specific to something in the code you are using, but at least the linked project gives you something to work from and compare with.
Btw, I changed the TCallbackClient.Execute return type to TJSONTrue.Create (same as the other override) because that's what it says in Marco Cantu's Delphi 2010 Handbook says it should return, admittedly in the context of a "lightweight" callback while a ServerMethod is executing: returning TJSONFalse tells the server to cancel the executing ServerMethod. However, the callbacks from the server work equally well with the TObject.Create you used.
I used the CharPrinter.pas unit to send commands in ZPLII for a ZEBRA printer in Delphi RAD2007 and everything worked well, but I change to XE7 and tried to use the same functions and sending to the printer and does not work, and does not give any error message. Does it have to do the new data type versions XE ?
This happened cause Delphi switched to Unicode strings in Delphi 2009.
Unicode strings use 2 bytes for every char. Older Delphi versions used 1 byte for every char.
So you must make some changes in ChatPrinter.pas.
Try to edit this (not tested):
procedure TCharPrinter.SendData (aData : String);
var
Data : array[0..255] of char;
cnt : integer;
ss : TStringStream;
begin
try
ss := TStringStream.Create(aData,TEncoding.ANSI);
fStream.CopyFrom (ss,0);
finally
ss.Free;
end;
// for cnt := 0 to length(aData) - 1
// do Data[cnt] := aData[cnt+1];
end;
Or simpler:
procedure TCharPrinter.SendData (aData : String);
var
Data : AnsiString;
begin
Data := AnsiString(aData);
fStream.Write(PAnsiChar(Data)^, Length(Data));
end;
I'm porting program from Delphi 2009 to XE4 and got problem with LockBox encryption. Encrypt/decrypt unit is using just one component:
interface
function Encrypt(aStr: String): String;
function Decrypt(aStr: String): String;
function NeedEncrypt(): Boolean;
implementation
uses
windows,
strUtils,
LbClass;
var
LbRijndael: TLbRijndael;
localNeedEncrypt: Boolean;
function NeedEncrypt(): Boolean;
begin
Result := localNeedEncrypt;
localNeedEncrypt := False;
end;
function Encrypt(aStr: AnsiString): AnsiString;
begin
Result := aStr;
if RightStr(aStr, 2) = '==' then
Exit;
Result := LbRijndael.EncryptString(aStr);
end;
function Decrypt(aStr: AnsiString): AnsiString;
begin
Result := aStr;
if RightStr(aStr, 2) = '==' then
Result := LbRijndael.DecryptString(aStr)
else
localNeedEncrypt := True;
end;
initialization
LbRijndael := TLbRijndael.Create(nil);
LbRijndael.GenerateKey('KEYABC');
LbRijndael.CipherMode := cmECB;
LbRijndael.KeySize := ks128;
end.
As I understood there is no LockBox2 for Delphi XE4.
Can I use LockBox3 for this purpose? If yes, can I use just needed units without installation into Delphi (this was done with LockBox2)?
Whilst the LB2 and LB3 APIs are very different, you should be able to port this code across without too much difficulty. As you are creating the components dynamically at runtime, you shouldn't need to install the packages into your IDE, providing your library path is set to include the LB3 source.
I inherited some Delphi components/code that currently compiles with C++ Builder 2007. I'm simply now trying to compile the components with C++ Builder RAD XE. I don't know Delphi (object pascal).
Here are the versions of the 'Supports' functions that appear to be in conflict. Is there a compiler switch I can use to make RAD XE backward compatible? Or is there something I can do to these function calls to correct the ambiguous nature?
The error I'm getting is:
[DCC Error] cxClasses.pas(566): E2251 Ambiguous overloaded call to 'Supports'
SysUtils.pas(19662): Related method: function Supports(const TObject; const TGUID; out): Boolean;
cxClasses.pas(467): Related method: function Supports(TObject; const TGUID; out): Boolean;
{$IFNDEF DELPHI5}
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj);
Pointer(Obj) := nil;
Temp.Free;
end;
function Supports(const Instance: IUnknown; const Intf: TGUID; out Inst): Boolean; overload;
begin
Result := (Instance <> nil) and (Instance.QueryInterface(Intf, Inst) = 0);
end;
function Supports(Instance: TObject; const Intf: TGUID; out Inst): Boolean; overload;
var
Unk: IUnknown;
begin
Result := (Instance <> nil) and Instance.GetInterface(IUnknown, Unk) and
Supports(Unk, Intf, Inst);
end;
{$ENDIF}
{$IFNDEF DELPHI6}
function Supports(const Instance: TObject; const IID: TGUID): Boolean;
var
Temp: IUnknown;
begin
Result := Supports(Instance, IID, Temp);
end;
{$ENDIF}
You are using some devexpress components and the problem is that you are using versions of the code that pre-date C++ Builder XE. The particular problem is that the conditional defines declared in cxVer.inc do not know about XE. Consequently, this cxClasses.pas file does not know which version of Delphi it is targeting.
In most circumstances you could simply add the necessary defines and the code would start working. However, your version of the devexpress code is for RAD Studio 2007 which uses ANSI strings, but you are trying to compile on XE which uses Unicode strings. This difference needs major changes to the rest of the source code.
Unfortunately, to get this code to work on XE you will need to get hold of the latest versions of all your 3rd party components. The updated versions have had the necessary changes to support Unicode text.
What's more, your code may also need some significant re-work to support Unicode but I am less sure on that point because my experience is with Delphi rather than C++ Builder.
I am working on a DLL in Delphi 2010. It exports a procedure that receives an array of variants. I want to be able to take one of these variants, and convert it into a string, but I keep getting ?????
I cannot change the input variable - it HAS to be an array of variants.
The host app that calls the DLL cannot be changed. It is written in Delphi 2006.
Sample DLL code:
Procedure TestArr(ArrUID : array of variant); stdcall;
var
i: integer;
s: string;
begin
s:= string(String(Arruid[0]));
showmessage(s);
end;
Using D2006 my DLL works fine. I have tried using VartoStr - no luck. When I check the VarType I am getting a varString. Any suggestions how to fix this?
You host application is sending an AnsiString and you dll is expecting a UnicodeString.
Unicode strings was introduced in Delphi 2009, it does not exist in Delphi 2006. How to fix it? Try [untested]:
Procedure TestArr(ArrUID : array of variant); stdcall;
var
i: integer;
s: AnsiString;
begin
s:= Ansistring(VarToStr(Arruid[0]));
showmessage(s);
end;
or maybe [also untested]:
Procedure TestArr(ArrUID : array of variant); stdcall;
var
i: integer;
s: AnsiString;
begin
s:= Ansistring(AnsiString(Arruid[0]));
showmessage(s);
end;
You can also check if theres is a function Like VarToStr that accepts AnsiStrings (maybe in the AnsiStrings unit?).
1/ How have you call the VarToStr() function ? VarToString(Arruid[0]) ?
2/ Does your Delphi2006 Application send AnsiString or WideString to the DLL ?
If so, and if (1) is not working, try to cast to AnsiString instead of string.