Using the C# SDK instead of the C SDK

Overview of the EOS C# SDK

Epic Online Services (EOS) features a C# SDK in addition to the C SDK. This document will help you integrate EOS with your project using the C# SDK, and will cover some of the implementation differences between the two. While the core functionality remains the same regardless of which SDK you use, the C# SDK differs in design in the following ways:

  • The C# SDK adheres to C# best practices and follows an object-oriented approach, such as using handle objects rather than C-style handles to manage asynchronous operations.

  • Naming conventions match typical C# patterns. For example, the EOS_Auth_Login in the C SDK is accessible in the C# SDK as Epic.OnlineServices.Auth.AuthInterface.Login.

  • Data structures in the C SDK require macro-based API version numbers to ensure compatibility; these values are pre-populated in the C# SDK.

The EOS C# SDK includes libraries for desktop and mobile platforms, such as Windows, Linux, Mac, iOS, and Android. Additional versions of the C# SDK for restricted platforms, such as Xbox, Nintendo Switch, and PlayStation are available after approval. For access to the restricted SDKs and related documentation, you must configure your organization access and the documentation will be part of your SDK download after First Party approval.

Ask your technical account manager for more information and availability.

C# SDK Dependencies

For the C# SDK, you must have...

  • .NET Framework 3.5 or later, or API compatible equivalent

  • Previous versions may work, but have not been tested

For the C# SDK Samples, you must have...

  • .NET Core 3.1

  • Visual Studio 2019 or higher

Getting Started

Before you begin, you'll need to download the C# SDK and set up a product on Epic's Developer Portal. You should have the following product information from your Product Settings on the Developer Portal available for the SDK:

  • Product ID

  • Sandbox ID

  • Deployment ID

  • Client ID

  • Client secret

When you start integrating the EOS SDK, these values will enable you to create the Platform Interface, which is required to access all other EOS SDK interfaces. Once you have this information, you can proceed to integrate the SDK with your product.

Integration

  1. Include the C# SDK source files in your project.

    • (Unity users) Place these files (including Core and Generated folders) into your Assets folder.

  2. Ensure that the appropriate library binary file for your target platform is accessible by your application.

    • (Unity users) copy the appropriate library binary file into your Assets folder for your target platform.

    • For example, a Windows x64 integration would use EOSSDK-Win64-Shipping.dll.

  3. The C# SDK determines the library binary name to target in Epic.OnlineServices.Config. Several platforms are preconfigured to point to the correct name for convenience.

    • If your target platform has not been preconfigured, make the appropriate changes to the configuration and your project symbols to avoid a build error. For example, you may need to set EOS_PLATFORM_WINDOWS_64 if targeting Windows x64 or EOS_PLATFORM_WINDOWS_32 if targeting Windows x86.

  4. (Unity users) Create a new script to control the SDK. For this example, we used the name EOSSDKComponent.

    New script Component

  5. (Unity users) To create a component when needed, select Add Component to assign it to an entity.For demonstration purposes, we added it to the Main Camera.

    Add Component

  6. Write your EOS SDK code in this component. See our sample code for examples.

Implementing the SDK

Once you have set up your product and integrated the C# SDK, you can begin writing code. You'll need to initialize the SDK, call its Tick method regularly to ensure that the SDK can execute and that callbacks can run, and shut it down when your application finishes.

Managing the SDK's Life Cycle

There are three main parts of the SDK's life cycle: Initialization, Ticking (normal operation), and Shutdown.

Initialization

The Platform Interface is the entry point to the SDK, and you will need an instance of this interface to use EOS. To create one, call Epic.OnlineServices.Platform.PlatformInterface.Initialize with some basic information about your application, then call Epic.OnlineServices.Platform.PlatformInterface.Create with the values you have obtained from the Developer Portal to get an Epic.OnlineServices.Platform.PlatformInterface instance. Store this instance; you will need it to interact with the SDK. For example, to perform authentication, you first need an instance of Epic.OnlineServices.Auth.AuthInterface, and you can retrieve this by calling GetAuthInterface() on your `Epic.OnlineServices.Platform.PlatformInterfac`e instance.

You can only initialize the SDK once; subsequent calls to Epic.OnlineServices.Platform.PlatformInterface.Initialize will return the AlreadyInitialized failure code. We recommend initializing the SDK when your application starts up and not releasing it until the application shuts down.

Ticking

In order for the SDK to operate, you must call Epic.OnlineServices.Platform.PlatformInterface.Tick on your Platform Interface instance regularly. These calls do not need to happen every frame, but should happen fairly often; one call every 100 milliseconds is considered reasonable, but you can adjust the exact frequency to your needs. SDK callbacks can only run when you call Tick, so all of your asynchronous operations depend on it.

