Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Coming in build 311, as yet unreleased
* `win32com.server.exception.COMException`
* `win32comext.axscript.client.error.AXScriptException`
* `win32comext.axscript.client.pyscript.NamedScriptAttribute`
* Added initial `DECIMAL/VT_DECIMAL` support (#1501, @gesslerpd)

Build 310, released 2025/03/16
------------------------------
Expand Down
46 changes: 46 additions & 0 deletions com/TestSources/PyCOMTest/PyCOMImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,38 @@ HRESULT CPyCOMTest::AddCurrencies(CY v1, CY v2, CY *pret)
return S_OK;
}

HRESULT CPyCOMTest::DoubleDecimalByVal(DECIMAL *v)
{
// Define a DECIMAL value for 2
DECIMAL decFactor = {};
decFactor.scale = 0;
decFactor.sign = 0;
decFactor.Hi32 = 0;
decFactor.Mid32 = 0;
decFactor.Lo32 = 2;

DECIMAL result;
HRESULT hr = VarDecMul(v, &decFactor, &result);
if (FAILED(hr))
return hr;
*v = result;
return S_OK;
}

HRESULT CPyCOMTest::DoubleDecimal(DECIMAL v, DECIMAL *ret)
{
DECIMAL decFactor = {};
decFactor.scale = 0;
decFactor.sign = 0;
decFactor.Hi32 = 0;
decFactor.Mid32 = 0;
decFactor.Lo32 = 2;

return VarDecMul(&v, &decFactor, ret);
}

HRESULT CPyCOMTest::AddDecimals(DECIMAL v1, DECIMAL v2, DECIMAL *pret) { return VarDecAdd(&v1, &v2, pret); }

HRESULT CPyCOMTest::NotScriptable(int *val)
{
(*val)++;
Expand Down Expand Up @@ -968,6 +1000,20 @@ HRESULT CPyCOMTest::get_CurrencyProp(CY *ret)
return S_OK;
}

HRESULT CPyCOMTest::put_DecimalProp(DECIMAL val)
{
m_dec = val;
return S_OK;
}

HRESULT CPyCOMTest::get_DecimalProp(DECIMAL *ret)
{
if (!ret)
return E_POINTER;
*ret = (DECIMAL)m_dec;
return S_OK;
}

HRESULT CPyCOMTest::get_ParamProp(int which, int *ret)
{
if (!ret)
Expand Down
10 changes: 10 additions & 0 deletions com/TestSources/PyCOMTest/PyCOMImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class CPyCOMTest : public IDispatchImpl<IPyCOMTest, &IID_IPyCOMTest, &LIBID_PyCO
{
memset(m_rsArray, 0, nMaxSessions * sizeof(PyCOMTestSessionData));
m_cy.int64 = 0;
m_dec.sign = 0;
m_dec.scale = 0;
m_dec.Hi32 = 0;
m_dec.Lo64 = 0;
m_long = 0;
}
~CPyCOMTest();
Expand Down Expand Up @@ -91,6 +95,9 @@ class CPyCOMTest : public IDispatchImpl<IPyCOMTest, &IID_IPyCOMTest, &LIBID_PyCO
STDMETHOD(DoubleCurrency)(CY, CY *);
STDMETHOD(DoubleCurrencyByVal)(CY *);
STDMETHOD(AddCurrencies)(CY v1, CY v2, CY *);
STDMETHOD(DoubleDecimal)(DECIMAL, DECIMAL *);
STDMETHOD(DoubleDecimalByVal)(DECIMAL *);
STDMETHOD(AddDecimals)(DECIMAL v1, DECIMAL v2, DECIMAL *);

// method to broadcast a call on the current connections
STDMETHOD(Fire)(long nID);
Expand All @@ -114,6 +121,8 @@ class CPyCOMTest : public IDispatchImpl<IPyCOMTest, &IID_IPyCOMTest, &LIBID_PyCO
STDMETHOD(put_IntProp)(int val);
STDMETHOD(get_CurrencyProp)(CY *ret);
STDMETHOD(put_CurrencyProp)(CY val);
STDMETHOD(get_DecimalProp)(DECIMAL *ret);
STDMETHOD(put_DecimalProp)(DECIMAL val);
STDMETHOD(get_ParamProp)(int which, int *ret2);
STDMETHOD(put_ParamProp)(int which, int val);

Expand All @@ -140,6 +149,7 @@ class CPyCOMTest : public IDispatchImpl<IPyCOMTest, &IID_IPyCOMTest, &LIBID_PyCO
long m_long;
unsigned long m_ulong;
CY m_cy;
DECIMAL m_dec;
int m_paramprop1, m_paramprop2;
};

