Returning a string in c++ UO as UO Parameter

CSchirmer
Posts: 5
Joined: 07 July 2020, 12:15

Returning a string in c++ UO as UO Parameter

Post by CSchirmer »

Is there a way to create a string as an output parameter, for example in in the parameter collection, so that it can be displayed in other applications that use the UO?
As far as I overlooked the c++ example UO I have not found a way for this issue.

Best,
Christoph
User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Returning a string in c++ UO as UO Parameter

Post by jasper »

There are two ways to produce strings. One is via a textual report (implement ICapeReport in CAPE-OPEN 1.2, or ICapeUnitReport in CAPE-OPEN 1.0/1.1), the other is indeed via a string output parameter.

Can you elaborate on the context? (CAPE-OPEN version, are you coding the unit operation yourself, ...)?
CSchirmer
Posts: 5
Joined: 07 July 2020, 12:15

Re: Returning a string in c++ UO as UO Parameter

Post by CSchirmer »

Yes I am coding the UO by myself. I think we are using CAPE-OPEN 1.1. The base of the implementation is the c++ Mixer-Splitter UO example.
It would be best for me if the string is returned together with all other output parameters. The aim is to integrate the UO to a Pro/II Simulation.
User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Returning a string in c++ UO as UO Parameter

Post by jasper »

Pro/II does support string parameters.

So a string parameter looks like the RealParameter, except:

- instead of ICapeRealParameterSpec it's get_Specification should return an object (e.g. itself) that implements ICapeOptionParameterSpec
- get_value/put_value take a VT_BSTR typed value. Missing can be either an empty BSTR or a VT_EMPTY
- get_Type should return CAPE_OPTION
- get_Dimensionality can be left unimplemented (e.g. ECapeNoImplHr)
- ICapeOptionParameterSpec::OptionList can optionally return a list of valid values
- ICapeOptionParameterSpec::RestrictedToList should return VARIANT_TRUE in case the list of options is exclusive (no other values allowed)
SLiebschner
Posts: 19
Joined: 01 February 2022, 10:14

Re: Returning a string in c++ UO as UO Parameter

Post by SLiebschner »

Hi,
I have the same question as CSchirmer. I am using CAPE-OPEN version 1.0.
I tried to implement the points jasper mentioned above. Having done this, the DLL compiles and the name of the string parameter (I called it "version") is displayed within Pro-II.

