Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Populate a Delphi structure, C-alike for a C DLL



Permlink Replies: 4 - Last Post: Dec 4, 2017 9:54 PM Last Post By: Peter Below
Pablo Romero

Posts: 21
Registered: 9/28/00
Populate a Delphi structure, C-alike for a C DLL
Click to report abuse...   Click to reply to this thread Reply
  Posted: Dec 2, 2017 6:26 AM
Hello fellas, i need some advice

I have this structure from a C DLL

typedef struct PURCHASE_IN{
 
	LPSTR amount;			  // max 12
	LPSTR receiptNumber;    	  // max 12
	LPSTR instalmentCount;	          // max 2
	LPSTR issuerCode;		  // max 3
	LPSTR planCode;			  // max 1
	LPSTR tip;			  // max 12
	LPSTR merchantCode;		  // max 15
	LPSTR merchantName;		  // max 23
	LPSTR cuit;		 	  // max 23
	char  linemode;			  // max 1
 
} vpiPurchaseIn_t;


I converted to Delphi like this

      type
        LPSTR = PAnsiChar;
 
      PURCHASE_IN = record
        amount: LPSTR;                     
        receiptNumber: LPSTR;              
        instalmentCount: LPSTR;            
        issuerCode: LPSTR;                 
        planCode: LPSTR;                   
        tip: LPSTR;                        
        merchantCode: LPSTR;               
        merchantName: LPSTR;               
        cuit: LPSTR;                       
        linemode: Char;                    
      end {PURCHASE_IN};
      VPIPURCHASEIN_T = PURCHASE_IN;


I populate the structure like this

var
  Venta :VPIPURCHASEIN_T;
begin
  // allocate
  Venta.merchantName     := AnsiStrAlloc(23)
  Venta.cuit             := AnsiStrAlloc(23)
  Venta.merchantCode     := AnsiStrAlloc(15)
  Venta.amount           := ..
  Venta.receiptNumber    := ..
  Venta.issuerCode       := ..
  Venta.instalmentCount  := 
  Venta.planCode         := 
  Venta.tip              := 
  Venta.linemode         := #0;
 
  // put some data..:Datos is a simple pascal record of strings
  Venta.merchantName     := PutStr( Datos.Comercio, 23 )
  Venta.cuit             := PutStr( Datos.Cuit, 23 )
  Venta.merchantCode     := PutStr( Datos.Code, 15 )


Inside my PutStr function....

function PutStr(pStr : string; pMaxLength : Integer) : LPSTR;
begin
   result := PAnsiChar(AnsiString( copy( pStr, 1, pMaxLength ) ));
end;


The problem: every time I put some data in the structure, other fields are filled with garbage, or with other data ...

What am I doing wrong? Any Advice? I use Delphi 2010/XE3/XE7

Regards