Expand Down
9 changes: 9 additions & 0 deletions com/TestSources/PyCOMTest/PyCOMTest.idl
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,13 @@ library PyCOMTestLib
HRESULT DoubleCurrency([in] CY v,
[out, retval] CY *ret );
HRESULT DoubleCurrencyByVal([in, out] CY *v);
HRESULT AddDecimals(
[in] DECIMAL v1,
[in] DECIMAL v2,
[out, retval] DECIMAL* pResult);
HRESULT DoubleDecimal([in] DECIMAL v,
[out, retval] DECIMAL *ret );
HRESULT DoubleDecimalByVal([in, out] DECIMAL *v);
// Some test properties
[propget] HRESULT LongProp([out, retval] long* retval);
[propput] HRESULT LongProp([in] long val);
Expand All @@ -294,6 +301,8 @@ library PyCOMTestLib
[propput] HRESULT IntProp([in] int val);
[propget] HRESULT CurrencyProp([out, retval] CY* retval);
[propput] HRESULT CurrencyProp([in] CY val);
[propget] HRESULT DecimalProp([out, retval] DECIMAL* retval);
[propput] HRESULT DecimalProp([in] DECIMAL val);
[propget] HRESULT ParamProp([in] int which, [out, retval] int *retval);
[propput] HRESULT ParamProp([in] int which, [in]int val);
// reserved words etc
Expand Down
155 changes: 155 additions & 0 deletions com/win32com/src/PyComHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,161 @@ PYCOM_EXPORT BOOL PyObject_AsCurrency(PyObject *ob, CURRENCY *pcy)
return TRUE;
}

PYCOM_EXPORT PyObject *PyObject_FromDecimal(DECIMAL &dec)
{
// init Decimal_class
if (!Decimal_class && !(Decimal_class = get_Decimal_class())) {
return NULL;
}

// assemble 128‑bit integer: (Hi32 << 64) + Lo64
PyObject *hi = PyLong_FromUnsignedLong(dec.Hi32);
if (!hi) {
return NULL;
}

PyObject *sh = PyLong_FromLong(64);
if (!sh) {
Py_DECREF(hi);
return NULL;
}

PyObject *hi_shift = PyNumber_Lshift(hi, sh);
Py_DECREF(hi);
Py_DECREF(sh);
if (!hi_shift) {
return NULL;
}

PyObject *lo = PyLong_FromUnsignedLongLong(dec.Lo64);
if (!lo) {
Py_DECREF(hi_shift);
return NULL;
}

PyObject *big = PyNumber_Add(hi_shift, lo);
Py_DECREF(hi_shift);
Py_DECREF(lo);
if (!big) {
return NULL;
}

// call Decimal(big)
PyObject *res = PyObject_CallFunctionObjArgs(Decimal_class, big, NULL);
Py_DECREF(big);
if (!res) {
return NULL;
}

// apply scale
if (dec.scale > 0) {
PyObject *tmp = PyObject_CallMethod(res, "scaleb", "l", -dec.scale);
Py_DECREF(res);
if (!tmp) {
return NULL;
}
res = tmp;
}

// apply sign
if (dec.sign > 0) {
PyObject *tmp = PyObject_CallMethod(res, "__mul__", "l", -1);
Py_DECREF(res);
if (!tmp) {
return NULL;
}
res = tmp;
}

return res;
}

PYCOM_EXPORT BOOL PyObject_AsDecimal(PyObject *ob, DECIMAL *pdec)
{
if (Decimal_class == NULL) {
Decimal_class = get_Decimal_class();
if (Decimal_class == NULL)
return FALSE;
}

int right_type = PyObject_IsInstance(ob, Decimal_class);
if (right_type == -1)
return FALSE;
else if (right_type == 0) {
PyErr_Format(PyExc_TypeError, "DECIMAL object must be a Decimal instance (got %s).", ob->ob_type->tp_name);
return FALSE;
}

// get (sign, digits, exponent)
PyObject *tup = PyObject_CallMethod(ob, "as_tuple", NULL);
if (!tup) {
return FALSE;
}
if (!PyTuple_Check(tup) || PyTuple_Size(tup) != 3) {
Py_DECREF(tup);
PyErr_SetString(PyExc_TypeError, "Decimal.as_tuple() did not return 3‐tuple");
return FALSE;
}

// extract sign and exponent
long sign = PyLong_AsLong(PyTuple_GET_ITEM(tup, 0));
long exp = PyLong_AsLong(PyTuple_GET_ITEM(tup, 2));
if (PyErr_Occurred()) {
Py_DECREF(tup);
return FALSE;
}

unsigned char scale = (exp < 0) ? (unsigned char)(-exp) : 0;

PyObject *val;
if (scale > 0) {
val = PyObject_CallMethod(ob, "scaleb", "l", scale);
if (!val) {
Py_DECREF(tup);
return FALSE;
}
}
else {
val = ob;
Py_INCREF(val);
}

TmpPyObject mant = PyNumber_Long(val);
Py_DECREF(val);
if (!mant) {
Py_DECREF(tup);
return FALSE;
}

unsigned long long lo64 = PyLong_AsUnsignedLongLong(mant);
if (PyErr_Occurred()) {
Py_DECREF(tup);
return FALSE;
}

TmpPyObject shamt = PyLong_FromLong(64);
TmpPyObject high = PyNumber_Rshift(mant, shamt);
if (!high) {
Py_DECREF(tup);
return FALSE;
}

unsigned long hi32 = PyLong_AsUnsignedLong(high);
if (PyErr_Occurred()) {
Py_DECREF(tup);
return FALSE;
}

Py_DECREF(tup);

pdec->wReserved = 0;
pdec->scale = scale;
pdec->sign = (unsigned char)sign;
pdec->Hi32 = hi32;
pdec->Lo64 = lo64;
return TRUE;
}