However, its value (which I hardcoded for the moment) is not displayed within Pro-II. It is a bit cheeky, but I post the code of the class I use instead of CRealParameter.
Does anybody spot a big mistake? (I know it is far from being decently written, e.g. there's no need for min and max values, but I just copied the code from the implementation of CRealParameter and want it to work.)

Code: Select all

// RealStringParameter.h : Declaration of the CStringParameter

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

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

#include <float.h>

//! Real parameter class
/*!
  CAPE-OPEN class that implements a real parameter.
  A CAPE-OPEN parameter gives a reference to a ParameterSpecification 
  object; the parameter specification of this parameter is implemented
  by the parameter itself. So this object implements a real parameter, 
  a parameter specification and a real parameter specification 
  interface (and derives from the CAPE-OPEN base object to implement
  identification and error interfaces)
  
  We ensure that the current value of the parameter is always valid.
  This way the implementation of the Validation is trivial
*/

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:

    double minVal; /*!< the upper limit of this parameter, can be NaN */
    double maxVal; /*!< the lower limit of this parameter, can be NaN */
	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 */
    CVariant dimensionality; /*!< the dimensionality of this parameter */
    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 minVal the minimum value of the parameter
      \param maxVal the maximum value of the parameter
      \param defVal the default value of the parameter
      \param dimensionality the dimensionality of this parameter      
      \param valStatus points to the unit operation's validation status
      \sa CStringParameter()
    */
    
    static CComObject<CStringParameter> *CreateParameter(const OLECHAR *name,const OLECHAR *description, double minVal, double maxVal, BSTR *defVal, vector<double> dimensionality,CapeValidationStatus *valStatus, CapeParamMode ModeIn)
    {unsigned int i;
     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->minVal=minVal;
     p->maxVal=maxVal;
     p->defVal=p->value=*defVal;
     p->dimensionality.MakeArray((int)dimensionality.size(),VT_R8);
     p->valStatus=valStatus;
	 p->Mode = ModeIn;
     for (i=0;i<dimensionality.size();i++) p->dimensionality.SetDoubleAt(i,dimensionality[i]);
     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()

	// ICapeParameter Methods

	//! ICapeParameter::get_Specification
    /*!
      Return a reference to the object implementing the parameter spec. The parameter
      spec is implemented by this object, so we return a reference to ourselves
      \param spec [out, retval] receives the requested specification. Cannot be NULL.
    */

	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;
	}

	//! ICapeParameter::get_value
    /*!
      Return the current value of the parameter
      \param value [out, retval] receives the value. Cannot be NULL.
    */

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

	//! ICapeParameter::put_value
    /*!
      Sets the current value of the parameter
      \param value [in] the value. Only accepted if of proper data type and within bounds
    */

	STDMETHOD(put_value)(VARIANT value)
	{	//convert to a string
	    VARIANT v;
	    v.vt=VT_EMPTY;
	    if (FAILED(VariantChangeType(&v,&value,0,VT_BSTR))) //this should not be required; simulation environments should pass a VT_R8 value to begin with
	     {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=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;
	}

	//! ICapeParameter::get_ValStatus
    /*!
      Get the validation status. As the value is always valid, we return valid
      \param ValStatus [out, retval] receives the validation status. Cannot be NULL
    */

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

	//! ICapeParameter::get_Mode
    /*!
      Get the mode. We only implement input parameters in this unit operation
      \param Mode [out, retval] receives the mode. Cannot be NULL
    */

	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;
	}

	//! ICapeParameter::put_Mode
    /*!
      Set the mode. Not supported
      \param Mode [in] the mode
    */

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

	//! ICapeParameter::Validate
    /*!
      Validate the value of the parameter. As the value is always valid, we return OK.
      \param message [in, out] textual message in case of validation failure
      \param isOK [out, retval] validation result
    */

	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;
	}

	//! ICapeParameter::Reset
    /*!
      Resets the value of the parameter to its default value
    */

	STDMETHOD(Reset)()
	{	value=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;
	}

	// ICapeParameterSpec Methods

	//! ICapeParameterSpec::get_Type
    /*!
      Gets the type of this parameter. Always CAPE_REAL
      \param Type [out, retval] receives the type. Cannot be NULL.
    */

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

	//! ICapeParameterSpec::get_Dimensionality
    /*!
      Gets the dimensionality of this parameter. Order of values is 
      m, kg, S, A, K, mole, cd, rad, optionally followed by a delta indicator.
      All trailing zeroes can be ommited; the returned data is initialized in 
      CreateParameter.
      \param dim [out, retval] receives the dimensionality. Cannot be NULL.
      \sa CreateParameter()
    */

	STDMETHOD(get_Dimensionality)(VARIANT * dim)
	{	if (!dim) return E_POINTER; //not a valid pointer
	    *dim=dimensionality.Copy(); //caller must free the result
		return NOERROR;
	}

	// ICapeRealParameterSpec Methods

	//! ICapeRealParameterSpec::get_DefaultValue
    /*!
      Gets the default value of this parameter
      \param DefaultValue [out, retval] receives the default value. Cannot be NULL.
    */

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

	//! ICapeRealParameterSpec::get_LowerBound
    /*!
      Gets the lower bound of this parameter. If not defined, will return NaN
      \param lBound [out, retval] receives the lower bound. Cannot be NULL.
      \sa get_UpperBound()
    */

	STDMETHOD(get_LowerBound)(double * lBound)
	{	if (!lBound) return E_POINTER; //not a valid pointer
	    *lBound=minVal;
		return NOERROR;
	}

	//! ICapeRealParameterSpec::get_UpperBound
    /*!
      Gets the upper bound of this parameter. If not defined, will return NaN
      \param uBound [out, retval] receives the upper bound. Cannot be NULL.
      \sa get_LowerBound()
    */

	STDMETHOD(get_UpperBound)(double * uBound)
	{	if (!uBound) return E_POINTER; //not a valid pointer
	    *uBound=maxVal;
		return NOERROR;
	}

	//! ICapeRealParameterSpec::Validate
    /*!
      Validate whether a given number is ok for this parameter.
      \param value [in] the value to check
      \param message [in, out] receives a textual error message of the reason for a value not being valid
      \param isOK [out, retval] receives the validation result
    */


	// The following methods are just there to enable compilation: If absent, the class is abstract.
	STDMETHOD(get_OptionList)(VARIANT*)
	{
		return NOERROR;
	}
	STDMETHOD(get_RestrictedToList)(VARIANT_BOOL*)
	{
		return VARIANT_TRUE;
	}
	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;

