|
Replies:
6
-
Last Post:
Apr 19, 2018 3:27 AM
Last Post By: Samuel Herzog
|
|
|
Posts:
16
Registered:
9/10/00
|
|
Hello,
I have a problem with passing data from a dll to the host-application.
It works fine if the size of the data is not to bigger than 2603 chars, but if it goes above 2603 chars I get an Access Violation.
Here is the example code:
library mydll;
uses
System.SysUtils,
System.Classes;
{$R *.res}
var
gMyBuffer:PChar;
function InitData(out BufferSize:integer):boolean; // this method prepares some data and writes to the global var <gMyBuffer>
var
i:integer;
_SomeExampleData:string;
begin
// prepare some data
for i := 1 to 2603 do _SomeExampleData:=_SomeExampleData+'a'; // this works
// for i := 1 to *2604* do _SomeExampleData:=_SomeExampleData+'a'; // this does NOT work.
BufferSize:=length(_SomeExampleData);
GetMem(gMyBuffer,BufferSize+1);
StrCopy(gMyBuffer,PChar(_SomeExampleData));
result:=true;
end;
function GetData(out Data:PChar):boolean;
begin
StrCopy(Data,gMyBuffer);
result:=true;
end;
exports
InitData,
GetData;
end.
The code of the host-application:
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 Button1Click(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TInitData= function (out BufferSize:integer):boolean;
TGetData= function (out Data:PChar):boolean;
procedure GetDataFromDll(out Data:string);
var
_DllHandle:THandle;
_InitData:TInitData;
_GetData:TGetData;
_BufferSize:integer;
_Data:PChar;
begin
_DllHandle:=LoadLibrary('mydll.dll');
if _DllHandle=0 then begin
Showmessage('Problem to load dll <mydll.dll>.');
exit;
end;
_InitData:=getprocaddress(_DllHandle,'InitData');
_GetData:=getprocaddress(_DllHandle,'GetData');
_InitData(_BufferSize); // this prepares some data in the dll and retunrs the buffer-size
GetMem(_Data,_BufferSize+1); // let's allocate the memoryspace
_GetData(_Data); // and get the data from the dll.
Data:=string(_Data); // Access-Violation here
FreeMem(_Data);
FreeLibrary(_DllHandle);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
_data:string;
begin
GetDataFromDll(_Data);
Showmessagefmt('Data is <%s>, Size is <%d>.',[_data,length(_data)]);
end;
end.
I am using Delphi 10.2.3 Tokyo.
Has someone an idea what is wrong here?
Thank you.
Edited by: Samuel Herzog on Apr 15, 2018 2:12 AM
|
|
|
Posts:
1,227
Registered:
12/16/99
|
|
Samuel Herzog wrote:
Hello,
I have a problem with passing data from a dll to the host-application.
It works fine if the size of the data is not to bigger than 2603
chars, but if it goes above 2603 chars I get an Access Violation.
That is just arbitrary, your problem is that you assume sizeof(char) =
1 in your code, which is not true anymore in the Delphi versions
released in the last 10 years or so.
Here is the example code:
library mydll;
uses
System.SysUtils,
System.Classes;
{$R *.res}
var
gMyBuffer:PChar;
function InitData(out BufferSize:integer):boolean; // this method
prepares some data and writes to the global var <gMyBuffer> var
i:integer;
_SomeExampleData:string;
begin
// prepare some data
for i := 1 to 2603 do _SomeExampleData:=_SomeExampleData+'a'; //
this works // for i := 1 to 2604 do
_SomeExampleData:=_SomeExampleData+'a'; // this does NOT work.
BufferSize:=length(_SomeExampleData);</div>
That should be
BufferSize:=length(_SomeExampleData) * Sizeof(char);
<div class="jive-quote">GetMem(gMyBuffer,BufferSize+1);
StrCopy(gMyBuffer,PChar(_SomeExampleData)); result:=true;
end;
function GetData(out Data:PChar):boolean;
begin
StrCopy(Data,gMyBuffer);
result:=true;
end;
exports
InitData,
GetData;
end.
The code of the host-application:
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 Button1Click(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TInitData= function (out BufferSize:integer):boolean;
TGetData= function (out Data:PChar):boolean;
procedure GetDataFromDll(out Data:string);
var
_DllHandle:THandle;
_InitData:TInitData;
_GetData:TGetData;
_BufferSize:integer;
_Data:PChar;
begin
_DllHandle:=LoadLibrary('mydll.dll');
if _DllHandle=0 then begin
Showmessage('Problem to load dll <mydll.dll>.');
exit;
end;
_InitData:=getprocaddress(_DllHandle,'InitData');
_GetData:=getprocaddress(_DllHandle,'GetData');
_InitData(_BufferSize); // this prepares some data in the
dll and retunrs the buffer-size GetMem(_Data,_BufferSize+1); //
let's allocate the memoryspace _GetData(_Data); //
and get the data from the dll. Data:=string(_Data); //
Access-Violation here FreeMem(_Data);
FreeLibrary(_DllHandle);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
_data:string;
begin
GetDataFromDll(_Data);
Showmessagefmt('Data is <%s>, Size is <%d>.',[_data,length(_data)]);
end;
end.
I am using Delphi 10.2.3 Tokyo.
Has someone an idea what is wrong here?
Thank you.
--
Peter Below
TeamB
|
|
|
|
Posts:
16
Registered:
9/10/00
|
|
Hello Peter,
thank you for your answer. So you suggest to use PByte and SizeOf to overcome the problem?
I tried that as well but ran also into AV's.
But I will spend some more time on the PByte/SizeOf variant.
Sam
Edited by: Samuel Herzog on Apr 15, 2018 9:53 PM
|
|
|
|
Posts:
1,227
Registered:
12/16/99
|
|
Samuel Herzog wrote:
Hello Peter,
thank you for your answer. So you suggest to use PByte and SizeOf to
overcome the problem? I tried that as well but ran also into AV's.
But I will spend some more time on the PByte/SizeOf variant.
Sam
Edited by: Samuel Herzog on Apr 15, 2018 9:53 PM
No, you can use PChar if the data you need to transfer is actually
text. You only have to take into account that sizeof(char) may not be 1
when you calculate the size of the buffer.
If the data is not text you should indeed transfer it as an array of
byte, though.
--
Peter Below
TeamB
|
|
|
|
Posts:
16
Registered:
9/10/00
|
|
After spending some time I came to the following solution.
*************************************************************************************
*** DLL ****
*************************************************************************************
{-----------------------------------------------------------------------------
Unit Name: mydll
Author: sam
Date: 15-Apr-2018
Purpose: example how to pass a byte array from a dll to a host-application.
History:
-----------------------------------------------------------------------------}
library mydll;
uses
System.SysUtils,
System.Classes;
{$R *.res}
type
TByteArray= array of byte;
var
gDllBuffer:TByteArray;
{-----------------------------------------------------------------------------
Procedure: InitData
Author: sam
Date: 15-Apr-2018
Arguments: out BufferSize:integer
Result: boolean
Description: this method prepares some data and writes them to the global
variable <gDllBuffer>.
-----------------------------------------------------------------------------}
function InitData(out BufferSize:integer):boolean;stdcall;
var
i:integer;
_SomeExampleData:TByteArray;
_ExampleDataSize:integer;
begin
result:=false;
SetLength(gDllBuffer,0);
// prepare some data
Randomize;
_ExampleDataSize:=Random(10000);
SetLength(_SomeExampleData,_ExampleDataSize);
for i := 0 to _ExampleDataSize-1 do _SomeExampleData[i]:=97+(i mod 26); // fill some data into the example array
// move the prepared data into the dll-internal buffer and return the buffersize to the host application.
BufferSize:=length(_SomeExampleData);
SetLength(gDllBuffer,BufferSize);
Move(PByteArray(_SomeExampleData)^,PByteArray(gDllBuffer)^,BufferSize);
result:=true;
end;
{-----------------------------------------------------------------------------
Procedure: GetData
Author: sam
Date: 15-Apr-2018
Arguments: Buffer:PByte
Result: boolean
Description: with this method the host-app will get the data from the dll.
-----------------------------------------------------------------------------}
function GetData(Buffer:PByte):boolean;stdcall;
begin
result:=false;
if Buffer=nil then exit;
Move(PByteArray(gDllBuffer)^,Buffer^,length(gDllBuffer));
result:=true;
end;
exports
InitData,
GetData;
end.
*************************************************************************************
*** APPLICATION ****
*************************************************************************************
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 Button1Click(Sender: TObject);
private
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
type
TByteArray=array of byte;
TInitData= function (out BufferSize:integer):boolean;stdcall;
TGetData= function (Buffer:PByte):boolean;stdcall;
var
_InitData:TInitData;
_GetData:TGetData;
function GetDataFromDll(out Data:TByteArray):boolean;
var
_DllHandle:THandle;
_BufferSize:integer;
_Data:TByteArray;
begin
result:=false;
_DllHandle:=LoadLibrary('mydll.dll');
if _DllHandle=0 then begin
Showmessage('Problem to load dll <mydll.dll>.');
exit;
end;
try
_InitData:=getprocaddress(_DllHandle,'InitData');
_GetData:=getprocaddress(_DllHandle,'GetData');
if not _InitData(_BufferSize) then exit; // this prepares some data in the dll and retunrs the buffer-size
SetLength(_Data,_BufferSize); // let's allocate the memoryspace
if not _GetData(PByte(_Data)) then exit; // and get the data from the dll.
Data:=_Data;
result:=true;
finally
FreeLibrary(_DllHandle);
_DllHandle:=0;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
_ByteData:TByteArray;
_AnsiString:AnsiString;
_String:String;
begin
if not GetDataFromDll(_ByteData) then exit;
_AnsiString:=TEncoding.ANSI.GetString(_ByteData);
ShowmessageFmt('size: <%d>,data: <%s>',[length(_AnsiString),_AnsiString]); // do whatever you want to do with the data. (in this example we build ANSI-string)
_String :=TEncoding.UNICODE.GetString(_ByteData);
ShowmessageFmt('size: <%d>,data: <%s>',[length(_String),_String]); // do whatever you want to do with the data. (in this example we build UNICODE-string)
end;
end.
Edited by: Samuel Herzog on Apr 15, 2018 11:36 PM fixed typo TByteAttay.
Edited by: Samuel Herzog on Apr 15, 2018 11:38 PM
Edited by: Samuel Herzog on Apr 19, 2018 3:23 AM Replaced PByteArray with PByte as suggested by Rudy.
|
|
|
|
Posts:
7,731
Registered:
9/22/99
|
|
Samuel Herzog wrote:
After spending some time I came to the following solution.
**********************************************************************
*************** *** DLL ****
**********************************************************************
*************** {code}
{---------------------------------------------------------------------
Unit Name: mydll
Author: sam
Date: 15-Apr-2018
Purpose: example how to pass a byte array from a dll to a
host-application. History:
-------} library mydll;
uses
System.SysUtils,
System.Classes;
{$R *.res}
type
TByteArray= array of byte;
PByteArray = ^TByteArray;
-------} function GetData(Buffer:PByteArray):boolean;stdcall;
Nooooooo! Don't do that, please. **NEVER** export types that are
Delphi-specific.
I wrote a whole article about such things. Please read and heed it. If
you want to write usable DLLs, follow the dos and don'ts:
http://rvelthuis.de/articles/articles-dlls.html
In this case, do:
function GetData(Buffer: PByte; BufSize: Integer): Boolean; stdcall;
And let the user allocate the buffer. Let the user pass a PByte to
the first element of this buffer, which can be a static or a dynamic
array, or a block of memory allocated with GetMem, or whatever he deems
necessary. Also let him pass the size, so the DLL can check if the data
fits.
There are other ways (described in my article), for cases where the
above doesn't work out (i.e. if you don't know beforehand how big the
buffer must be, etc.).
--
Rudy Velthuis http://www.rvelthuis.de
"He can compress the most words into the smallest idea of any man
I know." -- Abraham Lincoln
|
|
|
|
Posts:
16
Registered:
9/10/00
|
|
Hello Rudy,
thank you for your answer.
Your article helps a lot.
I will change my example according to your suggestion. (and post it here again)
In my case the data gets fetched/perpare from a DB and the size/amount of the data to be passed from Dll to host-app varies.
So I have split it into two methods InitData and GetData. I am aware of the often used technique to make just one Method and
then call it twice. (first time with buffer=nil).
Btw, you might want to add this example (our your own) to your article, because it demonstrates the absolute basics on how to transfer 1..n Bytes from a dll to a host-app.
I have not found such an example by googling arround.
Best regards,
Sam
Edited by: Samuel Herzog on Apr 18, 2018 10:56 PM
Edited by: Samuel Herzog on Apr 18, 2018 11:08 PM
|
|
|
|
Legend
|
|
Helpful Answer
(5 pts)
|
|
Correct Answer
(10 pts)
|
|
Connect with Us