ITQuants blog

Sophis Value/Risque: how to override the global functions in C# when using previous versions (<6.3)?

Nov 30

Written by:
11/30/2014 9:25 PM  RssIcon

This week, I just wondered why the Sophis R&D didn't permit to override the CSRGlobalFunctions class in C# before the 6.3 version. If you take a look at the DotNet toolkit, you will see that a CSMGlobalFunctions is defined, but no static method Register exist as for the other classes. This point was several times scrolled up to Sophis in the toolkit forums, and I know some clients who ask Sophis to provide a toolkit permitting their own implementation. Unfortunately, they don't have the code and the interface provided in C# does not implement all virtual of CSRGlobalFunctions, for instance StartPortfolioAddition which is a callback on which we can implement the agregation of results for user columns that we want to store in a cache.

For remember, the overriding of the CSRGlobalFunctions class permits to be called when the folios are calculated (StartPortfolioCalculation, EndPortfolioCalculation).

The goal of this post is to describe how to implement a C++ implementation of the CSRGlobalFunctions that permits to implement callback in C#.

The first thing to know and the hardest to implement is how to call managed code from unmanaged/native C++ code. There are a lot of articles on the web discussing how to do it (at least there: http://www.codeproject.com/Articles/9903/Calling-Managed-Code-from-Unmanaged-Code-and-vice and there: http://msdn.microsoft.com/en-us/library/ms973872.aspx). Instead of using COM, specific classes or whatever as described in the mentioned links, the method retained there consists to implement pure C methods in the CLR dll since we just have to override few methods.

If you take a look at the previous articles on which I've already implemented a mix of managed/unmanaged code (webservice for example), this corresponds to a simplifcation of the object factory. On the C++ managed side, I just need to implement a manager that will register all callbacks. Such a solution permits to workaround the singleton problem of CSRGlobalFunctions. By the way, even in C++, if you have several modules that are not linked each together and if you have to implement, in several modules, callbacks of available methods in the CSRGlobalFunctions, the right way is to declare your own singleton that manages registration, a global interface/abstract class, and implementation of your interface in your different modules.

Concretely, to override the CSRGlobalFunctions and to permit to be called in C#, the different steps are the following ones:

- declare a C# interface (in a specific dll)

- declare the overriding of the CSRGlobalFunctions in a native C++ toolkit

- declare a C++ managed dll that permits to manage the registration of C# interfaces and that can be called from the C++ native code

- finally implement our own C# interface in a C# toolkit.

In my case, I just want to override StartPortfolioCalculation, EndPortfolioCalculation and StartPortfolioAddition. The C native functions will be then declared as follow:

#pragma once
  
  
#ifdef ITQCALLER_EXPORTS
#define ITQCALLERAPI __declspec(dllexport)
#else
#define ITQCALLERAPI __declspec(dllimport)
#endif
  
  
#ifdef __cplusplus
extern "C" {
#endif
   
ITQCALLERAPI void StartPortfolioCalculation(const sophis::portfolio::CSRExtraction * extraction, long folio_id);
ITQCALLERAPI void StartPortfolioAddition(const sophis::portfolio::CSRExtraction * extraction, long folio_id);
ITQCALLERAPI void EndPortfolioCalculation(const sophis::portfolio::CSRExtraction* extraction, long folio_id); 
  
typedef  void (*STARTPORTFOLIOCALCULATION_CALL)(const sophis::portfolio::CSRExtraction * extraction, long folio_id);
typedef  void (*STARTPORTFOLIOADDITION_CALL)(const sophis::portfolio::CSRExtraction * extraction, long folio_id);
typedef  void (*ENDPORTFOLIOCALCULATION_CALL)(const sophis::portfolio::CSRExtraction * extraction, long folio_id);
  
#ifdef __cplusplus
}
#endif

 

The declaration of the callback permits to load dynamically the dll, to initialize a pointer to functions by using the GetProcAddress method and to use it  from the caller side without to link it statically. The body of these C native functions will just call the C++ managed code as follow:

#include "stdafx.h"
#include "ITQColumnGlobalFunctionsWrapper.h"
#include "ITQColumnGlobalFunctionsWrapperCallback.h"
  
#ifdef __cplusplus
extern "C" {
#endif
  
ITQCALLERAPI void  StartPortfolioCalculation(const sophis::portfolio::CSRExtraction * extraction, long folio_id)
{
    ITQuants::ITQColumnGlobalFunctions::ITQColumnGlobalFunctionsWrapper::Instance()->StartPortfolioCalculation(gcnew sophis::portfolio::CSMExtraction(extraction),folio_id);
}
  
ITQCALLERAPI void StartPortfolioAddition(const sophis::portfolio::CSRExtraction * extraction, long folio_id)
{
    ITQuants::ITQColumnGlobalFunctions::ITQColumnGlobalFunctionsWrapper::Instance()->StartPortfolioAddition(gcnew sophis::portfolio::CSMExtraction(extraction),folio_id);
}
  
ITQCALLERAPI void EndPortfolioCalculation(const sophis::portfolio::CSRExtraction* extraction, long folio_id)
{
    ITQuants::ITQColumnGlobalFunctions::ITQColumnGlobalFunctionsWrapper::Instance()->EndPortfolioCalculation(gcnew sophis::portfolio::CSMExtraction(extraction),folio_id);
}
  
#ifdef __cplusplus
}
#endif

 

The ITQColumnGlobalFunctionsWrapper is the manager that permits the registration of the C# callbacks. In order to declare native mehtods, we have to follow the recommendations of Microsoft: an entrypoint.cpp file is declared in the project with a DllMain callback that will be called by the OS when the dll is loaded/unloaded. Note that this file has to be compiled without the /clr option. If it is not disabled, the managed LoaderLock exception will be raised when loading and can generate a crash at the beginning. I know some dll specific to clients but provided by Misys that do not respect this rule...

Since I have common code to the C++ code and C# code (the declaration of the interface for example), I've added  a specific dll for declaring the interfaces. Using such a paradigm permits to be sure that the loading of the C++ toolkit will not interfere with the loading of the C# toolkit.

In the native C++ code, the main code to remember is the following one:

void CITQColumnGlobalFunctions::init()
{
    static HINSTANCE hInstCaller = NULL;
  
    if(hInstCaller==NULL)
    {
#ifdef _NDEBUG
        const char* dllName = "ITQColumnGlobalFunctionsCaller64d.dll";
#else
        const char* dllName = "ITQColumnGlobalFunctionsCaller64.dll";
#endif
        hInstCaller = LoadLibrary(dllName);
        if(hInstCaller!=NULL)
        {
            m_startPortfolioCalculation = (STARTPORTFOLIOCALCULATION_CALL)GetProcAddress(hInstCaller,"StartPortfolioCalculation");
            m_startPortfolioAddition = (STARTPORTFOLIOADDITION_CALL)GetProcAddress(hInstCaller,"StartPortfolioAddition");
            m_endPortfolioCalculation = (ENDPORTFOLIOCALCULATION_CALL)GetProcAddress(hInstCaller,"EndPortfolioCalculation");
        }
    }
}

 

And on C# side:

namespace ITQuants.ITQInterfaces
{
    public interface IITQColumnGlobalFunctionsInterface
    {
        void StartPortfolioCalculation(CSMExtraction extraction, int folio_id);
        void StartPortfolioAddition(CSMExtraction extraction, int folio_id);
        void EndPortfolioCalculation(CSMExtraction extraction, int folio_id);
    }
}
using System;
using System.Collections.Generic;
using System.Text;
using sophis.misc;
using sophis.portfolio;
  
namespace ITQuants.ITQColumnsCache
{
    public class ITQGlobalFunctions :ITQInterfaces.IITQColumnGlobalFunctionsInterface
    
        public void StartPortfolioCalculation(CSMExtraction extraction, int folio_id)
        {
        }
        public void EndPortfolioCalculation(CSMExtraction extraction, int folio_id)
        {
        }
        public void StartPortfolioAddition(CSMExtraction extraction, int folio_id)
        {
            // some C# code
        }
    }
}

 

And finally the whole code:ITQGlobalFunctions.zip

Tags: managed , unmanaged , C# , C++
Categories: Sophis

Search blog