//OBJECT_ENTRY_AUTO(__uuidof(RealParameter), CStringParameter)


User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Returning a string in c++ UO as UO Parameter

Post by jasper »

The get_value member returns the bstr as allocated by the class, which is then released (SysFreeString) as part of clearing the VARIANT. Instead of

Code: Select all

res.bstrVal=this->value;
you could perhaps use

Code: Select all

res.bstrVal=(this->value)?SysAllocString(tis->value):nullptr;
put_value has a similar problem, allocate a new string there; the one that you are copying is owned by the VARIANT argument. Also Reset has this issue. Alternatively you could make value and default value into a std::wstring - then only in get_value you have to worry about allocation (but be aware that null is a valid value for a BSTR, assigning it to a wstring will cause an access violation)

Best of course to remove all real related data.

Next, get_OptionList must do something sensible, particularly as get_RestrictedToList says the value is restricted to the option list.
SLiebschner
Posts: 19
Joined: 01 February 2022, 10:14

Re: Returning a string in c++ UO as UO Parameter

Post by SLiebschner »

Thanks for the prompt help!
1. I adapted the member functions get_value and put_value, but wouldn't know how to do this in Reset, since both value and defVal are class members.
2. Also I am a bit confused about

Code: Select all

 res.bstrVal=(this->value)?SysAllocString(this->value):nullptr; 
, as this assigns a pointer (not a BSTR) to res.bstrVal in case the value does not exist.
3. I tried std::wstring but got stuck in get_value, as I don't know to which value res.vt should then be assigned. Do I leave it VT_BSTR and cast the this->value of type std::wstring to BSTR?
4. Can you tell me how to make get_OptionList do something sensible? I wanted to return a std::list or array, but the return type is HRESULT. By the way, I don't want more functionality than necessary. Can I leave get_OptionList as is and simply change the output of get_RestrictedToList accordingly?
5. I kicked out unnecessary some legacy of CRealParameter, but I am not sure about certain lines, in particular the last one (commented out as it causes a compilation error:

Code: Select all

OBJECT_ENTRY_AUTO(__uuidof(RealParameter), CStringParameter)
) and the first class from which I inherit (

Code: Select all

public CComCoClass<CStringParameter, &CLSID_RealParameter>,
). Do you know whether those lines can savely be commented out or need to be changed?

The recent version of the code is this:

Code: Select all

// RealStringParameter.h : Declaration of the CStringParameter

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

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

#include <float.h>

