Table of contents

Delphi

SDK Files for Delphi

  • Flat API: GS5_Intf.pas

    Provides low-level api interface to native gsCore.dll.

  • OOP API: GS5.pas

    Based on flat api, these two files provides Object-Oriented programming api.

  • Extension: GS5_Ext.pas

    Based on OOP API, these two files are needed to develop customized license model, easy license event handling, etc.

  • Debug Helper: unDebugHelper.pas

    Provides debug helper utility functions.

The source list here is just for quick online reference, please refer to "How to get the latest SDK?" to get the original SDK files. The SDK/Pascal files are in "Lang\Pascal" folder.

Preparation

  1. Prepare gsCore lib, license file and parameters;
  2. Copy SDK/Pascal (*.pas) files to your app source directory, add them as part of your app project;

Initialize SDK

Before you can call most SDK apis, you must initialize the SDK first with the project-specific parameters. Depending on how you pass in the license file, there are two methods to initialize the SDK:

  • Initialize SDK with license file
uses GS5_Intf, GS5;

//These information are copied from IDE:
const 
    productId = '8d11ec62-bfa0-4794-9688-41f8aa04630d';
    password = 'walml_9282&APNQ&18163';
    license_filename = 'Advanced Tutorial of Notepad2.lic';

//Resolve full path to license file
//Depending where you put the license file, its path can be resolved in different way.
function getFullPathToLicenseFile: string;
begin
  return IncludeTrailingPathDelimiter(GetEnvironmentVariable('MY_DEV_PATH')) + license_filename; 
end;

function initSDK: Boolean;
var 
  core: TGSCore;
begin
    core := TGSCore.getInstance;
    if(not core.init(productId, getFullPathToLicenseFile,  password))
    {
      ShowMessageFmt('Error initializing SDK, error code: %d, error message: %s', [core.LastErrorCode, core.LastErrorMessage]);
    }
end;

In this demo, we copy our license file to a folder and setup an environment variable MY_DEV_PATH to point to it, so that the app can locate the license file easily.

You can also put the license file side by side with your exe, and resolve the exe path at run-time via Application.ExeName, anyway, you must pass in the full path of the license file, otherwise the SDK will blinkdly assume the license file is in current directory, which will fail if the app is launched from another folder.

  • Initialize SDK with raw license data

Instead of dealing with the license file path, you can also pass the license information as raw data array:

//The license info must be set in other function before calling init()
Const
  { f:\Advanced Tutorial of Notepad2.lic }
  BinaryData : array[0..644] of byte = (
        $47,$53,$03,$00,$01,$01,$0C,$03,$00,$00,$6B,$02,$00,$00,$95,$88,
        $47,$B8,$1E,$57,$E0,$82,$C1,$87,$8D,$6A,$DF,$6F,$A6,$14,$88,$23,
        ... );

ok := core.init(productId, @BinaryData[0], Length(BinaryData), password);

So given a license file, how do you transfer it to a block of memory that can be accessed by Pascal code? there are several ways:

  1. Convert raw license file to byte array in pascal soruce code

bin2pas

bin2pas: Convert binary file to pascal source

  
  Usage: bin2pas inputfile [ outfile ]
  
  Parameters:

    input: binary data file to convert
    
    outfile: [Optional] the output pascal source file
            if not specified, outfile = input + ".pas" 

The output looks like:

Const
  { f:\Advanced Tutorial of Notepad2.lic }
  BinaryData : array[0..644] of byte = (
        $47,$53,$03,$00,$01,$01,$0C,$03,$00,$00,$6B,$02,$00,$00,$95,$88,
        $47,$B8,$1E,$57,$E0,$82,$C1,$87,$8D,$6A,$DF,$6F,$A6,$14,$88,$23,
        ... );
  1. Embed the license file as exe resource.

Windows provides win32 api to allow you retrieve resource data in memory.

Query License Information

Get Entity

To query license information, you first need to find the entity objects:

  • Enumerate All Entities
var
  i: Integer;
  entity: TGSEntity;  
for i := 0 to core.EntityCount-1 do
begin  
  entity := core.Entities[i];
  
  //dump entity information...
  Writeln( "entity name: ", entity.Name, " id: ",  entity.Id );
  
  entity.Free;
end

You can also get the specific entity by its entityId:

//EntityId is copied from IDE
const entityId = "b15c8ac2-d87c-4483-be77-5a8cbc89a62b";