Shutdown

When you no longer need the SDK — generally, at application shutdown time — you can shut it down by calling Epic.OnlineServices.Platform.PlatformInterface.Release to release your Platform Interface instance, and then Epic.OnlineServices.Platform.PlatformInterface.Shutdown to complete the shutdown process. This process is final; Epic.OnlineServices.Platform.PlatformInterface.Release puts the SDK into a finalized state, and you cannot acquire another Platform Interface handle or reinitialize the SDK after that point. For this reason, we advise against shutting down the EOS SDK until application shutdown.

Some editor environments, including Unity, load external libraries like EOS during editor startup and do not unload them until the editor shuts down. In these cases, you should not call Epic.OnlineServices.Platform.PlatformInterface.Release or Epic.OnlineServices.Platform.PlatformInterface.Shutdown at the end of an in-editor play session, because you will be unable to initialize the SDK successfully in any future session without restarting the editor. In addition, because these editor environments use one continuous instance of the SDK, operations that began right before the end of a play session could finish and trigger callbacks in the following session.

Results

Most callback data structures and some return values use Epic.OnlineServices.Result to convey the results of SDK operations. Make sure to use this to handle errors and ensure operations are performing as expected.

Logging

The SDK outputs useful debugging information through an internal interface. To enable this feature, set up Epic.OnlineServices.Logging.LoggingInterface as early as possible, preferably immediately after initializing the SDK, by calling Epic.OnlineServices.Logging.LoggingInterface.SetLogLevel with parameters indicating the level of detail you require, followed by Epic.OnlineServices.Logging.LoggingInterface.SetCallback with a callback function to receive log information. This feature can provide insight into internal operations and can help with identifying the causes of unexpected behaviors you may encounter.

Unmanaged Memory

The SDK uses objects with an underlying type of Epic.OnlineServices.Handle. In some cases, these objects act as accessors to unmanaged data in the SDK's cache, with application-controlled lifetimes. These objects have Release methods that you must call to prevent memory leaks. For example, the Epic.OnlineServices.Presence.PresenceModification object that you get from Epic.OnlineServices.Presence.PresenceInterface.CreatePresenceModification has a Release method that you must call when you no longer need the object.

Custom memory delegates

On some platforms, such as consoles, the SDK requires you to implement your own allocate, reallocate, and release functions. Because the SDK will call these functions with very high frequency, it is highly performant for the SDK to be able to access them directly without being routed through delegates in managed code.

You have the option to pass these functions into the SDK when initializing the platform with Epic.OnlineServices.Platform.InitializeOptions. It is recommended that you:

  1. Create a native library and implement the 3 memory functions,

  2. For each of the 3 memory functions, implement an export function that returns a pointer to the memory function,

  3. Before initializing the platform in C#, call the export functions, and set the returned IntPtr values to the options struct.

The following sample code demonstrates the structure of what your native library may look like:

void* Allocate(size_t InSizeInBytes, size_t InAlignment)
{
}

void* Reallocate(void* InPointer, size_t InSizeInBytes, size_t InAlignment)
{
}

void Release(void* InPointer)
{
}

typedef void* (*AllocateFunctionPointer)(size_t InSizeInBytes, size_t InAlignment);
extern "C" __declspec(dllexport) AllocateFunctionPointer GetAllocateFunctionPointer();
{
    return Allocate;
}

typedef void* (*ReallocateFunctionPointer)(void* InPointer, size_t InSizeInBytes, size_t InAlignment);
extern "C" __declspec(dllexport) ReallocateFunctionPointer GetReallocateFunctionPointer()
{
    return Reallocate;
}

typedef void (*ReleaseFunctionPointer)(void* InPointer);
extern "C" __declspec(dllexport) ReleaseFunctionPointer GetReleaseFunctionPointer()
{
    return Release;
}

And the following sample code demonstrates what the C# bindings may look like:

[DllImport("MyNativeLibrary.dll")]
private static extern IntPtr GetAllocateFunctionPointer();

[DllImport("MyNativeLibrary.dll")]
private static extern IntPtr GetReallocateFunctionPointer();

[DllImport("MyNativeLibrary.dll")]
private static extern IntPtr GetReleaseFunctionPointer();

Threading

The SDK is not currently thread safe. We recommend that all calls to the SDK come from your application's main thread. At this time, we recommend against using async, await, Thread, Task, or similar patterns.

The EOS Overlay

To use the EOS Overlay in your application, complete the following before any graphics devices are created:

  1. Load the SDK library into memory

  2. Initialize with EOS_Initialize

  3. Create your platform with EOS_Platform_Create

