changeset 1995:f0acfa753973

New callback to handle non-worklists C-Find requests: OrthancPluginRegisterCFindCallback()
author Sebastien Jodogne <s.jodogne@gmail.com>
date Tue, 24 May 2016 17:45:56 +0200
parents 4d099fee5eca
children 66957f5c69ab
files NEWS OrthancServer/DicomProtocol/DicomFindAnswers.cpp OrthancServer/DicomProtocol/DicomFindAnswers.h OrthancServer/main.cpp Plugins/Engine/OrthancPlugins.cpp Plugins/Engine/OrthancPlugins.h Plugins/Include/orthanc/OrthancCPlugin.h
diffstat 7 files changed, 533 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/NEWS	Tue May 17 15:45:49 2016 +0200
+++ b/NEWS	Tue May 24 17:45:56 2016 +0200
@@ -22,6 +22,7 @@
 -------
 
 * New callback to filter incoming HTTP requests: OrthancPluginRegisterIncomingHttpRequestFilter()
+* New callback to handle non-worklists C-Find requests: OrthancPluginRegisterCFindCallback()
 * New function: "OrthancPluginHttpClient()" to do HTTP requests with full control
 * New function: "OrthancPluginGenerateUuid()" to generate a UUID
 
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Tue May 17 15:45:49 2016 +0200
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.cpp	Tue May 24 17:45:56 2016 +0200
@@ -67,7 +67,7 @@
       CleanupDicom();
     }
 
-    Answer(const char* dicom,
+    Answer(const void* dicom,
            size_t size) : 
       dicom_(new ParsedDicomFile(dicom, size)),
       map_(NULL)
@@ -153,7 +153,7 @@
   }
 
 
