Skip to content

Commit

Permalink
Use ServerSessionChangeListener to bind function handlers to SapServer
Browse files Browse the repository at this point in the history
  • Loading branch information
campersau committed Aug 30, 2022
1 parent c337307 commit 0e651de
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 37 deletions.
27 changes: 12 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -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();
Expand Down
10 changes: 10 additions & 0 deletions src/SapNwRfc/Internal/Interop/RfcCallType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace SapNwRfc.Internal.Interop
{
internal enum RfcCallType
{
RFC_SYNCHRONOUS,
RFC_TRANSACTIONAL,
RFC_QUEUED,
RFC_BACKGROUND_UNIT,
}
}
21 changes: 21 additions & 0 deletions src/SapNwRfc/Internal/Interop/RfcInterop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
25 changes: 25 additions & 0 deletions src/SapNwRfc/Internal/Interop/RfcServerContext.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
14 changes: 14 additions & 0 deletions src/SapNwRfc/Internal/Interop/RfcSessionChange.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
10 changes: 10 additions & 0 deletions src/SapNwRfc/Internal/Interop/RfcSessionEvent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace SapNwRfc.Internal.Interop
{
internal enum RfcSessionEvent
{
RFC_SESSION_CREATED,
RFC_SESSION_ACTIVATED,
RFC_SESSION_PASSIVATED,
RFC_SESSION_DESTROYED,
}
}
16 changes: 13 additions & 3 deletions src/SapNwRfc/Pooling/SapPooledConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,10 @@ public void InvokeFunction(string name, CancellationToken cancellationToken = de
/// <inheritdoc cref="ISapPooledConnection"/>
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);
}
Expand All @@ -104,6 +105,8 @@ public void InvokeFunction(string name, object input, CancellationToken cancella
/// <inheritdoc cref="ISapPooledConnection"/>
public TOutput InvokeFunction<TOutput>(string name, CancellationToken cancellationToken = default)
{
_connection = _connection ?? _pool.GetConnection(cancellationToken);

try
{
// no using here to prevent function from being disposed
Expand Down Expand Up @@ -132,10 +135,17 @@ public TOutput InvokeFunction<TOutput>(string name, CancellationToken cancellati
/// <inheritdoc cref="ISapPooledConnection"/>
public TOutput InvokeFunction<TOutput>(string name, object input, CancellationToken cancellationToken = default)
{
_connection = _connection ?? _pool.GetConnection(cancellationToken);

try
{
using (ISapFunction function = _connection.CreateFunction(name))
return function.Invoke<TOutput>(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<TOutput>(input);
}
catch (SapCommunicationFailedException)
{
Expand Down
77 changes: 58 additions & 19 deletions src/SapNwRfc/SapServer.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using SapNwRfc.Internal.Interop;

namespace SapNwRfc
Expand All @@ -12,35 +13,65 @@ public sealed class SapServer : ISapServer
private readonly RfcInterop _interop;
private readonly IntPtr _rfcServerHandle;
private readonly SapConnectionParameters _parameters;
private readonly Action<ISapServerConnection, ISapServerFunction> _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<ISapServerConnection, ISapServerFunction> 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 _);
}
}

/// <summary>
/// Creates and connects a new RFC Server.
/// </summary>
/// <param name="connectionString">The connection string.</param>
/// <param name="action">The RFC server function handler.</param>
/// <returns>The SAP RFC Server.</returns>
public static ISapServer Create(string connectionString)
public static ISapServer Create(string connectionString, Action<ISapServerConnection, ISapServerFunction> action)
{
return Create(SapConnectionParameters.Parse(connectionString));
return Create(SapConnectionParameters.Parse(connectionString), action);
}

/// <summary>
/// Creates and connects a new RFC Server.
/// </summary>
/// <param name="parameters">The connection parameters.</param>
/// <param name="action">The RFC server function handler.</param>
/// <returns>The SAP RFC Server.</returns>
public static ISapServer Create(SapConnectionParameters parameters)
public static ISapServer Create(SapConnectionParameters parameters, Action<ISapServerConnection, ISapServerFunction> 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<ISapServerConnection, ISapServerFunction> action)
{
RfcConnectionParameter[] interopParameters = parameters.ToInterop();

Expand All @@ -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<SapServerErrorEventArgs> _error;
Expand All @@ -65,7 +96,7 @@ public event EventHandler<SapServerErrorEventArgs> Error
{
RfcResultCode resultCode = _interop.AddServerErrorListener(
rfcHandle: _rfcServerHandle,
errorListener: ServerErrorListener,
errorListener: _serverErrorListener,
errorInfo: out RfcErrorInfo errorInfo);

resultCode.ThrowOnError(errorInfo);
Expand Down Expand Up @@ -96,7 +127,7 @@ public event EventHandler<SapServerStateChangeEventArgs> StateChange
{
RfcResultCode resultCode = _interop.AddServerStateChangedListener(
rfcHandle: _rfcServerHandle,
stateChangeListener: ServerStateChangeListener,
stateChangeListener: _serverStateChangeListener,
errorInfo: out RfcErrorInfo errorInfo);

resultCode.ThrowOnError(errorInfo);
Expand Down Expand Up @@ -145,6 +176,14 @@ public void Dispose()
_interop.DestroyServer(
rfcHandle: _rfcServerHandle,
errorInfo: out RfcErrorInfo _);

foreach (KeyValuePair<string, SapServer> kv in Servers)
{
if (kv.Value == this)
{
Servers.TryRemove(kv.Key, out _);
}
}
}

/// <summary>
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit 0e651de

Please sign in to comment.