entity := core.getEntityById( entityId );

If your app has only one entity, let's get it by a single line:

entity := core.Entities[0];

This is because the entity is the only single entity with index 0.

All SDK/Pascal objects (Entity, License, Action, etc.) are derived from TGSObject, which encapsulates an internal handle created from gsCore, when the object is destroyed, the handle is released and its associated resources are freed in gsCore. So it is very important to Destroy / Free any SDK/Pascal objects when it is not used.

Query Entity License Status

Given an entity object, we can easily retrieve its properties:

  • Get entity Properties
var
  id, name, description: string;
  
id = entity.Id;
name = entity.Name;
description = entity.Description;
  • Test if entity is fully licensed, in trial mode or already expired

If your entity is associated with a trial license model (Expire by Access Times, Expire By Hard Date, Expire By Period, Expire by Duration, Expire by Session Time), you can test the entity's license status as following:

if entity.Unlocked then
begin
  //this entity is already activated / fully purchased.
  //app should run in full featured mode for this entity (game level, app module, etc.)...
  run_in_full_featured_mode();
end else if entity.Locked then
begin
  //this entity is locked or trial already expired.
  //we may pop up info to prompt for purchase.
  cannot_run();
end else 
begin
  //this entity still in trial period or duration
  run_in_trial_mode();
end

If your entity is using License Model Always Lock, then the logic can be simplied as:

if entity.Unlocked then
begin
  //this entity is already activated / fully purchased.
  //app should run in full featured mode for this entity (game level, app module, etc.)...
  run_in_full_featured_mode();
end else 
begin
  //this entity is still locked, we need a license to continue.
  //we may pop up info to prompt for purchase.
  cannot_run();
end

Query License Model Parameters

In the above section, we have detected that an entity is still in trial mode, so in order to display proper license information to end user on app UI, such as tell the game player how many trial time left before the game will stop running, we must be able to get more details of the license model.

  • Get License Object Given an entity, it is quite simple to retrive the license object attached to this entity:
var
  lic: TGSLicense;
  
lic := entity.License;

Now that the license object is ready, we can inspect it in various perspectives:

  • Get License Model Type

As a developer we have designed the license project in SoftwareShield IDE before, so we should have known which kind of license model being used for an entity. However, if you are developing a universal logic to parse any entity, it is helpful to find out the license model type being bundled with the input entity:

var
  model_id, model_name, model_description: string;
  
model_id := lic.Id;
model_name := lic.Name;
model_description := lic.Description;

For example, for Expire By Period, its LicenseID is "gs.lm.expire.period.1", so by comparing the license id you can figure out the correct license model type to inspect further.

  • Get License Model Parameters

Now we have known which license model to inspect, let's retrieve the license parameters according to its model type:

Expire by Access Times

This license model (Expire by Access Times) has two parameters of int type: "usedTimes" and "maxAccessTimes"

var
  usedTimes, maxAccessTimes, how_many_access_left: Integer;
  
usedTimes := lic.ParamInt['usedTimes'];
maxAccessTimes := lic.ParamInt['maxAccessTimes'];

//How many times left the app can launch in trial mode? 
how_many_access_left := maxAccessTimes - usedTimes;

Expire By Hard Date

This model (Expire By Hard Date) is a bit complex since it have three different use cases, in this tutorial we only demonstrate the most commonly used one: "Expire After", in this case, the license has a predefined expiry date "timeEnd", the app trial mode expires after this date.

var
  expiryDateUTC, expiryDateLocal: TDateTime;
  
expiryDateUTC := lic.ParamUTCTime('timeEnd');

//Convert UTC to local time
expiryDateLocal := UTCToLocal( expiryDateUTC );

The interest part is that SoftwareShield SDK api always returns TDateTime in UTC time zone, you might have to convert it to local time to display the expiry date in local time zone. SoftwareShield SDK/Delphi has several time convert functions in GS5.pas:

//Helpers
//UTC <=> Local
function UTCToLocal(const dtUTC: TDateTime): TDateTime;
function LocalToUTC(const dtLocal: TDateTime): TDateTime;

//UTC <=> Unix time_t
function UnixTimeToUTC(const unixTime: Int64): TDateTime;
function UTCToUnixTime(const dtUTC: TDateTime): Int64;

