In cross-process invocation scenarios, the client usually needs to depend on the Proxy header file generated by the server at compile time to initiate interface invocation. When there are many interfaces or the server changes frequently, the client has to re-synchronize the header file and recompile each time, resulting in high maintenance costs. At the same time, static binding requires the invocation target to be fully determined at compile time, making it difficult to adapt to scenarios where the invocation target can only be determined at runtime.
Starting from API version 26.0.0, the system provides the client-side dynamic invocation engine ModularObjectDispatcher (for the related C API definitions, see modular_object_dispatcher.h). It is created based on the connected remote Proxy object, allowing the client to query the type library metadata registered by the server at runtime (interface names, method signatures, parameter types, struct fields, etc.), and to resolve the member ID dynamically through the method name and then directly initiate IPC invocation, without depending on any interface definition from the server at compile time.
It is suitable for scenarios where the invocation target needs to be determined dynamically at runtime, such as a general-purpose script engine dynamically invoking remote methods, an automated testing framework automatically scanning and verifying all server interfaces, and a cross-version gateway service adapting to backend interfaces of different versions through metadata. It is not suitable for scenarios where the interfaces are fully determined at compile time and performance is extremely sensitive; in such cases, static Proxy/Stub should be used for direct invocation.
Dispatcher (ModularObjectDispatcher): the client-side dynamic invocation engine, created based on the remote Proxy object, providing type library metadata query and dynamic method invocation capabilities.
Type Descriptor (TypeDescriptor): the metadata access handle obtained through the dispatcher, used to query information such as interfaces, methods, enums, and structs defined by the remote service.
Type Library Metadata: the type description information generated by the server through the Taihe tool, defining interfaces, method signatures, parameter types, enum values, and struct fields. The dispatcher lazily loads this information from the remote end on first access.
Variant: a general-purpose data container capable of storing values of multiple different types, distinguishing the actual stored data type through a type tag, used for parameter passing and return value reception in method invocation.
Member ID (MemberID): the unique identifier of a method in the type library. After the client resolves the MemberID through the method name, it can use this ID to initiate dynamic invocation.
The invocation flow of ModularObjectDispatcher consists of the following stages:
Connect to ExtensionAbility to obtain the Proxy object: The client connects to ModularObjectExtensionAbility to obtain the remote Proxy object (OHIPCRemoteProxy*), which serves as the communication foundation for subsequently creating the dispatcher and initiating dynamic invocation.
Create the dispatcher: Use OH_AbilityRuntime_ModObjDispatcher_CreateMainServiceInstance to create a dispatcher bound to the main service interface from the Proxy.
Get the type descriptor: It is recommended to first call OH_AbilityRuntime_ModObjDispatcher_HasTypeDescriptor to confirm that the remote service supports metadata, and then obtain the TypeDescriptor handle through OH_AbilityRuntime_ModObjDispatcher_GetTypeDescriptor. This call triggers the type library metadata to be pulled from the remote end via IPC and cached locally, so subsequent queries directly hit the cache.
Query interface metadata: Based on the type descriptor, the client can traverse at runtime all interfaces, methods, structs (including field names and field types), enums (including enum value names and values) defined by the server, as well as the type library version and the main service interface name.
Get method parameter types and MemberID: Before invoking a method, query the type information of the return value and each input parameter through OH_AbilityRuntime_TypeDescriptor_GetMethodReturnType and OH_AbilityRuntime_TypeDescriptor_GetMethodParamType, and resolve the method name to a MemberID through OH_AbilityRuntime_TypeDescriptor_GetMethodMemberId, so as to strictly construct Variant according to the type and avoid type mismatch errors.
Construct Variant parameters and perform dynamic invocation: Pass the MemberID and the constructed Variant parameters through OH_AbilityRuntime_ModObjDispatcher_CallMethod to initiate the invocation. The dispatcher serializes the parameters and sends them to the remote end via IPC, then deserializes the return value into the output Variant.
Release resources: The dispatcher, type descriptor, various container handles, Variant, and TypeInfo objects must all be released by calling the corresponding interfaces according to the ownership rules.
Before using ModularObjectDispatcher, ensure that the client has established a connection with ModularObjectExtensionAbility. For connection-related constraints and limitations, see Using ModularObjectExtensionAbility to Implement Modular Objects (C/C++).
The server-side Stub must be generated through the Taihe tool. ModularObjectDispatcher relies on type library metadata to perform dynamic invocation. Only Stubs generated by the Taihe tool embed type library metadata; manually written Stubs cannot support dynamic invocation.
The parameter and return value types of a method are defined by the server. The client must query them through metadata and strictly construct the Variant according to the types; otherwise, a type mismatch error is returned.
When the connection is actively disconnected or the peer dies, the ModularObjectDispatcher becomes unavailable.
The dispatcher is the entry point for client dynamic invocation. After creating the dispatcher, the client can query the server-side interface metadata at runtime and initiate invocation by method name. This section introduces how to create a dispatcher instance bound to the main service interface based on a connected Proxy object. The type library metadata is lazily loaded from the remote service when first needed.
Prerequisites: The client has connected to ModularObjectExtensionAbility and obtained the Proxy object. For details, see the "Connecting to ModularObjectExtensionAbility" and "Communicating with the Server Through Proxy" sections in Using ModularObjectExtensionAbility to Implement Modular Objects (C/C++).
The following steps assume that the g_remoteProxy object of type OHIPCRemoteProxy* has been obtained through the connection callback, and that the global variables g_ModObjDispatcher and g_TypeDescriptor are used to store the dispatcher and the type descriptor.
AbilityRuntime_ErrorCode err =
OH_AbilityRuntime_ModObjDispatcher_CreateMainServiceInstance(g_remoteProxy, &g_ModObjDispatcher);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "CreateMainServiceInstance err:%{public}d", err);
return nullptr;
} If the server defines multiple non-main service interfaces, you can create a sub-instance dispatcher through OH_AbilityRuntime_ModObjDispatcher_CreateSubInstance. The sub-instance shares the type library metadata of the main service dispatcher, but uses an independent IPC proxy to send requests.
The type descriptor is the access handle for querying the server-side interface metadata. After obtaining the type descriptor, the client can traverse all interfaces, methods, parameters, structs, and enums defined by the server, preparing for subsequent dynamic invocation by method name. This section introduces how to determine whether the remote service provides type library metadata and how to obtain the type descriptor handle.
Use the OH_AbilityRuntime_ModObjDispatcher_HasTypeDescriptor interface to determine whether the remote service provides type library metadata. Only a server that supports dynamic interfaces can perform subsequent metadata query and dynamic invocation.
uint32_t hasTypeDescriptor = 0;
AbilityRuntime_ErrorCode err =
OH_AbilityRuntime_ModObjDispatcher_HasTypeDescriptor(g_ModObjDispatcher, &hasTypeDescriptor);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR || hasTypeDescriptor == 0) {
// Handle query failure.
// The server does not provide type library metadata and does not support dynamic interfaces.
OH_LOG_ERROR(LOG_APP, "The type library metadata is not available from the remote service err:%{public}d",
err);
return nullptr;
} After confirming that the remote end supports type library metadata, use OH_AbilityRuntime_ModObjDispatcher_GetTypeDescriptor to obtain the type descriptor handle.
err = OH_AbilityRuntime_ModObjDispatcher_GetTypeDescriptor(g_ModObjDispatcher, &g_TypeDescriptor);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "GetTypeDescriptor err:%{public}d", err);
return nullptr;
} The client can automatically discover all interfaces, method signatures, struct fields, and enum values provided by the server at runtime. This section introduces how to query various types of metadata based on the type descriptor.
Obtain the TypeDescriptor handle through the dispatcher to query metadata such as interfaces, methods, enums, and structs defined by the server.
Get the type library version string through OH_AbilityRuntime_TypeDescriptor_GetVersion.
char version[256];
AbilityRuntime_ErrorCode err =
OH_AbilityRuntime_TypeDescriptor_GetVersion(g_TypeDescriptor, version, sizeof(version));
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "GetVersion err:%{public}d", err);
return nullptr;
}
OH_LOG_INFO(LOG_APP, "Version:%{public}s", version); Get the main service interface name through OH_AbilityRuntime_TypeDescriptor_GetMainServiceInterfaceName.
char mainServiceInterfaceName[256];
err = OH_AbilityRuntime_TypeDescriptor_GetMainServiceInterfaceName(g_TypeDescriptor, mainServiceInterfaceName,
sizeof(mainServiceInterfaceName));
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "GetMainServiceInterfaceName err:%{public}d", err);
return nullptr;
}
OH_LOG_INFO(LOG_APP, "MainServiceInterfaceName:%{public}s", mainServiceInterfaceName); Query all interface names: use OH_AbilityRuntime_TypeDescriptor_GetInterfaceCount to get the interface quantity, then use OH_AbilityRuntime_TypeDescriptor_GetInterfaceName to get the interface names one by one.
uint32_t interfaceCount = 0;
AbilityRuntime_ErrorCode err =
OH_AbilityRuntime_TypeDescriptor_GetInterfaceCount(g_TypeDescriptor, &interfaceCount);
if (!CheckApiErr(err, "GetInterfaceCount")) {
return nullptr;
}
OH_LOG_INFO(LOG_APP, "InterfaceCount:%{public}d", interfaceCount);
for (uint32_t interfaceIndex = 0; interfaceIndex < interfaceCount; interfaceIndex++) {
char interfaceName[256];
err = OH_AbilityRuntime_TypeDescriptor_GetInterfaceName(g_TypeDescriptor, interfaceIndex, interfaceName,
sizeof(interfaceName));
if (!CheckApiErr(err, "GetInterfaceName")) {
return nullptr;
}
OH_LOG_INFO(LOG_APP, "InterfaceName:%{public}s", interfaceName);
// ...
} Traverse the method names under the interface: for each interface, use OH_AbilityRuntime_TypeDescriptor_GetMethodCount to get the method quantity, then use OH_AbilityRuntime_TypeDescriptor_GetMethodName and OH_AbilityRuntime_TypeDescriptor_GetMethodMemberId to get the method names and their MemberIDs.
static void QueryMethodMetadata(const char *interfaceName)
{
// Obtain the method quantity under the interface, and traverse the method names one by one to resolve the corresponding MemberID.
uint32_t methodCount = 0;
AbilityRuntime_ErrorCode err =
OH_AbilityRuntime_TypeDescriptor_GetMethodCount(g_TypeDescriptor, interfaceName, &methodCount);
if (!CheckApiErr(err, "GetMethodCount")) {
return;
}
OH_LOG_INFO(LOG_APP, "MethodCount:%{public}d", methodCount);
// Traverse each method to obtain the method name and MemberID.
for (uint32_t methodIndex = 0; methodIndex < methodCount; methodIndex++) {
char methodName[256];
err = OH_AbilityRuntime_TypeDescriptor_GetMethodName(g_TypeDescriptor, interfaceName, methodIndex,
methodName, sizeof(methodName));
if (!CheckApiErr(err, "GetMethodName")) {
return;
}
OH_LOG_INFO(LOG_APP, "MethodName:%{public}s", methodName);
uint32_t memId = 0;
err = OH_AbilityRuntime_TypeDescriptor_GetMethodMemberId(g_TypeDescriptor, interfaceName, methodName,
&memId);
if (!CheckApiErr(err, "GetMethodMemberId")) {
return;
}
OH_LOG_INFO(LOG_APP, "MethodMemberId:%{public}d", memId);
// ...
}
} Query all struct names: use OH_AbilityRuntime_TypeDescriptor_GetStructCount to get the struct quantity, then use OH_AbilityRuntime_TypeDescriptor_GetStructName to get the struct names one by one.
uint32_t structCount = 0;
AbilityRuntime_ErrorCode err = OH_AbilityRuntime_TypeDescriptor_GetStructCount(g_TypeDescriptor, &structCount);
if (!CheckApiErr(err, "GetStructCount")) {
return nullptr;
}
OH_LOG_INFO(LOG_APP, "StructCount:%{public}d", structCount);
for (uint32_t structIndex = 0; structIndex < structCount; structIndex++) {
char structName[256];
err = OH_AbilityRuntime_TypeDescriptor_GetStructName(g_TypeDescriptor, structIndex, structName,
sizeof(structName));
if (!CheckApiErr(err, "GetStructName")) {
return nullptr;
}
OH_LOG_INFO(LOG_APP, "StructName:%{public}s", structName);
// ...
} Traverse the field names and types of structs: for each struct, use OH_AbilityRuntime_TypeDescriptor_GetStructFieldCount to get the field quantity, then use OH_AbilityRuntime_TypeDescriptor_GetStructFieldName and OH_AbilityRuntime_TypeDescriptor_GetStructFieldType to get the field names and types one by one.
// Obtain the field quantity of the struct, and traverse the field names and field types one by one.
uint32_t fieldCount = 0;
err = OH_AbilityRuntime_TypeDescriptor_GetStructFieldCount(g_TypeDescriptor, structName, &fieldCount);
if (!CheckApiErr(err, "GetStructFieldCount")) {
return nullptr;
}
OH_LOG_INFO(LOG_APP, "FieldCount:%{public}d", fieldCount);
// Traverse each field to obtain the field name and field type.
for (uint32_t fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
char fieldName[256];
err = OH_AbilityRuntime_TypeDescriptor_GetStructFieldName(g_TypeDescriptor, structName, fieldIndex,
fieldName, sizeof(fieldName));
if (!CheckApiErr(err, "GetStructFieldName")) {
return nullptr;
}
OH_LOG_INFO(LOG_APP, "FieldName:%{public}s", fieldName);
OH_AbilityRuntime_ModObjDispatcher_TypeInfo typeInfo = {
.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
err = OH_AbilityRuntime_TypeDescriptor_GetStructFieldType(g_TypeDescriptor, structName, fieldName,
&typeInfo);
if (!CheckApiErr(err, "GetStructFieldType")) {
return nullptr;
}
// ...
OH_AbilityRuntime_ModObjDispatcher_TypeInfoClear(&typeInfo);
} Query all enum names: obtain the enum quantity through OH_AbilityRuntime_TypeDescriptor_GetEnumCount, then get the enum names one by one through OH_AbilityRuntime_TypeDescriptor_GetEnumName.
uint32_t enumCount = 0;
AbilityRuntime_ErrorCode err = OH_AbilityRuntime_TypeDescriptor_GetEnumCount(g_TypeDescriptor, &enumCount);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "GetEnumCount err:%{public}d", err);
return nullptr;
}
OH_LOG_INFO(LOG_APP, "EnumCount:%{public}d", enumCount);
for (uint32_t enumIndex = 0; enumIndex < enumCount; enumIndex++) {
char enumName[256];
err = OH_AbilityRuntime_TypeDescriptor_GetEnumName(g_TypeDescriptor, enumIndex, enumName, sizeof(enumName));
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "GetEnumName err:%{public}d", err);
return nullptr;
}
OH_LOG_INFO(LOG_APP, "EnumName:%{public}s", enumName);
// ...
} Traverse all enum value names and enum values: for each enum, obtain the enum value quantity through OH_AbilityRuntime_TypeDescriptor_GetEnumValueCount, then get the enum value names and enum values one by one through OH_AbilityRuntime_TypeDescriptor_GetEnumValueName and OH_AbilityRuntime_TypeDescriptor_GetEnumValue.
// Traverse all enum value names and corresponding enum values of the enum.
uint32_t valueCount = 0;
err = OH_AbilityRuntime_TypeDescriptor_GetEnumValueCount(g_TypeDescriptor, enumName, &valueCount);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "GetEnumValueCount err:%{public}d", err);
return nullptr;
}
OH_LOG_INFO(LOG_APP, "valueCount:%{public}d", valueCount);
for (uint32_t valueIndex = 0; valueIndex < valueCount; valueIndex++) {
char valueName[256];
err = OH_AbilityRuntime_TypeDescriptor_GetEnumValueName(g_TypeDescriptor, enumName, valueIndex, valueName,
sizeof(valueName));
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "GetEnumValueName err:%{public}d", err);
return nullptr;
}
OH_LOG_INFO(LOG_APP, "ValueName:%{public}s", valueName);
int32_t value = 0;
err = OH_AbilityRuntime_TypeDescriptor_GetEnumValue(g_TypeDescriptor, enumName, valueName, &value);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "GetEnumValue err:%{public}d", err);
return nullptr;
}
OH_LOG_INFO(LOG_APP, "value:%{public}d", value);
} Before dynamically invoking a method, you need to query the return value type of the method and the types and names of its input parameters, and then construct compliant Variant parameters accordingly to avoid invocation failures caused by type mismatches.
Use OH_AbilityRuntime_TypeDescriptor_GetMethodParamCount to get the quantity of method parameters, then use OH_AbilityRuntime_TypeDescriptor_GetMethodParamName and OH_AbilityRuntime_TypeDescriptor_GetMethodParamType to get the parameter names and types one by one.
static void QueryMethodParamInfo(const char *interfaceName, const char *methodName)
{
// Get the quantity of method parameters, and traverse the parameter names and types one by one.
uint32_t paramCount = 0;
AbilityRuntime_ErrorCode err =
OH_AbilityRuntime_TypeDescriptor_GetMethodParamCount(g_TypeDescriptor, interfaceName, methodName,
¶mCount);
if (!CheckApiErr(err, "GetMethodParamCount")) {
return;
}
OH_LOG_INFO(LOG_APP, "MethodParamCount:%{public}d", paramCount);
// Traverse each parameter to get its name and type.
for (uint32_t paramIndex = 0; paramIndex < paramCount; paramIndex++) {
char methodParamName[256];
err = OH_AbilityRuntime_TypeDescriptor_GetMethodParamName(
g_TypeDescriptor, interfaceName, methodName, paramIndex, methodParamName, sizeof(methodParamName));
if (!CheckApiErr(err, "GetMethodParamName")) {
return;
}
OH_LOG_INFO(LOG_APP, "GetMethodParamName:%{public}s", methodParamName);
OH_AbilityRuntime_ModObjDispatcher_TypeInfo paramTypeInfo = {
.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
err = OH_AbilityRuntime_TypeDescriptor_GetMethodParamType(g_TypeDescriptor, interfaceName, methodName,
paramIndex, ¶mTypeInfo);
if (!CheckApiErr(err, "GetMethodParamType")) {
return;
}
// ...
OH_AbilityRuntime_ModObjDispatcher_TypeInfoClear(¶mTypeInfo);
}
} Use OH_AbilityRuntime_TypeDescriptor_GetMethodReturnType to get the return value type information of the method. The vt field in TypeInfo identifies the data type.
static void QueryMethodReturnType(const char *interfaceName, const char *methodName)
{
OH_AbilityRuntime_ModObjDispatcher_TypeInfo returnTypeInfo = {
.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
AbilityRuntime_ErrorCode err =
OH_AbilityRuntime_TypeDescriptor_GetMethodReturnType(g_TypeDescriptor, interfaceName, methodName,
&returnTypeInfo);
if (!CheckApiErr(err, "GetMethodReturnType")) {
return;
}
// ...
OH_AbilityRuntime_ModObjDispatcher_TypeInfoClear(&returnTypeInfo);
} The vt field in TypeInfo indicates the data type. Common types are shown in the following table:
| vt Value | Type | Description |
|---|---|---|
| VT_EMPTY | - | Empty value, indicating uninitialized or invalid |
| VT_VOID | void | No return value, used for method return types |
| VT_BOOL | bool | Boolean value |
| VT_I8 | int8_t | 8-bit signed integer |
| VT_I16 | int16_t | 16-bit signed integer |
| VT_I32 | int32_t | 32-bit signed integer |
| VT_I64 | int64_t | 64-bit signed integer |
| VT_U8 | uint8_t | 8-bit unsigned integer |
| VT_U16 | uint16_t | 16-bit unsigned integer |
| VT_U32 | uint32_t | 32-bit unsigned integer |
| VT_U64 | uint64_t | 64-bit unsigned integer |
| VT_F32 | float | 32-bit floating-point number (single precision) |
| VT_F64 | double | 64-bit floating-point number (double precision) |
| VT_STRING | char* | UTF-8 string |
| VT_ARRAY | Array | Fixed-length array |
| VT_VECTOR | Vector | Dynamic vector |
| VT_SET | Set | Set of non-repeating elements |
| VT_MAP | Map | Key-value pair mapping |
| VT_STRUCT | Struct | Struct |
| VT_ENUM | enum | Enumeration value (stored as int32_t) |
| VT_IPC_REMOTE_PROXY | OHIPCRemoteProxy* | Remote Proxy object |
| VT_IPC_REMOTE_STUB | OHIPCRemoteStub* | Remote Stub object |
For the complete enumeration definition and the value range of each type, see OH_AbilityRuntime_ModObjDispatcher_ValueType.
This scenario is the core of dynamic invocation. Using the MemberID corresponding to the method and the constructed parameters, the client can initiate an IPC invocation at runtime and obtain the return value without binding to the server-side interface at compile time. It is suitable for scenarios where the interface can only be determined at runtime, such as general-purpose invocation frameworks, script engines, and dynamic testing tools.
Resolve the MemberID by the method name, construct the parameters, and then initiate the dynamic invocation. The MemberID can be queried from the TypeDescriptor through the OH_AbilityRuntime_TypeDescriptor_GetMethodMemberId interface. For details, see Query All Interfaces and Method Information Under Interfaces.
Take invoking the Add(int32_t a, int32_t b) method as an example. Encapsulate the parameters into a Variant array, and initiate the invocation through OH_AbilityRuntime_ModObjDispatcher_CallMethod.
// Construct the parameters.
OH_AbilityRuntime_ModObjDispatcher_Variant params[2] = {
{.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32, .u.i32Val = 10},
{.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32, .u.i32Val = 20},
};
OH_AbilityRuntime_ModObjDispatcher_InputParams inputParams = {.rgvarg = params, .cArgs = 2};
// Invoke the method. memberId 17 is the MemberID corresponding to the Add method, which can be obtained through OH_AbilityRuntime_TypeDescriptor_GetMethodMemberId.
OH_AbilityRuntime_ModObjDispatcher_Variant result = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
int32_t methodErrCode = 0;
uint32_t memID = 17;
AbilityRuntime_ErrorCode err = OH_AbilityRuntime_ModObjDispatcher_CallMethod(g_ModObjDispatcher, memID,
&inputParams, &result, &methodErrCode);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
// Framework-level error: IPC communication failure, parameter type mismatch, etc.
OH_LOG_ERROR(LOG_APP, "add err:%{public}d", err);
return nullptr;
}
if (methodErrCode != 0) {
// Method-level error: the business error code returned by the remote method execution.
OH_LOG_ERROR(LOG_APP, "methodErrCode:%{public}d", methodErrCode);
return nullptr;
}
// result.u.i32Val is the return value (30).
int32_t addResult = result.u.i32Val;
OH_LOG_INFO(LOG_APP, "addResult :%{public}d", addResult);
// Simple types do not need to be cleaned up. Description
CallMethod uses a two-layer error handling mechanism:
When the server-side method parameters are complex types such as arrays, vectors, sets, maps, or structs, you need to construct the corresponding composite Variant for passing, so as to support invoking interfaces with arbitrary signatures. This applies to scenarios where batch data, key-value pairs, or composite structs need to be passed.
When method parameters contain container types (arrays, vectors, sets, maps) or structs, you need to first create the corresponding container instance and fill in the data, then wrap the container handle as a Variant parameter.
Create an array through OH_AbilityRuntime_ModObjDispatcher_ArrayCreate, and fill elements using OH_AbilityRuntime_ModObjDispatcher_ArraySet. The following example uses a two-dimensional array array<array<i32,5>,5> and is divided into three steps: creating the array, invoking the method, and parsing the result.
Create and fill the two-dimensional array: first construct the nested TypeInfo (inner array<i32,5>, outer array<inner,5>), then use ArrayCreate to create the outer array, create the inner arrays row by row, fill their elements, and set them into the outer array.
static OH_AbilityRuntime_ModObjDispatcher_ArrayHandle BuildMatrixArray(uint32_t arrSize)
{
// Construct the nested array type information: inner array<i32,size>, outer array<inner,size>
OH_AbilityRuntime_ModObjDispatcher_TypeInfo elemType = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
OH_AbilityRuntime_ModObjDispatcher_TypeInfo innerType = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_ARRAY};
innerType.u.arrayType.pElementType = &elemType;
innerType.u.arrayType.size = arrSize;
// Create the outer array, create the inner arrays row by row, fill their elements, and set them into the outer array
OH_AbilityRuntime_ModObjDispatcher_ArrayHandle matrixArray = NULL;
AbilityRuntime_ErrorCode err = OH_AbilityRuntime_ModObjDispatcher_ArrayCreate(&innerType, arrSize, &matrixArray);
if (!CheckApiErr(err, "ArrayCreate outer")) {
return NULL;
}
for (uint32_t i = 0; i < arrSize; i++) {
OH_AbilityRuntime_ModObjDispatcher_ArrayHandle rowArray = NULL;
err = OH_AbilityRuntime_ModObjDispatcher_ArrayCreate(&elemType, arrSize, &rowArray);
if (!CheckApiErr(err, "ArrayCreate inner")) {
OH_AbilityRuntime_ModObjDispatcher_ArrayRelease(&matrixArray);
return NULL;
}
for (uint32_t j = 0; j < arrSize; j++) {
// Fill the inner array elements
OH_AbilityRuntime_ModObjDispatcher_Variant cell = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
uint32_t multiply = 10;
cell.u.i32Val = static_cast<int32_t>(i * multiply + j);
OH_AbilityRuntime_ModObjDispatcher_ArraySet(rowArray, j, &cell);
}
// Set the inner array into the corresponding position of the outer array
OH_AbilityRuntime_ModObjDispatcher_Variant rowVar = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_ARRAY};
rowVar.u.parrayVal = rowArray;
OH_AbilityRuntime_ModObjDispatcher_ArraySet(matrixArray, i, &rowVar);
OH_AbilityRuntime_ModObjDispatcher_ArrayRelease(&rowArray);
}
return matrixArray;
} Assemble parameters and invoke the method: wrap the array handle as a Variant parameter, and initiate the invocation through CallMethod.
uint32_t arrSize = 5;
OH_AbilityRuntime_ModObjDispatcher_ArrayHandle matrixArray = BuildMatrixArray(arrSize);
if (matrixArray == NULL) {
return nullptr;
}
OH_AbilityRuntime_ModObjDispatcher_Variant param = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_ARRAY};
param.u.parrayVal = matrixArray;
OH_AbilityRuntime_ModObjDispatcher_InputParams inputParams = {.rgvarg = ¶m, .cArgs = 1};
OH_AbilityRuntime_ModObjDispatcher_Variant result = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
int32_t methodErrCode = 0;
uint32_t memID = 19;
AbilityRuntime_ErrorCode err = OH_AbilityRuntime_ModObjDispatcher_CallMethod(g_ModObjDispatcher, memID,
&inputParams, &result, &methodErrCode);
OH_AbilityRuntime_ModObjDispatcher_ArrayRelease(&matrixArray);
if (!CheckCallResult(err, methodErrCode, "TestType_Array")) {
return nullptr;
}
ParseArrayResult(&result);
OH_AbilityRuntime_ModObjDispatcher_VariantClear(&result); Parse the returned two-dimensional array: obtain the number of rows and columns through ArrayGetSize, and read elements layer by layer using ArrayGet.
static void ParseArrayResult(OH_AbilityRuntime_ModObjDispatcher_Variant *result)
{
// Validate the return type, and traverse the two-dimensional array in a nested manner to read each element
if (result->vt != OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_ARRAY || result->u.parrayVal == NULL) {
OH_LOG_ERROR(LOG_APP, "TestType_Array result type unexpected");
return;
}
// Traverse row by row, and read the element value column by column in each row.
uint32_t rowCount = 0;
OH_AbilityRuntime_ModObjDispatcher_ArrayGetSize(result->u.parrayVal, &rowCount);
for (uint32_t i = 0; i < rowCount; i++) {
OH_AbilityRuntime_ModObjDispatcher_Variant rowVar = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
OH_AbilityRuntime_ModObjDispatcher_ArrayGet(result->u.parrayVal, i, &rowVar);
uint32_t colCount = 0;
if (rowVar.u.parrayVal != NULL) {
OH_AbilityRuntime_ModObjDispatcher_ArrayGetSize(rowVar.u.parrayVal, &colCount);
for (uint32_t j = 0; j < colCount; j++) {
OH_AbilityRuntime_ModObjDispatcher_Variant cell = {
.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
OH_AbilityRuntime_ModObjDispatcher_ArrayGet(rowVar.u.parrayVal, j, &cell);
OH_LOG_INFO(LOG_APP, "array[%{public}u][%{public}u]=%{public}d", i, j, cell.u.i32Val);
}
}
OH_AbilityRuntime_ModObjDispatcher_VariantClear(&rowVar);
}
} Create a vector through OH_AbilityRuntime_ModObjDispatcher_VectorCreate, and add elements using OH_AbilityRuntime_ModObjDispatcher_VectorAdd. A vector is a dynamically sized container.
// Create a vector<i32> and add elements.
OH_AbilityRuntime_ModObjDispatcher_TypeInfo elemType = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
OH_AbilityRuntime_ModObjDispatcher_VectorHandle vec = NULL;
AbilityRuntime_ErrorCode err = OH_AbilityRuntime_ModObjDispatcher_VectorCreate(&elemType, &vec);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "VectorCreate err:%{public}d", err);
return nullptr;
}
// Add elements to the vector.
int32_t arrSize = 5;
for (int32_t i = 1; i <= arrSize; i++) {
OH_AbilityRuntime_ModObjDispatcher_Variant v = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
v.u.i32Val = i;
OH_AbilityRuntime_ModObjDispatcher_VectorAdd(vec, &v);
}
// Assemble parameters and invoke the method.
OH_AbilityRuntime_ModObjDispatcher_Variant param = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_VECTOR};
param.u.pvectorVal = vec;
OH_AbilityRuntime_ModObjDispatcher_InputParams inputParams = {.rgvarg = ¶m, .cArgs = 1};
OH_AbilityRuntime_ModObjDispatcher_Variant result = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
int32_t methodErrCode = 0;
uint32_t memID = 21;
err = OH_AbilityRuntime_ModObjDispatcher_CallMethod(g_ModObjDispatcher, memID, &inputParams, &result,
&methodErrCode);
OH_AbilityRuntime_ModObjDispatcher_VectorRelease(&vec);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "TestType_Vector err:%{public}d", err);
return nullptr;
}
if (methodErrCode != 0) {
OH_LOG_ERROR(LOG_APP, "TestType_Vector methodErrCode:%{public}d", methodErrCode);
return nullptr;
}
// Parse the returned vector.
if (result.vt != OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_VECTOR || result.u.pvectorVal == NULL) {
OH_LOG_ERROR(LOG_APP, "TestType_Vector result type unexpected");
return nullptr;
}
uint32_t size = 0;
OH_AbilityRuntime_ModObjDispatcher_VectorGetSize(result.u.pvectorVal, &size);
for (uint32_t i = 0; i < size; i++) {
OH_AbilityRuntime_ModObjDispatcher_Variant v = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
OH_AbilityRuntime_ModObjDispatcher_VectorGet(result.u.pvectorVal, i, &v);
OH_LOG_INFO(LOG_APP, "vector[%{public}u]=%{public}d", i, v.u.i32Val);
}
OH_AbilityRuntime_ModObjDispatcher_VariantClear(&result); Create a collection through OH_AbilityRuntime_ModObjDispatcher_SetCreate, and add elements using OH_AbilityRuntime_ModObjDispatcher_SetAdd. Elements in a collection are not duplicated.
// Create a set<i32> and add elements.
OH_AbilityRuntime_ModObjDispatcher_TypeInfo elemType = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
OH_AbilityRuntime_ModObjDispatcher_SetHandle set = NULL;
AbilityRuntime_ErrorCode err = OH_AbilityRuntime_ModObjDispatcher_SetCreate(&elemType, &set);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "SetCreate err:%{public}d", err);
return nullptr;
}
// Add elements to the collection.
int32_t values[] = {11, 22, 33};
for (int32_t v : values) {
OH_AbilityRuntime_ModObjDispatcher_Variant item = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
item.u.i32Val = v;
OH_AbilityRuntime_ModObjDispatcher_SetAdd(set, &item);
}
// Assemble parameters and invoke the method.
OH_AbilityRuntime_ModObjDispatcher_Variant param = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_SET};
param.u.psetVal = set;
OH_AbilityRuntime_ModObjDispatcher_InputParams inputParams = {.rgvarg = ¶m, .cArgs = 1};
OH_AbilityRuntime_ModObjDispatcher_Variant result = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
int32_t methodErrCode = 0;
uint32_t memID = 23;
err = OH_AbilityRuntime_ModObjDispatcher_CallMethod(g_ModObjDispatcher, memID, &inputParams, &result,
&methodErrCode);
OH_AbilityRuntime_ModObjDispatcher_SetRelease(&set);
if (err != ABILITY_RUNTIME_ERROR_CODE_NO_ERROR) {
OH_LOG_ERROR(LOG_APP, "TestType_Set err:%{public}d", err);
return nullptr;
}
if (methodErrCode != 0) {
OH_LOG_ERROR(LOG_APP, "TestType_Set methodErrCode:%{public}d", methodErrCode);
return nullptr;
}
// Parse the returned set.
if (result.vt != OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_SET || result.u.psetVal == NULL) {
OH_LOG_ERROR(LOG_APP, "TestType_Set result type unexpected");
return nullptr;
}
uint32_t size = 0;
OH_AbilityRuntime_ModObjDispatcher_SetGetSize(result.u.psetVal, &size);
for (uint32_t i = 0; i < size; i++) {
OH_AbilityRuntime_ModObjDispatcher_Variant v = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
OH_AbilityRuntime_ModObjDispatcher_SetGetAt(result.u.psetVal, i, &v);
OH_LOG_INFO(LOG_APP, "set[%{public}u]=%{public}d", i, v.u.i32Val);
}
OH_AbilityRuntime_ModObjDispatcher_VariantClear(&result); Create a map through OH_AbilityRuntime_ModObjDispatcher_MapCreate, and add key-value pairs using OH_AbilityRuntime_ModObjDispatcher_MapPut.
Create a map and fill key-value pairs: after specifying the key and value types through MapCreate, construct Variants for the key and value and call MapPut to add them one by one.
static void FillMapEntries(OH_AbilityRuntime_ModObjDispatcher_MapHandle map)
{
const char *keys[] = {"cn", "us", "jp"};
int32_t values[] = {86, 1, 81};
const int32_t length = sizeof(values) / sizeof(values[0]);
for (int32_t i = 0; i < length; i++) {
OH_AbilityRuntime_ModObjDispatcher_Variant key = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_STRING};
key.u.bstrVal = strdup(keys[i]);
OH_AbilityRuntime_ModObjDispatcher_Variant val = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
val.u.i32Val = values[i];
OH_AbilityRuntime_ModObjDispatcher_MapPut(map, &key, &val);
free(key.u.bstrVal);
}
} Assemble parameters and invoke the method: wrap the map handle as a Variant parameter, and initiate the invocation through CallMethod.
// Create a map<String,i32>, fill key-value pairs, and then invoke the method.
OH_AbilityRuntime_ModObjDispatcher_TypeInfo valType = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
OH_AbilityRuntime_ModObjDispatcher_MapHandle map = NULL;
AbilityRuntime_ErrorCode err =
OH_AbilityRuntime_ModObjDispatcher_MapCreate(OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_STRING, &valType, &map);
if (!CheckApiErr(err, "MapCreate")) {
return nullptr;
}
FillMapEntries(map);
// Assemble parameters and invoke the method.
OH_AbilityRuntime_ModObjDispatcher_Variant param = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_MAP};
param.u.pmapVal = map;
OH_AbilityRuntime_ModObjDispatcher_InputParams inputParams = {.rgvarg = ¶m, .cArgs = 1};
OH_AbilityRuntime_ModObjDispatcher_Variant result = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
int32_t methodErrCode = 0;
uint32_t memID = 25;
err = OH_AbilityRuntime_ModObjDispatcher_CallMethod(g_ModObjDispatcher, memID, &inputParams, &result,
&methodErrCode);
OH_AbilityRuntime_ModObjDispatcher_MapRelease(&map);
if (!CheckCallResult(err, methodErrCode, "TestType_Map")) {
return nullptr;
}
ParseMapResult(&result);
OH_AbilityRuntime_ModObjDispatcher_VariantClear(&result); Parse the returned map: get the element quantity through MapGetSize, and read keys and values one by one using MapGetKeyAt and MapGetValueAt.
static void ParseMapResult(OH_AbilityRuntime_ModObjDispatcher_Variant *result)
{
if (result->vt != OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_MAP || result->u.pmapVal == NULL) {
OH_LOG_ERROR(LOG_APP, "TestType_Map result type unexpected");
return;
}
uint32_t size = 0;
OH_AbilityRuntime_ModObjDispatcher_MapGetSize(result->u.pmapVal, &size);
OH_LOG_INFO(LOG_APP, "result map size:%{public}u", size);
for (uint32_t i = 0; i < size; i++) {
OH_AbilityRuntime_ModObjDispatcher_Variant k = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
OH_AbilityRuntime_ModObjDispatcher_Variant v = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
OH_AbilityRuntime_ModObjDispatcher_MapGetKeyAt(result->u.pmapVal, i, &k);
OH_AbilityRuntime_ModObjDispatcher_MapGetValueAt(result->u.pmapVal, i, &v);
OH_LOG_INFO(LOG_APP, "map[%{public}s]=%{public}d", (k.u.bstrVal != NULL) ? k.u.bstrVal : "(null)", v.u.i32Val);
OH_AbilityRuntime_ModObjDispatcher_VariantClear(&k);
}
} Create an instance by struct name through OH_AbilityRuntime_ModObjDispatcher_StructCreate, and fill field values using OH_AbilityRuntime_ModObjDispatcher_StructSetField.
Create a struct and set fields: create an instance by name through StructCreate, then set field values one by one with StructSetField.
static OH_AbilityRuntime_ModObjDispatcher_StructHandle CreatePointStruct(int32_t x, int32_t y)
{
OH_AbilityRuntime_ModObjDispatcher_StructHandle handle = NULL;
AbilityRuntime_ErrorCode err = OH_AbilityRuntime_ModObjDispatcher_StructCreate("Point", &handle);
if (!CheckApiErr(err, "StructCreate")) {
return NULL;
}
OH_AbilityRuntime_ModObjDispatcher_Variant vx = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
vx.u.i32Val = x;
OH_AbilityRuntime_ModObjDispatcher_Variant vy = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32};
vy.u.i32Val = y;
OH_AbilityRuntime_ModObjDispatcher_StructSetField(handle, "x", &vx);
OH_AbilityRuntime_ModObjDispatcher_StructSetField(handle, "y", &vy);
return handle;
} Assemble parameters and invoke the method: wrap the struct handle as a Variant parameter, and initiate the invocation through CallMethod.
// Create a Point struct as the input parameter, assemble parameters, and then invoke the method.
OH_AbilityRuntime_ModObjDispatcher_StructHandle structA = CreatePointStruct(10, 20);
if (structA == NULL) {
return nullptr;
}
int32_t idx = 5;
int32_t idy = 8;
// Assemble input parameters: struct a, idx, idy.
OH_AbilityRuntime_ModObjDispatcher_Variant params[3] = {
{.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_STRUCT, .u.pstructVal = structA},
{.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32, .u.i32Val = idx},
{.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_I32, .u.i32Val = idy},
};
OH_AbilityRuntime_ModObjDispatcher_InputParams inputParams = {.rgvarg = params, .cArgs = 3};
OH_AbilityRuntime_ModObjDispatcher_Variant result = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
int32_t methodErrCode = 0;
uint32_t memID = 29;
AbilityRuntime_ErrorCode err = OH_AbilityRuntime_ModObjDispatcher_CallMethod(g_ModObjDispatcher, memID,
&inputParams, &result, &methodErrCode);
OH_AbilityRuntime_ModObjDispatcher_StructRelease(&structA);
if (!CheckCallResult(err, methodErrCode, "TestType_Struct")) {
return nullptr;
}
ParseStructResult(&result);
OH_AbilityRuntime_ModObjDispatcher_VariantClear(&result); Parse the returned struct: read field values from the returned struct by field name through StructGetField.
static void ParseStructResult(OH_AbilityRuntime_ModObjDispatcher_Variant *result)
{
if (result->vt != OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_STRUCT || result->u.pstructVal == NULL) {
OH_LOG_ERROR(LOG_APP, "TestType_Struct result type unexpected, vt:%{public}d", result->vt);
return;
}
OH_AbilityRuntime_ModObjDispatcher_Variant outX = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
OH_AbilityRuntime_ModObjDispatcher_Variant outY = {.vt = OH_ABILITY_RUNTIME_MOD_OBJ_DISPATCHER_VT_EMPTY};
OH_AbilityRuntime_ModObjDispatcher_StructGetField(result->u.pstructVal, "x", &outX);
OH_AbilityRuntime_ModObjDispatcher_StructGetField(result->u.pstructVal, "y", &outY);
OH_LOG_INFO(LOG_APP, "Point b: x=%{public}d, y=%{public}d", outX.u.i32Val, outY.u.i32Val);
OH_AbilityRuntime_ModObjDispatcher_VariantClear(&outX);
OH_AbilityRuntime_ModObjDispatcher_VariantClear(&outY);
} All handles obtained through Create interfaces and Variant/TypeInfo returned through Get interfaces must be released when no longer in use to avoid memory leaks.
// Release the TypeDescriptor.
if (g_TypeDescriptor != NULL) {
OH_AbilityRuntime_TypeDescriptor_Release(&g_TypeDescriptor);
}
// Release the dispatcher.
if (g_ModObjDispatcher != NULL) {
OH_AbilityRuntime_ModObjDispatcher_Release(&g_ModObjDispatcher);
} Resource release must follow these rules:
Use OH_AbilityRuntime_ModObjDispatcher_TypeInfoClear to clean up the heap resources held by TypeInfo. After cleanup, the internal pointer is set to NULL and vt is reset to VT_EMPTY.
Use OH_AbilityRuntime_ModObjDispatcher_VariantClear to clean up the heap resources held by Variant (strings, container handles, etc.). After cleanup, the variant is reset to VT_EMPTY. Simple types (boolean, integer, floating-point) do not hold heap resources and do not need cleanup.
All container Release interfaces set the handle pointer to NULL and support passing a NULL pointer.
When a Variant is passed into container functions (such as ArraySet and MapPut), a deep copy is performed. The caller retains ownership of the original Variant and must release it separately.
Variants returned from container functions (such as ArrayGet, MapGet, and CallMethod return values) are deep copies. The caller obtains ownership and must call VariantClear to release them.
Do not call Clear on a shallow copy of Variant or TypeInfo; clear only one of them.