-  void DicomFindAnswers::Add(const char* dicom,
+  void DicomFindAnswers::Add(const void* dicom,
                              size_t size)
   {
     answers_.push_back(new Answer(dicom, size));
--- a/OrthancServer/DicomProtocol/DicomFindAnswers.h	Tue May 17 15:45:49 2016 +0200
+++ b/OrthancServer/DicomProtocol/DicomFindAnswers.h	Tue May 24 17:45:56 2016 +0200
@@ -64,7 +64,7 @@
 
     void Add(ParsedDicomFile& dicom);
 
-    void Add(const char* dicom,
+    void Add(const void* dicom,
              size_t size);
 
     size_t GetSize() const
--- a/OrthancServer/main.cpp	Tue May 17 15:45:49 2016 +0200
+++ b/OrthancServer/main.cpp	Tue May 24 17:45:56 2016 +0200
@@ -756,10 +756,17 @@
   dicomServer.SetFindRequestHandlerFactory(serverFactory);
 
 #if ORTHANC_PLUGINS_ENABLED == 1
-  if (plugins &&
-      plugins->HasWorklistHandler())
+  if (plugins != NULL)
   {
-    dicomServer.SetWorklistRequestHandlerFactory(*plugins);
+    if (plugins->HasWorklistHandler())
+    {
+      dicomServer.SetWorklistRequestHandlerFactory(*plugins);
+    }
+
+    if (plugins->HasFindHandler())
+    {
+      dicomServer.SetFindRequestHandlerFactory(*plugins);
+    }
   }
 #endif
 
--- a/Plugins/Engine/OrthancPlugins.cpp	Tue May 17 15:45:49 2016 +0200
+++ b/Plugins/Engine/OrthancPlugins.cpp	Tue May 24 17:45:56 2016 +0200
@@ -39,6 +39,7 @@
 
 
 #include "../../Core/ChunkedBuffer.h"
+#include "../../Core/DicomFormat/DicomArray.h"
 #include "../../Core/HttpServer/HttpToolbox.h"
 #include "../../Core/Logging.h"
 #include "../../Core/OrthancException.h"
@@ -107,6 +108,27 @@
   }
 
 
+  static char* CopyString(const std::string& str)
+  {
+    char *result = reinterpret_cast<char*>(malloc(str.size() + 1));
+    if (result == NULL)
+    {
+      throw OrthancException(ErrorCode_NotEnoughMemory);
+    }
+
+    if (str.size() == 0)
+    {
+      result[0] = '\0';
+    }
+    else
+    {
+      memcpy(result, &str[0], str.size() + 1);
+    }
+
+    return result;
+  }
+
+
   namespace
   {
     class PluginStorageArea : public IStorageArea
@@ -291,6 +313,7 @@
     RestCallbacks restCallbacks_;
     OnStoredCallbacks  onStoredCallbacks_;
     OnChangeCallbacks  onChangeCallbacks_;
+    OrthancPluginFindCallback  findCallback_;
     OrthancPluginWorklistCallback  worklistCallback_;
     OrthancPluginDecodeImageCallback  decodeImageCallback_;
     IncomingHttpRequestFilters  incomingHttpRequestFilters_;
@@ -298,6 +321,7 @@
     boost::recursive_mutex restCallbackMutex_;
     boost::recursive_mutex storedCallbackMutex_;
     boost::recursive_mutex changeCallbackMutex_;
+    boost::mutex findCallbackMutex_;
     boost::mutex worklistCallbackMutex_;
     boost::mutex decodeImageCallbackMutex_;
     boost::recursive_mutex invokeServiceMutex_;
@@ -309,6 +333,7 @@
 
     PImpl() : 
       context_(NULL), 
+      findCallback_(NULL),
       worklistCallback_(NULL),
       decodeImageCallback_(NULL),
       argc_(1),
@@ -345,12 +370,13 @@
                         const std::string& calledAet)
     {
       bool caseSensitivePN = Configuration::GetGlobalBoolParameter("CaseSensitivePN", false);
-      matcher_.reset(new HierarchicalMatcher(query, caseSensitivePN));
-      currentQuery_ = &query;
 
       {
         boost::mutex::scoped_lock lock(that_.pimpl_->worklistCallbackMutex_);
 
+        matcher_.reset(new HierarchicalMatcher(query, caseSensitivePN));
+        currentQuery_ = &query;
+
         if (that_.pimpl_->worklistCallback_)
         {
           OrthancPluginErrorCode error = that_.pimpl_->worklistCallback_
@@ -366,14 +392,18 @@
             throw OrthancException(static_cast<ErrorCode>(error));
           }
         }
+
+        Reset();
       }
-
-      Reset();
     }
 
     void GetDicomQuery(OrthancPluginMemoryBuffer& target) const
     {
-      assert(currentQuery_ != NULL);
+      if (currentQuery_ == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
       std::string dicom;
       currentQuery_->SaveToMemoryBuffer(dicom);
       CopyToMemoryBuffer(target, dicom.c_str(), dicom.size());
@@ -382,7 +412,11 @@
     bool IsMatch(const void* dicom,
                  size_t size) const
     {
-      assert(matcher_.get() != NULL);
+      if (matcher_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
       ParsedDicomFile f(dicom, size);
       return matcher_->Match(f);
     }
@@ -391,7 +425,11 @@
                    const void* dicom,
                    size_t size) const
     {
-      assert(matcher_.get() != NULL);
+      if (matcher_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
       ParsedDicomFile f(dicom, size);
       std::auto_ptr<ParsedDicomFile> summary(matcher_->Extract(f));
       reinterpret_cast<DicomFindAnswers*>(answers)->Add(*summary);
@@ -399,25 +437,107 @@
   };
 
   
-  static char* CopyString(const std::string& str)
+  class OrthancPlugins::FindHandler : public IFindRequestHandler
   {
-    char *result = reinterpret_cast<char*>(malloc(str.size() + 1));
-    if (result == NULL)
+  private:
+    OrthancPlugins&            that_;
+    std::auto_ptr<DicomArray>  currentQuery_;
+
+    void Reset()
     {
-      throw OrthancException(ErrorCode_NotEnoughMemory);
+      currentQuery_.reset(NULL);
+    }
+
+  public:
+    FindHandler(OrthancPlugins& that) : that_(that)
+    {
+      Reset();
     }
 
-    if (str.size() == 0)
+    virtual void Handle(DicomFindAnswers& answers,
+                        const DicomMap& input,
+                        const std::list<DicomTag>& sequencesToReturn,
+                        const std::string& remoteIp,
+                        const std::string& remoteAet,
+                        const std::string& calledAet)
     {
-      result[0] = '\0';
+      DicomMap tmp;
+      tmp.Assign(input);
+
+      for (std::list<DicomTag>::const_iterator it = sequencesToReturn.begin(); 
+           it != sequencesToReturn.end(); ++it)
+      {
+        if (!input.HasTag(*it))
+        {
+          tmp.SetValue(*it, "");
+        }
+      }      
+
+      {
+        boost::mutex::scoped_lock lock(that_.pimpl_->findCallbackMutex_);
+        currentQuery_.reset(new DicomArray(tmp));
+
+        if (that_.pimpl_->findCallback_)
+        {
+          OrthancPluginErrorCode error = that_.pimpl_->findCallback_
+            (reinterpret_cast<OrthancPluginFindAnswers*>(&answers),
+             reinterpret_cast<const OrthancPluginFindQuery*>(this),
+             remoteAet.c_str(),
+             calledAet.c_str());
+
+          if (error != OrthancPluginErrorCode_Success)
+          {
+            Reset();
+            that_.GetErrorDictionary().LogError(error, true);
+            throw OrthancException(static_cast<ErrorCode>(error));
+          }
+        }
+
+        Reset();
+      }
     }
-    else
+
+    void Invoke(_OrthancPluginService service,
+                const _OrthancPluginFindOperation& operation) const
     {
-      memcpy(result, &str[0], str.size() + 1);
+      if (currentQuery_.get() == NULL)
+      {
+        throw OrthancException(ErrorCode_Plugin);
+      }
+
+      switch (service)
+      {
+        case _OrthancPluginService_GetFindQuerySize:
+          *operation.resultUint32 = currentQuery_->GetSize();
+          break;
+
+        case _OrthancPluginService_GetFindQueryTag:
+        {
+          const DicomTag& tag = currentQuery_->GetElement(operation.index).GetTag();
+          *operation.resultGroup = tag.GetGroup();
+          *operation.resultElement = tag.GetElement();
+          break;
+        }
+
+        case _OrthancPluginService_GetFindQueryTagName:
+        {
+          const DicomTag& tag = currentQuery_->GetElement(operation.index).GetTag();
+          *operation.resultString = CopyString(FromDcmtkBridge::GetName(tag));
+          break;
+        }
+
+        case _OrthancPluginService_GetFindQueryValue:
+        {
+          *operation.resultString = CopyString(currentQuery_->GetElement(operation.index).GetValue().GetContent());
+          break;
+        }
+
+        default:
+          throw OrthancException(ErrorCode_InternalError);
+      }
     }
-
-    return result;
-  }
+  };
+  
 
 
   OrthancPlugins::OrthancPlugins()
@@ -741,6 +861,26 @@
   }
 
 
+  void OrthancPlugins::RegisterFindCallback(const void* parameters)
+  {
+    const _OrthancPluginFindCallback& p = 
+      *reinterpret_cast<const _OrthancPluginFindCallback*>(parameters);
+
+    boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_);
+
+    if (pimpl_->findCallback_ != NULL)
+    {
+      LOG(ERROR) << "Can only register one plugin to handle C-FIND requests";
+      throw OrthancException(ErrorCode_Plugin);
+    }
+    else
+    {
+      LOG(INFO) << "Plugin has registered a callback to handle C-FIND requests";
+      pimpl_->findCallback_ = p.callback;
+    }
+  }
+
+
   void OrthancPlugins::RegisterDecodeImageCallback(const void* parameters)
   {
     const _OrthancPluginDecodeImageCallback& p = 
@@ -1838,6 +1978,10 @@
         RegisterWorklistCallback(parameters);
         return true;
 
+      case _OrthancPluginService_RegisterFindCallback:
+        RegisterFindCallback(parameters);
+        return true;
+
       case _OrthancPluginService_RegisterDecodeImageCallback:
         RegisterDecodeImageCallback(parameters);
         return true;
@@ -2314,6 +2458,33 @@
         return true;
       }
 
+      case _OrthancPluginService_FindAddAnswer:
+      {
+        const _OrthancPluginFindOperation& p =
+          *reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
+        reinterpret_cast<DicomFindAnswers*>(p.answers)->Add(p.dicom, p.size);
+        return true;
+      }
+
+      case _OrthancPluginService_FindMarkIncomplete:
+      {
+        const _OrthancPluginFindOperation& p =
+          *reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
+        reinterpret_cast<DicomFindAnswers*>(p.answers)->SetComplete(false);
+        return true;
+      }
+
+      case _OrthancPluginService_GetFindQuerySize:
+      case _OrthancPluginService_GetFindQueryTag:
+      case _OrthancPluginService_GetFindQueryTagName:
+      case _OrthancPluginService_GetFindQueryValue:
+      {
+        const _OrthancPluginFindOperation& p =
+          *reinterpret_cast<const _OrthancPluginFindOperation*>(parameters);
+        reinterpret_cast<const FindHandler*>(p.query)->Invoke(service, p);
+        return true;
+      }
+
       case _OrthancPluginService_CreateImage:
       case _OrthancPluginService_CreateImageAccessor:
       case _OrthancPluginService_DecodeDicomImage:
@@ -2475,6 +2646,26 @@
   }
 
 