//Format seconds to a string in format "XX day(s) YY hour(s) ZZ minute(s) DD second(s)"
function TimeSpanStr(const seconds: Integer): string;

Expire By Period

This license model (Expire By Period) has a UTC datetime parameter "timeFirstAccess", specify the timestamp the entity is first accessed, and an integer parameter "periodInSeconds", specify the total trial period allowed. The following code tries to figure out when the license will expire and how many trial time left before expire.

uses DateUtils;

var
  periodInSeconds, timeLeftInSeconds: Integer;
  timeFirstAccess, expiryDate: TDateTime;

//Total trial period in seconds
periodInSeconds := lic.ParamInt('periodInSeconds');

//Figure out when the entity is first accessed (aka. entity.beginAccess() is first called)
//if never accessed before, the _timeFirstAccess_ parameter won't hold a valid datetime and the api can throw exception.

try
  timeFirstAccess := lic.ParamUTCTime('timeFirstAccess');
  
  //accessed before, so we can calculate the expiry date
  expiryDate := IncSecond(timeFirstAccess, periodInSeconds);
  
  //How many trial time left?
  timeLeftInSeconds := SecondsBetween( Now, expiryDate );
except
  //Never accessed, the expiry date is still unknown!

  //How many trial time left = full trial period.
  int timeLeftInSeconds := periodInSeconds;
end;

The interesting part of the above code is that: if the "timeFirstAccess" does not hold a valid timestamp (the default value is undefined), which occurs when the entity is never accessed before, we cannot deduce the exact expiry date, however we can deduce how many time left before expire.

Expire by Duration

This license model (Expire by Duration) is simple, it just adds up all elapsed trial time cumulatively (in "usedDurationInSeconds") until the total trial time exceeds the pre-defined maximum value ("maxDurationInSeconds"). We cannot figure out the expiry date.

//Total trial period in seconds
int usedDurationInSeconds = lic->getParamInt("usedDurationInSeconds");
int maxDurationInSeconds = lic->getParamInt("maxDurationInSeconds");

//How many trial time left?
int timeLeftInSeconds = maxDurationInSeconds - usedDurationInSeconds;

Expire by Session Time

This license model (Expire by Session Time) allows an entity can always be accessed (never expire) but in a limited time, we can get the session time left but cannot get the expiry date.

int sessionTimeUsed = lic->getParamInt("sessionTimeUsed");
int maxSessionTime = lic->getParamInt("maxSessionTime");

//How many trial time left?
int timeLeftInSeconds = maxSessionTime - sessionTimeUsed;

Activation

There are two methods to activate your app, online and offline.

  • Online Activation

After user've made purchase of your app, you send a serial number to the customer as a proof of valid customer and the key to activate your app.

var
  rc: Integer; //returned error code
  success: Boolean;
  
if core.isServerAlive then
begin
   success := core.applySN(serial, rc);
end else
begin
  // license server is not available!
  //optionally fall back to offline activation...
end;
  • Offline Activation

In order to do offline activation, we need to go through three steps:

  1. Generate Request Code
var
  requeseCode: string;
  
requestCode := core.DummyRequestCode;

We are using dummy request code because the license server can deduce all valid actions from the customer's serial number.

  1. Get License Code from License Server

You display the request code on UI, asking the customer to contact your support team for a valid license code, or if possible, let the customer uses another machine (or a mobile device) to generate the license code by himself/herself via the app's public App Web Activator.

  1. Apply License Code

Once the user input a license code, we can apply it to the app:

var
  licenseCode: string; //user input the license code.
  success: boolean;
  
success := core.applyLicenseCode(licenseCode, serial);

Revoke License

SoftwareShield SDK allows you to revoke serial numbers to license server so that the revoked serial numbers can be reused in another machine or the same machine (after hardware upgrade, system re-install, etc.)

  1. Revoke Single Serial Number
var
  serialToRevoke: string;
  
success := core.revokeSN(serialToRevoke);

After successful revoke, all those entities previously unlocked by this serial are locked.

  1. Revoke All Serial Numbers
success := core.revokeApp;

After successful revoke, all previously unlocked entities are locked.

Transfer License

Revoke serial number is good for most cases, but there are other scenerios that the serial-based revoke won't work. For example, your license model is based on Pay-as-you-go concept, your app is not fully activated, the serial is used to extend the app's life span, so the unlock-lock based serial-number revoke does not make sense at all.

  • To online transfer the current license information from machine A to machine B

