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
11 changes: 9 additions & 2 deletions comtypes/automation.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ class _tagBRECORD(Structure):
("VT_UI4", c_ulong),
("VT_UI8", c_ulonglong),
("VT_UINT", c_uint),
("scode", SCODE),
("VT_R4", c_float),
("VT_R8", c_double),
("VT_CY", c_longlong),
Expand Down Expand Up @@ -214,7 +215,11 @@ def __repr__(self):
return "VARIANT.empty"
elif self is type(self).missing:
return "VARIANT.missing"
return f"VARIANT(vt=0x{self.vt:x}, {self.value!r})"
elif self.vt == VT_ERROR:
value = self._.scode
else:
value = self.value
return f"VARIANT(vt=0x{self.vt:x}, {value!r})"

@classmethod
def from_param(cls, value):
Expand Down Expand Up @@ -454,6 +459,8 @@ def _get_value(self, dynamic=False):
return self._.VT_R8
elif vt == VT_BOOL:
return self._.VT_BOOL
elif vt == VT_ERROR:
return self
elif vt == VT_BSTR:
return self._.bstrVal
elif vt == VT_DATE:
Expand Down Expand Up @@ -533,7 +540,6 @@ def __getitem__(self, index):
return v.value

# these are missing:
# getter[VT_ERROR]
# getter[VT_ARRAY]
# getter[VT_BYREF|VT_UI1]
# getter[VT_BYREF|VT_I2]
Expand Down Expand Up @@ -602,6 +608,7 @@ def ChangeType(self, typecode):
VARIANT.missing = v = VARIANT()
v.vt = VT_ERROR
v._.VT_I4 = 0x80020004
assert v._.scode == hresult.DISP_E_PARAMNOTFOUND, "the shared 32-bit storage problem"
del v


Expand Down
9 changes: 9 additions & 0 deletions comtypes/test/test_excel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import unittest
from typing import ClassVar

from comtypes import automation, hresult
from comtypes.client import CreateObject, GetModule

################################################################
Expand Down Expand Up @@ -119,6 +120,14 @@ def test(self):
sh.Range[sh.Cells.Item[1, 1], sh.Cells.Item[3, 3]].Copy(sh.Cells.Item[4, 1])
sh.Range[sh.Cells.Item[4, 1], sh.Cells.Item[6, 3]].Select()

# Error Variant
err_rng = sh.Range("A1")
err_rng.Formula = "=1/0"
self.assertIsInstance(err_rng.Value(), automation.VARIANT) # type: ignore
self.assertEqual( # type: ignore
err_rng.Value()._.scode, hresult.signed32bithex_to_int("0x800A07D7")
)


PY_VER = "Python {0}.{1}.{2}".format(*sys.version_info[:3])

Expand Down
75 changes: 72 additions & 3 deletions comtypes/test/test_variant.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,51 @@
pointer,
)

from comtypes import GUID, IUnknown
from comtypes import GUID, IUnknown, hresult
from comtypes.automation import (
DISPPARAMS,
VARIANT,
VT_BLOB,
VT_BLOB_OBJECT,
VT_BSTR,
VT_BYREF,
VT_CARRAY,
VT_CF,
VT_CLSID,
VT_CY,
VT_DATE,
VT_DECIMAL,
VT_EMPTY,
VT_ERROR,
VT_FILETIME,
VT_HRESULT,
VT_I1,
VT_I2,
VT_I4,
VT_I8,
VT_ILLEGALMASKED,
VT_INT_PTR,
VT_LPSTR,
VT_LPWSTR,
VT_NULL,
VT_PTR,
VT_R4,
VT_R8,
VT_RESERVED,
VT_SAFEARRAY,
VT_STORAGE,
VT_STORED_OBJECT,
VT_STREAM,
VT_STREAMED_OBJECT,
VT_UI1,
VT_UI2,
VT_UI4,
VT_UI8,
VT_UINT_PTR,
VT_USERDEFINED,
VT_VECTOR,
VT_VERSIONED_STREAM,
VT_VOID,
)
from comtypes.test.find_memleak import find_memleak
from comtypes.typeinfo import LoadRegTypeLib
Expand All @@ -66,8 +89,8 @@ def test_constants(self):

missing = VARIANT.missing
self.assertEqual(missing.vt, VT_ERROR)
with self.assertRaises(NotImplementedError):
missing.value
self.assertIs(missing.value, missing)
self.assertEqual(missing._.scode, hresult.DISP_E_PARAMNOTFOUND)

def test_com_refcounts(self):
# typelib for oleaut32
Expand Down Expand Up @@ -253,9 +276,15 @@ def test_repr(self):
self.assertEqual(
repr(VARIANT(byref(c_int(42)))), "VARIANT(vt=0x4003, byref(42))"
)