+  IFindRequestHandler* OrthancPlugins::ConstructFindRequestHandler()
+  {
+    if (HasFindHandler())
+    {
+      return new FindHandler(*this);
+    }
+    else
+    {
+      return NULL;
+    }
+  }
+
+
+  bool OrthancPlugins::HasFindHandler()
+  {
+    boost::mutex::scoped_lock lock(pimpl_->findCallbackMutex_);
+    return pimpl_->findCallback_ != NULL;
+  }
+
+
   bool OrthancPlugins::HasCustomImageDecoder()
   {
     boost::mutex::scoped_lock lock(pimpl_->decodeImageCallbackMutex_);
--- a/Plugins/Engine/OrthancPlugins.h	Tue May 17 15:45:49 2016 +0200
+++ b/Plugins/Engine/OrthancPlugins.h	Tue May 24 17:45:56 2016 +0200
@@ -53,6 +53,7 @@
 #include "../../OrthancServer/IServerListener.h"
 #include "../../OrthancServer/IDicomImageDecoder.h"
 #include "../../OrthancServer/DicomProtocol/IWorklistRequestHandlerFactory.h"
+#include "../../OrthancServer/DicomProtocol/IFindRequestHandlerFactory.h"
 #include "OrthancPluginDatabase.h"
 #include "PluginsManager.h"
 
@@ -69,13 +70,15 @@
     public IServerListener,
     public IWorklistRequestHandlerFactory,
     public IDicomImageDecoder,
-    public IIncomingHttpRequestFilter
+    public IIncomingHttpRequestFilter,
+    public IFindRequestHandlerFactory
   {
   private:
     struct PImpl;
     boost::shared_ptr<PImpl> pimpl_;
 
     class WorklistHandler;
+    class FindHandler;
 
     void CheckContextAvailable();
 
@@ -88,6 +91,8 @@
 
     void RegisterWorklistCallback(const void* parameters);
 
+    void RegisterFindCallback(const void* parameters);
+
     void RegisterDecodeImageCallback(const void* parameters);
 
     void RegisterIncomingHttpRequestFilter(const void* parameters);
@@ -251,6 +256,10 @@
                            const char* ip,
                            const char* username,
                            const IHttpHandler::Arguments& httpHeaders) const;
+
+    bool HasFindHandler();
+
+    virtual IFindRequestHandler* ConstructFindRequestHandler();
   };
 }
 