On machine A (sender):

var
  receiptSN: string;
  
receiptSN := core.uploadApp;

On machine B (receiver):

success := core.applySN(receiptSN);
  • To offline transfer the current license information from machine A to machine B:

On machine A (sender):

var
  lic_backup: string;
  
lic_backup := core.exportApp;

On machine B (receiver):

var
  mv: TMovePackage;
  success: Boolean;
  moveRequestCode: string;
  
  // the user reads the request code from app UI and gets license code from support team (or via App Web Activator)
  licenseCode: string; 
try  
  mv := core.createMovePackage( lic_backup );
  
  if core.isServerAlive then
    success := mv.importOnline( serial ) //online import
  else
  begin
    //offline import
    moveRequestCode := mv.getImportOfflineRequestCode;
    
    success := mv.importOffline(licenseCode);
  end;
finally
  mv.Free;
end;

The workflow seems a little messy, but you do not have to fully support all online/offline cases in your app. The SoftwareShield SDK provides complete apis to support all possible use cases.

How to lock app when the serial number becomes invalid

Sometimes you want to implement a logic to test if a serial number is still valid before app can run at client side. You can disable or delete a serial number at CheckPoint2 license management web portal, as a result you can remote control if the app should run by manipulating the serial number on the server side.

var
  serial: string; //the serial issued from app vendor
  
if not core.isValidSN( serial ) then
begin
  core.lockAllEntities;
  //quit app...
end;

Listen to Events

To handle SoftwareShield license events, you can add event handler to interested event:

In GS5.pas, there are three event handler protocals corresponding to three categories of event type (Application, License and Entity):

TGSAppEventHandler = procedure (eventId: Integer) of object;
TGSLicenseEventHandler = procedure (eventId: Integer) of object;
TGSEntityEventHandler = procedure (eventId: Integer; entity: TGSEntity) of object;

In MyApp.pas:

unit MyApp;

interface
  uses GS5, GS5_Intf;

type

  TMyApp = class 
  private
    _core : TGSCore;

    //Event listeners
    procedure OnAppEvent(eventId: Integer);
    procedure OnLicenseEvent(eventId: Integer);
    procedure OnEntityEvent(eventId: Integer; entity: TGSEntity);
  public
    procedure initSDK;
  end;


Implementation
  uses dialogs;

  procedure TMyApp.initSDK;
  begin
    _core := TGSCore.getInstance;

    //Hook event listeners
    _core.OnAppEvent := OnAppEvent;
    _core.OnLicenseEvent := OnLicenseEvent;
    _core.OnEntityEvent := OnEntityEvent;

    //init SDK
   // if not _core.init(...) then raise Exception.Create('Error initializing SDK!');
  end;

  procedure TMyApp.OnAppEvent(eventId: Integer);
  var
    entity: TGSEntity;
  begin
    case eventId of
      EVENT_APP_BEGIN:
        //App launches, say hello...
        exit;
      EVENT_APP_END:
        try
          //Render Exit-UI
          entity := _core.getEntityByIndex(0);

          if entity.Unlocked then
          begin
            //activated, do nothing
          end
          else if entity.Locked then
          begin
            //trial already expired, need license to run
            ShowMessage('your trial is expired, please activate now');
          end
          else
          begin
            //trial mode...
            ShowMessage('your trial will expire in XXX');
          end;
        finally
          entity.Free;
        end;
    end;
  end;
  
  procedure TMyApp.OnLicenseEvent(eventId: Integer);
  begin
  end;
 
  procedure TMyApp.OnEntityEvent(eventId: Integer; entity: TGSEntity);
  begin
    case eventId of
      EVENT_ENTITY_TRY_ACCESS:
        begin
          if entity.Unlocked then
          begin
            //activated, do nothing
          end
          else if entity.Locked then
          begin
            //trial already expired, need license to run
            //render_activate_UI(entity);
          end
          else
          begin
            //trial mode...
          end;
        end;

      EVENT_ENTITY_ACCESS_INVALID:
          //trial expired!
          //render_activate_UI(entity);
          Exit;

      EVENT_ENTITY_ACCESS_HEARTBEAT:
          //check when the entity will expire, inspect its license parameter to give user feedback
          //when 5 minutes left before expire...
    end;
  end;

Upon app launching, you should call TMyApp.initSDK() first to initialize SoftwareShield SDK.