Page 1 of 3

Value of StringParameter not saved in Pro-II

PostPosted: 15 September 2022, 12:16
by SLiebschner
In the CPPMixerSplitter example I implemented a StringParameter analogously to a RealParameter, like described here: viewtopic.php?f=17&t=743&start=20
I use the UO within Pro-II.
This works well for both cases: the string parameter is an output or input parameter.
The only point I am not happy about is the following: If I specify input values for RealParameters and StringParameters and save the corresponding Pro-II file (.prz), the values of the StringParameters have vanished when opening the .prz file again. This is not so for the RealParameters.

Does anybody know, what needs to be done to save the values of the StringParameters?

Here is my implementation of the StringParameter in StringParameter.h:

Code: Select all

#pragma once
#include "resource.h"       // main symbols

#include "CPPMixerSplitterexample.h"
#include "CAPEOPENBaseObject.h"

#include <float.h>
#include <iostream>
#include <string>
#include <fstream>

class ATL_NO_VTABLE CStringParameter :
   //public CComCoClass<CStringParameter, &CLSID_RealParameter>,
   public IDispatchImpl<ICapeParameter, &__uuidof(ICapeParameter), &LIBID_CAPEOPEN110, /* wMajor = */ 1, /* wMinor = */ 1>,
   public IDispatchImpl<ICapeParameterSpec, &__uuidof(ICapeParameterSpec), &LIBID_CAPEOPEN110, /* wMajor = */ 1, /* wMinor = */ 1>,
   public IDispatchImpl<ICapeOptionParameterSpec, &__uuidof(ICapeOptionParameterSpec), &LIBID_CAPEOPEN110, /* wMajor = */ 1, /* wMinor = */ 1>,
   public CAPEOPENBaseObject
{
public:

   BSTR defVal; /*!< the default value of this parameter; used to initialize as well, so cannot be NaN*/
    BSTR value;  /*!< the current value of this parameter, must always be valid */
   std::string quantity; // determines possible values, which are listed in get_OptionList()
    CapeValidationStatus *valStatus; /*!< points to the unit operation's validation status */
   CapeParamMode  Mode;
   //! Helper function for creating the parameter
    /*!
      Helper function for creating the parameter as an exposable COM object. After calling CreateParameter, the reference
      count is one; do not delete the returned object. Use Release() instead
      \param name name of the parameter
      \param description description of the parameter
      \param defVal the default value of the parameter     
      \param valStatus points to the unit operation's validation status
      \sa CStringParameter()
    */
   
    static CComObject<CStringParameter> *CreateParameter(const OLECHAR *name,const OLECHAR *description, BSTR *defVal, std::string quantity, CapeValidationStatus *valStatus, CapeParamMode ModeIn)
    {
     CComObject<CStringParameter> *p;
     CComObject<CStringParameter>::CreateInstance(&p); //create the instance with zero references
     p->AddRef(); //now it has one reference, the caller must Release this object
     p->name=name;
     p->description=description;
    p->defVal = SysAllocString(*defVal);
    p->value = SysAllocString(*defVal);
    p->quantity = quantity;
     p->valStatus=valStatus;
    p->Mode = ModeIn;
     return p;
    }

   //! Constructor.
    /*!
      Creates an parameter of which the name cannot be changed by external applications.
      Use CreateParameter instead of new
      \sa CreateParameter()
    */

   CStringParameter() : CAPEOPENBaseObject(false)
   {//everything is initialized via CreateParameter
   }

   //this object cannot be created using CoCreateInstance, so we do not need to put anything in the registry

   DECLARE_NO_REGISTRY()

   //COM map, including that of the CAPEOPENBaseObject

   BEGIN_COM_MAP(CStringParameter)
      COM_INTERFACE_ENTRY2(IDispatch, ICapeParameter)
      COM_INTERFACE_ENTRY(ICapeParameter)
      COM_INTERFACE_ENTRY(ICapeParameterSpec)
      COM_INTERFACE_ENTRY(ICapeOptionParameterSpec)
      BASEMAP
   END_COM_MAP()


   DECLARE_PROTECT_FINAL_CONSTRUCT()

   STDMETHOD(get_Specification)(LPDISPATCH * spec)
   {   if (!spec) return E_POINTER; //not a valid pointer
       //this object implements the parameter spec. Instead of below, we can also call QueryInterface on ourselves
       *spec=(ICapeParameter*)this;
       (*spec)->AddRef(); //caller must release
      return NOERROR;
   }

STDMETHOD(get_value)(VARIANT* value)
{
   if (!value) return FALSE; //not a valid pointer
   VARIANT res;
   res.vt = VT_BSTR;
   res.bstrVal = (this->value) ? SysAllocString(this->value) : nullptr;
   *value = res;
   return NOERROR;
}

STDMETHOD(put_value)(VARIANT value)
{   //convert to a string
   VARIANT v;
   v.vt = VT_EMPTY;
   if (FAILED(VariantChangeType(&v, &value, 0, VT_BSTR)))
   {
      SetError(L"Invalid data type. Expected basic string: BSTR", L"ICapeParameter", L"put_value");
      return ECapeUnknownHR;
   }
   ATLASSERT(v.vt == VT_BSTR);
   //value is ok
   this->value = SysAllocString(v.bstrVal);
   *valStatus = CAPE_NOT_VALIDATED; //we changed the parameter, the unit needs to be re-validated
   dirty = true; //something changed that affects saving
   return NOERROR;
}

STDMETHOD(get_ValStatus)(CapeValidationStatus* ValStatus)
{
   if (!ValStatus) return E_POINTER; //not a valid value
   *ValStatus = CAPE_VALID; //value is always valid
   return NOERROR;
}

STDMETHOD(get_Mode)(CapeParamMode* Mode)
{
   if (!Mode) return E_POINTER; //not a valid value
   *Mode = this->Mode; //this unit operation only has input parameters
   return NOERROR;
}

STDMETHOD(put_Mode)(CapeParamMode Mode)
{
   SetError(L"The mode of this parameter is read-only", L"ICapeParameter", L"put_Mode");
   return ECapeUnknownHR;
}

STDMETHOD(Validate)(BSTR* message, VARIANT_BOOL* isOK)
{
   if ((!message) || (!isOK)) return E_POINTER; //not valid values
//note the [in, out] status of the message; if we were to put a new value in there, we should clear its existing value or its memory would leak
   *isOK = VARIANT_TRUE; //we are always valid
   return NOERROR;
}

STDMETHOD(Reset)()
{
   value = SysAllocString(defVal);
   *valStatus = CAPE_NOT_VALIDATED; //we changed the parameter, the unit needs to be re-validated
   dirty = true; //something changed that affects saving
   return NOERROR;
}

STDMETHOD(get_Type)(CapeParamType* Type)
{
   if (!Type) return E_POINTER; //not a valid pointer
   *Type = CAPE_OPTION;
   return NOERROR;
}

STDMETHOD(get_Dimensionality)(VARIANT* dim)
{
   if (!dim) return E_POINTER; //not a valid pointer
   dim->vt = VT_EMPTY; // return empty VARIANT
   return NOERROR;
}


STDMETHOD(get_DefaultValue)(BSTR* DefaultValue)
{
   if (!DefaultValue) return E_POINTER; //not a valid pointer
   *DefaultValue = SysAllocString(defVal);
   return NOERROR;
}

STDMETHOD(get_OptionList)(VARIANT* v)
{
   // This function specifies all allowed version numbers.
   // Pro-II displays only version numbers, which are allowed by this function.

   // set type to string array
   v->vt = VT_ARRAY | VT_BSTR;

   //ofstream myfile;
   //myfile.open("C:\\Users\\liebschs\\src\\repos\\pro-ii-api\\SOC-1D-Solver\\dll_debug.log", std::ios_base::app);
   //myfile << "quantity = " << quantity << "\n";
   //myfile.close();

   // specify allowed BSTRs
   vector<BSTR> allowed_BSTRs = {};
   if (quantity == "version") {
      allowed_BSTRs.push_back(SysAllocString(L"x.y.z"));
      for (unsigned int major = 0; major <= 2; major++)
      {
         for (unsigned int minor = 0; minor <= 11; minor++)
         {
            for (unsigned int bugfix = 0; bugfix <= 11; bugfix++)
            {
               // build version STL string
               string version_STLstring = to_string(major) + "." + to_string(minor) + "." + to_string(bugfix);
               // convert STL string to BSTR and append to vector
               CComBSTR temp(version_STLstring.c_str());
               allowed_BSTRs.push_back(temp.Detach());
            }
         }
      }
   }
   else if (quantity == "stack") {
      allowed_BSTRs.push_back(SysAllocString(L"default"));
      allowed_BSTRs.push_back(SysAllocString(L"Stand 2018"));
      allowed_BSTRs.push_back(SysAllocString(L"Stand 2020"));
      allowed_BSTRs.push_back(SysAllocString(L"base 2023"));
      allowed_BSTRs.push_back(SysAllocString(L"ambitious 2023"));
      allowed_BSTRs.push_back(SysAllocString(L"ambitious 2025"));
      allowed_BSTRs.push_back(SysAllocString(L"user defined"));
   }
   else if (quantity == "module") {
      allowed_BSTRs.push_back(SysAllocString(L"StackUnit A101"));
      allowed_BSTRs.push_back(SysAllocString(L"SU Gen.2 (alpha)"));
      allowed_BSTRs.push_back(SysAllocString(L"reiner Stack"));
      allowed_BSTRs.push_back(SysAllocString(L"Einzel-STB-B411"));
      allowed_BSTRs.push_back(SysAllocString(L"Einzel-STB in EBZ3 (4.4)"));
      allowed_BSTRs.push_back(SysAllocString(L"Einzel-STB in FC4 (4.1)"));
      allowed_BSTRs.push_back(SysAllocString(L"ICM (240E) (GrInHy)"));
      allowed_BSTRs.push_back(SysAllocString(L"SU Gen.3"));
      allowed_BSTRs.push_back(SysAllocString(L"user defined"));
   }
   else if (quantity == "input_quantities") {
      allowed_BSTRs.push_back(SysAllocString(L"default"));
      allowed_BSTRs.push_back(SysAllocString(L"I, V_F_feed"));
      allowed_BSTRs.push_back(SysAllocString(L"FC, V_F_feed (SOEC)"));
   }
   else if (quantity == "gas_evolution_model") {
      allowed_BSTRs.push_back(SysAllocString(L"default"));
      allowed_BSTRs.push_back(SysAllocString(L"WGS_eq, SR_exp"));
      allowed_BSTRs.push_back(SysAllocString(L"kinetic ansatz"));
   }
   else if (quantity == "flow_configuration") {
      allowed_BSTRs.push_back(SysAllocString(L"default"));
      allowed_BSTRs.push_back(SysAllocString(L"parallel current"));
      allowed_BSTRs.push_back(SysAllocString(L"counter current"));
   }

   // write allowed versions to output parameter
   v->parray = SafeArrayCreateVector(VT_BSTR, 0, (unsigned int) allowed_BSTRs.size()); // start indexing at 0
   long i = 0;
   for (vector<BSTR>::iterator it = allowed_BSTRs.begin(); it != allowed_BSTRs.end(); it++, i++ )
   {
      SafeArrayPutElement(v->parray, &i, *it);
   }

      return NOERROR;
}

STDMETHOD(get_RestrictedToList)(VARIANT_BOOL * vb)
{
   *vb = VARIANT_TRUE;
   return NOERROR;
}

STDMETHOD(Validate)(BSTR value, BSTR* message, VARIANT_BOOL* isOK)
{
   if ((!message) || (!isOK)) return E_POINTER; //invalid pointer
   //notice that message is [in,out]; if we set a value, we must clear the existing value or its memory will leak. Let's do that now
   if (*message)
   {//it is not wise of the caller to pass a value in here... one cannot trust that all implementors do this properly
      SysFreeString(*message);
      *message = NULL;
   }
   *isOK = VARIANT_TRUE;
   return NOERROR;
}

};

