Value of StringParameter not saved in Pro-II

SLiebschner
Posts: 19
Joined: 01 February 2022, 10:14

Value of StringParameter not saved in Pro-II

Post by SLiebschner »

In the CPPMixerSplitter example I implemented a StringParameter analogously to a RealParameter, like described here: http://www.cape-open-forum.org/viewtopi ... 3&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
User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Value of StringParameter not saved in Pro-II

Post 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)
SLiebschner
Posts: 19
Joined: 01 February 2022, 10:14

Re: Value of StringParameter not saved in Pro-II

Post 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;
	}
User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Value of StringParameter not saved in Pro-II

Post by jasper »

Can you perhaps check what happens in Save and Load from a debugger, by attaching it to the running PME instance?
User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Value of StringParameter not saved in Pro-II

Post by jasper »

Does GetSizeMax return the right amount of required storage?
SLiebschner
Posts: 19
Joined: 01 February 2022, 10:14

Re: Value of StringParameter not saved in Pro-II

Post 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.
User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Value of StringParameter not saved in Pro-II

Post 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.
SLiebschner
Posts: 19
Joined: 01 February 2022, 10:14

Re: Value of StringParameter not saved in Pro-II

Post 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?
User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Value of StringParameter not saved in Pro-II

Post 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?)
SLiebschner
Posts: 19
Joined: 01 February 2022, 10:14

Re: Value of StringParameter not saved in Pro-II

Post 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.
Post Reply

Return to “Unit Operations”