Returning a string in c++ UO as UO Parameter
Returning a string in c++ UO as UO Parameter
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
As far as I overlooked the c++ example UO I have not found a way for this issue.
Best,
Christoph
Re: Returning a string in c++ UO as UO Parameter
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, ...)?
Can you elaborate on the context? (CAPE-OPEN version, are you coding the unit operation yourself, ...)?
Re: Returning a string in c++ UO as UO Parameter
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.
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.
Re: Returning a string in c++ UO as UO Parameter
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)
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)
-
- Posts: 19
- Joined: 01 February 2022, 10:14
Re: Returning a string in c++ UO as UO Parameter
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.)
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)
Re: Returning a string in c++ UO as UO Parameter
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
you could perhaps use
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.
Code: Select all
res.bstrVal=this->value;
Code: Select all
res.bstrVal=(this->value)?SysAllocString(tis->value):nullptr;
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.
-
- Posts: 19
- Joined: 01 February 2022, 10:14
Re: Returning a string in c++ UO as UO Parameter
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, 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:) and the first class from which I inherit (). Do you know whether those lines can savely be commented out or need to be changed?
The recent version of the code is this:
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;
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)
Code: Select all
public CComCoClass<CStringParameter, &CLSID_RealParameter>,
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)
Re: Returning a string in c++ UO as UO Parameter
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.
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.
-
- Posts: 19
- Joined: 01 February 2022, 10:14
Re: Returning a string in c++ UO as UO Parameter
I think I followed your hints. Unfortunately, the results is unchanged: The parameter's value is not set. Do you have any other ideas?
The parameter is set elsewhere using
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;
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;
Re: Returning a string in c++ UO as UO Parameter
Does the parameter behave properly in e.g. COCO/COFE?