//convenience definition for variables of COM objects defined by this class
typedef CComObject<CStringParameter> StringParameterObject;



I add the StringParameters (and RealParameters) in the constructor of CCPPMixerSplitterUnitOperation to the parameter collection
Code: Select all
parameterCollection = CCollection::CreateCollection(L"Parameter collection", L"Parameter collection for SOC-1D-Solver");

RealParameterObject* par;
StringParameterObject* string_par;

BSTR defBSTR = SysAllocString(L"default");  // default value for string input parameters
string_par = StringParameterObject::CreateParameter(L"module type", L"[-]", &defBSTR, "module", & valStatus, CAPE_INPUT);
parameterCollection->AddItem(string_par); // parameter 0

...

par = RealParameterObject::CreateParameter(L"I in A (input)", L"[-]", NaN, NaN, NaN, dimensionality, &valStatus, CAPE_INPUT);
parameterCollection->AddItem(par); // parameter 8

Re: Value of StringParameter not saved in Pro-II

PostPosted: 16 September 2022, 07:16
by jasper
The unit operation is responsible for saving and restoring the values. This should be implemented in IPersistStream(Init)::Save and IPersistStream(Init)::Load (and if IPersistPropertyBag is implemented, also in IPersistPropertyBag ::Save and IPersistPropertyBag ::Load)

