Jump to content
dpallas

C++ to Delphi conversion problem

Recommended Posts

There is this h header in VC++

typedef struct EFTPOS_ERROR 
{ 
 ERROR_LEVEL errorLevel; 
 ERROR_CODE errorCode; 
 char* errorMessage; 
}

typedef struct EFTPOS_DEVICE 
{ 
 char* posId; 
 char* terminalId; 
 char* apiKey; 
 char* host; 
 LONG port; 
 POS_PROTOCOL posProtocol; 
}

typedef struct EFTPOS_CONTEXT 
{ 
 char* licenseKey; 
 char* vatNumber; 
 vector<EFTPOS_DEVICE> devices; 
 vector<SIGNATURE_PROVIDER> providers; 
}

extern "C" __declspec(dllexport) void __cdecl InitContext(char** contextId, EFTPOS_ERROR* error, 
EFTPOS_CONTEXT context); 

I tried with PAnsiChar and PChar types and when I call InitContext it shows External Exception or Access violation
Can anyone help?
Thank you
 

Share this post


Link to post

I'm not expert on C++, but you must expose all members of your "struct", and some of them are not integral or something equivalent to Delphi like "vector<...>". See this: https://blogs.embarcadero.com/vector-containers-for-delphi/

 

How do you translate the "struct" and ALL the members in Delphi ?

 

There are few information (or better none information) to help you, but I think that the problems are the "translation" the structs from c++ and records in Delphi.
After you resolve this you must go on and try to implement a procedure ....

 

Bye

 

 

Share this post


Link to post
2 hours ago, dpallas said:

There is this h header in VC++

Are you able to compile a working test program in VC++?

If yes, the you'll be able to see the size of every field member and offset from start of structure. Then you can define Delphi record with same member names and offset from start or record. The offset is very important: compiler have rules to align field/record and in Delphi you'll probably end up using "packed record" and add dummy data to reproduce VC++ field alignment.

 

For the char pointer, debugging your test application, you'll see if data is ascii or unicode and use the proper Delphi pointer type.

Share this post


Link to post

Thank you for your answer. Translation in delphi:


 

PEFTPOS_DEVICE = ^EFTPOS_DEVICE;
  EFTPOS_DEVICE = record
    posId: PChar;
    terminalId: PChar;
    apiKey: PChar;
    host: PChar;
    port: LongInt;
    posProtocol: POS_PROTOCOL;
  end;

  PEFTPOS_CONTEXT = ^EFTPOS_CONTEXT;
  EFTPOS_CONTEXT = record
    licenseKey: PChar;
    vatNumber: PChar;
    devices: TList<EFTPOS_DEVICE>;
    providers: TList<SIGNATURE_PROVIDER>;
  end;

procedure InitializeEFTPOSDevice(var device: EFTPOS_DEVICE);
begin
  device.posId := nil;
  device.terminalId := nil;
  device.apiKey := nil;
  device.host := nil;
  device.posProtocol := NOT_SET;
  device.port := 0;
end;

procedure InitializeEFTPOSContext(var context: EFTPOS_CONTEXT);
begin
  context.licenseKey := nil;
  context.vatNumber := nil;
  context.devices := TList<EFTPOS_DEVICE>.Create;
  context.providers := TList<SIGNATURE_PROVIDER>.Create;
end;

The same happens if I use 
 

  devices: Array<EFTPOS DEVICE>;

Thanks again

Share this post


Link to post
8 minutes ago, FPiette said:

Are you able to compile a working test program in VC++?

If yes, the you'll be able to see the size of every field member and offset from start of structure. Then you can define Delphi record with same member names and offset from start or record. The offset is very important: compiler have rules to align field/record and in Delphi you'll probably end up using "packed record" and add dummy data to reproduce VC++ field alignment.

 

For the char pointer, debugging your test application, you'll see if data is ascii or unicode and use the proper Delphi pointer type.


Thank you Mr Francois.
Unfortunately I don't have the ability to use C++

Share this post


Link to post
Posted (edited)
1 hour ago, dpallas said:

Is there an alternative way to do this?

To translate it as-is, you would need to know the EXACT implementation details of the std::vector class, which varies from one STL library to another, and even from one C++ compiler to another.  So, no, it is not (easily) translated to Delphi as-is.

 

Whoever wrote this SDK clearly did not intend for it to be used outside of C++.  On the other hand, they also apparently did not understand that STL containers are not safe to access across the DLL boundary, either.  If the DLL code creates the vector and then the EXE code accesses it, or vice versa, then the DLL and the EXE must be written using the exact same compiler and STL implementation, otherwise it will cause undefined behavior.  Since this struct holds context data, those details should have been hidden in the SDK interface to avoid this problem.

 