In Unity, one way you can achieve this is by creating a low-level native rendering plugin. At a minimum, you will need to:

  1. Create a native library with a name prefixed with GfxPlugin,

  2. Add an export function named UnityPluginLoad(void*) that loads the SDK library and invokes EOS_Initialize and EOS_Platform_Create successfully,

  3. Add an export function to return the created platform interface handle back to C# when needed, where it can be used to construct a new instance of Epic.OnlineServices.Platform.PlatformInterface.

The EOS overlay is not supported in editor environments such as the Unity editor. It is recommended that you explicitly disable the overlay in editor environments to prevent unintended behavior.

Samples

The C# SDK ships with sample projects that demonstrate various features and can help you get started faster.

The sample code in this section is intended to demonstrate and help familiarize you with the C# SDK.

Setting up the Platform

    // Set these values as appropriate. For more information, see the Developer Portal documentation.
    string productName = "MyCSharpApplication";
    string productVersion = "1.0";
    string productId = "";
    string sandboxId = "";
    string deploymentId = "";
    string clientId = "";
    string clientSecret = "";

    var initializeOptions = new Epic.OnlineServices.Platform.InitializeOptions()
    {
        ProductName = productName,
        ProductVersion = productVersion
    };

    var initializeResult = Epic.OnlineServices.Platform.PlatformInterface.Initialize(initializeOptions);
    if (initializeResult != Epic.OnlineServices.Result.Success)
    {
        throw new System.Exception("Failed to initialize platform: " + initializeResult);
    }

    // The SDK outputs lots of information that is useful for debugging.
    // Make sure to set up the logging interface as early as possible: after initializing.
    Epic.OnlineServices.Logging.LoggingInterface.SetLogLevel(Epic.OnlineServices.Logging.LogCategory.AllCategories, Epic.OnlineServices.Logging.LogLevel.VeryVerbose);
    Epic.OnlineServices.Logging.LoggingInterface.SetCallback((Epic.OnlineServices.Logging.LogMessage logMessage) =>
    {
        System.Console.WriteLine(logMessage.Message);
    });

    var options = new Epic.OnlineServices.Platform.Options()
    {
        ProductId = productId,
        SandboxId = sandboxId,
        DeploymentId = deploymentId,
        ClientCredentials = new Epic.OnlineServices.Platform.ClientCredentials()
        {
            ClientId = clientId,
            ClientSecret = clientSecret
        }
    };

    var platformInterface = Epic.OnlineServices.Platform.PlatformInterface.Create(options);
    if (platformInterface == null)
    {
        throw new System.Exception("Failed to create platform");
    }

Logging In

    var loginCredentialType = Epic.OnlineServices.Auth.LoginCredentialType.AccountPortal;
    /// These fields correspond to <see cref="Epic.OnlineServices.Auth.Credentials.Id" /> and <see cref="Epic.OnlineServices.Auth.Credentials.Token" />,
    /// and their use differs based on the login type. For more information, see <see cref="Epic.OnlineServices.Auth.Credentials" />
    /// and the Auth Interface documentation.
    string loginCredentialId = null;
    string loginCredentialToken = null;

    var authInterface = platformInterface.GetAuthInterface();
    if (authInterface == null)
    {
        throw new System.Exception("Failed to get auth interface");
    }

    var loginOptions = new Epic.OnlineServices.Auth.LoginOptions()
    {
        Credentials = new Epic.OnlineServices.Auth.Credentials()
        {
            Type = loginCredentialType,
            Id = loginCredentialId,
            Token = loginCredentialToken
        }
    };

    // Ensure platform tick is called on an interval, or the following call will never callback.
    authInterface.Login(loginOptions, null, (Epic.OnlineServices.Auth.LoginCallbackInfo loginCallbackInfo) =>
    {
        if (loginCallbackInfo.ResultCode == Epic.OnlineServices.Result.Success)
        {
            System.Console.WriteLine("Login succeeded");
        }
        else if (Epic.OnlineServices.Common.IsOperationComplete(loginCallbackInfo.ResultCode))
        {
            System.Console.WriteLine("Login failed: " + loginCallbackInfo.ResultCode);
        }
    });