Re: Value of StringParameter not saved in Pro-II

PostPosted: 19 September 2022, 21:42
by SLiebschner
Cheers. It works well for the first 33 input parameters (8 string parameters, 25 real parameters), but somehow the 34th input parameter is not saved and loaded. All further input parameters (35th, ...) are also not saved and loaded.

If I save and load all variables (in and output) - which are 58 in total - all are saved and loaded, except for the very 34th input parameter. Any ideas?

PS: It is not even saved in the same session: I.e. specifying it in Pro-II, closing the window and opening it immediately again, the number has vanished.

Here is the implementation of IPersistStream::Save and Load:

Code: Select all
   STDMETHOD(Load)(IStream * pstm)
   {   if (!pstm) return E_POINTER;
      UINT fileVersion;
      unsigned int i;
      RealParameterObject *par;
      StringParameterObject *string_par;
      ULONG read;
      UINT length;
      OLECHAR *buf;
      if (FAILED(pstm->Read(&fileVersion,sizeof(UINT),&read))) return E_FAIL; //this is not a CAPE-OPEN function, we do not return a CAPE-OPEN error
      if (read!=sizeof(UINT)) return E_FAIL;
      if (fileVersion>CURRENTFILEVERSIONNUMBER)
         {//popping up messages is in general not a good idea; however, this one is a rather important one, so we make an exception here:
         MessageBox(NULL,L"This unit operation was saved with a newer version of the software. Please obtain the latest CPP Mixer Splitter Example from the CO-LaN web site",L"Error loading:",MB_ICONHAND);
         return E_FAIL;
         }
      //read name
      if (FAILED(pstm->Read(&length,sizeof(UINT),&read))) return E_FAIL;
      if (read!=sizeof(UINT)) return E_FAIL;
      buf=new OLECHAR[length+1]; //space for terminating zero
      if (FAILED(pstm->Read(buf,2*(length+1),&read))) {delete []buf;return E_FAIL;}
      if (read!=2*(length+1)) {delete []buf;return E_FAIL;}
      name=buf;
      delete []buf;
      //read description
      if (FAILED(pstm->Read(&length,sizeof(UINT),&read))) return E_FAIL;
      if (read!=sizeof(UINT)) return E_FAIL;
      buf=new OLECHAR[length+1]; //space for terminating zero
      if (FAILED(pstm->Read(buf,2*(length+1),&read))) {delete []buf;return E_FAIL;}
      if (read!=2*(length+1)) {delete []buf;return E_FAIL;}
      description=buf;
      delete []buf;
      //read parameter values

      for (i=0;i<parameterCollection->items.size();i++)
      {
         if (i < 8) // 8 = number of string parameters
         {
            string_par = (StringParameterObject*)parameterCollection->items[i];

            if (FAILED(pstm->Read(&length, sizeof(UINT), &read))) return E_FAIL;
            if (read != sizeof(UINT)) return E_FAIL;
            buf = new OLECHAR[length + 1]; //space for terminating zero
            if (FAILED(pstm->Read(buf, 2 * (length + 1), &read))) { delete[]buf; return E_FAIL; }
            if (read != 2 * (length + 1)) { delete[]buf; return E_FAIL; }
            std::wstring ws = buf;
            string_par->value = SysAllocStringLen(ws.data(), ws.size());
            delete[]buf;
         }
         else
         {
            par = (RealParameterObject*)parameterCollection->items[i];
            if (FAILED(pstm->Read(&par->value, sizeof(double), &read))) return E_FAIL;
            if (read != sizeof(double)) return E_FAIL;
         }
         }
      //all ok
      return S_OK;
   }

   //! IPersistStream::Save
   /*!
   Save to persistence
   \param pstm [in] IStream to save to
   \param fClearDirty [in] if set, we must clear the dirty flags
   \return S_OK for success, or S_FALSE
   \sa Load(), GetSizeMax()
   */
   STDMETHOD(Save)(IStream * pstm, BOOL fClearDirty)
   {   if (!pstm) return E_POINTER;
      unsigned int i;
      ULONG written;
      UINT length;
      UINT fileVersion=CURRENTFILEVERSIONNUMBER;
      RealParameterObject *par;
      StringParameterObject* string_par;
      //save version number, in case of future changes to the format
      if (FAILED(pstm->Write(&fileVersion,sizeof(UINT),&written))) return E_FAIL; //this is not a CAPE-OPEN function, we do not return a CAPE-OPEN error
      if (written!=sizeof(UINT)) return E_FAIL;
      //save name
      length=(UINT)name.size();
      if (FAILED(pstm->Write(&length,sizeof(UINT),&written))) return E_FAIL;
      if (written!=sizeof(UINT)) return E_FAIL;
      if (FAILED(pstm->Write(name.c_str(),2*(length+1),&written))) return E_FAIL;
      if (written!=2*(length+1)) return E_FAIL;
      //save description
      length=(UINT)description.size();
      if (FAILED(pstm->Write(&length,sizeof(UINT),&written))) return E_FAIL;
      if (written!=sizeof(UINT)) return E_FAIL;
      if (FAILED(pstm->Write(description.c_str(),2*(length+1),&written))) return E_FAIL;
      if (written!=2*(length+1)) return E_FAIL;
      //save parameter values
      for (i=0;i<parameterCollection->items.size();i++) // 34 = number of input parameters
      {
         if (i < 8) // 8 = number of string parameters
         {
            string_par = (StringParameterObject*)parameterCollection->items[i];
            std::wstring ws(string_par->value, SysStringLen(string_par->value));

            length = (UINT)ws.size();
            if (FAILED(pstm->Write(&length, sizeof(UINT), &written))) return E_FAIL;
            if (written != sizeof(UINT)) return E_FAIL;
            if (FAILED(pstm->Write(ws.c_str(), 2 * (length + 1), &written))) return E_FAIL;
            if (written != 2 * (length + 1)) return E_FAIL;
         }
         else
         {
            par=(RealParameterObject *)parameterCollection->items[i];
            if (FAILED(pstm->Write(&par->value,sizeof(double),&written))) return E_FAIL;
            if (written!=sizeof(double)) return E_FAIL;
         }
      }
      //clear the dirty flags if so asked
      if (fClearDirty)
      {
         dirty=false;
         //also on the parameters
         for (i=0;i<parameterCollection->items.size();i++)
         {
            if (i < 8) // 8 = number of string parameters
            {
               string_par = (StringParameterObject*)parameterCollection->items[i];
               string_par->dirty = false;
            }
            else
            {
               par = (RealParameterObject*)parameterCollection->items[i];
               par->dirty = false;
            }
         }
      }
      return S_OK;
   }