That being said, if you are just trying to declare this context struct in Delphi and pass it to this SDK, and only the SDK will ever access the context members, then you might try simply declaring the std::vector members as fixed byte arrays with the same byte size as the std::vector class.  But, you would still need a C++ compiler to determine the correct byte size (unless you decompile the SDK to see what size it is actually expecting).  One common implementation of std::vector is to use 3 pointer members, thus sizeof(vector) equals sizeof(Pointer)*3, which would be 12 bytes in a 32bit build and 24 bytes in a 64bit build.  But, another common implementation of std::vector is to use 1 pointer member and 2 integer members, so sizeof(vector) would depend on the size of the integer type being used (usually std::size_t, which can be either 4-8 bytes depending on the STL implementation), so sizeof(vector) could be either 12 bytes or 20 bytes in a 32bit build, and likely would be 24 bytes in a 64bit build.

 

Also, another issue is that the SDK code you have shown has the InitContext() function taking in the context struct by value, which means the caller is responsible for allocating and later freeing the context, including the std::vector members.  And Delphi simply can't allocate and free the memory block that std::vector uses internally.  So, worst case, you would have to leak the memory or risk crashing your code.

 

So, you are basically SOL, I think.  Complain to the SDK author and see if they have an alternative solution for non-C++ languages.

Edited by Remy Lebeau

Share this post


Link to post
44 minutes ago, Remy Lebeau said:

To translate it as-is, you would need to know the EXACT implementation details of the std::vector class, which varies from one STL library to another, and even from one C++ compiler to another.  So, no, it is not (easily) translated to Delphi as-is.

 

Whoever wrote this SDK clearly did not intend for it to be used outside of C++.  On the other hand, they also apparently did not understand that STL containers are not safe to access across the DLL boundary, either.  If the DLL code creates the vector and then the EXE code accesses it, or vice versa, then the DLL and the EXE must be written using the exact same compiler and STL implementation, otherwise it will cause undefined behavior.  Since this struct holds context data, those details should have been hidden in the SDK interface to avoid this problem.

 

That being said, if you are just trying to declare this context struct in Delphi and pass it to this SDK, and only the SDK will ever access the context members, then you might try simply declaring the std::vector members as fixed byte arrays with the same byte size as the std::vector class.  But, you would still need a C++ compiler to determine the correct byte size (unless you decompile the SDK to see what size it is actually expecting).  One common implementation of std::vector is to use 3 pointer members, thus sizeof(vector) equals sizeof(Pointer)*3, which would be 12 bytes in a 32bit build and 24 bytes in a 64bit build.  But, another common implementation of std::vector is to use 1 pointer member and 2 integer members, so sizeof(vector) would depend on the size of the integer type being used (usually std::size_t, which can be either 4-8 bytes depending on the STL implementation), so sizeof(vector) could be either 12 bytes or 20 bytes in a 32bit build, and likely would be 24 bytes in a 64bit build.

 

Also, another issue is that the SDK code you have shown has the InitContext() function taking in the context struct by value, which means the caller is responsible for allocating and later freeing the context, including the std::vector members.  And Delphi simply can't allocate and free the memory block that std::vector uses internally.  So, worst case, you would have to leak the memory or risk crashing your code.

 

So, you are basically SOL, I think.  Complain to the SDK author and see if they have an alternative solution for non-C++ languages.

Remy thank you for your answer
Please look at the code below, I think the field sizes are defined.
Thank you again