def test_repr_error(self):
self.assertEqual(repr(VARIANT.empty), "VARIANT.empty")
self.assertEqual(repr(VARIANT.null), "VARIANT.null")
self.assertEqual(repr(VARIANT.missing), "VARIANT.missing")
v = VARIANT()
v.vt = VT_ERROR
v._.scode = hresult.DISP_E_MEMBERNOTFOUND
self.assertEqual(repr(v), f"VARIANT(vt=0xa, {hresult.DISP_E_MEMBERNOTFOUND})")


class ArrayTest(unittest.TestCase):
Expand All @@ -280,6 +309,46 @@ def test_int(self):
self.assertEqual(v.value, (1, 1, 1, 1))


class UnsupportedVariantTestCase(unittest.TestCase):
def _create_variant(self, vt: int) -> VARIANT:
variant = VARIANT()
variant.vt = vt
return variant

def test_value_unsupported_types(self):
unsupported_types = (
VT_VOID,
VT_HRESULT,
VT_PTR,
VT_SAFEARRAY,
VT_CARRAY,
VT_USERDEFINED,
VT_LPSTR,
VT_LPWSTR,
VT_INT_PTR,
VT_UINT_PTR,
VT_FILETIME,
VT_BLOB,
VT_STREAM,
VT_STORAGE,
VT_STREAMED_OBJECT,
VT_STORED_OBJECT,
VT_BLOB_OBJECT,
VT_CF,
VT_CLSID,
VT_VERSIONED_STREAM,
VT_VECTOR,
VT_RESERVED,
VT_ILLEGALMASKED,
)
for vt in unsupported_types:
variant = self._create_variant(vt)
with self.subTest(vt=vt), self.assertRaises(NotImplementedError):
variant.value
# Supress `Exception ignored while calling deallocator: <function tagVARIANT.__del__ at ...>`
variant.vt = VT_EMPTY


