using System.Runtime.InteropServices;
namespace ChangeT50.Adapter;
///
/// Dynamic .NET wrapper for all 16 documented Change.dll exports.
/// The supplied binary requires a Windows x86 process.
///
public sealed class ChangeNativeLibrary : IDisposable
{
private IntPtr _module;
private readonly OpenComDelegate _openCom;
private readonly CloseComDelegate _closeCom;
private readonly QueryCardDelegate _queryCard;
private readonly GetCardInfoDelegate _getCardInfo;
private readonly SetCardInfoDelegate _setCardInfo;
private readonly UploadDelegate _upload;
private readonly DbConnectDelegate _dbConnect;
private readonly DbDisconnectDelegate _dbDisconnect;
private readonly GetCustomerListDelegate _getCustomerList;
private readonly GetCountDelegate _getCustomerCount;
private readonly GetDepartmentListDelegate _getDepartmentList;
private readonly GetCountDelegate _getDepartmentCount;
private readonly DownBlacklistDelegate _downBlacklist;
private readonly GetCountDelegate _downBlacklistCount;
private readonly DownCustomerInfoDelegate _downCustomerInfo;
private readonly GetCustomerByNumberDelegate _getCustomerByNumber;
public ChangeNativeLibrary(string dllPath)
{
ArgumentException.ThrowIfNullOrWhiteSpace(dllPath);
ChangeAbi.EnsureNativeLoadEnvironment();
var fullPath = Path.GetFullPath(dllPath);
if (!File.Exists(fullPath))
{
throw new FileNotFoundException("Change.dll was not found.", fullPath);
}
_module = NativeLibrary.Load(fullPath);
try
{
_openCom = Bind("OpenCom");
_closeCom = Bind("CloseCom");
_queryCard = Bind("CapNBQueryCard");
_getCardInfo = Bind("CapGetNBCardInfo");
_setCardInfo = Bind("CapSetNBCardInfo");
_upload = Bind("CapUpload");
_dbConnect = Bind("CapDBConnect");
_dbDisconnect = Bind("CapDBDiscon");
_getCustomerList = Bind("CapGetCustomerList");
_getCustomerCount = Bind("CapGetCustomerCount");
_getDepartmentList = Bind("CapGetDepList");
_getDepartmentCount = Bind("CapGetDepCount");
_downBlacklist = Bind("CapDownBlacklist");
_downBlacklistCount = Bind("CapDownBlacklistCount");
_downCustomerInfo = Bind("CapDownCustomerInfo");
_getCustomerByNumber = Bind("CapGetCustomerByNum");
}
catch
{
NativeLibrary.Free(_module);
_module = IntPtr.Zero;
throw;
}
}
public int OpenReader() => _openCom();
public void CloseReader() => _closeCom();
public int QueryCard(out int uid) => _queryCard(out uid);
public int ReadCard(out CustomerInfoNative customer)
{
customer = default;
return _getCardInfo(ref customer);
}
///
/// Writes a debit result to the physical card. Do not call without an approved test card and amount.
///
public int DebitCard(
int customerId,
int amountCents,
int walletNumber,
DateTime tradeTime,
out long psamId,
out int psamTradeNumber,
out int tac)
{
var nativeTradeTime = Marshal.StringToHGlobalAnsi(tradeTime.ToString("yyyy-MM-dd HH:mm:ss"));
try
{
return _setCardInfo(
customerId,
amountCents,
walletNumber,
nativeTradeTime,
out psamId,
out psamTradeNumber,
out tac);
}
finally
{
Marshal.FreeHGlobal(nativeTradeTime);
}
}
///
/// Uploads a completed trade. The vendor API does not document the output message length,
/// so the caller owns and sizes the unmanaged message buffer.
///
public int UploadTrade(ref UploadInfoNative upload, IntPtr messageBuffer) =>
_upload(ref upload, messageBuffer);
public int ConnectWebService(string serviceUrl)
{
ArgumentException.ThrowIfNullOrWhiteSpace(serviceUrl);
var nativeUrl = Marshal.StringToHGlobalAnsi(serviceUrl);
try
{
return _dbConnect(nativeUrl);
}
finally
{
Marshal.FreeHGlobal(nativeUrl);
}
}
public void DisconnectWebService() => _dbDisconnect();
public int GetCustomerCount() => _getCustomerCount();
public int GetCustomerList(IntPtr customerBuffer) => _getCustomerList(customerBuffer);
public int GetDepartmentCount() => _getDepartmentCount();
public int GetDepartmentList(IntPtr departmentBuffer) => _getDepartmentList(departmentBuffer);
public int GetBlacklistCount() => _downBlacklistCount();
public int GetBlacklist(IntPtr cardNumberBuffer) => _downBlacklist(cardNumberBuffer);
public int GetCustomer(int customerId, out DownInfoNative customer)
{
customer = default;
return _downCustomerInfo(customerId, ref customer);
}
public int GetCustomers(int firstCustomerId, int count, IntPtr customerBuffer) =>
_getCustomerByNumber(firstCustomerId, count, customerBuffer);
public void Dispose()
{
if (_module == IntPtr.Zero)
{
return;
}
NativeLibrary.Free(_module);
_module = IntPtr.Zero;
GC.SuppressFinalize(this);
}
private T Bind(string exportName) where T : Delegate
{
if (!NativeLibrary.TryGetExport(_module, exportName, out var address))
{
throw new EntryPointNotFoundException($"Change.dll export '{exportName}' was not found.");
}
return Marshal.GetDelegateForFunctionPointer(address);
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int OpenComDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void CloseComDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int QueryCardDelegate(out int uid);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int GetCardInfoDelegate(ref CustomerInfoNative customer);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int SetCardInfoDelegate(
int customerId,
int amountCents,
int walletNumber,
IntPtr tradeTime,
out long psamId,
out int psamTradeNumber,
out int tac);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int UploadDelegate(ref UploadInfoNative upload, IntPtr messageBuffer);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int DbConnectDelegate(IntPtr serviceUrl);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void DbDisconnectDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int GetCustomerListDelegate(IntPtr customers);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int GetCountDelegate();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int GetDepartmentListDelegate(IntPtr departments);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int DownBlacklistDelegate(IntPtr cardNumbers);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int DownCustomerInfoDelegate(int customerId, ref DownInfoNative customer);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int GetCustomerByNumberDelegate(int firstCustomerId, int count, IntPtr customers);
}