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); }