--- a/Plugins/Include/orthanc/OrthancCPlugin.h	Tue May 17 15:45:49 2016 +0200
+++ b/Plugins/Include/orthanc/OrthancCPlugin.h	Tue May 24 17:45:56 2016 +0200
@@ -18,6 +18,7 @@
  *    - Possibly register its callback for changes to the DICOM store using ::OrthancPluginRegisterOnChangeCallback().
  *    - Possibly register a custom storage area using ::OrthancPluginRegisterStorageArea().
  *    - Possibly register a custom database back-end area using OrthancPluginRegisterDatabaseBackendV2().
+ *    - Possibly register a handler for C-Find SCP using OrthancPluginRegisterFindCallback().
  *    - Possibly register a handler for C-Find SCP against DICOM worklists using OrthancPluginRegisterWorklistCallback().
  *    - Possibly register a custom decoder for DICOM images using OrthancPluginRegisterDecodeImageCallback().
  *    - Possibly register a callback to filter incoming HTTP requests using OrthancPluginRegisterIncomingHttpRequestFilter().
@@ -52,8 +53,8 @@
  * @defgroup Callbacks Callbacks
  * @brief Functions to register and manage callbacks by the plugins.
  *
- * @defgroup Worklists Worklists
- * @brief Functions to register and manage worklists.
+ * @defgroup DicomCallbaks DicomCallbaks
+ * @brief Functions to register and manage DICOM callbacks (worklists, C-Find, C-MOVE).
  *
  * @defgroup Orthanc Orthanc
  * @brief Functions to access the content of the Orthanc server.