//! Real parameter class
/*!
  CAPE-OPEN class that implements a real parameter.
  A CAPE-OPEN parameter gives a reference to a ParameterSpecification 
  object; the parameter specification of this parameter is implemented
  by the parameter itself. So this object implements a real parameter, 
  a parameter specification and a real parameter specification 
  interface (and derives from the CAPE-OPEN base object to implement
  identification and error interfaces)
  
  We ensure that the current value of the parameter is always valid.
  This way the implementation of the Validation is trivial
*/

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:

    double minVal; /*!< the upper limit of this parameter, can be NaN */
    double maxVal; /*!< the lower limit of this parameter, can be NaN */
	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 */
    CVariant dimensionality; /*!< the dimensionality of this parameter */
    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 minVal the minimum value of the parameter
      \param maxVal the maximum value of the parameter
      \param defVal the default value of the parameter
      \param dimensionality the dimensionality of this 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, vector<double> dimensionality, CapeValidationStatus *valStatus, CapeParamMode ModeIn)
    {unsigned int i;
     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=p->value=*defVal;
     p->dimensionality.MakeArray((int)dimensionality.size(),VT_R8);
     p->valStatus=valStatus;
	 p->Mode = ModeIn;
     for (i=0;i<dimensionality.size();i++) p->dimensionality.SetDoubleAt(i,dimensionality[i]);
     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()

	// ICapeParameter Methods

	//! ICapeParameter::get_Specification
    /*!
      Return a reference to the object implementing the parameter spec. The parameter
      spec is implemented by this object, so we return a reference to ourselves
      \param spec [out, retval] receives the requested specification. Cannot be NULL.
    */

	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;
	}

	//! ICapeParameter::get_value
    /*!
      Return the current value of the parameter
      \param value [out, retval] receives the value. Cannot be NULL.
    */

	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;
	}

	//! ICapeParameter::put_value
    /*!
      Sets the current value of the parameter
      \param value [in] the value. Only accepted if of proper data type and within bounds
    */

	STDMETHOD(put_value)(VARIANT value)
	{	//convert to a string
	    VARIANT v;
	    v.vt=VT_EMPTY;
	    if (FAILED(VariantChangeType(&v,&value,0,VT_BSTR))) //this should not be required; simulation environments should pass a VT_R8 value to begin with
	     {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;
	}

	//! ICapeParameter::get_ValStatus
    /*!
      Get the validation status. As the value is always valid, we return valid
      \param ValStatus [out, retval] receives the validation status. Cannot be NULL
    */

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

	//! ICapeParameter::get_Mode
    /*!
      Get the mode. We only implement input parameters in this unit operation
      \param Mode [out, retval] receives the mode. Cannot be NULL
    */

	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;
	}

	//! ICapeParameter::put_Mode
    /*!
      Set the mode. Not supported
      \param Mode [in] the mode
    */

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

	//! ICapeParameter::Validate
    /*!
      Validate the value of the parameter. As the value is always valid, we return OK.
      \param message [in, out] textual message in case of validation failure
      \param isOK [out, retval] validation result
    */

	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;
	}

	//! ICapeParameter::Reset
    /*!
      Resets the value of the parameter to its default value
    */

	STDMETHOD(Reset)()
	{	value=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;
	}

	// ICapeParameterSpec Methods

	//! ICapeParameterSpec::get_Type
    /*!
      Gets the type of this parameter. Always CAPE_REAL
      \param Type [out, retval] receives the type. Cannot be NULL.
    */

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

	//! ICapeParameterSpec::get_Dimensionality
    /*!
      Gets the dimensionality of this parameter. Order of values is 
      m, kg, S, A, K, mole, cd, rad, optionally followed by a delta indicator.
      All trailing zeroes can be ommited; the returned data is initialized in 
      CreateParameter.
      \param dim [out, retval] receives the dimensionality. Cannot be NULL.
      \sa CreateParameter()
    */

	STDMETHOD(get_Dimensionality)(VARIANT * dim)
	{	if (!dim) return E_POINTER; //not a valid pointer
	    *dim=dimensionality.Copy(); //caller must free the result
		return NOERROR;
	}

	// ICapeRealParameterSpec Methods

	//! ICapeRealParameterSpec::get_DefaultValue
    /*!
      Gets the default value of this parameter
      \param DefaultValue [out, retval] receives the default value. Cannot be NULL.
    */

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

	//! ICapeRealParameterSpec::Validate
    /*!
      Validate whether a given number is ok for this parameter.
      \param value [in] the value to check
      \param message [in, out] receives a textual error message of the reason for a value not being valid
      \param isOK [out, retval] receives the validation result
    */


	// The following methods are just there to enable compilation: If absent, the class is abstract.
	STDMETHOD(get_OptionList)(VARIANT*)
	{
		BSTR* mylist;
		mylist[0] = SysAllocString(L"x.y.z"); // output must be of type HRESULT

		return NOERROR;
	}
	STDMETHOD(get_RestrictedToList)(VARIANT_BOOL*)
	{
		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;

//OBJECT_ENTRY_AUTO(__uuidof(RealParameter), CStringParameter)


User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Returning a string in c++ UO as UO Parameter

Post by jasper »

1,2) A BSTR value is a pointer (a wchar_t*, to be precise), the value of which is either NULL, or something allocated by SysAllocString or similar functions, and needs to be released by SysFreeString. As the string is passed between applications, it is rather important that not both applications free the same string, so you are responsble for freeing and allocating the correct strings. In general, if COM arguments are marked as [in] you should not free the string (hence you should make a copy at put_value), if arguments are marked as [out] you allocate it but the other side releases it (hence again a copy is needed). If you have two internal variables of the BSTR type your class destructor should release the non-zero values. Hence they should probably not point to the same thing. Many applications use BSTR wrappers, similar to smart pointers, to keep track of this.

