Skip to content

C++ SDK

Supported Platforms

The C++ SDK is compatible with the following platforms:

  • Linux
  • MacOS
  • Windows
  • Android
  • iOS

Code Building Requirements

  • C++ version should be C++17 or higher
  • CMake version should be 3.24 or higher (this can be adjusted)
  • Protobuf version should be 3.21.12 (this can be adjusted to match the project by modifying CMakeLists.txt, but it must be pb3)

Getting Started

This guide will walk you through the process of getting started with ABetterChoice for C++. To utilize the SDK, an ABetterChoice project is required. If you haven't already, please follow the doc to create a new project and some experiments. While you can choose to bypass this step for now, please note that you may require the project ID and the names of the experiment layers in subsequent sections.

Installation

There are two methods to use the SDK: you can either directly import the source code or link it after precompilation. Choose the approach that best suits your needs.

1.1 Source Code Import

add_subdirectory

There's no need for precompilation. You can directly copy the source code into your project directory. This is only applicable for the target language CPP. For operating systems like Android, iOS, etc., precompilation into libraries is necessary.

cd ${YOUR_PROJECT_PATH}
mkdir third_party
cd third_party
git clone https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git
shell

If you're using CMake, include the following in your CMakeLists.txt file:

# CMakeLists.txt
add_subdirectory(${YOUR_PROJECT_PATH}/third_party/abetterchoice-cpp-sdk)
target_include_directories(your_library_name PRIVATE ${YOUR_PROJECT_PATH}/third_party/abetterchoice-cpp-sdk/include/)
target_link_libraries(your_library_name PRIVATE abetterchoice_cpp_sdk)
shell

You can check out the latest versions at https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git .

1.2 Precompile

find_library

For OS like Android, iOS, etc., precompiling into libraries is required.

First, precompile and generate the corresponding platform-specific dependency packages. Then, link the dependency libraries using "find_library".

1.2.1 How to Precompile

Linux、Macos、Windows

shell
git clone https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git
cd abetterchoice_cpp_sdk
mkdir build
cd build
cmake ..
make

IOS

Requirements:

  • Xcode.

Xcode is required to be installed and the environment needs to be configured.

On iOS, only static libraries can be used.

Make sure to configure CMakeLists.txt to generate a static library.

shell
git clone https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git
cd abetterchoice_cpp_sdk
mkdir build
cd build

# You can modify the iOS environment.
cmake .. -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_SYSTEM_VERSION=9.0 -DCMAKE_OSX_ARCHITECTURES=arm64
make

Android

Requirements:

  • NDK

This depends on NDK, so you need to install the corresponding version of NDK first.

shell
git clone https://git.woa.com/tencent_abtest/open-source/abetterchoice-cpp-sdk.git
cd abetterchoice_cpp_sdk
mkdir build
cd build

# You can modify the Android environment and specify the NDK address.
cmake .. -DCMAKE_TOOLCHAIN_FILE=/Users/${USERNAME}/Library/Android/sdk/ndk/23.0.7599858/build/cmake/android.toolchain.cmake -DANDROID_NATIVE_API_LEVEL=21 -DANDROID_ABI=arm64-v8a -DCMAKE_PREFIX_PATH=/Users/${USERNAME}/vcpkg/installed/arm64-android -DCMAKE_SYSTEM_NAME=Android -DCMAKE_ANDROID_ARCH_ABI=arm64-v8a -DCMAKE_ANDROID_NDK=${ANDROID_NDK_HOME} -DCMAKE_ANDROID_STL_TYPE=c++_shared
make

Linux、Macos、Windows

If you are using CMake, include the following in your CMakeLists.txt file