@@ -415,6 +416,7 @@
     _OrthancPluginService_RegisterWorklistCallback = 1005,
     _OrthancPluginService_RegisterDecodeImageCallback = 1006,
     _OrthancPluginService_RegisterIncomingHttpRequestFilter = 1007,
+    _OrthancPluginService_RegisterFindCallback = 1008,
 
     /* Sending answers to REST calls */
     _OrthancPluginService_AnswerBuffer = 2000,
@@ -484,11 +486,17 @@
     _OrthancPluginService_CreateImageAccessor = 6013,
     _OrthancPluginService_DecodeDicomImage = 6014,
 
-    /* Primitives for handling worklists */
+    /* Primitives for handling C-Find, C-Move and worklists */
     _OrthancPluginService_WorklistAddAnswer = 7000,
     _OrthancPluginService_WorklistMarkIncomplete = 7001,
     _OrthancPluginService_WorklistIsMatch = 7002,
     _OrthancPluginService_WorklistGetDicomQuery = 7003,
+    _OrthancPluginService_FindAddAnswer = 7004,
+    _OrthancPluginService_FindMarkIncomplete = 7005,
+    _OrthancPluginService_GetFindQuerySize = 7006,
+    _OrthancPluginService_GetFindQueryTag = 7007,
+    _OrthancPluginService_GetFindQueryTagName = 7008,
+    _OrthancPluginService_GetFindQueryValue = 7009,
 
     _OrthancPluginService_INTERNAL = 0x7fffffff
   } _OrthancPluginService;
@@ -812,22 +820,38 @@
 
 
   /**
-   * @brief Opaque structure to an object that represents a C-Find query.
-   * @ingroup Worklists
+   * @brief Opaque structure to an object that represents a C-Find query for worklists.
+   * @ingroup DicomCallbacks
    **/
   typedef struct _OrthancPluginWorklistQuery_t OrthancPluginWorklistQuery;
 
 
 
   /**
-   * @brief Opaque structure to an object that represents the answers to a C-Find query.
-   * @ingroup Worklists
+   * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
+   * @ingroup DicomCallbacks
    **/
   typedef struct _OrthancPluginWorklistAnswers_t OrthancPluginWorklistAnswers;
 
 
 
   /**
+   * @brief Opaque structure to an object that represents a C-Find query.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginFindQuery_t OrthancPluginFindQuery;
+
+
+
+  /**
+   * @brief Opaque structure to an object that represents the answers to a C-Find query for worklists.
+   * @ingroup DicomCallbacks
+   **/
+  typedef struct _OrthancPluginFindAnswers_t OrthancPluginFindAnswers;
+
+
+
+  /**
    * @brief Signature of a callback function that answers to a REST request.
    * @ingroup Callbacks
    **/