typedef struct EFTPOS_CONTEXT 
{ 
 char* licenseKey; 
 char* vatNumber; 
 vector<EFTPOS_DEVICE> devices; 
 vector<SIGNATURE_PROVIDER> providers; 
  
 EFTPOS_CONTEXT() 
  : licenseKey(NULL) 
  , vatNumber(NULL) 
 { 
  devices.clear(); 
  providers.clear(); 
 } 
EFTPOS_CONTEXT(EFTPOS_CONTEXT* context) 
 { 
  if (NULL != context->licenseKey) 
  { 
   licenseKey = new char[strlen(context->licenseKey) + 1]; 
   lstrcpy(licenseKey, context->licenseKey); 
  } 
  
  if (NULL != context->vatNumber) 
  { 
   vatNumber = new char[strlen(context->vatNumber) + 1]; 
   lstrcpy(vatNumber, context->vatNumber); 
  } 
  
  devices.clear(); 
  providers.clear(); 
  
  for (vector<EFTPOS_DEVICE>::iterator it = context->devices.begin(); it != context
>devices.end(); it++) 
  { 
   EFTPOS_DEVICE device(it); 
   devices.push_back(device); 
  } 
  
  for (vector<SIGNATURE_PROVIDER>::iterator it = context->providers.begin(); it != 
context->providers.end(); it++) 
  { 
   providers.push_back(*it); 
  } 
 } 

 

Share this post


Link to post
Posted (edited)
5 hours ago, dpallas said:

Please look at the code below, I think the field sizes are defined.

You don't seem to be understanding the issue here.

 

That code shows you how one EFTPOS_CONTEXT is being copied to another.  Yes, it does show you how the 'char*' members are allocated, which CAN easily be translated to Delphi.  But the operations on the std::vector members CANNOT be translated to Delphi, without knowing the PRECISE implementation of std::vector that the SDK is using.  STL containers like std::vector DO NOT TRANSLATE to Delphi, without re-writing the SDK.

 

This would be a whole different story if the SDK DLL were the one creating and managing the std::vector objects, but based on what you have shown, it is not, at least not completely.  It is expecting the EXE code to participate in the object management.  And that means your Delphi code would have to create and free the objects, and without the implementation details, there is nothing you can do in Delphi to make those std::vector objects valid so the SDK can use them properly.  So, this is a lost cause trying to use EFTPOS_CONTEXT in Delphi directly, as it is simply not compatible with non-C++ languages.  That is the way the SDK authors wrote it.  Period.

 

I'm sorry, but you are just going to have to find another solution.

 

If you absolutely must use this SDK in a Delphi project, then you will have to wrap any needed SDK access inside of your own C++ DLL so the std::vector objects can be used correctly.  And then you can have that DLL export a plain C-style interface (ie, one that doesn't expose access to the vectors) for your Delphi code to use.  There is no other "safe" way to handle this situation.

 

For example:

// MyDLL.cpp

#include <algorithm>
#include "sdk.h"

extern "C" __declspec(dllexport) void __cdecl InitSDKContext(char** contextId, EFTPOS_ERROR* error, void** context)
{
	*context = new EFTPOS_CONTEXT;
	InitContext(contextId, error, *static_cast<EFTPOS_CONTEXT*>(context)); 
}

extern "C" __declspec(dllexport) void __cdecl FreeSDKContext(void* context)
{
	delete static_cast<EFTPOS_CONTEXT*>(context);
}

extern "C" __declspec(dllexport) char* __cdecl GetSDKContextLicenseKey(void* context)
{ 
	return static_cast<EFTPOS_CONTEXT*>(context)->licenseKey; 
}

extern "C" __declspec(dllexport) char* __cdecl GetSDKContextVatNumber(void* context)
{ 
	return static_cast<EFTPOS_CONTEXT*>(context)->vatNumber; 
}

extern "C" __declspec(dllexport) unsigned int __cdecl GetSDKContextDevices(void* context, EFTPOS_DEVICE *devices, unsigned int maxDevices)
{
	EFTPOS_CONTEXT *ctx = static_cast<EFTPOS_CONTEXT*>(context);
	if (!devices) return ctx->devices.size();
	auto numDevices = std::min(ctx->devices.size(), maxDevices);
	std::copy_n(ctx->devices.begin(), numDevices, devices);
	return numDevices;
}

extern "C" __declspec(dllexport) unsigned int __cdecl GetSDKContextProviders(void* context, SIGNATURE_PROVIDER *providers, unsigned int maxProviders)
{
	EFTPOS_CONTEXT *ctx = static_cast<EFTPOS_CONTEXT*>(context);
	if (!providers) return ctx->providers.size();
	auto numProviders = std::min(ctx->devices.size(), maxProviders);
	std::copy_n(ctx->providers.begin(), numProviders, providers);
	return numProviders;
}

...
// MyApp.pas

type
  ERROR_LEVEL = ...; 
  ERROR_CODE = ...; 
  POS_PROTOCOL = ...;

  EFTPOS_ERROR = ^PEFTPOS_ERROR;
  EFTPOS_ERROR = record
    errorLevel: ERROR_LEVEL; 
    errorCode: ERROR_CODE; 
    errorMessage: PAnsiChar;
  end;

  PEFTPOS_DEVICE = ^EFTPOS_DEVICE;
  EFTPOS_DEVICE = record
    posId: PAnsiChar; 
    terminalId: PAnsiChar; 
    apiKey: PAnsiChar; 
    host: PAnsiChar; 
    port: LONG;
    posProtocol: POS_PROTOCOL;
  end;

  PSIGNATURE_PROVIDER = ^SIGNATURE_PROVIDER;
  SIGNATURE_PROVIDER = record
    ...
  end;

  PEFTPOS_CONTEXT = ^EFTPOS_CONTEXT;
  EFTPOS_CONTEXT = record end; // leave this empty!

  procedure InitSDKContext(contextId: PPAnsiChar; error: PEFTPOS_ERROR; var context: PEFTPOS_CONTEXT); cdecl; external 'my.dll';
  procedure FreeSDKContext(context: PEFTPOS_CONTEXT); cdecl; external 'my.dll';
  function GetSDKContextLicenseKey(context: PEFTPOS_CONTEXT): PAnsiChar; cdecl; external 'my.dll';
  function GetSDKContextVatNumber(context: PEFTPOS_CONTEXT): PAnsiChar; cdecl; external 'my.dll';
  function GetSDKContextDevices(context: PEFTPOS_CONTEXT; devices: PEFTPOS_DEVICE; maxDevices: UInt32): UInt32; cdecl; external 'my.dll';
  function GetSDKContextProviders(context: PEFTPOS_CONTEXT; providers: PSIGNATURE_PROVIDER; maxProviders: UInt32): UInt32; cdecl; external 'my.dll';

...

var
  context: PEFTPOS_CONTEXT;
  devices: array of EFTPOS_DEVICE;
  providers: array of SIGNATURE_PROVIDER;

InitSDKContext(..., context);
...
... := GetSDKContextLicenseKey(context);
... := GetSDKContextVatNumber(context);
...
SetLength(devices, GetSDKContextDevices(context, nil, 0));
GetSDKContextDevices(context, PEFTPOS_DEVICE(devices), Length(devices));
...
SetLength(providers, GetSDKContextProviders(context, nil, 0));
GetSDKContextProviders(context, PSIGNATURE_PROVIDER(providers), Length(providers));
...
FreeSDKContext(context);

 

Edited by Remy Lebeau

Share this post


Link to post

I don't know why you aren't facing up to the reality that you need to write some C++ code. 

Share this post


Link to post

The problem is that I don't know how to write in VC++
Thank you David.
 

Share this post


Link to post
8 hours ago, Remy Lebeau said:

You don't seem to be understanding the issue here.

 

That code shows you how one EFTPOS_CONTEXT is being copied to another.  Yes, it does show you how the 'char*' members are allocated, which CAN easily be translated to Delphi.  But the operations on the std::vector members CANNOT be translated to Delphi, without knowing the PRECISE implementation of std::vector that the SDK is using.  STL containers like std::vector DO NOT TRANSLATE to Delphi, without re-writing the SDK.

 

This would be a whole different story if the SDK DLL were the one creating and managing the std::vector objects, but based on what you have shown, it is not, at least not completely.  It is expecting the EXE code to participate in the object management.  And that means your Delphi code would have to create and free the objects, and without the implementation details, there is nothing you can do in Delphi to make those std::vector objects valid so the SDK can use them properly.  So, this is a lost cause trying to use EFTPOS_CONTEXT in Delphi directly, as it is simply not compatible with non-C++ languages.  That is the way the SDK authors wrote it.  Period.

 

I'm sorry, but you are just going to have to find another solution.

 

If you absolutely must use this SDK in a Delphi project, then you will have to wrap any needed SDK access inside of your own C++ DLL so the std::vector objects can be used correctly.  And then you can have that DLL export a plain C-style interface (ie, one that doesn't expose access to the vectors) for your Delphi code to use.  There is no other "safe" way to handle this situation.

 

For example:


// MyDLL.cpp

#include <algorithm>
#include "sdk.h"

extern "C" __declspec(dllexport) void __cdecl InitSDKContext(char** contextId, EFTPOS_ERROR* error, void** context)
{
	*context = new EFTPOS_CONTEXT;
	InitContext(contextId, error, *static_cast<EFTPOS_CONTEXT*>(context)); 
}

extern "C" __declspec(dllexport) void __cdecl FreeSDKContext(void* context)
{
	delete static_cast<EFTPOS_CONTEXT*>(context);
}

extern "C" __declspec(dllexport) char* __cdecl GetSDKContextLicenseKey(void* context)
{ 
	return static_cast<EFTPOS_CONTEXT*>(context)->licenseKey; 
}

extern "C" __declspec(dllexport) char* __cdecl GetSDKContextVatNumber(void* context)
{ 
	return static_cast<EFTPOS_CONTEXT*>(context)->vatNumber; 
}

extern "C" __declspec(dllexport) unsigned int __cdecl GetSDKContextDevices(void* context, EFTPOS_DEVICE *devices, unsigned int maxDevices)
{
	EFTPOS_CONTEXT *ctx = static_cast<EFTPOS_CONTEXT*>(context);
	if (!devices) return ctx->devices.size();
	auto numDevices = std::min(ctx->devices.size(), maxDevices);
	std::copy_n(ctx->devices.begin(), numDevices, devices);
	return numDevices;
}

extern "C" __declspec(dllexport) unsigned int __cdecl GetSDKContextProviders(void* context, SIGNATURE_PROVIDER *providers, unsigned int maxProviders)
{
	EFTPOS_CONTEXT *ctx = static_cast<EFTPOS_CONTEXT*>(context);
	if (!providers) return ctx->providers.size();
	auto numProviders = std::min(ctx->devices.size(), maxProviders);
	std::copy_n(ctx->providers.begin(), numProviders, providers);
	return numProviders;
}

...

// MyApp.pas

type
  ERROR_LEVEL = ...; 
  ERROR_CODE = ...; 
  POS_PROTOCOL = ...;

  EFTPOS_ERROR = ^PEFTPOS_ERROR;
  EFTPOS_ERROR = record
    errorLevel: ERROR_LEVEL; 
    errorCode: ERROR_CODE; 
    errorMessage: PAnsiChar;
  end;

  PEFTPOS_DEVICE = ^EFTPOS_DEVICE;
  EFTPOS_DEVICE = record
    posId: PAnsiChar; 
    terminalId: PAnsiChar; 
    apiKey: PAnsiChar; 
    host: PAnsiChar; 
    port: LONG;
    posProtocol: POS_PROTOCOL;
  end;

  PSIGNATURE_PROVIDER = ^SIGNATURE_PROVIDER;
  SIGNATURE_PROVIDER = record
    ...
  end;

  PEFTPOS_CONTEXT = ^EFTPOS_CONTEXT;
  EFTPOS_CONTEXT = record end; // leave this empty!

  procedure InitSDKContext(contextId: PPAnsiChar; error: PEFTPOS_ERROR; var context: PEFTPOS_CONTEXT); cdecl; external 'my.dll';
  procedure FreeSDKContext(context: PEFTPOS_CONTEXT); cdecl; external 'my.dll';
  function GetSDKContextLicenseKey(context: PEFTPOS_CONTEXT): PAnsiChar; cdecl; external 'my.dll';
  function GetSDKContextVatNumber(context: PEFTPOS_CONTEXT): PAnsiChar; cdecl; external 'my.dll';
  function GetSDKContextDevices(context: PEFTPOS_CONTEXT; devices: PEFTPOS_DEVICE; maxDevices: UInt32): UInt32; cdecl; external 'my.dll';
  function GetSDKContextProviders(context: PEFTPOS_CONTEXT; providers: PSIGNATURE_PROVIDER; maxProviders: UInt32): UInt32; cdecl; external 'my.dll';

...

var
  context: PEFTPOS_CONTEXT;
  devices: array of EFTPOS_DEVICE;
  providers: array of SIGNATURE_PROVIDER;

InitSDKContext(..., context);
...
... := GetSDKContextLicenseKey(context);
... := GetSDKContextVatNumber(context);
...
SetLength(devices, GetSDKContextDevices(context, nil, 0));
GetSDKContextDevices(context, PEFTPOS_DEVICE(devices), Length(devices));
...
SetLength(providers, GetSDKContextProviders(context, nil, 0));
GetSDKContextProviders(context, PSIGNATURE_PROVIDER(providers), Length(providers));
...
FreeSDKContext(context);

 

Remy thanks again for your response. But I have no idea about C++. 
So it is impossible for me to implement the C code you wrote.
Yes I definitely need this SDK.
The only solution is to find someone to write this code.

Share this post


Link to post
On 7/28/2024 at 10:32 AM, dpallas said:

Yes I definitely need this SDK

For using with Delphi, you should ask for C SDK, not a C++. C and C++ are two very different beasts.

By the way, what is your target hardware? And where the Header files you showed extract are coming from?

 

 

Share this post


Link to post

I don't get why the target hardware has anything to do with it, though it is desktop computers (x64). The header comes from a driver library, for which I do not have the source.  I agree, however, that the only solution is either a C SDK, or writing a C-like interface dll.  

Share this post


Link to post
1 hour ago, dpallas said:

The header comes from a driver library

If we know the driver editor, we could search for you the correct SDK, documentation, header files and so on. I asked the hardware because I thought you were using a terminal like Zeller EFTPOS terminal.

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×