Re: Value of StringParameter not saved in Pro-II

PostPosted: 20 September 2022, 15:09
by jasper
Can you perhaps check what happens in Save and Load from a debugger, by attaching it to the running PME instance?

Re: Value of StringParameter not saved in Pro-II

PostPosted: 20 September 2022, 15:10
by jasper
Does GetSizeMax return the right amount of required storage?

Re: Value of StringParameter not saved in Pro-II

PostPosted: 21 September 2022, 09:51
by SLiebschner
I put the log file here: https://mettwolke.dukun.de/s/WaE4AskFmyXnedE
What I did is open the "CAPE-OPEN Settings" of the item in the flowsheet and
1. setting a parameter, called "theta_ref ..." to 860
2. clearing a parameter, called "h_layer ..."
3. setting the parameter, called "porosity" to 0.5

Then I closed the window and opened it again. Only the first change (setting of "theta_ref") succeeded.
I general, the clearing of any parameter (not just "h_layer") is not working. In addition there is the problem of this 34th parameter called "porosity".

About GetSizeMax: From my point of view it returns only the size of an int (a version number) + 2 wstrings (name and description) + 1 (or 2?) doubles:
Code: Select all
STDMETHOD(GetSizeMax)(_ULARGE_INTEGER * pcbSize)
   {   if (!pcbSize) return E_POINTER;
      //calculate total size
      UINT total;
      UINT length;
      total=sizeof(UINT); //version number
      length=(UINT)name.size();
      total+=sizeof(UINT)+2*(length+1); //size and data of name
      length=(UINT)description.size();
      total+=sizeof(UINT)+2*(length+1); //size and data of description
      total+=sizeof(double)*2; //size of values of parameters
      pcbSize->QuadPart=total;
      return NOERROR;
   }

