Desktop Application Integration
Comprehensive guide for integrating elm licensing into desktop applications using ControlManager
Desktop Licensing Model
Desktop applications use a dual-identity model:
- User Identity: Tracks who is using the software (Active Directory, system user, etc.)
- Instance Identity: Tracks which machine/device the software is running on.
The server compares it with the identity recorded at registration and tolerates a configurable
number of mismatched keys (one by default) before rejecting the session with
InvalidReason.InconsistentRegistration. - Named User Licensing: Supports per-user seat licensing with hardware binding
- Offline Operation: A validation-failure grace period covers temporary outages, and offline checkout covers planned disconnected use
The desktop ControlManager is available in the .NET (reference), Java, Python and C++
SDKs. The JavaScript/TypeScript and Ruby SDKs target server and web scenarios and do not include a
desktop ControlManager.
Registration Examples
Learn how to register desktop application instances and users across different programming languages using the ControlManager.
Desktop Registration (.NET)
using evoleap.Licensing;
using evoleap.Licensing.Desktop;
// Product settings from the elm portal
var productId = new Guid("your-product-id");
var version = new Version(1, 0, 0);
var publicKey = PublicKeyFile.ReadFrom("path/to/public-key-file"); // or PublicKeyFile.Create(fileText)
// Create user identity - use built-in helper method
var userIdentity = await ActiveDirectoryBasedUserIdentity.GetCurrentAsync();
// Generate hardware-based instance identity (host name, CPU id, volume hash, network info)
var instanceIdentity = await new InstanceIdentity().AddAllHardwareKeysAsync();
// Optional: allow the app to run for a period before it must be registered (default: zero)
var strategy = new ControlStrategy { GracePeriodForUnregisteredProduct = TimeSpan.FromDays(14) };
// Create the desktop control manager. On later launches pass the IValidationState and
// IValidatedSessionState you persisted last time (see State Management); null on first run.
var controlManager = new ControlManager(
productId,
version,
publicKey,
userIdentity,
instanceIdentity,
controlStrategy: strategy,
savedState: savedValidationState,
sessionState: savedSessionState
);
// Register the desktop application
var userInfo = new UserInfo("John Doe", "john@company.com");
RegistrationResult result = await controlManager.RegisterAsync("LICENSE-KEY-HERE", userInfo);
if (result.Success)
{
Console.WriteLine("Desktop application registered successfully!");
Console.WriteLine($"Instance ID: {controlManager.ValidationState.InstanceId}");
Console.WriteLine($"Registered at: {controlManager.ValidationState.RegisteredAt}");
}
else
{
// RegistrationResult carries only Success and the server's ErrorMessage
// (e.g. invalid license key, no seats available, license expired).
Console.WriteLine($"Registration failed: {result.ErrorMessage}");
}
// Registration (successful or not) updates ValidationState - persist it now.
Desktop Registration (Java)
import com.evoleap.licensing.*;
import com.evoleap.licensing.Desktop.ControlManager;
import com.evoleap.licensing.Utilities.Version;
import java.util.UUID;
// Product settings from the elm portal
UUID productId = UUID.fromString("your-product-id");
Version version = new Version("1.0.0");
PublicKeyFile publicKey = PublicKeyFile.readFrom("path/to/public-key-file"); // or PublicKeyFile.create(fileText)
// Create user identity - use built-in helper method
UserIdentity userIdentity = ActiveDirectoryBasedUserIdentity.getCurrent();
// Generate hardware-based instance identity
InstanceIdentity instanceIdentity = HardwareBasedInstanceIdentity.addAllHardwareKeys(new InstanceIdentity());
// Create the desktop control manager. Java has no short overload: pass the control
// strategy and the persisted IValidationState / IValidatedSessionState (null on first run).
ControlManager controlManager = new ControlManager(
productId,
version,
publicKey,
userIdentity,
instanceIdentity,
new ControlStrategy(),
savedValidationState,
savedSessionState
);
// Async methods return com.evoleap.licensing.Utilities.Task<T>, which implements Future<T>
UserInfo userInfo = new UserInfo("John Doe", "john@company.com");
RegistrationResult result = controlManager.registerAsync("LICENSE-KEY-HERE", userInfo).get();
if (result.getSuccess()) {
System.out.println("Desktop application registered successfully!");
System.out.println("Instance ID: " + controlManager.getValidationState().getInstanceId());
System.out.println("Registered at: " + controlManager.getValidationState().getRegisteredAt());
} else {
// RegistrationResult carries only success and the server's error message
System.out.println("Registration failed: " + result.getErrorMessage());
}
Desktop Registration (Python)
from uuid import UUID, getnode
import getpass
import socket
from evoleap_licensing.desktop.ControlManager import ControlManager
from evoleap_licensing.UserInfo import UserInfo
from evoleap_licensing.UserIdentity import UserIdentity
from evoleap_licensing.InstanceIdentity import InstanceIdentity, InstanceIdentityValue
# Product settings from the elm portal
product_id = UUID('your-product-id')
with open('public-key.pem') as f:
public_key = f.read() # PEM text of the product's public key
# The Python SDK has no Active Directory or hardware helpers - supply your own identifiers.
user_identity = UserIdentity({'username': getpass.getuser()})
# Instance identity values must be wrapped in InstanceIdentityValue
instance_identity = InstanceIdentity({
'hostname': InstanceIdentityValue(socket.gethostname()),
'mac': InstanceIdentityValue(format(getnode(), '012x')),
})
# Create the desktop control manager. On later launches pass the state you persisted
# last time (see State Management); omit both on first run.
control_manager = ControlManager(
product_id,
"1.0.0",
public_key,
user_identity,
instance_identity,
saved_state=saved_state,
session_state=saved_session_state,
)
# Register the desktop application (Python methods are synchronous).
# Note: the released Python SDK calls client API v3.2.
user_info = UserInfo("John Doe", "john@company.com")
result = control_manager.Register("LICENSE-KEY-HERE", user_info)
if result.Success:
print("Desktop application registered successfully!")
print(f"Instance ID: {control_manager.State.InstanceId}")
print(f"Registered at: {control_manager.State.RegisteredAt}")
else:
# RegistrationResult carries only Success and the server's ErrorMessage
print(f"Registration failed: {result.ErrorMessage}")
Desktop Registration (C++)
#include "ControlManager.h"
#include "UserInfo.h"
#include "UserIdentity.h"
#include "InstanceIdentity.h"
#include "ActiveDirectoryBasedUserIdentity.h"
#include "HardwareBasedInstanceIdentity.h"
#include "PublicKeyFile.h"
#include "Version.h"
#include <iostream>
#include <string>
int main() {
// Product settings from the elm portal
uuids::uuid productId = uuids::uuid::from_string("your-product-id").value();
Version version("1.0.0");
// PublicKeyFile is created from the key file's text (not its path)
PublicKeyFile* publicKey = PublicKeyFile::Create(publicKeyFileText);
// Create user identity using helper method (populated on Windows)
UserIdentity userIdentity = ActiveDirectoryBasedUserIdentity::GetCurrent();
// Generate hardware-based instance identity (modifies the identity in place)
InstanceIdentity instanceIdentity;
HardwareBasedInstanceIdentity::AddAllHardwareKeys(instanceIdentity);
// Create the desktop control manager. Optionally pass a ControlStrategy* and the
// IValidationState* / IValidatedSessionState* you persisted last time.
ControlManager controlManager(
productId,
version,
publicKey,
userIdentity,
instanceIdentity,
nullptr, // ControlStrategy*
savedState, // IValidationState* (nullptr on first run)
savedSessionState // IValidatedSessionState* (nullptr on first run)
);
// Register the desktop application (C++ methods are synchronous)
UserInfo userInfo("John Doe", "john@company.com");
RegistrationResult result = controlManager.Register("LICENSE-KEY-HERE", userInfo);
if (result.get_Success()) {
std::cout << "Desktop application registered successfully!" << std::endl;
// get_newValidationState() returns a copy you must release with deleteValidationState()
IValidationState* state = controlManager.get_newValidationState();
std::cout << "Instance ID: " << uuids::to_string(state->get_InstanceId()) << std::endl;
controlManager.deleteValidationState(state);
} else {
// RegistrationResult carries only success and the server's error message
std::cout << "Registration failed: " << result.get_ErrorMessage() << std::endl;
}
return 0;
}
Session Validation Examples
Examples of how to validate user sessions and manage licensing states in desktop applications using ControlManager.
How Session Validation Behaves
- The first call starts a session; later calls on the same
ControlManagerextend it. The optionalrequestedSessionDurationargument asks the server for a specific session length. IsValidistruein three non-standard cases you should surface to the user:IsInUnregisteredGracePeriod(not registered yet, withinControlStrategy.GracePeriodForUnregisteredProduct),IsInValidationFailureGracePeriod(server unreachable, within the license's grace period for validation failures) andIsOfflineCheckout.- If the server is unreachable and no grace period applies, the session is invalid with
InvalidReason.ServiceUnreachable. The current .NET SDK reportsInvalidReason.InvalidServerResponseinstead when the server answered but the response could not be parsed (a client/server version mismatch, not a network problem). - Clock rollback is detected locally by comparing against the last successful validation and
previously recorded failure times; this yields
InvalidReason.UserTamperingDetected. - An instance identity that no longer matches the registered machine yields
InvalidReason.InconsistentRegistration; an administrator can allow a hardware update from the portal.
Desktop Session Validation (.NET)
// Start (or extend) a desktop session
SessionValidity sessionValidity = await controlManager.ValidateSessionAsync();
if (sessionValidity.IsValid)
{
if (sessionValidity.IsInUnregisteredGracePeriod)
{
Console.WriteLine($"Unregistered trial - register before {sessionValidity.GracePeriodExpiration}");
}
else if (sessionValidity.IsInValidationFailureGracePeriod)
{
Console.WriteLine($"Server unreachable - running offline until {sessionValidity.GracePeriodExpiration}");
}
else
{
Console.WriteLine($"Session key: {controlManager.SessionState.SessionKey}");
}
Console.WriteLine($"Instance: {controlManager.ValidationState.InstanceId}");
Console.WriteLine($"Valid for: {sessionValidity.ValidityDuration.TotalMinutes} minutes");
Console.WriteLine($"Session type: {(sessionValidity.IsOfflineCheckout ? "Offline checkout" : "Online")}");
// Check available features and components
Console.WriteLine($"Available features: {string.Join(", ", controlManager.ValidationState.Features)}");
Console.WriteLine($"Available components: {string.Join(", ", controlManager.ValidationState.Components.Select(c => c.Name))}");
// Your desktop application logic here
RunDesktopApplication();
// End the session when application closes
bool ended = await controlManager.EndSessionAsync();
if (ended)
{
Console.WriteLine("Desktop session ended successfully");
}
}
else
{
Console.WriteLine($"Desktop session invalid: {sessionValidity.InvalidReason}");
switch (sessionValidity.InvalidReason)
{
case InvalidReason.InstanceNotRegistered:
ShowRegistrationDialog();
break;
case InvalidReason.ServiceUnreachable:
case InvalidReason.OfflineCheckoutExpired:
ShowConnectivityPrompt("Please connect to the internet to renew your license.");
break;
case InvalidReason.InconsistentRegistration:
ShowReregistrationPrompt("This machine no longer matches the registered installation.");
break;
case InvalidReason.NoSeatsAvailable:
ShowMessage("All seats for this license are in use.");
break;
case InvalidReason.UserTamperingDetected:
ShowMessage("The system clock appears to have been changed.");
break;
}
}
// Validation updates ValidationState and SessionState - persist both now.
Desktop Session Validation (Java)
// Start (or extend) a desktop session; pass null for the server's default duration
SessionValidity sessionValidity = controlManager.validateSessionAsync(null).get();
if (sessionValidity.getIsValid()) {
if (sessionValidity.getIsInUnregisteredGracePeriod()) {
System.out.println("Unregistered trial - register before " + sessionValidity.getGracePeriodExpiration());
} else if (sessionValidity.getIsInValidationFailureGracePeriod()) {
System.out.println("Server unreachable - running offline until " + sessionValidity.getGracePeriodExpiration());
} else {
System.out.println("Session key: " + controlManager.getSessionState().getSessionKey());
}
System.out.println("Instance: " + controlManager.getValidationState().getInstanceId());
System.out.println("Valid for: " + sessionValidity.getValidityDuration().getTotalSeconds() + " seconds");
System.out.println("Session type: " + (sessionValidity.getIsOfflineCheckout() ? "Offline checkout" : "Online"));
// Check available features
System.out.println("Available features: " + String.join(", ", controlManager.getValidationState().getFeatures()));
// Your desktop application logic here
runDesktopApplication();
// End the session when application closes
boolean ended = controlManager.endSessionAsync().get();
if (ended) {
System.out.println("Desktop session ended successfully");
}
} else {
System.out.println("Desktop session invalid: " + sessionValidity.getInvalidReason());
switch (sessionValidity.getInvalidReason()) {
case InstanceNotRegistered:
showRegistrationDialog();
break;
case ServiceUnreachable:
case OfflineCheckoutExpired:
showConnectivityPrompt("Please connect to the internet to renew your license.");
break;
case InconsistentRegistration:
showReregistrationPrompt("This machine no longer matches the registered installation.");
break;
default:
break;
}
}
Desktop Session Validation (Python)
from evoleap_licensing.enumerations.licensing_enumerations import InvalidReason
# Start (or extend) a desktop session
session_validity = control_manager.ValidateSession()
if session_validity.IsValid:
if session_validity.IsInUnregisteredGracePeriod:
print(f"Unregistered trial - register before {session_validity.GracePeriodExpiration}")
elif session_validity.IsInValidationFailureGracePeriod:
print(f"Server unreachable - running offline until {session_validity.GracePeriodExpiration}")
else:
print(f"Session key: {control_manager.SessionState.SessionKey}")
print(f"Instance: {control_manager.State.InstanceId}")
print(f"Valid for: {session_validity.ValidityDuration.total_seconds()} seconds")
print(f"Session type: {'Offline checkout' if session_validity.IsOfflineCheckout else 'Online'}")
# Check available features and components
print(f"Available features: {', '.join(control_manager.State.Features)}")
print(f"Available components: {', '.join(c.Name for c in control_manager.State.Components)}")
# Your desktop application logic here
run_desktop_application()
# End the session when application closes
if control_manager.EndSession():
print("Desktop session ended successfully")
else:
print(f"Desktop session invalid: {session_validity.InvalidReason}")
if session_validity.InvalidReason == InvalidReason.InstanceNotRegistered:
show_registration_dialog()
elif session_validity.InvalidReason in (InvalidReason.ServiceUnreachable,
InvalidReason.OfflineCheckoutExpired):
show_connectivity_prompt()
elif session_validity.InvalidReason == InvalidReason.InconsistentRegistration:
show_reregistration_prompt()
Desktop Session Validation (C++)
// Start (or extend) a desktop session
SessionValidity sessionValidity = controlManager.ValidateSession();
if (sessionValidity.IsValid) {
if (sessionValidity.IsInUnregisteredGracePeriod || sessionValidity.IsInValidationFailureGracePeriod) {
std::cout << "Running in a grace period" << std::endl;
}
// get_new*State() return copies that must be released with the matching delete* call
IValidatedSessionState* sessionState = controlManager.get_newSessionState();
std::cout << "Session key: " << sessionState->get_SessionKey() << std::endl;
IValidationState* validationState = controlManager.get_newValidationState();
std::cout << "Instance: " << uuids::to_string(validationState->get_InstanceId()) << std::endl;
std::cout << "Session type: " << (sessionValidity.IsOfflineCheckout ? "Offline checkout" : "Online") << std::endl;
// Validity duration is exposed as a (possibly null) pointer, in milliseconds
if (auto duration = sessionValidity.get_ValidityDuration()) {
std::cout << "Valid for: "
<< std::chrono::duration_cast<std::chrono::minutes>(*duration).count()
<< " minutes" << std::endl;
}
// Check available features
std::cout << "Available features: ";
for (const auto& feature : validationState->get_Features()) {
std::cout << feature << " ";
}
std::cout << std::endl;
controlManager.deleteSessionState(sessionState);
controlManager.deleteValidationState(validationState);
// Your desktop application logic here
runDesktopApplication();
// End the session when application closes
if (controlManager.EndSession()) {
std::cout << "Desktop session ended successfully" << std::endl;
}
} else {
// InvalidReason supports operator<<
std::cout << "Desktop session invalid: " << sessionValidity.InvalidReason << std::endl;
switch (sessionValidity.InvalidReason) {
case InvalidReason::InstanceNotRegistered:
showRegistrationDialog();
break;
case InvalidReason::ServiceUnreachable:
case InvalidReason::OfflineCheckoutExpired:
showConnectivityPrompt();
break;
case InvalidReason::InconsistentRegistration:
showReregistrationPrompt();
break;
default:
break;
}
}
Component Management Examples
Learn how to manage licensed components and features in desktop applications using ControlManager.
Component checkout requires an active session (call session validation first). If the session is
running offline, checkouts are evaluated locally against offline token checkouts and recorded for
later upload; ProcessedOffline on the result tells you which path was taken.
Desktop Component Management (.NET)
// First, check available features and enable UI components accordingly
var features = controlManager.ValidationState.Features;
if (features.Contains("ReportGenerator"))
{
EnableReportGeneratorFeature(); // Enable menu items, buttons, etc.
}
if (features.Contains("DataExport"))
{
EnableDataExportFeature(); // Enable export menu options
}
// Later, when user actually wants to generate a report, check out the component
public async Task GenerateReport()
{
// CheckOutComponentsAsync takes params string[] - one or more component names
ComponentCheckoutResult checkoutResult = await controlManager.CheckOutComponentsAsync("ReportGenerator");
if (checkoutResult.Success)
{
try
{
await PerformReportGeneration();
}
finally
{
// Always check in when done
ComponentCheckinResult checkinResult = await controlManager.CheckInComponentsAsync("ReportGenerator");
if (!checkinResult.Success)
{
Console.WriteLine($"Check-in failed: {checkinResult.FailureReason}");
}
}
}
else
{
switch (checkoutResult.FailureReason)
{
case ComponentCheckoutFailureReason.InsufficientSessions:
ShowMessage("Report generator is in use by the maximum number of users. Please try again later.");
break;
case ComponentCheckoutFailureReason.InsufficientTokens:
ShowUpgradeDialog("You don't have enough tokens. Upgrade your license to continue.");
break;
case ComponentCheckoutFailureReason.InvalidComponent:
ShowMessage("This component is not part of this product version.");
break;
case ComponentCheckoutFailureReason.ServiceUnreachable:
ShowMessage("The licensing server could not be reached.");
break;
}
}
}
// Several components can be checked out (and in) together
public async Task RunAnalyticsExport()
{
var result = await controlManager.CheckOutComponentsAsync("AdvancedAnalytics", "DataExport");
if (result.Success)
{
try { await PerformAnalyticsExport(); }
finally { await controlManager.CheckInComponentsAsync("AdvancedAnalytics", "DataExport"); }
}
}
// Consume tokens for pay-per-use features (consumable components are not checked in)
public async Task ProcessLargeDataset()
{
// tokenCount is optional; when omitted the component's configured token cost is used
var consumableResult = await controlManager.CheckOutConsumableComponentAsync("DataProcessing", 5);
if (consumableResult.Success)
{
await PerformDatasetProcessing();
}
else if (consumableResult.FailureReason == ComponentCheckoutFailureReason.InsufficientTokens)
{
ShowPurchaseTokensDialog("You need more processing tokens to continue");
}
}
// Current status (license model, token usage, entitlements) for all components
ComponentsStatus status = await controlManager.GetComponentsStatusAsync();
Desktop Component Management (Java)
// First, check available features and enable UI components accordingly
if (controlManager.getValidationState().getFeatures().contains("ReportGenerator")) {
enableReportGeneratorFeature(); // Enable menu items, buttons, etc.
}
if (controlManager.getValidationState().getFeatures().contains("DataExport")) {
enableDataExportFeature(); // Enable export menu options
}
// Later, when user actually wants to generate a report, check out the component
public void generateReport() throws Exception {
// checkOutComponentsAsync takes String... - one or more component names
ComponentCheckoutResult checkoutResult = controlManager.checkOutComponentsAsync("ReportGenerator").get();
if (checkoutResult.getSuccess()) {
try {
performReportGeneration();
} finally {
// Always check in when done
ComponentCheckinResult checkinResult = controlManager.checkInComponentsAsync("ReportGenerator").get();
if (!checkinResult.getSuccess()) {
System.out.println("Check-in failed: " + checkinResult.getFailureReason());
}
}
} else {
switch (checkoutResult.getFailureReason()) {
case InsufficientSessions:
showMessage("Report generator is in use by the maximum number of users. Please try again later.");
break;
case InsufficientTokens:
showUpgradeDialog("You don't have enough tokens. Upgrade your license to continue.");
break;
case InvalidComponent:
showMessage("This component is not part of this product version.");
break;
default:
break;
}
}
}
// Consume tokens for pay-per-use features (pass null to use the configured token cost)
public void processLargeDataset() throws Exception {
ComponentCheckoutResult consumableResult = controlManager.checkOutConsumableComponentAsync("DataProcessing", 5).get();
if (consumableResult.getSuccess()) {
performDatasetProcessing();
} else if (consumableResult.getFailureReason() == ComponentCheckoutFailureReason.InsufficientTokens) {
showPurchaseTokensDialog("You need more processing tokens to continue");
}
}
Desktop Component Management (Python)
from evoleap_licensing.enumerations.licensing_enumerations import ComponentCheckoutFailureReason
# First, check available features and enable UI components accordingly
if "ReportGenerator" in control_manager.State.Features:
enable_report_generator_feature() # Enable menu items, buttons, etc.
if "DataExport" in control_manager.State.Features:
enable_data_export_feature() # Enable export menu options
# Later, when user actually wants to generate a report, check out the component
def generate_report():
components = ["ReportGenerator"] # CheckOutComponents takes a list of names
checkout_result = control_manager.CheckOutComponents(components)
if checkout_result.Success:
# The released Python SDK has no CheckInComponents: checked-out components
# stay in use until the session ends (control_manager.EndSession()).
perform_report_generation()
else:
if checkout_result.FailureReason == ComponentCheckoutFailureReason.InsufficientSessions:
show_message("Report generator is in use by the maximum number of users. Please try again later.")
elif checkout_result.FailureReason == ComponentCheckoutFailureReason.InsufficientTokens:
show_upgrade_dialog("You don't have enough tokens. Upgrade your license to continue.")
elif checkout_result.FailureReason == ComponentCheckoutFailureReason.InvalidComponent:
show_message("This component is not part of this product version.")
# Consume tokens for pay-per-use features (token_count is optional)
def process_large_dataset():
consumable_result = control_manager.CheckOutConsumableComponent("DataProcessing", 5)
if consumable_result.Success:
process_dataset()
elif consumable_result.FailureReason == ComponentCheckoutFailureReason.InsufficientTokens:
show_purchase_tokens_dialog("You need more processing tokens to continue")
Desktop Component Management (C++)
// First, check available features and enable UI components accordingly
IValidationState* validationState = controlManager.get_newValidationState();
auto features = validationState->get_Features();
if (features.Contains("ReportGenerator")) {
enableReportGeneratorFeature(); // Enable menu items, buttons, etc.
}
if (features.Contains("DataExport")) {
enableDataExportFeature(); // Enable export menu options
}
controlManager.deleteValidationState(validationState);
// Later, when user actually wants to generate a report, check out the component
void generateReport() {
// CheckOutComponents accepts a single name or a std::vector<std::string>
ComponentCheckoutResult checkoutResult = controlManager.CheckOutComponents("ReportGenerator");
if (checkoutResult.get_Success()) {
try {
performReportGeneration();
} catch (...) {
controlManager.CheckInComponents("ReportGenerator");
throw;
}
// Always check in when done
ComponentCheckinResult checkinResult = controlManager.CheckInComponents("ReportGenerator");
if (!checkinResult.get_Success()) {
std::cout << "Component check-in failed: "
<< static_cast<int>(checkinResult.get_FailureReason()) << std::endl;
}
} else {
// ComponentCheckoutFailureReason supports operator<<
std::cout << "Cannot generate report: " << checkoutResult.get_FailureReason() << std::endl;
switch (checkoutResult.get_FailureReason()) {
case ComponentCheckoutFailureReason::InsufficientSessions:
showMessage("Report generator is in use by the maximum number of users. Please try again later.");
break;
case ComponentCheckoutFailureReason::InsufficientTokens:
showUpgradeDialog("You don't have enough tokens. Upgrade your license to continue.");
break;
case ComponentCheckoutFailureReason::InvalidComponent:
showMessage("This component is not part of this product version.");
break;
default:
break;
}
}
}
// Consume tokens for pay-per-use features (token count is optional)
void processLargeDataset() {
ComponentCheckoutResult consumableResult = controlManager.CheckOutConsumableComponent("DataProcessing", 5);
if (consumableResult.get_Success()) {
processDataset();
} else if (consumableResult.get_FailureReason() == ComponentCheckoutFailureReason::InsufficientTokens) {
showPurchaseTokensDialog("You need more processing tokens to continue");
}
}
Offline Checkout
For planned disconnected use, check out the license for a fixed period while online. While a checkout
is active, session validation starts an offline session from the local checkout without contacting
the server. Usage from offline sessions is stored in state and uploaded with
FlushOfflineSessionsAsync() once the machine is back online. The license must allow
offline checkout; otherwise the result fails with
InvalidReason.OfflineCheckoutNotSupported.
Offline Checkout (.NET)
// Check out the license for one week of offline use
OfflineCheckoutValidity checkout = await controlManager.CheckOutLicenseForOfflineUseAsync(TimeSpan.FromDays(7));
if (checkout.Success)
{
Console.WriteLine($"Checked out until {checkout.ExpirationTime}");
}
else
{
Console.WriteLine($"Offline checkout failed: {checkout.FailureReason}");
}
// Persist ValidationState - the checkout lives in ValidationState.OfflineCheckouts
// Return the checkout early (frees the seat on the server)
var checkoutKey = controlManager.ValidationState.OfflineCheckouts.First().CheckoutKey;
bool returned = await controlManager.CheckInOfflineLicenseAsync(checkoutKey);
// Tokens for consumable components can be checked out for offline use as well
await controlManager.CheckOutComponentTokensForOfflineUseAsync(componentEntitlementId, 10, TimeSpan.FromDays(7));
// After reconnecting, upload usage recorded during offline sessions
await controlManager.FlushOfflineSessionsAsync();
Offline Checkout (Java)
import CS2JNet.System.TimeSpan;
// Check out the license for offline use (TimeSpan constructed from milliseconds)
OfflineCheckoutValidity checkout = controlManager
.checkOutLicenseForOfflineUseAsync(new TimeSpan(7L * 24 * 60 * 60 * 1000)).get();
if (checkout.getSuccess()) {
System.out.println("Checked out until " + checkout.getExpirationTime());
} else {
System.out.println("Offline checkout failed: " + checkout.getFailureReason());
}
// Return the checkout early
boolean returned = controlManager.checkInOfflineLicenseAsync(checkoutKey).get();
// After reconnecting, upload usage recorded during offline sessions
controlManager.flushOfflineSessionsAsync().get();
Offline Checkout (Python)
The released Python SDK does not support offline checkout. Disconnected Python clients rely on the validation-failure grace period only.
Preview — API v3.5, not yet released. An upcoming Python SDK release adds
CheckOutLicenseForOfflineUse(timedelta), CheckInOfflineLicense(),
CheckInComponents() and FlushOfflineSessions() to the desktop
ControlManager.
Offline Checkout (C++)
// Check out the license for offline use
OfflineCheckoutValidity checkout = controlManager.CheckOutLicenseForOfflineUse(sevenDays); // TimeSpan
if (checkout.get_Success()) {
std::cout << "Offline checkout succeeded" << std::endl;
} else {
std::cout << "Offline checkout failed: " << checkout.get_FailureReason() << std::endl;
}
// Return the checkout early
bool returned = controlManager.CheckInOfflineLicense(checkoutKey);
// After reconnecting, upload usage recorded during offline sessions
controlManager.FlushOfflineSessions();
License Properties (Custom Data) Preview
Preview — API v3.5, not yet released. The released server supports client API up to v3.4 and the released SDKs do not include this feature. Details may change before release.
Starting with client API v3.5, successful session responses (begin session, extend session, validate
instance, offline checkout) include custom_data: the license's key/value properties
as configured in the elm portal. Values are always strings. Use them for license-specific settings
such as tier names or limits that do not warrant a separate feature.
SDK Support (preview builds)
- .NET: declared as
CustomData(IReadOnlyDictionary<string, string>) onIControlStateandIValidatedSessionState, so it is read fromcontrolManager.ValidationState.CustomData. CustomIValidationStateimplementations must implement this member. - Java, Python, C++: the SDKs request API v3.5 and parse
custom_datafrom server responses, but do not yet expose it through the desktopControlManager.
// .NET - read license properties after a successful ValidateSessionAsync()
if (controlManager.ValidationState.CustomData.TryGetValue("tier", out var tier))
{
ApplyTier(tier);
}
State Management
The elm SDK leaves state persistence entirely up to the consumer application. The SDK provides the
IValidationState interface which defines the contract for state information, but
consumers must implement their own persistence mechanism.
Important: State Persistence Contract
The ValidationState class in the SDK is internal and cannot be persisted directly.
Instead, you must:
- Create your own implementation of
IValidationState(which extendsISessionControlStateandIControlState) - Optionally do the same for
IValidatedSessionState, so a session can be resumed after a restart - Handle saving and loading state data yourself
- The SDK doesn't prescribe object structure - only the interface contract
In Python, control_manager.State and control_manager.SessionState are
plain objects that can be pickled and passed back as saved_state /
session_state.
State Management Principles
Understanding how elm licensing state works:
- No Background Tasks: The SDK has no background processes and only performs actions when explicitly called
- Consumer-Driven Updates: State changes only occur after consumer actions (validation, registration, etc.)
- Persistence Responsibility: The consumer must save state after each SDK operation that modifies it
- Session Management: The .NET, Java and C++ SDKs include a
SessionExtensionTimerthat raisesLicenseCheckNecessarywhen the currentValidityDurationelapses; respond by calling session validation again, which extends the session. The Python SDK has no timer.
Implementation Example (.NET)
using System.Collections.ObjectModel;
using evoleap.Licensing;
using evoleap.Licensing.Desktop;
// Serializable IValidationState snapshot (serialize with JSON, protobuf, etc.)
internal class LicenseValidationData : IValidationState
{
// IControlState
public bool Registered { get; set; }
public List<DateTime> FailedRegistrationTimes { get; set; } = new List<DateTime>();
ReadOnlyCollection<DateTime> IControlState.FailedRegistrationTimes => FailedRegistrationTimes.AsReadOnly();
public DateTime? RegisteredAt { get; set; }
public DateTime FirstLaunchTime { get; set; }
public List<DateTime> FailedValidationTimes { get; set; } = new List<DateTime>();
ReadOnlyCollection<DateTime> IControlState.FailedValidationTimes => FailedValidationTimes.AsReadOnly();
public DateTime? LastSuccessfulValidationTime { get; set; }
public int LastValidationStatus { get; set; }
public List<string> Features { get; set; } = new List<string>();
ReadOnlyCollection<string> IControlState.Features => Features.AsReadOnly();
public List<string> FeatureGroups { get; set; } = new List<string>();
ReadOnlyCollection<string> IControlState.FeatureGroups => FeatureGroups.AsReadOnly();
// Preview (API v3.5, not yet released): IControlState also gains
// IReadOnlyDictionary<string, string> CustomData - implement it when you adopt that SDK version.
public TimeSpan GracePeriodForValidationFailures { get; set; }
// SessionExpiration / SessionExpired are internal to the SDK and are not restored, so they are not part of the snapshot.
// ISessionControlState
public TimeSpan SessionDuration { get; set; }
// IValidationState
public Guid InstanceId { get; set; }
public bool HasUserId { get; set; }
public Guid UserId { get; set; }
public bool ComponentsLoaded { get; set; }
public List<ComponentInfo> Components { get; set; } = new List<ComponentInfo>();
ReadOnlyCollection<ComponentInfo> IValidationState.Components => Components.AsReadOnly();
public bool ComponentEntitlementsLoaded { get; set; }
public List<ComponentEntitlementInfo> ComponentEntitlements { get; set; } = new List<ComponentEntitlementInfo>();
ReadOnlyCollection<ComponentEntitlementInfo> IValidationState.ComponentEntitlements => ComponentEntitlements.AsReadOnly();
public List<OfflineCheckoutInfo> OfflineCheckouts { get; set; } = new List<OfflineCheckoutInfo>();
ReadOnlyCollection<OfflineCheckoutInfo> IValidationState.OfflineCheckouts => OfflineCheckouts.AsReadOnly();
public List<OfflineTokenCheckoutInfo> OfflineTokenCheckouts { get; set; } = new List<OfflineTokenCheckoutInfo>();
ReadOnlyCollection<OfflineTokenCheckoutInfo> IValidationState.OfflineTokenCheckouts => OfflineTokenCheckouts.AsReadOnly();
public List<OfflineSessionInfo> OfflineSessions { get; set; } = new List<OfflineSessionInfo>();
ReadOnlyCollection<OfflineSessionInfo> IValidationState.OfflineSessions => OfflineSessions.AsReadOnly();
// Copy the ControlManager's current state into a serializable snapshot
public static LicenseValidationData From(IValidationState s) => new LicenseValidationData
{
Registered = s.Registered,
FailedRegistrationTimes = s.FailedRegistrationTimes.ToList(),
RegisteredAt = s.RegisteredAt,
FirstLaunchTime = s.FirstLaunchTime,
FailedValidationTimes = s.FailedValidationTimes.ToList(),
LastSuccessfulValidationTime = s.LastSuccessfulValidationTime,
LastValidationStatus = s.LastValidationStatus,
Features = s.Features.ToList(),
FeatureGroups = s.FeatureGroups.ToList(),
GracePeriodForValidationFailures = s.GracePeriodForValidationFailures,
SessionDuration = s.SessionDuration,
InstanceId = s.InstanceId,
HasUserId = s.HasUserId,
UserId = s.UserId,
ComponentsLoaded = s.ComponentsLoaded,
Components = s.Components.ToList(),
ComponentEntitlementsLoaded = s.ComponentEntitlementsLoaded,
ComponentEntitlements = s.ComponentEntitlements.ToList(),
OfflineCheckouts = s.OfflineCheckouts.ToList(),
OfflineTokenCheckouts = s.OfflineTokenCheckouts.ToList(),
OfflineSessions = s.OfflineSessions.ToList(),
};
}
// Licensing controller that orchestrates operations and handles persistence
internal class LicensingController
{
private readonly ControlManager _manager;
private readonly SessionExtensionTimer _timer = new SessionExtensionTimer();
private readonly string _stateFile;
internal LicensingController(Guid productId, Version version, PublicKeyFile publicKey,
UserIdentity userIdentity, InstanceIdentity instanceIdentity, string stateFile)
{
_stateFile = stateFile;
// Load saved state (null on first run - the SDK then initializes fresh state)
LicenseValidationData saved = LoadState(_stateFile);
_manager = new ControlManager(productId, version, publicKey, userIdentity, instanceIdentity,
savedState: saved);
// Re-validate (which extends the session) whenever the current validity runs out
_timer.LicenseCheckNecessary += async (s, e) => await RunValidation();
}
internal async Task<RegistrationResult> Register(string licenseKey, string name, string email)
{
_timer.Stop();
var result = await _manager.RegisterAsync(licenseKey, new UserInfo(name, email));
Persist(); // Persist after every registration attempt
return result;
}
internal async Task<SessionValidity> RunValidation()
{
var validity = await _manager.ValidateSessionAsync();
_timer.Update(validity); // Schedules the next LicenseCheckNecessary
Persist();
return validity;
}
internal void Persist()
{
SaveState(_stateFile, LicenseValidationData.From(_manager.ValidationState));
}
internal async Task EndSession()
{
_timer.Stop();
await _manager.EndSessionAsync();
Persist();
}
// LoadState / SaveState: your serialization of choice
}
Key Implementation Points
1. Custom State Implementation
Implement IValidationState with your preferred serialization approach (JSON,
Protobuf, etc.). Include FeatureGroups, OfflineTokenCheckouts and
OfflineSessions so feature groups and offline usage survive restarts.
Preview API v3.5 SDKs add CustomData to the
interface.
2. Persist After Every Operation
Call your persistence method after registration, validation, component checkouts, offline checkouts, or any SDK operation that modifies state.
3. Session Extension (Optional)
Use SessionExtensionTimer (.NET, Java, C++), or implement your own timer based on
SessionValidity.ValidityDuration returned from session validation.
4. State Initialization
Pass null for the saved state on first run; the SDK records the first launch time
(used for the unregistered grace period) and generates the instance ID. Load the persisted
state on subsequent application starts.
Best Practices
Follow these best practices when integrating elm licensing into your desktop applications.
Application Lifecycle Management
1. Initialize Early
Initialize the ControlManager and validate licensing before showing your main application UI.
// In your application startup (LicensingController from the State Management example)
protected override async void OnStartup(StartupEventArgs e)
{
_licensing = new LicensingController(productId, version, publicKey, userIdentity, instanceIdentity, stateFile);
var validity = await _licensing.RunValidation();
if (validity.IsValid)
{
// Show main application window
MainWindow = new MainWindow();
MainWindow.Show();
}
else
{
// Show registration/licensing dialog
var registerDialog = new RegisterDialog();
registerDialog.ShowDialog();
}
}
2. Handle Graceful Shutdown
Always end sessions properly when your application closes to ensure accurate usage reporting and to release concurrent seats.
// In your application shutdown handler
protected override async void OnExit(ExitEventArgs e)
{
await _licensing.EndSession();
base.OnExit(e);
}
Error Handling
1. Network Connectivity Issues
Handle network issues gracefully: tell users when they are running in the validation-failure
grace period, offer offline checkout for planned travel, and call
FlushOfflineSessionsAsync() after reconnecting.
2. Hardware Changes
Handle InvalidReason.InconsistentRegistration, which indicates the instance identity
no longer matches the registered machine, by directing users to support or re-registration.
User Experience
1. Progressive Feature Disclosure
Show/hide features based on license entitlements rather than showing disabled controls.
2. Clear Licensing Status
Provide clear indication of licensing status, offline mode, and feature availability.
3. Upgrade Prompts
When users attempt to access features they don't have, show clear upgrade/purchase options.