diff --git a/include/onnxruntime/core/session/onnxruntime_c_api.h b/include/onnxruntime/core/session/onnxruntime_c_api.h index 176b988ad0..76b325a471 100644 --- a/include/onnxruntime/core/session/onnxruntime_c_api.h +++ b/include/onnxruntime/core/session/onnxruntime_c_api.h @@ -6,8 +6,8 @@ #include #include -// This value is used in structures passed to ORT so that a newer version of ORT will still work with -#define ORT_API_VERSION 1 +// This value is used in structures passed to ORT so that a newer version of ORT will still work with them +#define ORT_API_VERSION 2 #ifdef __cplusplus extern "C" { @@ -214,6 +214,8 @@ typedef struct OrtApi OrtApi; struct OrtApiBase { const OrtApi*(ORT_API_CALL* GetApi)(uint32_t version)NO_EXCEPTION; // Pass in ORT_API_VERSION + // nullptr will be returned if the version is unsupported, for example when using a runtime older than this header file + const char*(ORT_API_CALL* GetVersionString)() NO_EXCEPTION; }; typedef struct OrtApiBase OrtApiBase; diff --git a/onnxruntime/core/session/custom_ops.cc b/onnxruntime/core/session/custom_ops.cc index 0276b167bc..919a398bbc 100644 --- a/onnxruntime/core/session/custom_ops.cc +++ b/onnxruntime/core/session/custom_ops.cc @@ -72,7 +72,7 @@ namespace onnxruntime { struct CustomOpKernel : OpKernel { CustomOpKernel(const OpKernelInfo& info, OrtCustomOp& op) : OpKernel(info), op_(op) { - if (op_.version != 1) + if (op_.version > ORT_API_VERSION) throw std::invalid_argument("Unsupported version '" + std::to_string(op_.version) + "' in custom op '" + op.GetName(&op)); op_kernel_ = op_.CreateKernel(&op_, OrtGetApiBase()->GetApi(op_.version), reinterpret_cast(const_cast(&info))); } diff --git a/onnxruntime/core/session/onnxruntime_c_api.cc b/onnxruntime/core/session/onnxruntime_c_api.cc index b9d4714e00..cf44028e71 100644 --- a/onnxruntime/core/session/onnxruntime_c_api.cc +++ b/onnxruntime/core/session/onnxruntime_c_api.cc @@ -1334,7 +1334,49 @@ static constexpr OrtApiBase ort_api_base = { &OrtApis::GetVersionString, }; -static constexpr OrtApi ort_api_1 = { +/* Rules on how to add a new Ort API version + +In general, NEVER remove or rearrange the members in this structure unless a new version is being created. The +goal is for newer shared libraries of the Onnx Runtime to work with binaries targeting the previous versions. +In order to do that we need to ensure older binaries get the older interfaces they are expecting. + +If the next version of the OrtApi only adds members, new members can be added at the end of the OrtApi structure +without breaking anything. In this case, rename the ort_api_# structure in a way that shows the range of versions +it supports, for example 'ort_api_1_to_2', and then GetApi can return the same structure for a range of versions. + +If methods need to be removed or rearranged, then make a copy of the OrtApi structure and name it 'OrtApi#to#'. +The latest Api should always be named just OrtApi. Then make a copy of the latest ort_api_* structure below and +name it ort_api_# to match the latest version number supported, you'll need to be sure the structure types match +the API they're for (the compiler should complain if this isn't correct). + +If there is no desire to have the headers still expose the older APIs (clutter, documentation, etc) then the +definition should be moved to a file included by this file so that it's still defined here for binary compatibility +but isn't visible in public headers. + +So for example, if we wanted to just add some new members to the ort_api_1_to_2, we'd take the following steps: + + In include\onnxruntime\core\session\onnxruntime_c_api.h we'd just add the members to the end of the structure + + In this file, we'd correspondingly add the member values to the end of the ort_api_1_to_2 structure, and also rename + it to ort_api_1_to_3. + + Then in GetApi we'd make it return ort_api_1_to_3 for versions 1 through 3. + +Second example, if we wanted to add and remove some members, we'd do this: + + In include\onnxruntime\core\session\onnxruntime_c_api.h we'd make a copy of the OrtApi structure and name the + old one OrtApi1to2. In the new OrtApi we'd add or remove any members that we desire. + + In this file, we'd create a new copy of ort_api_1_to_2 called ort_api_3 and make the corresponding changes that were + made to the new OrtApi. + + In GetApi we now make it return ort_api_3 for version 3. +*/ + +static constexpr OrtApi ort_api_1_to_2 = { + // NOTE: The ordering of these fields MUST not change after that version has shipped since existing binaries depend on this ordering. + + // Shipped as version 1 - DO NOT MODIFY (see above text for more information) &OrtApis::CreateStatus, &OrtApis::GetErrorCode, &OrtApis::GetErrorMessage, @@ -1449,13 +1491,20 @@ static constexpr OrtApi ort_api_1 = { &OrtApis::ReleaseTensorTypeAndShapeInfo, &OrtApis::ReleaseSessionOptions, &OrtApis::ReleaseCustomOpDomain, + // End of Version 1 - DO NOT MODIFY ABOVE (see above text for more information) + + // Version 2 - In development, feel free to add/remove/rearrange here }; -ORT_API(const OrtApi*, OrtApis::GetApi, uint32_t version) { - if (version > 1) - return nullptr; +// Assert to do a limited check to ensure Version 1 of OrtApi never changes (will detect an addition or deletion but not if they cancel out each other) +// If this assert hits, read the above 'Rules on how to add a new Ort API version' +static_assert(offsetof(OrtApi, ReleaseCustomOpDomain) / sizeof(void*) == 101, "Size of version 1 API cannot change"); - return &ort_api_1; +ORT_API(const OrtApi*, OrtApis::GetApi, uint32_t version) { + if (version >= 1 && version <= 2) + return &ort_api_1_to_2; + + return nullptr; // Unsupported version } ORT_API(const char*, OrtApis::GetVersionString) {