From 0e651dea41312e03d6322b82560d08435a4a01f7 Mon Sep 17 00:00:00 2001 From: campersau Date: Thu, 10 Feb 2022 18:26:49 +0100 Subject: [PATCH] Use ServerSessionChangeListener to bind function handlers to SapServer --- README.md | 27 +++---- src/SapNwRfc/Internal/Interop/RfcCallType.cs | 10 +++ src/SapNwRfc/Internal/Interop/RfcInterop.cs | 21 +++++ .../Internal/Interop/RfcServerContext.cs | 25 ++++++ .../Internal/Interop/RfcSessionChange.cs | 14 ++++ .../Internal/Interop/RfcSessionEvent.cs | 10 +++ src/SapNwRfc/Pooling/SapPooledConnection.cs | 16 +++- src/SapNwRfc/SapServer.cs | 77 ++++++++++++++----- 8 files changed, 163 insertions(+), 37 deletions(-) create mode 100644 src/SapNwRfc/Internal/Interop/RfcCallType.cs create mode 100644 src/SapNwRfc/Internal/Interop/RfcServerContext.cs create mode 100644 src/SapNwRfc/Internal/Interop/RfcSessionChange.cs create mode 100644 src/SapNwRfc/Internal/Interop/RfcSessionEvent.cs diff --git a/README.md b/README.md index bc501d4..c26fca9 100644 --- a/README.md +++ b/README.md @@ -194,15 +194,20 @@ class SomeFunctionResult ```csharp string connectionString = "AppServerHost=MY_SERVER_HOST; SystemNumber=00; User=MY_SAP_USER; Password=SECRET; Client=100; Language=EN; PoolSize=5; Trace=8"; -var connectionPool = new SapConnectionPool(connectionString); - -SapServer.InstallGenericServerFunctionHandler( -(string functionName, SapAttributes attributes) => +SapServer.InstallGenericServerFunctionHandler((string functionName, SapAttributes attributes) => { - using var connection = connectionPool.GetConnection(); + using var connection = new SapConnection(connectionString); + connection.Connect(); return connection.GetFunctionMetadata(functionName); -}, -(ISapServerConnection connection, ISapServerFunction function) => +}); +``` + +### RFC Server + +```csharp +string connectionString = "GWHOST=MY_GW_HOST; GWSERV=MY_GW_SERV; PROGRAM_ID=MY_PROGRAM_ID; REG_COUNT=1"; + +using var server = SapServer.Create(connectionString, (ISapServerConnection connection, ISapServerFunction function) => { var attributes = connection.GetAttributes(); @@ -214,14 +219,6 @@ SapServer.InstallGenericServerFunctionHandler( break; } }); -``` - -### RFC Server - -```csharp -string connectionString = "GWHOST=MY_GW_HOST; GWSERV=MY_GW_SERV; PROGRAM_ID=MY_PROGRAM_ID; REG_COUNT=1"; - -using var server = SapServer.Create(connectionString); server.Error += (object sender, SapServerErrorEventArgs args) => Console.WriteLine(args.Error.Message); server.StateChange += (object sender, SapServerStateChangeEventArgs args) => Console.WriteLine(args.OldState + " -> " + args.NewState); server.Launch(); diff --git a/src/SapNwRfc/Internal/Interop/RfcCallType.cs b/src/SapNwRfc/Internal/Interop/RfcCallType.cs new file mode 100644 index 0000000..8b3ce86 --- /dev/null +++ b/src/SapNwRfc/Internal/Interop/RfcCallType.cs @@ -0,0 +1,10 @@ +namespace SapNwRfc.Internal.Interop +{ + internal enum RfcCallType + { + RFC_SYNCHRONOUS, + RFC_TRANSACTIONAL, + RFC_QUEUED, + RFC_BACKGROUND_UNIT, + } +} diff --git a/src/SapNwRfc/Internal/Interop/RfcInterop.cs b/src/SapNwRfc/Internal/Interop/RfcInterop.cs index e188a1a..9a90709 100644 --- a/src/SapNwRfc/Internal/Interop/RfcInterop.cs +++ b/src/SapNwRfc/Internal/Interop/RfcInterop.cs @@ -331,6 +331,9 @@ public virtual IntPtr AppendNewRow(IntPtr tableHandle, out RfcErrorInfo errorInf [UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)] public delegate RfcResultCode RfcFunctionDescriptionCallback(string functionName, RfcAttributes attributes, out IntPtr funcDescHandle); + [UnmanagedFunctionPointer(CallingConvention.StdCall)] + public delegate void RfcServerSessionChangeListener(IntPtr serverHandle, in RfcSessionChange sessionChange, in RfcErrorInfo errorInfo); + [UnmanagedFunctionPointer(CallingConvention.StdCall)] public delegate void RfcServerErrorListener(IntPtr serverHandle, in RfcAttributes clientInfo, in RfcErrorInfo errorInfo); @@ -367,6 +370,24 @@ public virtual RfcResultCode LaunchServer(IntPtr rfcHandle, out RfcErrorInfo err public virtual RfcResultCode ShutdownServer(IntPtr rfcHandle, uint timeout, out RfcErrorInfo errorInfo) => RfcShutdownServer(rfcHandle, timeout, out errorInfo); + [DllImport(SapNwRfcDllName)] + private static extern RfcResultCode RfcGetServerContext(IntPtr rfcHandle, out RfcServerContext serverContext, out RfcErrorInfo errorInfo); + + public virtual RfcResultCode GetServerContext(IntPtr rfcHandle, out RfcServerContext serverContext, out RfcErrorInfo errorInfo) + => RfcGetServerContext(rfcHandle, out serverContext, out errorInfo); + + [DllImport(SapNwRfcDllName)] + private static extern RfcResultCode RfcSetServerStateful(IntPtr rfcHandle, uint isStateful, out RfcErrorInfo errorInfo); + + public virtual RfcResultCode SetServerStateful(IntPtr rfcHandle, uint isStateful, out RfcErrorInfo errorInfo) + => RfcSetServerStateful(rfcHandle, isStateful, out errorInfo); + + [DllImport(SapNwRfcDllName)] + private static extern RfcResultCode RfcAddServerSessionChangedListener(IntPtr rfcHandle, RfcServerSessionChangeListener sessionChangeListener, out RfcErrorInfo errorInfo); + + public virtual RfcResultCode AddServerSessionChangedListener(IntPtr rfcHandle, RfcServerSessionChangeListener sessionChangeListener, out RfcErrorInfo errorInfo) + => RfcAddServerSessionChangedListener(rfcHandle, sessionChangeListener, out errorInfo); + [DllImport(SapNwRfcDllName)] private static extern RfcResultCode RfcAddServerErrorListener(IntPtr rfcHandle, RfcServerErrorListener errorListener, out RfcErrorInfo errorInfo); diff --git a/src/SapNwRfc/Internal/Interop/RfcServerContext.cs b/src/SapNwRfc/Internal/Interop/RfcServerContext.cs new file mode 100644 index 0000000..8266b63 --- /dev/null +++ b/src/SapNwRfc/Internal/Interop/RfcServerContext.cs @@ -0,0 +1,25 @@ +using System; +using System.Runtime.InteropServices; + +namespace SapNwRfc.Internal.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct RfcServerContext + { + [MarshalAs(UnmanagedType.I4)] + public RfcCallType CallType; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 24 + 1)] + public string Tid; + + public IntPtr UnitIdentifier; + + public IntPtr UnitAttributes; + + [MarshalAs(UnmanagedType.U4)] + public uint IsStateful; + + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32 + 1)] + public string SessionId; + } +} diff --git a/src/SapNwRfc/Internal/Interop/RfcSessionChange.cs b/src/SapNwRfc/Internal/Interop/RfcSessionChange.cs new file mode 100644 index 0000000..b7eda09 --- /dev/null +++ b/src/SapNwRfc/Internal/Interop/RfcSessionChange.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace SapNwRfc.Internal.Interop +{ + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal struct RfcSessionChange + { + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30 + 1)] + public string SessionId; + + [MarshalAs(UnmanagedType.I4)] + public RfcSessionEvent Event; + } +} diff --git a/src/SapNwRfc/Internal/Interop/RfcSessionEvent.cs b/src/SapNwRfc/Internal/Interop/RfcSessionEvent.cs new file mode 100644 index 0000000..29a3b8d --- /dev/null +++ b/src/SapNwRfc/Internal/Interop/RfcSessionEvent.cs @@ -0,0 +1,10 @@ +namespace SapNwRfc.Internal.Interop +{ + internal enum RfcSessionEvent + { + RFC_SESSION_CREATED, + RFC_SESSION_ACTIVATED, + RFC_SESSION_PASSIVATED, + RFC_SESSION_DESTROYED, + } +} diff --git a/src/SapNwRfc/Pooling/SapPooledConnection.cs b/src/SapNwRfc/Pooling/SapPooledConnection.cs index ff9d3ef..9c2ab75 100644 --- a/src/SapNwRfc/Pooling/SapPooledConnection.cs +++ b/src/SapNwRfc/Pooling/SapPooledConnection.cs @@ -83,9 +83,10 @@ public void InvokeFunction(string name, CancellationToken cancellationToken = de /// public void InvokeFunction(string name, object input, CancellationToken cancellationToken = default) { + _connection = _connection ?? _pool.GetConnection(cancellationToken); + try { - _connection = _connection ?? _pool.GetConnection(cancellationToken); using (ISapFunction function = _connection.CreateFunction(name)) function.Invoke(input); } @@ -104,6 +105,8 @@ public void InvokeFunction(string name, object input, CancellationToken cancella /// public TOutput InvokeFunction(string name, CancellationToken cancellationToken = default) { + _connection = _connection ?? _pool.GetConnection(cancellationToken); + try { // no using here to prevent function from being disposed @@ -132,10 +135,17 @@ public TOutput InvokeFunction(string name, CancellationToken cancellati /// public TOutput InvokeFunction(string name, object input, CancellationToken cancellationToken = default) { + _connection = _connection ?? _pool.GetConnection(cancellationToken); + try { - using (ISapFunction function = _connection.CreateFunction(name)) - return function.Invoke(input); + // no using here to prevent function from being disposed + // when possibly an IEnumerable is used in TOutput + // and rows are accessed after InvokeFunction + ISapFunction function = _connection.CreateFunction(name); + _functions.Add(function); + + return function.Invoke(input); } catch (SapCommunicationFailedException) { diff --git a/src/SapNwRfc/SapServer.cs b/src/SapNwRfc/SapServer.cs index 90722dd..185512d 100644 --- a/src/SapNwRfc/SapServer.cs +++ b/src/SapNwRfc/SapServer.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using SapNwRfc.Internal.Interop; namespace SapNwRfc @@ -12,35 +13,65 @@ public sealed class SapServer : ISapServer private readonly RfcInterop _interop; private readonly IntPtr _rfcServerHandle; private readonly SapConnectionParameters _parameters; + private readonly Action _action; - private SapServer(RfcInterop interop, IntPtr rfcServerHandle, SapConnectionParameters parameters) + // Keep a reference to the server function delegates so they don't get GCed + private readonly RfcInterop.RfcServerSessionChangeListener _serverSessionChangeListener; + private readonly RfcInterop.RfcServerErrorListener _serverErrorListener; + private readonly RfcInterop.RfcServerStateChangeListener _serverStateChangeListener; + + private SapServer(RfcInterop interop, IntPtr rfcServerHandle, SapConnectionParameters parameters, Action action) { _interop = interop; _rfcServerHandle = rfcServerHandle; _parameters = parameters; + _action = action; + + _serverSessionChangeListener = ServerSessionChangeListener; + _serverErrorListener = ServerErrorListener; + _serverStateChangeListener = ServerStateChangeListener; + + RfcResultCode resultCode = _interop.AddServerSessionChangedListener( + rfcHandle: rfcServerHandle, + sessionChangeListener: _serverSessionChangeListener, + errorInfo: out RfcErrorInfo errorInfo); + } + + private void ServerSessionChangeListener(IntPtr serverHandle, in RfcSessionChange sessionChange, in RfcErrorInfo errorInfo) + { + if (sessionChange.Event == RfcSessionEvent.RFC_SESSION_CREATED) + { + Servers.TryAdd(sessionChange.SessionId, this); + } + else if (sessionChange.Event == RfcSessionEvent.RFC_SESSION_DESTROYED) + { + Servers.TryRemove(sessionChange.SessionId, out _); + } } /// /// Creates and connects a new RFC Server. /// /// The connection string. + /// The RFC server function handler. /// The SAP RFC Server. - public static ISapServer Create(string connectionString) + public static ISapServer Create(string connectionString, Action action) { - return Create(SapConnectionParameters.Parse(connectionString)); + return Create(SapConnectionParameters.Parse(connectionString), action); } /// /// Creates and connects a new RFC Server. /// /// The connection parameters. + /// The RFC server function handler. /// The SAP RFC Server. - public static ISapServer Create(SapConnectionParameters parameters) + public static ISapServer Create(SapConnectionParameters parameters, Action action) { - return Create(new RfcInterop(), parameters); + return Create(new RfcInterop(), parameters, action); } - private static ISapServer Create(RfcInterop rfcInterop, SapConnectionParameters parameters) + private static ISapServer Create(RfcInterop rfcInterop, SapConnectionParameters parameters, Action action) { RfcConnectionParameter[] interopParameters = parameters.ToInterop(); @@ -51,7 +82,7 @@ private static ISapServer Create(RfcInterop rfcInterop, SapConnectionParameters errorInfo.ThrowOnError(); - return new SapServer(rfcInterop, rfcServerHandle, parameters); + return new SapServer(rfcInterop, rfcServerHandle, parameters, action); } private EventHandler _error; @@ -65,7 +96,7 @@ public event EventHandler Error { RfcResultCode resultCode = _interop.AddServerErrorListener( rfcHandle: _rfcServerHandle, - errorListener: ServerErrorListener, + errorListener: _serverErrorListener, errorInfo: out RfcErrorInfo errorInfo); resultCode.ThrowOnError(errorInfo); @@ -96,7 +127,7 @@ public event EventHandler StateChange { RfcResultCode resultCode = _interop.AddServerStateChangedListener( rfcHandle: _rfcServerHandle, - stateChangeListener: ServerStateChangeListener, + stateChangeListener: _serverStateChangeListener, errorInfo: out RfcErrorInfo errorInfo); resultCode.ThrowOnError(errorInfo); @@ -145,6 +176,14 @@ public void Dispose() _interop.DestroyServer( rfcHandle: _rfcServerHandle, errorInfo: out RfcErrorInfo _); + + foreach (KeyValuePair kv in Servers) + { + if (kv.Value == this) + { + Servers.TryRemove(kv.Key, out _); + } + } } /// @@ -196,16 +235,6 @@ private static RfcResultCode HandleGenericFunction(IntPtr connectionHandle, IntP return RfcResultCode.RFC_EXTERNAL_FAILURE; } - IntPtr functionDesc = interop.DescribeFunction( - rfcHandle: functionHandle, - errorInfo: out RfcErrorInfo functionDescErrorInfo); - - if (functionDescErrorInfo.Code != RfcResultCode.RFC_OK) - { - errorInfo = functionDescErrorInfo; - return functionDescErrorInfo.Code; - } - RfcResultCode serverContextResultCode = interop.GetServerContext( rfcHandle: connectionHandle, serverContext: out RfcServerContext serverContext, @@ -233,6 +262,16 @@ private static RfcResultCode HandleGenericFunction(IntPtr connectionHandle, IntP if (Servers.TryGetValue(serverContext.SessionId, out SapServer sapServer)) { + IntPtr functionDesc = interop.DescribeFunction( + rfcHandle: functionHandle, + errorInfo: out RfcErrorInfo functionDescErrorInfo); + + if (functionDescErrorInfo.Code != RfcResultCode.RFC_OK) + { + errorInfo = functionDescErrorInfo; + return functionDescErrorInfo.Code; + } + var connection = new SapServerConnection(interop, connectionHandle); var function = new SapServerFunction(interop, functionHandle, functionDesc);