Still, 33 parameters (8 of them are strings) are saved and loaded correctly. Btw, changing
Code: Select all
pcbSize->QuadPart=total;
to
Code: Select all
pcbSize->QuadPart=200*total;
at the method's end does not change anything.

Re: Value of StringParameter not saved in Pro-II

PostPosted: 24 September 2022, 11:05
by jasper
As saving and loading the parameters is internal to the unit operation, the log does not help much in this case - it logs the interaction between the PME and PMC.

I think best here is to set a break point in the debugger, and check whether Save and Load to as you expect them to do.

GetSizeMax is supposed to return the amount of memory needed for storage - it should be the number of double parameters times the size of a double, plus the number of string parameters plus for each string parameter the size of the length and the size of the data.

Re: Value of StringParameter not saved in Pro-II

PostPosted: 04 October 2022, 11:14
by SLiebschner
I haven't succeeded in actual debugging, but printed values of parameters to file.

It turns out that the parameters, with which I am having problems, have a **fixed** value, e.g. nan, -83, -0.649452
when I print them to file. It is not influenced by my giving numbers to the parameters in question. However, their value is not displayed within the CAPE-OPEN Settings withn Pro-II. Again the fact: this happens only with input-parameters succeeding number 32 (start counting from 0). You don't know about a maximum size of input parameters in the parameterCollection, do you?
Btw, I check that this happens also, if all of those parameters are RealParameters, i.e. there are no StringParameters.

I changed the return value of GetSizeMax() accordingly, but it does not seem to have an influence at all.
Actually it should not matter what GetSizeMax() returns, as long as its value is larger or equal the actual required size and I have enough memory. Is that so?

Re: Value of StringParameter not saved in Pro-II

PostPosted: 04 October 2022, 13:35
by jasper
Are you using Visual Studio?

CAPE-OPEN does not define a maximum size of the parameter collection. Perhaps there is such a limitation in Pro/II although I am not aware of it - can you try whether it works in any other simulator? (e.g. COCO/COFE?)

Re: Value of StringParameter not saved in Pro-II

PostPosted: 10 October 2022, 12:24
by SLiebschner
Yes, I use Visual Studio. Why do you ask?

It works in DWSIM. So the problem seems to be Pro-II related.
I see whether I can get some help from Pro-II.