shell
# CMakeLists.txt
find_library(abetterchoice_cpp_sdk
        NAMES abetterchoice_cpp_sdk
        HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(protobuf
        NAMES protobuf-lite
        HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(roaring
        NAMES roaring
        HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(curl
        NAMES curl
        HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(ssl
        NAMES ssl
        HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(crypto
        NAMES crypto
        HINTS ${YOUR_PROJECT_PATH}/lib)
find_library(z
        NAMES z
        HINTS ${YOUR_PROJECT_PATH}/lib)
target_include_directories(your_library_name PRIVATE ${YOUR_PROJECT_PATH}/third_party/abetterchoice-cpp-sdk/include/)
target_link_libraries(your_library_name PRIVATE
        ${abetterchoice_cpp_sdk} ${protobuf} ${roaring}
        ${curl} ${ssl} ${crypto} ${z})

IOS

Here's a simple example, but the specific implementation will depend on the design of your business logic:

  1. Add library files:

    • Add the libabetterchoice_cpp_sdk.a, libroaring.a, libprotobuf-lite.a, libcurl.a, libssl.a, libcrypto.a, and libz.a files to your Xcode project.
    • Drag the library files into the "Navigator" pane in Xcode, and make sure they are added to the target application.
  2. Add header files:

    • Add the header files in the abetterchoice_cpp_sdk/include folder to your Xcode project. You can drag the header files directly into the "Navigator" pane in Xcode, or add the directory where the header files are located to the "Header Search Paths" setting of your project. To set the "Header Search Paths", go to the "Build Settings" tab of your project, find the "Header Search Paths" setting, and add the directory where the header files are located.
  3. Link library files:

    • In your Xcode project, go to the "Build Phases" tab, and expand the "Link Binary With Libraries" section.
    • Click the + button to add a new library. In the pop-up window, select the library files you just added and click "Add".
  4. Create an Objective-C wrapper :

    • Since abetterchoice_cpp_sdk is a C++ library, you need to create an Objective-C++ wrapper to use it in your iOS project. Refer to the previous answer for how to create a wrapper. Here's a simple example of how to create the AbetterchoiceCppSdkWrapper:

Create a header file

cpp
//
//  AbetterchoiceCppSdkWrapper.h
//

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN

@interface AbetterchoiceCppSdkWrapper : NSObject
(NSString *)testSDKWithProjectID:(NSString *)projectID Layer:(NSString *)layer Parameter:(NSString *)parameter Guid:(NSString *)guid;

@end NS_ASSUME_NONNULL_END

Create an implementation file

You can name the file AbetterchoiceCppSdkWrapper.mm. Note that we use the .mm extension instead of .m to use Objective-C++.。

cpp

 //
//  AbetterchoiceCppSdkWrapper.mm
//

#import "AbetterchoiceCppSdkWrapper.h"

#include <string>
#include <random>

// Replace path/to/abc_cpp_sdk.h with the actual path of your C++ library header file
#include "path/to/abc_cpp_sdk.h"

@implementation AbetterchoiceCppSdkWrapper

- (NSString *)testSDKWithProjectID:(NSString *)projectID Layer:(NSString *)layer Parameter:(NSString *)parameter Guid:(NSString *)guid {
    // Call functions in the C++ library
    std::vector<std::string> project_id;
    std::string project_id = [projectID UTF8String];
    project_ids.push_back(project_id);
    std::string token = "xxx";
    int ret = abc_cpp_sdk::AbcService::GetInstance()->Init(project_ids,
        abc_cpp_sdk::WithSetSecretKey(token),
        abc_cpp_sdk::WithSetInitOvertimeTime(30),
        abc_cpp_sdk::WithSetEnv(abc_cpp_sdk::PROD_ENV));

    // Call functions in the C++ library
    std::string result = std::to_string(ret);
	// Please refer to the later section for more recommended APIs
    auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment(project_id, [layer UTF8String],
        abc_cpp_sdk::WithSetUnitID([guid UTF8String]));

    // Convert the result to an NSString and return it
    return [NSString stringWithUTF8String:experiment.GetString([parameter UTF8String]).c_str()];
}

@end

After completing the above steps, your AbetterchoiceCppSdkWrapper class should be able to call functions in the abetterchoice_cpp_sdk library and use them in Objective-C.

Now you can use this wrapper class in your iOS project to access the functionality of the C++ library.

Linux、Macos、Windows

If you are using CMake, include the following in your CMakeLists.txt file

shell
add_library(abetterchoice_cpp_sdk SHARED IMPORTED)
set_target_properties(abetterchoice_cpp_sdk PROPERTIES
        IMPORTED_LOCATION "${YOUR_PROJECT_PATH}/libabetterchoice_cpp_sdk.so")
target_include_directories(your_library_name PRIVATE ${YOUR_PROJECT_PATH}/third_party/abetterchoice-cpp-sdk/include/)
target_link_libraries(your_library_name PRIVATE abetterchoice_cpp_sdk)

Android

Here's a simple example in Java, but the specific implementation will depend on the design of your business:

  1. Add library files: Add the libabetterchoice_cpp_sdk.so (dynamic library) file to your Android project. Put the library file in the src/main/jniLibs directory of your project and create ABI subdirectories as needed (e.g., armeabi-v7a, arm64-v8a, x86, etc.). For example, put the library file for the arm64-v8a version in the src/main/jniLibs/arm64-v8a directory.

  2. Add header files: Add the header files of the abetterchoice_cpp_sdk library to your Android project. You can put the header files in the src/main/cpp/include directory of your project (create a new directory if it doesn't exist yet). Copy the header files to the include directory.

  3. Modify CMakeLists.txt: Create or modify the existing CMakeLists.txt file in the src/main/cpp directory of your project. In this file, do the following:

    shell
    add_library(abetterchoice_cpp_sdk SHARED IMPORTED)
    set_target_properties(abetterchoice_cpp_sdk PROPERTIES
            IMPORTED_LOCATION "${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libabetterchoice_cpp_sdk.so"
            )
    include_directories("${CMAKE_SOURCE_DIR}/include")
    target_link_libraries(your_executable_target abetterchoice_cpp_sdk)
  4. Modify build.gradle: In the build.gradle file (Module level) of your project, add a reference to the CMakeLists.txt file in the externalNativeBuild section of the android section:

    shell
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
       }
    }
  5. Create a Java wrapper: For an Android project, you need to create a Java or Kotlin wrapper class to call functions in the abetterchoice_cpp_sdk library in Java or Kotlin code. Here's a simple example:

  • Create a Java wrapper class: In the src/main/java directory of your Android project, create a Java class named AbetterchoiceCppSdkWrapper. In this class, declare a native method that corresponds to the function you declared in the JNI interface.
    public class AbetterchoiceCppSdkWrapper {
      static {

          System.loadLibrary("abetterchoice_cpp_sdk");
      }
      public native String init(); // Declare a native method
     }
  • Create a JNI interface: In the src/main/cpp directory, create a C++ file, such as abetterchoice_cpp_sdk_jni.cpp. In this file, include your library header file and declare a JNI interface function that will call your C++ library function.
 #include <jni.h>
     #include <string>
     #include <random>
     #include "include/abetterchoice_cpp_sdk/abc_cpp_sdk.h"

     std::string jstringToString(JNIEnv *env, jstring jstr) {
      if (env == nullptr) return "";
      const char *cstr = env->GetStringUTFChars(jstr, nullptr);
      std::string str(cstr);
      env->ReleaseStringUTFChars(jstr, cstr);
      return str;
     }

     extern "C"
     JNIEXPORT jstring JNICALL
     Java_com_example_test_1cpp_1sdk_AbetterchoiceCppSdkWrapper_testAbcSDK(JNIEnv *env, jobject thiz, jstring project_id, jstring layer_key, jstring parameter, jstring guid) {
      std::vector<std::string> project_ids;
      std::string project_id = jstringToString(env, project_id);
      project_ids.push_back(project_id);
      std::string token = "xxxx";
      int ret = abc_cpp_sdk::AbcService::GetInstance()->Init(project_ids,
      abc_cpp_sdk::WithSetSecretKey(token),
      abc_cpp_sdk::WithSetInitOvertimeTime(30),
      abc_cpp_sdk::WithSetEnv(abc_cpp_sdk::PROD_ENV));

      // Call functions in the C++ library
      std::string result = std::to_string(ret);
      std::string unit_id = jstringToString(env, guid);
      auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment(project_id,
                                                                                jstringToString(env, layer_key),
                                                                                abc_cpp_sdk::WithSetUnitID(unit_id));

      // Convert the result to a Java string and return it
      return env->NewStringUTF(experiment.GetString(parameter).c_str());
     }

After completing the above steps, your Android project will contain a Java wrapper class that can call functions in the abetterchoice_cpp_sdk library.

Now you can use the AbetterchoiceCppSdkWrapper class in your Android project to access the functionality of the C++ library. For example, you can create an instance of the AbetterchoiceCppSdkWrapper class in an Activity or other class and call the testAbcSDK method.

Working with the SDK

After installation, you will need to initialize the SDK . The Init function requires two parameters:

  1. The first parameter is the ID list of projects under which you need to fetch experiment/feature flag, usually this list only contain one element.
  2. The second parameter is the secret key. You can locate this under the API key section within the settings tab. Please contact us if you need to fetch more than one projects.
cpp
int ret = abc_cpp_sdk::AbcService::GetInstance()->Init(project_ids, abc_cpp_sdk::WithSetSecretKey({{.sdk_token}}));
if (ret != 0) {
  // init fail
}

In the following section, we will guide you to create a new experiment and fetch the experiment assignments via our experiment fetching APIs provided by our SDK.

Terms
Meaning
UnitIDA unit ID can be a user ID, a session ID or a machine ID. The ABC SDK assigns a unit ID to the same group consistantly.
LayerA layer usually represent 100% of the units that you can run experiment in your product, it can be further splited into one or more experiments, the traffic of the experiments under same layer are mutually exclusive.
ExperimentAn experiment can take up to 100% of the layer traffic, it will further contain two or more experiment groups.
GroupUnits within one experiment will be split into two or more groups, e.g. control and treatment, they will be compared against each other.
ParameterThese are the values binded to a particular layer. Experiments under the same layer can share the same parameters with each other.

1. Creating a New Experiment

If you haven't done so already, please navigate to the console and create a new experiment by following our documentation on how to create an experiment.

2. Retrieving Experiment Assignments

Assume that in step 1, under the project project_id, we have already created an experiment with the layer name abc_layer_name. Within this layer, there is a parameter named should_show_banner of Boolean type. Please note that in our basic version, the layer name defaults to the experiment name. However, if you create new experiments under the same layer, you will need to find the layer name on the experiment page. Currently, we offer three methods to fetch the traffic assignment for a specific unit id.

a. Retrieve Experiment by Layer Key

The first method involves obtaining the experiment assignment by the layer key and fetching the parameters within the layer using the strong type APIs. This is useful when you have multiple parameters within the layer/experiment. This API will automatically provide the correct result when there are multiple experiments under the same layer, as the unit will and can only fall into one of them. An additional advantage of this method is that it allows you to iterate through the experiments quickly by deallocating the old experiment and creating new ones under the same layer without modifying the code.

c++
auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment({{.project_id}}, {{.abc_layer_name}},
        abc_cpp_sdk::WithSetUnitID({{.unit_id}}));
string shouldShowBanner = experiment.GetString("should_show_banner");

b. Retrieve All Experiment Assignments Under the Project

In certain scenarios, users might require to retrieve all experiment assignment results within a single project. To cater to this need, we offer a batch API. This API returns a map linking the experiment layer name to the experiment assignment of the unit_id specific to that layer. However, it's important to note that using this API could potentially cause dilution problems, thus it is recommended to proceed with caution. Depending on your specific needs, you may want to consider disabling exposure logging through the 'opts' parameter, as demonstrated below. The exposure logging API can be used to manually log the exposure at a later time. Additionally, it's worth noting that you have the option to apply an extra filter to fetch results for specific layers only.

c++
auto experiments = abc_cpp_sdk::AbcService::GetInstance()->GetExperiments({{.project_id}},
      abc_cpp_sdk::WithSetUnitID({{.unit_id}}),
	    abc_cpp_sdk::WithSetDisableReport(true),
      abc_cpp_sdk::WithSetLayerKeys({{{.abc_layer_name_1}}, {{.abc_layer_name_n}}}));

c. Retrieve Experiment by Parameter Key

This method is similar to the first one, with the only difference being that instead of fetching by layer name, we retrieve the result by parameter name, which is unique within the same project. We are planning to introduce features that may allow users to move the parameter from one layer to another (e.g., launcher layer), and this API will be useful in handling such cases.

c++
auto value = abc_cpp_sdk::AbcService::GetInstance()->GetValueByVariantKey({{.project_id}}, "should_show_banner",
        abc_cpp_sdk::WithSetUnitID({{.unit_id}}));
string shouldShowBanner = value.GetString();

3. Logging Exposure Data

When retrieving the experiment assignments as described above, you have the option to disable exposure logging to avoid exposure dilution. You can manually log the exposure later at an appropriate time:

c++
auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment({{.project_id}}, {{.abc_layer_name}},
        abc_cpp_sdk::WithSetUnitID({{.unit_id}}));
int ret = experiment.LogExposure();

4. Checking feature flags

In this section, we will guide you through the process of retrieving the value of a feature flag. If you haven't already done so, please first follow our documentation to create one. Assuming we have already created a new feature flag named new_feature_flag under the project project_id, and its value type is Boolean, we can fetch the value in the following manner:

c++
auto feature_flag = abc_cpp_sdk::AbcService::GetInstance()->GetFeatureFlag({{.project_id}}, {{.new_feature_flag}},
        abc_cpp_sdk::WithSetUnitID({{.unit_id}}));
string value = feature_flag.value.GetString();

5. Example

Here is a simple example:

cpp
#include "include/abc_cpp_sdk.h"

int main() {
   std::vector<std::string> project_ids;

   //  Project ID requested on ABC
   std::string project_id = "${PROJECT_ID}";
   project_ids.push_back(project_id);

   // TODO Replace SDK_TOKEN with the SDK token for the business
   int ret = abc_cpp_sdk::AbcService::GetInstance()->Init(project_ids, abc_cpp_sdk::WithSetSecretKey("${SDK_TOKEN}"));
   std::cout << ret << std::endl;
   if (ret != 0) {
      std::cout << "init err" << std::endl;
      return 0;
   }

   // TODO Replace LAYER_KEY with the key of the layer to be queried
   std::string layer_key = "${LAYER_KEY}";
    // TODO Replace PARAM_KEY with the key of the param to be queried
    std::string param_key = "${PARAM_KEY}";
    // TODO Replace UNIT_ID with the unit_id
    std::string unit_id = "${UNIT_ID}"
   auto experiment = abc_cpp_sdk::AbcService::GetInstance()->GetExperiment(project_id, layer_key,
        abc_cpp_sdk::WithSetUnitID(unit_id));
    string shouldShowBanner = experiment.GetString(param_key);
}

NOTE

You need to install the curl library (supporting HTTPS) by yourself.

During the compilation process, if the curl library cannot be found, you need to specify the path of the curl library in CMakeLists.txt.

shell
set(CURL_LIBRARY "path/to/your/curl/lib/libcurl.a")
set(CURL_INCLUDE_DIR "/path/to/your/curl/include")

# Replace path/to/your with the actual path of your library file,  when compiling packages for different platforms, specify the corresponding library file path for each platform.

If there is a protobuf conflict error, you can adjust CMakeLists.txt to specify the dependent protobuf package.

shell
# is use protobuf-lib

# You can turn on the switch and can specify the address of the dependent Protobuf, using the same version of protobuf as the project.
set(ABC_SDK_USE_PROTOBUF_LIB ON)

You can choose to generate a static library or a dynamic library by yourself.

shell
#  dynamic library
set(LIBRARY_TYPE SHARED)

# static library
set(LIBRARY_TYPE STATIC)

If the ABC_SDK_USE_PUBSUB_SDK switch is turned on, you need to specify the path of the pubsub library by yourself.