// If PyCom_PyObjectFromIUnknown is called with bAddRef==FALSE, the
// caller is asking us to take ownership of the COM reference. If we
// fail to create a Python object, we must release the reference.
Expand Down
5 changes: 5 additions & 0 deletions com/win32com/src/include/PythonCOM.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,10 @@ PYCOM_EXPORT BOOL PyCom_PyObjectAsSTATPROPSETSTG(PyObject *, STATPROPSETSTG *);
PYCOM_EXPORT PyObject *PyObject_FromCurrency(CURRENCY &cy);
PYCOM_EXPORT BOOL PyObject_AsCurrency(PyObject *ob, CURRENCY *pcy);

// Decimal support.
PYCOM_EXPORT PyObject *PyObject_FromDecimal(DECIMAL &dec);
PYCOM_EXPORT BOOL PyObject_AsDecimal(PyObject *ob, DECIMAL *pdec);

// OLEMENUGROUPWIDTHS are used by axcontrol, shell, etc
PYCOM_EXPORT BOOL PyObject_AsOLEMENUGROUPWIDTHS(PyObject *oblpMenuWidths, OLEMENUGROUPWIDTHS *pWidths);
PYCOM_EXPORT PyObject *PyObject_FromOLEMENUGROUPWIDTHS(const OLEMENUGROUPWIDTHS *pWidths);
Expand Down Expand Up @@ -692,6 +696,7 @@ class PYCOM_EXPORT PythonOleArgHelper {
VARIANT *m_varBuf;
DATE m_dateBuf;
CY m_cyBuf;
DECIMAL m_decBuf;
};
};

Expand Down
27 changes: 26 additions & 1 deletion com/win32com/src/oleargs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ BOOL PyCom_VariantFromPyObject(PyObject *obj, VARIANT *var)
}
// Decimal class from new _decimal module in Python 3.3 shows different name
else if (strcmp(obj->ob_type->tp_name, "Decimal") == 0 || strcmp(obj->ob_type->tp_name, "decimal.Decimal") == 0) {
// VT_DECIMAL supports more precision here, use in error case? leave existing behavior for now
if (!PyObject_AsCurrency(obj, &V_CY(var)))
return FALSE;
V_VT(var) = VT_CY;
Expand Down Expand Up @@ -482,6 +483,10 @@ PyObject *PyCom_PyObjectFromVariant(const VARIANT *var)
result = PyObject_FromCurrency(varValue.cyVal);
break;

case VT_DECIMAL:
result = PyObject_FromDecimal(varValue.decVal);
break;

case VT_RECORD: {
ULONG cb;
V_RECORDINFO(&varValue)->GetSize(&cb);
Expand Down Expand Up @@ -872,6 +877,14 @@ static PyObject *PyCom_PyObjectFromSAFEARRAYDimensionItem(SAFEARRAY *psa, VARENU
subitem = PyObject_FromCurrency(c);
break;
}
case VT_DECIMAL: {
DECIMAL dec;
hres = SafeArrayGetElement(psa, arrayIndices, &dec);
if (FAILED(hres))
break;
subitem = PyObject_FromDecimal(dec);
break;
}
case VT_DATE: {
DATE dt;
hres = SafeArrayGetElement(psa, arrayIndices, &dt);
Expand Down Expand Up @@ -923,7 +936,6 @@ static PyObject *PyCom_PyObjectFromSAFEARRAYDimensionItem(SAFEARRAY *psa, VARENU
subitem = PyCom_PyObjectFromIUnknown(pUnk, IID_IUnknown, FALSE);
break;
}
// case VT_DECIMAL
// case VT_RECORD

case VT_I1:
Expand Down Expand Up @@ -1605,6 +1617,19 @@ BOOL PythonOleArgHelper::MakeObjToVariant(PyObject *obj, VARIANT *var, PyObject
else
V_CYREF(var)->int64 = 0;
break;
case VT_DECIMAL:
rc = PyObject_AsDecimal(obj, &V_DECIMAL(var));
break;
case VT_DECIMAL | VT_BYREF:
if (bCreateBuffers)
V_DECIMALREF(var) = &m_decBuf;
if (!VALID_BYREF_MISSING(obj)) {
if (!PyObject_AsDecimal(obj, V_DECIMALREF(var)))
BREAK_FALSE;
}
else
memset(V_DECIMALREF(var), 0, sizeof(DECIMAL));
break;
default:
// could try default, but this error indicates we need to
// beef up the VARIANT support, rather than default.
Expand Down
Loading