Unity EOSSDK Component

    // This code is provided for demonstration purposes and is not intended to represent ideal practices.
    using Epic.OnlineServices;
    using Epic.OnlineServices.Auth;
    using Epic.OnlineServices.Logging;
    using Epic.OnlineServices.Platform;
    using System;
    using System.Runtime.InteropServices;
    using UnityEngine;

    public class EOSSDKComponent : MonoBehaviour
    {
        // Set these values as appropriate. For more information, see the Developer Portal documentation.
        public string m_ProductName = "MyUnityApplication";
        public string m_ProductVersion = "1.0";
        public string m_ProductId = "";
        public string m_SandboxId = "";
        public string m_DeploymentId = "";
        public string m_ClientId = "";
        public string m_ClientSecret = "";
        public LoginCredentialType m_LoginCredentialType = LoginCredentialType.AccountPortal;
        /// These fields correspond to <see cref="Credentials.Id" /> and <see cref="Credentials.Token" />,
        /// and their use differs based on the login type. For more information, see <see cref="Credentials" />
        /// and the Auth Interface documentation.
        public string m_LoginCredentialId = null;
        public string m_LoginCredentialToken = null;

        private static PlatformInterface s_PlatformInterface;
        private const float c_PlatformTickInterval = 0.1f;
        private float m_PlatformTickTimer = 0f;

        // If we're in editor, we should dynamically load and unload the SDK between play sessions.
        // This allows us to initialize the SDK each time the game is run in editor.
    #if UNITY_EDITOR
        [DllImport("Kernel32.dll")]
        private static extern IntPtr LoadLibrary(string lpLibFileName);

        [DllImport("Kernel32.dll")]
        private static extern int FreeLibrary(IntPtr hLibModule);

        [DllImport("Kernel32.dll")]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName);

        private IntPtr m_LibraryPointer;
    #endif

        private void Awake()
        {
    #if UNITY_EDITOR
            var libraryPath = $"Assets/{Config.LibraryName}";

            m_LibraryPointer = LoadLibrary(libraryPath);
            if (m_LibraryPointer == IntPtr.Zero)
            {
                throw new Exception($"Failed to load library {libraryPath}");
            }

            Bindings.Hook(m_LibraryPointer, GetProcAddress);
    #endif
        }

        private void OnApplicationQuit()
        {
            if (s_PlatformInterface != null)
            {
                s_PlatformInterface.Release();
                s_PlatformInterface = null;
                PlatformInterface.Shutdown();
            }

    #if UNITY_EDITOR
            if (m_LibraryPointer != IntPtr.Zero)
            {
                // Free until the module ref count is 0
                while (FreeLibrary(m_LibraryPointer) != 0) { }
                m_LibraryPointer = IntPtr.Zero;

                Bindings.Unhook();
            }
    #endif
        }

        void Start()
        {
            var initializeOptions = new InitializeOptions()
            {
                ProductName = m_ProductName,
                ProductVersion = m_ProductVersion
            };

            var initializeResult = PlatformInterface.Initialize(initializeOptions);
            if (initializeResult != Result.Success)
            {
                throw new Exception("Failed to initialize platform: " + initializeResult);
            }

            // The SDK outputs lots of information that is useful for debugging.
            // Make sure to set up the logging interface as early as possible: after initializing.
            LoggingInterface.SetLogLevel(LogCategory.AllCategories, LogLevel.VeryVerbose);
            LoggingInterface.SetCallback((LogMessage logMessage) => Debug.Log(logMessage.Message));

            var options = new Options()
            {
                ProductId = m_ProductId,
                SandboxId = m_SandboxId,
                DeploymentId = m_DeploymentId,
                ClientCredentials = new ClientCredentials()
                {
                    ClientId = m_ClientId,
                    ClientSecret = m_ClientSecret
                }
            };

            s_PlatformInterface = PlatformInterface.Create(options);
            if (s_PlatformInterface == null)
            {
                throw new Exception("Failed to create platform");
            }

            var loginOptions = new LoginOptions()
            {
                Credentials = new Credentials()
                {
                    Type = m_LoginCredentialType,
                    Id = m_LoginCredentialId,
                    Token = m_LoginCredentialToken
                }
            };

            // Ensure platform tick is called on an interval, or this will not callback.
            s_PlatformInterface.GetAuthInterface().Login(loginOptions, null, (LoginCallbackInfo loginCallbackInfo) =>
            {
                if (loginCallbackInfo.ResultCode == Result.Success)
                {
                    Debug.Log("Login succeeded");
                }
                Else if (Common.IsOperationComplete(loginCallbackInfo.ResultCode))
                {
                    Debug.Log("Login failed: " + loginCallbackInfo.ResultCode);
                }
            });
        }

        // Calling tick on a regular interval is required for callbacks to work.
        private void Update()
        {
            if (s_PlatformInterface != null)
            {
                m_PlatformTickTimer += Time.deltaTime;

                if (m_PlatformTickTimer >= c_PlatformTickInterval)
                {
                    m_PlatformTickTimer = 0;
                    s_PlatformInterface.Tick();
                }
            }
        }
    }