Pablo Romero
Cordoba, Argentina
Rudy Velthuis (...


Posts: 7,731
Registered: 9/22/99
Re: Populate a Delphi structure, C-alike for a C DLL
Click to report abuse...   Click to reply to this thread Reply
  Posted: Dec 2, 2017 7:02 AM   in response to: Pablo Romero in response to: Pablo Romero
Pablo Romero wrote:

Hello fellas, i need some advice

I have this structure from a C DLL

typedef struct PURCHASE_IN{
 
	LPSTR amount;			  // max 12
	LPSTR receiptNumber;    	  // max 12
	LPSTR instalmentCount;	          // max 2
	LPSTR issuerCode;		  // max 3
	LPSTR planCode;			  // max 1
	LPSTR tip;			  // max 12
	LPSTR merchantCode;		  // max 15
	LPSTR merchantName;		  // max 23
	LPSTR cuit;		 	  // max 23
	char  linemode;			  // max 1
 
} vpiPurchaseIn_t;


I converted to Delphi like this

      type
        LPSTR = PAnsiChar;

That is correct, but you should also convert C's "char" as "AnsiChar":

  
      PURCHASE_IN = record
        amount: LPSTR;                     
        receiptNumber: LPSTR;              
        instalmentCount: LPSTR;            
        issuerCode: LPSTR;                 
        planCode: LPSTR;                   
        tip: LPSTR;                        
        merchantCode: LPSTR;               
        merchantName: LPSTR;               
        cuit: LPSTR;
        // Here:              
        linemode: AnsiChar;                    
      end {PURCHASE_IN};
      VPIPURCHASEIN_T = PURCHASE_IN;


In such cases, do what I advise here:

http://rvelthuis.de/articles/articles-convert.html#aligns

In other words, use a C compiler to get the expected size and check if
that is the same as the size of your struct. If possible, also check
the offsets of the members, if you can.

--
Rudy Velthuis http://www.rvelthuis.de

"Life is too short to waste in critical peep or cynic bark,
quarrel or reprimand: It will soon be dark."
-- Ralph Waldo Emerson
Peter Below

Posts: 1,227
Registered: 12/16/99
Re: Populate a Delphi structure, C-alike for a C DLL
Click to report abuse...   Click to reply to this thread Reply
  Posted: Dec 2, 2017 7:04 AM   in response to: Pablo Romero in response to: Pablo Romero
Pablo Romero wrote:

Hello fellas, i need some advice

I have this structure from a C DLL

typedef struct PURCHASE_IN{
 
	LPSTR amount;			  // max 12
	LPSTR receiptNumber;    	  // max 12
	LPSTR instalmentCount;	          // max 2
	LPSTR issuerCode;		  // max 3
	LPSTR planCode;			  // max 1
	LPSTR tip;			  // max 12
	LPSTR merchantCode;		  // max 15
	LPSTR merchantName;		  // max 23
	LPSTR cuit;		 	  // max 23
	char  linemode;			  // max 1
 
} vpiPurchaseIn_t;


I converted to Delphi like this

      type
        LPSTR = PAnsiChar;
 
      PURCHASE_IN = record
        amount: LPSTR;                     
        receiptNumber: LPSTR;              
        instalmentCount: LPSTR;            
        issuerCode: LPSTR;                 
        planCode: LPSTR;                   
        tip: LPSTR;                        
        merchantCode: LPSTR;               
        merchantName: LPSTR;               
        cuit: LPSTR;                       
        linemode: Char;                    
      end {PURCHASE_IN};</div>
 
This is wrong, as I will detail below.
 
<div class="jive-quote">I populate the structure like this
 

var
Venta :VPIPURCHASEIN_T;
begin
// allocate
Venta.merchantName := AnsiStrAlloc(23)

Whatis the point of that? With your declaration merchantName is
pointer, setting it up to point at an Ansistring is worse than useless
if you then go and replace the pointer with that for another string,
as you are doing with your PutStr function. It causes a memory leak for
the memory allocated by AnsiStrAlloc.

OK, let's rewind to the start <g>.

A C structure used as a parameter for a DLL function and holding LPSTR
type fields is typically designed to be used in a fashion that makes
the pointers point at memory that is part of the record itself, to make
the whole thing completely self-contained and not dependent on a
specific memory manager for the LPSTR fields. THat means that the
record is actually not of a fixed size, the linemode "field" is
actually only the start of the actual character data, and the other
fields point at characters is this part of the record. Since you have
upper size limits for each of the substrings, and they are not very
large, you can in fact declare a fixed-size record type in Delphi that
matches this setup:

PurchaseLinedata = packed record
_amount: array [0..12] of ansichar;
_receiptNumber: array [0..12] of ansichar;
_instalmentCount: array [0..2] of ansichar;
_issuerCode: array [0..3] of ansichar;
_planCode: array [0..1] of ansichar;
_tip: array [0..12] of ansichar;
_merchantCode: array [0..15] of ansichar;
_merchantName: array [0..23] of ansichar;
_cuit: array [0..23] of ansichar;
end;
PURCHASE_IN = record
amount: LPSTR;
receiptNumber: LPSTR;
instalmentCount: LPSTR;
issuerCode: LPSTR;
planCode: LPSTR;
tip: LPSTR;
merchantCode: LPSTR;
merchantName: LPSTR;
cuit: LPSTR;
linemode: PurchaseLineData;
end {PURCHASE_IN};

Before you can use such a record it needs to be initialized:

procedure InitializePurchaseRecord(Var Rec: PURCHASE_IN);
begin
FillChar(Rec, Sizeof(Rec), #0);
Rec.amount := @Rec.linemode._Amount;
Rec.receiptNumber := @Rec.linemode._receiptNumber;
... and so on
end;

To put data into the record you have to actually copy the character
data into the fields of the linemode subrecord. *Do not touch the LPSTR
fields!*

The version of the StrLCopy function from the System.Ansistrings unit
is suitable for that.

// put some data..:Datos is a simple pascal record of strings
StrLCopy(venta.linemode._merchantName,
PAnsiChar(Ansistring(Datos.Comercio)), 23 );

and so on.

--
Peter Below
TeamB
Pablo Romero

Posts: 21
Registered: 9/28/00
Re: Populate a Delphi structure, C-alike for a C DLL
Click to report abuse...   Click to reply to this thread Reply
  Posted: Dec 4, 2017 7:25 AM   in response to: Peter Below in response to: Peter Below
Thank You, Guys.

I have done what Peter told me and when I pass the parameters to the C function, I get numerous access violations.

The aplication is about conectivity with a POS terminal, using a DLL made in C, which manages it.

The "linemode" field is actually a data field that tells the POS terminal if it's ONLINE or not.

I have modified my PutStr function so allocates memory and after use it, free memory. The parameters go to the function, I do not get access violation, but the data do not go to the servers where the POS terminal is connected.

function PutStr(pStr : string; pLon: Cardinal) : LPSTR;
var s: LPSTR;
begin
   s := AllocMem(pLon+1);
   pStr := Copy( pStr,1, pLon );
   s := StrLCopy(s, PAnsiChar(Ansistring(pStr)), pLon);
   Result := s;
end;
 
function PadL(AString : String; ALen : Integer) : String;
begin
  result := AString;
  while Length(result) < ALen Do
     Result := '0' + result;
end;
 
function VISAPurchase( pstCodigoTarjeta, pstCodigoPlan : string;
                       pniNumeroCuotas : Integer; pndNumeroComprobante, pndMontoTotal : Double;
                       chOnline: AnsiChar ;
                       DatosComercio: TDatosComercio;
                       var pRespuestaVisa: TRespuestaDelHost;
                       pTimeOut: Integer ): word;
var
   Venta :VPIPURCHASEIN_T;
   OutPut: VPITRXOUT_T;
   res: word;
 
begin
  OutPut.hostRespCode := AllocMem(3);
  OutPut.hostMessage  := AllocMem(33);
  OutPut.authCode     := AllocMem(7);
  OutPut.ticketNumber := AllocMem(8);
  OutPut.batchNumber  := AllocMem(4);
  OutPut.customerName := AllocMem(27);
  OutPut.panFirst6    := AllocMem(7);
  OutPut.panLast4     := AllocMem(5);
  OutPut.date         := AllocMem(11);
  OutPut.time         := AllocMem(9);
  OutPut.terminalID   := AllocMem(9);
 
  FillChar(Venta, SizeOf(Venta), #0 );
 
  venta.merchantCode := PutStr( DatosComercio.CodigoComercio, 15);
  venta.merchantName := PutStr( DatosComercio.RazonSocialComercio, 23);
  venta.cuit         := PutStr( DatosComercio.Cuit         , 23);
  venta.amount       := PutStr(  IntToStr(Trunc(pndMontoTotal*100)) , 12);  // monto 12
 
  if pndNumeroComprobante = 0 then     //numero factura
     Venta.receiptNumber := '0'
  else
     venta.receiptNumber := PutStr( FloatToStr(pndNumeroComprobante), 12);
 
  venta.instalmentCount  := PutStr( FormatFloat('00', pniNumeroCuotas), 2);
  venta.issuerCode       := PutStr( PADL( pstCodigoTarjeta, 3 ), 3);
  Venta.planCode := PutStr( pstCodigoPlan , 1);
 
  Venta.tip := '0';  
  Venta.linemode := chOnline;
 
  res := vpiPurchase(Venta, OutPut, pTimeout);    // the C function on a DLL
 
  FreeMem(venta.amount);
  FreeMem(venta.receiptNumber);
  FreeMem(venta.instalmentCount);
  FreeMem(venta.issuerCode);
  FreeMem(venta.planCode);
  FreeMem(venta.merchantCode);
  FreeMem(venta.merchantName);
  FreeMem(venta.cuit);
 
  pRespuestaVisa.CodigoRespuestaHost  := string(OutPut.hostRespCode);
  pRespuestaVisa.MensajeRespuestaHost := string(OutPut.hostMessage);
  pRespuestaVisa.CodigoAutorizacion   := string(OutPut.authCode);
  pRespuestaVisa.NumeroCupon          := string(OutPut.ticketNumber);
  pRespuestaVisa.NumeroLote           := string(OutPut.batchNumber);
  pRespuestaVisa.NombreDuenoTarjeta   := string(OutPut.customerName);
  pRespuestaVisa.Primeros6digitos     := string(OutPut.panFirst6);
  pRespuestaVisa.Ultimos4digitos      := string(OutPut.panLast4);
  pRespuestaVisa.Fecha                := string(OutPut.date);
  pRespuestaVisa.Hora                 := string(OutPut.time);
  pRespuestaVisa.terminalID           := string(OutPut.terminalID);
 
  FreeMem(OutPut.hostRespCode);
  FreeMem(OutPut.hostMessage);
  FreeMem(OutPut.authCode);
  FreeMem(OutPut.ticketNumber);
  FreeMem(OutPut.batchNumber);
  FreeMem(OutPut.customerName);
  FreeMem(OutPut.panFirst6);
  FreeMem(OutPut.panLast4);
  FreeMem(OutPut.date);
  FreeMem(OutPut.time);
  FreeMem(OutPut.terminalID);
 
  Result := res;
end;


I still do not know if I'm doing the right thing. My be not, 'cause terminal says "OPERATION NOW ALLOWED"

Any Ideas?

Regards.

Pablo Romero
Cordoba, Argentina
Peter Below

Posts: 1,227
Registered: 12/16/99
Re: Populate a Delphi structure, C-alike for a C DLL
Click to report abuse...   Click to reply to this thread Reply
  Posted: Dec 4, 2017 9:54 PM   in response to: Pablo Romero in response to: Pablo Romero
Pablo Romero wrote:

Thank You, Guys.

I have done what Peter told me and when I pass the parameters to the
C function, I get numerous access violations.

The aplication is about conectivity with a POS terminal, using a DLL
made in C, which manages it.

The "linemode" field is actually a data field that tells the POS
terminal if it's ONLINE or not.

OK, so my analysis was completely off base. Sorry, lack of data.


I have modified my PutStr function so allocates memory and after use
it, free memory. The parameters go to the function, I do not get
access violation, but the data do not go to the servers where the POS
terminal is connected.

The next suspect would be your declaration of the DLL function. Please
show your Delphi declaration you use and the C declaration from the
DLL's header file.

res := vpiPurchase(Venta, OutPut, pTimeout); // the C function
on a DLL

--
Peter Below
TeamB
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02