################################################################
def run_test(rep, msg, func=None, previous={}, results={}):
# items = [None] * rep
Expand Down
68 changes: 68 additions & 0 deletions comtypes/test/test_variant_error.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""VT_ERROR VARIANT round-trip tests via the native C++ COM test server."""

import unittest
from _ctypes import COMError

from comtypes import CLSCTX_LOCAL_SERVER, hresult
from comtypes.automation import VARIANT
from comtypes.client import CreateObject, GetModule

try:
GetModule(("{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}", 1, 0, 0))
from comtypes.gen.ComtypesCppTestSrvLib import (
CoVariantErrorTest,
IVariantErrorTest,
)

IMPORT_SERVER_FAILED = False
except (ImportError, OSError):
IMPORT_SERVER_FAILED = True


def _create_server() -> "IVariantErrorTest":
return CreateObject(
CoVariantErrorTest,
clsctx=CLSCTX_LOCAL_SERVER,
interface=IVariantErrorTest,
)


@unittest.skipIf(IMPORT_SERVER_FAILED, "Requires the C++ out-of-process COM server.")
class TestCreateVariantError(unittest.TestCase):
def setUp(self):
self.server = _create_server()

def test_returns_error_code(self):
for errcode in [
hresult.DISP_E_TYPEMISMATCH,
hresult.DISP_E_BADINDEX,
hresult.DISP_E_UNKNOWNNAME,
hresult.DISP_E_OVERFLOW,
]:
with self.subTest(errcode=errcode):
errvar = self.server.CreateVariantError(errcode)
self.assertEqual(errcode, errvar._.scode)


@unittest.skipIf(IMPORT_SERVER_FAILED, "Requires the C++ out-of-process COM server.")
class TestGetErrorCode(unittest.TestCase):
def setUp(self):
self.server = _create_server()

def test_raises_type_mismatch(self):
"""Passing a NON VT_ERROR VARIANT raises COMError(DISP_E_TYPEMISMATCH)."""
for value in [VARIANT(123), VARIANT("hello"), VARIANT()]:
with self.subTest(value=value):
with self.assertRaises(COMError) as cm:
self.server.GetErrorCode(value)
self.assertEqual(cm.exception.hresult, hresult.DISP_E_TYPEMISMATCH)

def test_returns_errorcode(self):
self.assertEqual(
self.server.GetErrorCode(VARIANT.missing), hresult.DISP_E_PARAMNOTFOUND
)
self.assertEqual(VARIANT.missing._.scode, hresult.DISP_E_PARAMNOTFOUND)


if __name__ == "__main__":
unittest.main()
68 changes: 68 additions & 0 deletions source/CppTestSrv/CoVariantErrorTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// CoVariantErrorTest.cpp
// Test double for VT_ERROR VARIANT marshalling.
//
// CreateVariantError: constructs a VARIANT entirely in native C++ — sets vt
// to VT_ERROR and scode to errorCode directly. This matches what VBA's
// CVErr() produces.
// GetErrorCode: reads the SCODE from a VT_ERROR VARIANT and returns it as a
// signed LONG. This matches what Excel VBA's CLng(myErrCell.Value).
#include <objbase.h>
#include "CoVariantErrorTest.h"


HRESULT CVariantErrorTest::CreateInstance(IUnknown* pUnknownOuter,
CUnknown** ppNewComponent)
{
if (pUnknownOuter != NULL)
{
return CLASS_E_NOAGGREGATION ;
}
*ppNewComponent = new CVariantErrorTest(pUnknownOuter) ;
return S_OK ;
}

HRESULT __stdcall CVariantErrorTest::NondelegatingQueryInterface(const IID& iid,
void** ppv)
{
if (iid == IID_IVariantErrorTest)
{
return FinishQI(static_cast<IVariantErrorTest*>(this), ppv) ;
}
return CUnknown::NondelegatingQueryInterface(iid, ppv) ;
}

// CreateVariantError
// Sets *result to VT_ERROR with the given SCODE.
HRESULT __stdcall CVariantErrorTest::CreateVariantError(LONG errorCode, VARIANT* result)
{
if (result == NULL)
{
return E_POINTER ;
}

VariantInit(result) ;

result->vt = VT_ERROR ;
result->scode = static_cast<SCODE>(errorCode) ;

return S_OK ;
}

// GetErrorCode
// Extracts the SCODE from a VT_ERROR VARIANT.
// Returns DISP_E_TYPEMISMATCH if the variant is not VT_ERROR.
HRESULT __stdcall CVariantErrorTest::GetErrorCode(VARIANT value, LONG* errorCode)
{
if (errorCode == NULL)
{
return E_POINTER ;
}

if (value.vt != VT_ERROR)
{
return DISP_E_TYPEMISMATCH ;
}

*errorCode = value.scode ;
return S_OK ;
}
20 changes: 20 additions & 0 deletions source/CppTestSrv/CoVariantErrorTest.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// CoVariantErrorTest.h
// Test double for VT_ERROR VARIANT marshalling.
#include "CUnknown.h"
#include "Iface.h"

class CVariantErrorTest : public CUnknown, public IVariantErrorTest
{
public:
static HRESULT CreateInstance(IUnknown* pUnknownOuter, CUnknown** ppNewComponent) ;

private:
DECLARE_IUNKNOWN
virtual HRESULT __stdcall NondelegatingQueryInterface(const IID& iid, void** ppv) ;

// Interface IVariantErrorTest
virtual HRESULT __stdcall CreateVariantError(LONG errorCode, VARIANT* result) ;
virtual HRESULT __stdcall GetErrorCode(VARIANT value, LONG* errorCode) ;

CVariantErrorTest(IUnknown* pUnknownOuter) : CUnknown(pUnknownOuter) {} ;
} ;
7 changes: 6 additions & 1 deletion source/CppTestSrv/MAKEFILE
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ guids.obj : guids.c
# Component/server source files
#

server.obj : server.cpp cunknown.h cfactory.h iface.h CoNumericSafearrayTest.h
server.obj : server.cpp cunknown.h cfactory.h iface.h CoNumericSafearrayTest.h CoVariantErrorTest.h
cl $(CPP_FLAGS) server.cpp

CoComtypesDispRecordParamTest.obj : CoComtypesDispRecordParamTest.cpp \
Expand All @@ -60,6 +60,10 @@ CoStringSafearrayInsideVariantTest.obj : CoStringSafearrayInsideVariantTest.cpp
CoStringSafearrayInsideVariantTest.h iface.h registry.h CUnknown.h
cl $(CPP_FLAGS) CoStringSafearrayInsideVariantTest.cpp

CoVariantErrorTest.obj : CoVariantErrorTest.cpp \
CoVariantErrorTest.h iface.h CUnknown.h
cl $(CPP_FLAGS) CoVariantErrorTest.cpp

#
# Helper classes
#
Expand Down Expand Up @@ -91,6 +95,7 @@ SERVER_OBJS = Server.obj \
CoComtypesDispSafearrayParamTest.obj \
CoNumericSafearrayTest.obj \
CoStringSafearrayInsideVariantTest.obj \
CoVariantErrorTest.obj \
Registry.obj \
Cfactory.obj \
Cunknown.obj \
Expand Down
Loading
Loading