@@ -935,7 +959,7 @@
 
 
   /**
-   * @brief Callback to handle the C-Find SCP requests received by Orthanc.
+   * @brief Callback to handle the C-Find SCP requests for worklists.
    *
    * Signature of a callback function that is triggered when Orthanc
    * receives a C-Find SCP request against modality worklists.
@@ -945,7 +969,7 @@
    * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
    * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
    * @return 0 if success, other value if error.
-   * @ingroup Worklists
+   * @ingroup DicomCallbacks
    **/
   typedef OrthancPluginErrorCode (*OrthancPluginWorklistCallback) (
     OrthancPluginWorklistAnswers*     answers,
@@ -956,6 +980,27 @@
 
 
   /**
+   * @brief Callback to handle the C-Find SCP requests.
+   *
+   * Signature of a callback function that is triggered when Orthanc
+   * receives a C-Find SCP request not concerning modality worklists.
+   *
+   * @param answers The target structure where answers must be stored.
+   * @param query The worklist query.
+   * @param remoteAet The Application Entity Title (AET) of the modality from which the request originates.
+   * @param calledAet The Application Entity Title (AET) of the modality that is called by the request.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  typedef OrthancPluginErrorCode (*OrthancPluginFindCallback) (
+    OrthancPluginFindAnswers*     answers,
+    const OrthancPluginFindQuery* query,
+    const char*                   remoteAet,
+    const char*                   calledAet);
+
+
+
+  /**
    * @brief Callback to filter incoming HTTP requests received by Orthanc.
    *
    * Signature of a callback function that is triggered whenever
@@ -4194,7 +4239,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param callback The callback.
    * @return 0 if success, other value if error.
-   * @ingroup Worklists
+   * @ingroup DicomCallbacks
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterWorklistCallback(
     OrthancPluginContext*          context,
@@ -4229,7 +4274,8 @@
    * @param dicom The worklist to answer, encoded as a DICOM file.
    * @param size The size of the DICOM file.
    * @return 0 if success, other value if error.
-   * @ingroup Worklists
+   * @ingroup DicomCallbacks
+   * @see OrthancPluginCreateDicom()
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistAddAnswer(
     OrthancPluginContext*             context,
@@ -4259,7 +4305,7 @@
    * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
    * @param answers The set of answers.
    * @return 0 if success, other value if error.
-   * @ingroup Worklists
+   * @ingroup DicomCallbacks
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistMarkIncomplete(
     OrthancPluginContext*          context,
@@ -4297,7 +4343,7 @@
    * @param dicom The worklist to answer, encoded as a DICOM file.
    * @param size The size of the DICOM file.
    * @return 1 if the worklist matches the query, 0 otherwise.
-   * @ingroup Worklists
+   * @ingroup DicomCallbacks
    **/
   ORTHANC_PLUGIN_INLINE int32_t  OrthancPluginWorklistIsMatch(
     OrthancPluginContext*              context,
@@ -4336,7 +4382,7 @@
    * @param target Memory buffer where to store the DICOM file. It must be freed with OrthancPluginFreeMemoryBuffer().
    * @param query The worklist query, as received by the callback.
    * @return 0 if success, other value if error.
-   * @ingroup Worklists
+   * @ingroup DicomCallbacks
    **/
   ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginWorklistGetDicomQuery(
     OrthancPluginContext*              context,
@@ -4897,6 +4943,246 @@
   }
 
 
+
+
+  typedef struct
+  {
+    OrthancPluginFindCallback callback;
+  } _OrthancPluginFindCallback;
+
+  /**
+   * @brief Register a callback to handle C-Find requests.
+   *
+   * This function registers a callback to handle C-Find SCP requests
+   * that are not related to modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param callback The callback.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode OrthancPluginRegisterFindCallback(
+    OrthancPluginContext*      context,
+    OrthancPluginFindCallback  callback)
+  {
+    _OrthancPluginFindCallback params;
+    params.callback = callback;
+
+    return context->InvokeService(context, _OrthancPluginService_RegisterFindCallback, &params);
+  }
+
+
+  typedef struct
+  {
+    OrthancPluginFindAnswers      *answers;
+    const OrthancPluginFindQuery  *query;
+    const void                    *dicom;
+    uint32_t                       size;
+    uint32_t                       index;
+    uint32_t                      *resultUint32;
+    uint16_t                      *resultGroup;
+    uint16_t                      *resultElement;
+    char                         **resultString;
+  } _OrthancPluginFindOperation;
+
+  /**
+   * @brief Add one answer to some C-Find request.
+   *
+   * This function adds one answer (encoded as a DICOM file) to the
+   * set of answers corresponding to some C-Find SCP request that is
+   * not related to modality worklists.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @param dicom The answer to be added, encoded as a DICOM file.
+   * @param size The size of the DICOM file.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   * @see OrthancPluginCreateDicom()
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginFindAddAnswer(
+    OrthancPluginContext*      context,
+    OrthancPluginFindAnswers*  answers,
+    const void*                dicom,
+    uint32_t                   size)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.answers = answers;
+    params.dicom = dicom;
+    params.size = size;
+
+    return context->InvokeService(context, _OrthancPluginService_FindAddAnswer, &params);
+  }
+
+
+  /**
+   * @brief Mark the set of C-Find answers as incomplete.
+   *
+   * This function marks as incomplete the set of answers
+   * corresponding to some C-Find SCP request that is not related to
+   * modality worklists. This must be used if canceling the handling
+   * of a request when too many answers are to be returned.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param answers The set of answers.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginFindMarkIncomplete(
+    OrthancPluginContext*      context,
+    OrthancPluginFindAnswers*  answers)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.answers = answers;
+
+    return context->InvokeService(context, _OrthancPluginService_FindMarkIncomplete, &params);
+  }
+
+
+
+  /**
+   * @brief Get the number of tags in a C-Find query.
+   *
+   * This function returns the number of tags that are contained in
+   * the given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @return The number of tags.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE uint32_t  OrthancPluginGetFindQuerySize(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query)
+  {
+    uint32_t count = 0;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.resultUint32 = &count;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQuerySize, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return 0;
+    }
+    else
+    {
+      return count;
+    }
+  }
+
+
+  /**
+   * @brief Get one tag in a C-Find query.
+   *
+   * This function returns the group and the element of one DICOM tag
+   * in the given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param group The group of the tag (output).
+   * @param element The element of the tag (output).
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE OrthancPluginErrorCode  OrthancPluginGetFindQueryTag(
+    OrthancPluginContext*          context,
+    uint16_t*                      group,
+    uint16_t*                      element,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultGroup = group;
+    params.resultElement = element;
+
+    return context->InvokeService(context, _OrthancPluginService_GetFindQueryTag, &params);
+  }
+
+
+  /**
+   * @brief Get the symbolic name of one tag in a C-Find query.
+   *
+   * This function returns the symbolic name of one DICOM tag in the
+   * given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return The NULL value in case of error, or a string containing the name of the tag.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char*  OrthancPluginGetFindQueryTagName(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    char* result;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultString = &result;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQueryTagName, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+
+
+  /**
+   * @brief Get the value associated with one tag in a C-Find query.
+   *
+   * This function returns the value associated with one tag in the
+   * given C-Find query.
+   *
+   * @param context The Orthanc plugin context, as received by OrthancPluginInitialize().
+   * @param query The C-Find query.
+   * @param index The index of the tag of interest.
+   * @return The NULL value in case of error, or a string containing the value of the tag.
+   * @return 0 if success, other value if error.
+   * @ingroup DicomCallbacks
+   **/
+  ORTHANC_PLUGIN_INLINE char*  OrthancPluginGetFindQueryValue(
+    OrthancPluginContext*          context,
+    const OrthancPluginFindQuery*  query,
+    uint32_t                       index)
+  {
+    char* result;
+
+    _OrthancPluginFindOperation params;
+    memset(&params, 0, sizeof(params));
+    params.query = query;
+    params.index = index;
+    params.resultString = &result;
+
+    if (context->InvokeService(context, _OrthancPluginService_GetFindQueryValue, &params) != OrthancPluginErrorCode_Success)
+    {
+      /* Error */
+      return NULL;
+    }
+    else
+    {
+      return result;
+    }
+  }
+ 
 #ifdef  __cplusplus
 }
 #endif