3) res.vt=VT_BSTR,
res.bstrVal=SysAllocStringLen(someWstr.c_str(),someWstr.size()) , or res.bstrVal=SysAllocString(someWstr.c_str())

note that the above is only correct for non-empty strings. Empty BSTRs should be nullptr/NULL.

4) making an option list generally causes a GUI to provide a drop down box with options, which is ok for input values. Output values typically do not have such a list of options. You can return an empty VARIANT (vt=VT_EMPTY) or a string array (vt=VT_ARRAY|VT_BSTR) - in the latter case you should put a SAFEARRAY in the parray member, the SAFEARRAY should be 1 dimensional, starting at index 0, and have BSTR members. All of this is allocated by your function, and released by the caller.

5) as the parameter cannot be directly created by the outside world, its CLSID does not matter, and you do not need an OBJECT_ENTRY_AUTO at all.
SLiebschner
Posts: 19
Joined: 01 February 2022, 10:14

Re: Returning a string in c++ UO as UO Parameter

Post by SLiebschner »

I think I followed your hints. Unfortunately, the results is unchanged: The parameter's value is not set. Do you have any other ideas?

Code: Select all

// RealStringParameter.h : Declaration of the CStringParameter

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

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

#include <float.h>

//! Real parameter class
/*!
  CAPE-OPEN class that implements a real parameter.
  A CAPE-OPEN parameter gives a reference to a ParameterSpecification 
  object; the parameter specification of this parameter is implemented
  by the parameter itself. So this object implements a real parameter, 
  a parameter specification and a real parameter specification 
  interface (and derives from the CAPE-OPEN base object to implement
  identification and error interfaces)
  
  We ensure that the current value of the parameter is always valid.
  This way the implementation of the Validation is trivial
*/

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:

    double minVal; /*!< the upper limit of this parameter, can be NaN */
    double maxVal; /*!< the lower limit of this parameter, can be NaN */
	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 */
    CVariant dimensionality; /*!< the dimensionality of this parameter */
    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 minVal the minimum value of the parameter
      \param maxVal the maximum value of the parameter
      \param defVal the default value of the parameter
      \param dimensionality the dimensionality of this 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, vector<double> dimensionality, CapeValidationStatus *valStatus, CapeParamMode ModeIn)
    {unsigned int i;
     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->dimensionality.MakeArray((int)dimensionality.size(),VT_R8);
     p->valStatus=valStatus;
	 p->Mode = ModeIn;
     for (i=0;i<dimensionality.size();i++) p->dimensionality.SetDoubleAt(i,dimensionality[i]);
     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()

	// ICapeParameter Methods

	//! ICapeParameter::get_Specification
    /*!
      Return a reference to the object implementing the parameter spec. The parameter
      spec is implemented by this object, so we return a reference to ourselves
      \param spec [out, retval] receives the requested specification. Cannot be NULL.
    */

	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;
	}

	//! ICapeParameter::get_value
    /*!
      Return the current value of the parameter
      \param value [out, retval] receives the value. Cannot be NULL.
    */

	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;
	}

	//! ICapeParameter::put_value
    /*!
      Sets the current value of the parameter
      \param value [in] the value. Only accepted if of proper data type and within bounds
    */

	STDMETHOD(put_value)(VARIANT value)
	{	//convert to a string
	    VARIANT v;
	    v.vt=VT_EMPTY;
	    if (FAILED(VariantChangeType(&v,&value,0,VT_BSTR))) //this should not be required; simulation environments should pass a VT_R8 value to begin with
	     {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;
	}

	//! ICapeParameter::get_ValStatus
    /*!
      Get the validation status. As the value is always valid, we return valid
      \param ValStatus [out, retval] receives the validation status. Cannot be NULL
    */

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

	//! ICapeParameter::get_Mode
    /*!
      Get the mode. We only implement input parameters in this unit operation
      \param Mode [out, retval] receives the mode. Cannot be NULL
    */

	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;
	}

	//! ICapeParameter::put_Mode
    /*!
      Set the mode. Not supported
      \param Mode [in] the mode
    */

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

	//! ICapeParameter::Validate
    /*!
      Validate the value of the parameter. As the value is always valid, we return OK.
      \param message [in, out] textual message in case of validation failure
      \param isOK [out, retval] validation result
    */

	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;
	}

	//! ICapeParameter::Reset
    /*!
      Resets the value of the parameter to its default value
    */

	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;
	}

	// ICapeParameterSpec Methods

	//! ICapeParameterSpec::get_Type
    /*!
      Gets the type of this parameter. Always CAPE_REAL
      \param Type [out, retval] receives the type. Cannot be NULL.
    */

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

	//! ICapeParameterSpec::get_Dimensionality
    /*!
      Gets the dimensionality of this parameter. Order of values is 
      m, kg, S, A, K, mole, cd, rad, optionally followed by a delta indicator.
      All trailing zeroes can be ommited; the returned data is initialized in 
      CreateParameter.
      \param dim [out, retval] receives the dimensionality. Cannot be NULL.
      \sa CreateParameter()
    */

	STDMETHOD(get_Dimensionality)(VARIANT * dim)
	{	if (!dim) return E_POINTER; //not a valid pointer
	    *dim=dimensionality.Copy(); //caller must free the result
		return NOERROR;
	}

	// ICapeRealParameterSpec Methods

	//! ICapeRealParameterSpec::get_DefaultValue
    /*!
      Gets the default value of this parameter
      \param DefaultValue [out, retval] receives the default value. Cannot be NULL.
    */

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

	//! ICapeRealParameterSpec::Validate
    /*!
      Validate whether a given number is ok for this parameter.
      \param value [in] the value to check
      \param message [in, out] receives a textual error message of the reason for a value not being valid
      \param isOK [out, retval] receives the validation result
    */


	// The following methods are just there to enable compilation: If absent, the class is abstract.
	STDMETHOD(get_OptionList)(VARIANT* v)
	{
		v->vt = VT_EMPTY;

		return NOERROR;
	}
	STDMETHOD(get_RestrictedToList)(VARIANT_BOOL*)
	{
		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;
The parameter is set elsewhere using

Code: Select all

parameterCollection = CCollection::CreateCollection(L"Parameter collection", L"Parameter collection for 1D-Solver");

...

StringParameterObject* string_par;
// version
BSTR defVal = SysAllocString(L"x.y.z");
string_par = StringParameterObject::CreateParameter(L"version", L"[-]", &defVal, dimensionality, &valStatus, CAPE_OUTPUT);
parameterCollection->AddItem(string_par); // parameter 42

...

StringParameterObject* string_par;
BSTR version = SysAllocString(L"x.y.z");
string_par = (StringParameterObject*)parameterCollection->items[42];
string_par->value = version;
parameterCollection->items[42] = string_par;
User avatar
jasper
Posts: 1129
Joined: 24 October 2012, 15:33
Location: Spain
Contact:

Re: Returning a string in c++ UO as UO Parameter

Post by jasper »

Does the parameter behave properly in e.g. COCO/COFE?
Post Reply

Return to “Unit Operations”