diff --git a/comtypes/test/test_compatibility_tlbparser_VT_I1_assignment.py b/comtypes/test/test_compatibility_tlbparser_VT_I1_assignment.py new file mode 100644 index 00000000..64aee53f --- /dev/null +++ b/comtypes/test/test_compatibility_tlbparser_VT_I1_assignment.py @@ -0,0 +1,566 @@ +"""Compare the VT_I1 mappings across the native COM boundary. + +Mapping and compatibility discussion: https://github.com/enthought/comtypes/issues/969 +""" + +import unittest +from ctypes import ( + HRESULT, + POINTER, + ArgumentError, + byref, + c_byte, + c_char, + c_char_p, + c_ubyte, + c_void_p, + cast, + create_string_buffer, + pointer, +) +from typing import Any, Protocol + +from comtypes import CLSCTX_LOCAL_SERVER, COMMETHOD, GUID, IUnknown +from comtypes.client import CreateObject, GetModule +from comtypes.malloc import _CoTaskMemFree + +try: + GetModule(("{07D2AEE5-1DF8-4D2C-953A-554ADFD25F99}", 1, 0, 0)) + from comtypes.gen import ComtypesCppTestSrvLib + + IMPORT_SERVER_FAILED = False +except (ImportError, OSError): + IMPORT_SERVER_FAILED = True + + +STRING = c_char_p + +IID_IByteCompatibilityTest = GUID("{047E34C1-33A8-4DAA-A1E5-B941756A8319}") +PROPOSED_COMMETHODS = [ + COMMETHOD( + [], + HRESULT, + "EchoInt8", + (["in"], c_byte, "value"), + (["out", "retval"], POINTER(c_byte), "result"), + ), + COMMETHOD( + [], + HRESULT, + "EchoUint8", + (["in"], c_ubyte, "value"), + (["out", "retval"], POINTER(c_ubyte), "result"), + ), + COMMETHOD( + [], + HRESULT, + "EchoLpStr", + (["in"], STRING, "value"), + (["out", "retval"], POINTER(STRING), "result"), + ), + COMMETHOD( + [], + HRESULT, + "ReadCharPointer", + (["in"], POINTER(c_byte), "value"), + (["out", "retval"], POINTER(c_ubyte), "result"), + ), + COMMETHOD( + [], + HRESULT, + "GetCharacterBytes", + (["in"], c_ubyte, "value"), + (["out", "retval"], POINTER(c_byte), "result"), + ), +] +LEGACY_COMMETHODS = [ + COMMETHOD( + [], + HRESULT, + "EchoInt8", + (["in"], c_char, "value"), + (["out", "retval"], STRING, "result"), + ), + COMMETHOD( + [], + HRESULT, + "EchoUint8", + (["in"], c_ubyte, "value"), + (["out", "retval"], POINTER(c_ubyte), "result"), + ), + COMMETHOD( + [], + HRESULT, + "EchoLpStr", + (["in"], STRING, "value"), + (["out", "retval"], POINTER(STRING), "result"), + ), + COMMETHOD( + [], + HRESULT, + "ReadCharPointer", + (["in"], STRING, "value"), + (["out", "retval"], POINTER(c_ubyte), "result"), + ), + COMMETHOD( + [], + HRESULT, + "GetCharacterBytes", + (["in"], c_ubyte, "value"), + (["out", "retval"], STRING, "result"), + ), +] + +# The generated definitions are checked in TestGeneratedMapping below. + + +class _PByteCompatibilityTest(Protocol): + def EchoInt8(self, value: Any) -> Any: ... + def EchoUint8(self, value: Any) -> Any: ... + def EchoLpStr(self, value: Any) -> Any: ... + def ReadCharPointer(self, value: Any) -> Any: ... + def GetCharacterBytes(self, value: Any) -> Any: ... + + +def create_legacy_automatic_int8_output() -> type[_PByteCompatibilityTest]: + class IByteCompatibilityTest(IUnknown): + _case_insensitive_ = True + _iid_ = IID_IByteCompatibilityTest + _idlflags_ = ["oleautomation"] + _methods_ = LEGACY_COMMETHODS + + def EchoLpStr(self, value): + result = c_char_p() + try: + self.__com_EchoLpStr(value, byref(result)) + return result.value + finally: + _CoTaskMemFree(cast(result, c_void_p)) + + def GetCharacterBytes(self, value): + result = create_string_buffer(2) + self.__com_GetCharacterBytes(value, result) + return result.value + + return IByteCompatibilityTest + + +def create_legacy_buffered_int8_output() -> type[_PByteCompatibilityTest]: + class IByteCompatibilityTest(create_legacy_automatic_int8_output()): + def EchoInt8(self, value): + # STRING cannot allocate a scalar output. Supply character storage + # and retain one byte, including a possible NUL value. + result = create_string_buffer(2) + self._IByteCompatibilityTest__com_EchoInt8(value, result) + return result.raw[:1] + + return IByteCompatibilityTest + + +def create_proposed_itf() -> type[_PByteCompatibilityTest]: + class IByteCompatibilityTest(IUnknown): + _case_insensitive_ = True + _iid_ = IID_IByteCompatibilityTest + _idlflags_ = ["oleautomation"] + _methods_ = PROPOSED_COMMETHODS + + def EchoLpStr(self, value): + result = c_char_p() + try: + self.__com_EchoLpStr(value, byref(result)) + return result.value + finally: + _CoTaskMemFree(cast(result, c_void_p)) + + return IByteCompatibilityTest + + +def create_proposed_char_ptr_output_buf() -> type[_PByteCompatibilityTest]: + class IByteCompatibilityTest(IUnknown): + _case_insensitive_ = True + _iid_ = IID_IByteCompatibilityTest + _idlflags_ = ["oleautomation"] + _methods_ = PROPOSED_COMMETHODS + + def EchoLpStr(self, value): + result = c_char_p() + try: + self.__com_EchoLpStr(value, byref(result)) + return result.value + finally: + _CoTaskMemFree(cast(result, c_void_p)) + + def GetCharacterBytes(self, value): + result = pointer(c_byte()) + self.__com_GetCharacterBytes(value, result) + return result.contents.value + + return IByteCompatibilityTest + + +def create_proposed_char_ref_output_buf() -> type[_PByteCompatibilityTest]: + class IByteCompatibilityTest(IUnknown): + _case_insensitive_ = True + _iid_ = IID_IByteCompatibilityTest + _idlflags_ = ["oleautomation"] + _methods_ = PROPOSED_COMMETHODS + + def EchoLpStr(self, value): + result = c_char_p() + try: + self.__com_EchoLpStr(value, byref(result)) + return result.value + finally: + _CoTaskMemFree(cast(result, c_void_p)) + + def GetCharacterBytes(self, value): + result = c_byte() + self.__com_GetCharacterBytes(value, byref(result)) + return result.value + + return IByteCompatibilityTest + + +def create_proposed_arr_output_buf() -> type[_PByteCompatibilityTest]: + class IByteCompatibilityTest(IUnknown): + _case_insensitive_ = True + _iid_ = IID_IByteCompatibilityTest + _idlflags_ = ["oleautomation"] + _methods_ = PROPOSED_COMMETHODS + + def EchoLpStr(self, value): + result = c_char_p() + try: + self.__com_EchoLpStr(value, byref(result)) + return result.value + finally: + _CoTaskMemFree(cast(result, c_void_p)) + + def GetCharacterBytes(self, value): + result = (c_byte * 1)() + self.__com_GetCharacterBytes(value, result) + return result[0] + + return IByteCompatibilityTest + + +def create_server(interface: type[_PByteCompatibilityTest]) -> _PByteCompatibilityTest: + return CreateObject( + ComtypesCppTestSrvLib.CoByteCompatibilityTest, + clsctx=CLSCTX_LOCAL_SERVER, + interface=interface, + ) + + +@unittest.skipIf(IMPORT_SERVER_FAILED, "Requires the C++ out-of-process COM server.") +class ByteCompatibilityTest(unittest.TestCase): + def setUp(self): + self.legacy_automatic = create_server(create_legacy_automatic_int8_output()) + self.legacy_buffered = create_server(create_legacy_buffered_int8_output()) + self.proposed_automatic = create_server(create_proposed_itf()) + self.proposed_pointer = create_server(create_proposed_char_ptr_output_buf()) + self.proposed_reference = create_server(create_proposed_char_ref_output_buf()) + self.proposed_array = create_server(create_proposed_arr_output_buf()) + self.generated = create_server(ComtypesCppTestSrvLib.IByteCompatibilityTest) + + +class TestGeneratedMapping(ByteCompatibilityTest): + def test_generated_mapping(self): + self.assertEqual( + ComtypesCppTestSrvLib.IByteCompatibilityTest._methods_, LEGACY_COMMETHODS + ) + # The proposed mapping would change Python argument/result types. + self.assertNotEqual( + ComtypesCppTestSrvLib.IByteCompatibilityTest._methods_, PROPOSED_COMMETHODS + ) + + def test_echo_int8_raises_type_error(self): + # The current generated STRING retval cannot allocate a scalar result. + for value in (b"\x80", b"\xf9", b"\xff", b"\0", b"\x01", b"\x7f"): + with self.subTest(value=value): + with self.assertRaises(TypeError): + self.generated.EchoInt8(value) + + def test_echo_int8_raises_argument_error(self): + for value in (-128, -7, -1): + with self.subTest(value=value): + # Supply the output buffer so its automatic allocation error + # does not hide validation of the c_char input argument. + result = create_string_buffer(2) + with self.assertRaises(ArgumentError): + self.generated._IByteCompatibilityTest__com_EchoInt8(value, result) + + def test_echo_int8_with_explicit_storage(self): + for value, expected in ( + (b"\x80", b"\x80"), + (b"\xf9", b"\xf9"), + (b"\xff", b"\xff"), + (0, b"\0"), + (1, b"\x01"), + (127, b"\x7f"), + ): + with self.subTest(value=value): + result = create_string_buffer(2) + self.generated._IByteCompatibilityTest__com_EchoInt8(value, result) + self.assertEqual(result.raw[:1], expected) + + def test_read_char_pointer_takes_bytes(self): + for value, expected in ( + (b"\0", 0), + (b"h", 104), + (b"\x80", 128), + (b"\xff", 255), + ): + with self.subTest(value=value): + self.assertEqual(self.generated.ReadCharPointer(value), expected) + + def test_read_char_pointer_rejects_signed_byte_storage(self): + for argument in (pointer(c_byte(104)), byref(c_byte(104)), (c_byte * 1)(104)): + with self.subTest(argument=argument): + with self.assertRaises(ArgumentError): + self.generated.ReadCharPointer(argument) + + def test_get_character_bytes_raises_type_error(self): + with self.assertRaises(TypeError): + self.generated.GetCharacterBytes(104) + + def test_get_character_bytes_with_explicit_storage(self): + for value, expected in ( + (0, b"\0"), + (104, b"h"), + (128, b"\x80"), + (255, b"\xff"), + ): + with self.subTest(value=value): + result = create_string_buffer(2) + self.generated._IByteCompatibilityTest__com_GetCharacterBytes( + value, result + ) + self.assertEqual(result.raw[:1], expected) + + def test_uint8_unchanged(self): + for value in (0, 1, 128, 255): + with self.subTest(value=value): + self.assertEqual(self.generated.EchoUint8(value), value) + + def test_lpstr_unchanged(self): + for value in (b"", b"hello", b"\x80\xff"): + with self.subTest(value=value): + result = c_char_p() + try: + self.generated._IByteCompatibilityTest__com_EchoLpStr( + value, byref(result) + ) + self.assertEqual(result.value, value) + finally: + _CoTaskMemFree(cast(result, c_void_p)) + + +class TestEchoInt8(ByteCompatibilityTest): + def test_takes_and_returns_as_intended(self): + for value, expected in ( + (b"\x80", b"\x80"), + (b"\xf9", b"\xf9"), + (b"\xff", b"\xff"), + (b"\0", b"\0"), + (b"\x01", b"\x01"), + (b"\x7f", b"\x7f"), + ): + with self.subTest(mapping="legacy_buffered", value=value): + self.assertEqual(self.legacy_buffered.EchoInt8(value), expected) + for name, server in [ + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + for value, expected in ( + (-128, -128), + (-7, -7), + (-1, -1), + (0, 0), + (1, 1), + (127, 127), + ): + with self.subTest(mapping=name, value=value): + self.assertEqual(server.EchoInt8(value), expected) + + def test_raises_type_error(self): + for value in (b"\x80", b"\xf9", b"\xff", b"\0", b"\x01", b"\x7f"): + with self.subTest(mapping="legacy_automatic", value=value): + with self.assertRaises(TypeError): + self.legacy_automatic.EchoInt8(value) + + def test_raises_argument_error(self): + for name, server in [ + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + for value in (b"\x80", b"h", b"\xff"): + with self.subTest(mapping=name, value=value): + with self.assertRaises(ArgumentError): + server.EchoInt8(value) + + +class TestEchoUint8(ByteCompatibilityTest): + def test_takes_and_returns_as_intended(self): + for name, server in [ + ("legacy_automatic", self.legacy_automatic), + ("legacy_buffered", self.legacy_buffered), + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + for value, expected in ((0, 0), (1, 1), (42, 42), (128, 128), (255, 255)): + with self.subTest(mapping=name, value=value): + self.assertEqual(server.EchoUint8(value), expected) + + def test_raises_argument_error(self): + for name, server in [ + ("legacy_automatic", self.legacy_automatic), + ("legacy_buffered", self.legacy_buffered), + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + with self.subTest(mapping=name): + with self.assertRaises(ArgumentError): + server.EchoUint8(object()) + + +class TestEchoLpStr(ByteCompatibilityTest): + def test_takes_and_returns_as_intended(self): + # Use helpers that free the native CoTaskMem allocation after copying it. + for name, server in [ + ("legacy_automatic", self.legacy_automatic), + ("legacy_buffered", self.legacy_buffered), + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + for value, expected in ( + (b"", b""), + (b"hello", b"hello"), + (b"\x80\xff", b"\x80\xff"), + ): + with self.subTest(mapping=name, value=value): + self.assertEqual(server.EchoLpStr(value), expected) + + def test_raises_argument_error(self): + for name, server in [ + ("legacy_automatic", self.legacy_automatic), + ("legacy_buffered", self.legacy_buffered), + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + with self.subTest(mapping=name): + with self.assertRaises(ArgumentError): + server.EchoLpStr(object()) + + +class TestReadCharPointer(ByteCompatibilityTest): + def test_takes_and_returns_as_intended(self): + for name, server in [ + ("legacy_automatic", self.legacy_automatic), + ("legacy_buffered", self.legacy_buffered), + ]: + for value, expected in ( + (b"\0", 0), + (b"h", 104), + (b"\x80", 128), + (b"\xff", 255), + ): + with self.subTest(mapping=name, value=value): + self.assertEqual(server.ReadCharPointer(value), expected) + for name, server in [ + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + for value, expected in ((0, 0), (104, 104), (-128, 128), (-1, 255)): + for argument in ( + pointer(c_byte(value)), + byref(c_byte(value)), + (c_byte * 1)(value), + ): + with self.subTest(mapping=name, value=value, argument=argument): + self.assertEqual(server.ReadCharPointer(argument), expected) + + def test_raises_argument_error(self): + for name, server in [ + ("legacy_automatic", self.legacy_automatic), + ("legacy_buffered", self.legacy_buffered), + ]: + for argument in ( + pointer(c_byte(104)), + byref(c_byte(104)), + (c_byte * 1)(104), + ): + with self.subTest(mapping=name, argument=argument): + with self.assertRaises(ArgumentError): + server.ReadCharPointer(argument) + # Handwritten proposed interfaces reject the legacy bytes input. + # The generated interface still uses the legacy mapping, tested above. + for name, server in [ + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + for value in (b"\0", b"h", b"\x80", b"\xff"): + with self.subTest(mapping=name, value=value): + with self.assertRaises(ArgumentError): + server.ReadCharPointer(value) + + +class TestGetCharacterBytes(ByteCompatibilityTest): + def test_takes_and_returns_as_intended(self): + for name, server in [ + ("legacy_automatic", self.legacy_automatic), + ("legacy_buffered", self.legacy_buffered), + ]: + for value, expected in ( + (0, b""), + (1, b"\x01"), + (104, b"h"), + (128, b"\x80"), + (255, b"\xff"), + ): + with self.subTest(mapping=name, value=value): + self.assertEqual(server.GetCharacterBytes(value), expected) + for name, server in [ + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + for value, expected in ((0, 0), (1, 1), (104, 104), (128, -128), (255, -1)): + with self.subTest(mapping=name, value=value): + self.assertEqual(server.GetCharacterBytes(value), expected) + # The new signed numeric result is not the old bytes value. + self.assertNotEqual(server.GetCharacterBytes(value), bytes([value])) + + def test_raises_argument_error(self): + for name, server in [ + ("proposed_automatic", self.proposed_automatic), + ("proposed_pointer", self.proposed_pointer), + ("proposed_reference", self.proposed_reference), + ("proposed_array", self.proposed_array), + ]: + with self.subTest(mapping=name): + result = create_string_buffer(2) + # Legacy character storage is incompatible with POINTER(c_byte). + with self.assertRaises(ArgumentError): + server._IByteCompatibilityTest__com_GetCharacterBytes(104, result) + + +if __name__ == "__main__": + unittest.main() diff --git a/comtypes/test/test_cpp_safearray.py b/comtypes/test/test_cpp_safearray.py index 1c8e77d2..47020002 100644 --- a/comtypes/test/test_cpp_safearray.py +++ b/comtypes/test/test_cpp_safearray.py @@ -141,16 +141,37 @@ def test_ndarray_values_2d(self): self.assertEqual(method(2, 6), expected) def test_int8_codegen_1d(self): - # VT_I1 is currently parsed as c_char (see #935), whose pointer slice + # VT_I1 is currently parsed as c_char (see issue #969), whose pointer slice # returns bytes. Check the real COM/code-generator path and raw bytes; # signed int8/NumPy dtype assertions await the separate c_char/c_byte fix. result = self.server.GetInt8Array(1, 6) self.assertEqual(result, (249, 0, 42, 249, 0, 42)) + self.assertNotEqual(result, (-7, 0, 42, -7, 0, 42)) def test_int8_codegen_2d(self): # As above, 249 is the raw byte for -7, not a signed dtype assertion. result = self.server.GetInt8Array(2, 6) self.assertEqual(result, ((249, 42, 0), (0, 249, 42))) + self.assertNotEqual(result, ((-7, 42, 0), (0, -7, 42))) + + @unittest.skipIf(IMPORT_NUMPY_FAILED, "Requires NumPy.") + def test_int8_codegen_ndarray_1d_is_byte_string_scalar(self): + # c_char pointer slicing produces bytes, not signed numeric elements. + # This records the current problem; the mapping decision is in #969. + with safearray_as_ndarray: + result = self.server.GetInt8Array(1, 6) + self.assertIsInstance(result, numpy.ndarray) + self.assertEqual(result.shape, ()) + self.assertEqual(result.dtype.kind, "S") + self.assertEqual(result.item(), b"\xf9\0*\xf9\0*") + self.assertNotEqual(result.dtype, numpy.dtype("int8")) + + @unittest.skipIf(IMPORT_NUMPY_FAILED, "Requires NumPy.") + def test_int8_codegen_ndarray_2d_raises_value_error(self): + # A single byte-string scalar cannot be reshaped to the native 2x3 array. + with safearray_as_ndarray: + with self.assertRaises(ValueError): + self.server.GetInt8Array(2, 6) if __name__ == "__main__": diff --git a/comtypes/tools/tlbparser.py b/comtypes/tools/tlbparser.py index cad940d7..49d173ed 100644 --- a/comtypes/tools/tlbparser.py +++ b/comtypes/tools/tlbparser.py @@ -21,6 +21,7 @@ def PTR(typ): # basic C data types, with size and alignment in bits char_type = typedesc.FundamentalType("char", 8, 8) +schar_type = typedesc.FundamentalType("signed char", 8, 8) uchar_type = typedesc.FundamentalType("unsigned char", 8, 8) wchar_t_type = typedesc.FundamentalType("wchar_t", 16, 16) short_type = typedesc.FundamentalType("short int", 16, 16) @@ -81,6 +82,7 @@ def midlSAFEARRAY(typ): automation.VT_VARIANT: VARIANT_type, # 12 automation.VT_UNKNOWN: PTR(IUNKNOWN_type), # 13 automation.VT_DECIMAL: DECIMAL_type, # 14 + # Signed-byte mapping and compatibility: https://github.com/enthought/comtypes/issues/969 automation.VT_I1: char_type, # 16 automation.VT_UI1: uchar_type, # 17 automation.VT_UI2: ushort_type, # 18 diff --git a/source/CppTestSrv/CoByteCompatibilityTest.cpp b/source/CppTestSrv/CoByteCompatibilityTest.cpp new file mode 100644 index 00000000..3f92b4c6 --- /dev/null +++ b/source/CppTestSrv/CoByteCompatibilityTest.cpp @@ -0,0 +1,84 @@ +#include +#include +#include "CoByteCompatibilityTest.h" + +HRESULT CByteCompatibilityTest::CreateInstance(IUnknown* pUnknownOuter, CUnknown** ppNewComponent) +{ + if (pUnknownOuter != NULL) + { + return CLASS_E_NOAGGREGATION ; + } + *ppNewComponent = new CByteCompatibilityTest(pUnknownOuter) ; + return S_OK ; +} + +HRESULT __stdcall CByteCompatibilityTest::NondelegatingQueryInterface(const IID& iid, void** ppv) +{ + if (iid == IID_IByteCompatibilityTest) + { + return FinishQI(static_cast(this), ppv) ; + } + return CUnknown::NondelegatingQueryInterface(iid, ppv) ; +} + +HRESULT __stdcall CByteCompatibilityTest::EchoInt8(char value, char* result) +{ + if (result == NULL) + { + return E_POINTER ; + } + *result = value ; + return S_OK ; +} + +HRESULT __stdcall CByteCompatibilityTest::EchoUint8(byte value, byte* result) +{ + if (result == NULL) + { + return E_POINTER ; + } + *result = value ; + return S_OK ; +} + +HRESULT __stdcall CByteCompatibilityTest::EchoLpStr(LPSTR value, LPSTR* result) +{ + if (result == NULL) + { + return E_POINTER ; + } + *result = NULL ; + if (value == NULL) + { + return E_INVALIDARG ; + } + size_t size = strlen(value) + 1 ; + *result = static_cast(CoTaskMemAlloc(size)) ; + if (*result == NULL) + { + return E_OUTOFMEMORY ; + } + memcpy(*result, value, size) ; + return S_OK ; +} + +HRESULT __stdcall CByteCompatibilityTest::ReadCharPointer(unsigned char* value, byte* result) +{ + if (value == NULL || result == NULL) + { + return E_POINTER ; + } + // A raw char* marshals one character, not a NUL-terminated string. + *result = static_cast(*value) ; + return S_OK ; +} + +HRESULT __stdcall CByteCompatibilityTest::GetCharacterBytes(byte value, unsigned char* result) +{ + if (result == NULL) + { + return E_POINTER ; + } + *result = value ; + return S_OK ; +} diff --git a/source/CppTestSrv/CoByteCompatibilityTest.h b/source/CppTestSrv/CoByteCompatibilityTest.h new file mode 100644 index 00000000..412dbad9 --- /dev/null +++ b/source/CppTestSrv/CoByteCompatibilityTest.h @@ -0,0 +1,20 @@ +// A separate COM component for byte and narrow-string compatibility. +#include "CUnknown.h" +#include "Iface.h" + +class CByteCompatibilityTest : public CUnknown, public IByteCompatibilityTest +{ +public: + static HRESULT CreateInstance(IUnknown* pUnknownOuter, CUnknown** ppNewComponent) ; + +private: + DECLARE_IUNKNOWN + virtual HRESULT __stdcall NondelegatingQueryInterface(const IID& iid, void** ppv) ; + virtual HRESULT __stdcall EchoInt8(char value, char* result) ; + virtual HRESULT __stdcall EchoUint8(byte value, byte* result) ; + virtual HRESULT __stdcall EchoLpStr(LPSTR value, LPSTR* result) ; + virtual HRESULT __stdcall ReadCharPointer(unsigned char* value, byte* result) ; + virtual HRESULT __stdcall GetCharacterBytes(byte value, unsigned char* result) ; + + CByteCompatibilityTest(IUnknown* pUnknownOuter) : CUnknown(pUnknownOuter) {} ; +} ; diff --git a/source/CppTestSrv/MAKEFILE b/source/CppTestSrv/MAKEFILE index 13d66b1c..d42893b4 100644 --- a/source/CppTestSrv/MAKEFILE +++ b/source/CppTestSrv/MAKEFILE @@ -41,7 +41,7 @@ guids.obj : guids.c # Component/server source files # -server.obj : server.cpp cunknown.h cfactory.h iface.h CoNumericSafearrayTest.h CoVariantErrorTest.h +server.obj : server.cpp cunknown.h cfactory.h iface.h CoNumericSafearrayTest.h CoByteCompatibilityTest.h CoVariantErrorTest.h cl $(CPP_FLAGS) server.cpp CoComtypesDispRecordParamTest.obj : CoComtypesDispRecordParamTest.cpp \ @@ -56,6 +56,9 @@ CoNumericSafearrayTest.obj : CoNumericSafearrayTest.cpp \ CoNumericSafearrayTest.h iface.h CUnknown.h cl $(CPP_FLAGS) CoNumericSafearrayTest.cpp +CoByteCompatibilityTest.obj : CoByteCompatibilityTest.cpp \ + CoByteCompatibilityTest.h iface.h CUnknown.h + cl $(CPP_FLAGS) CoByteCompatibilityTest.cpp CoStringSafearrayInsideVariantTest.obj : CoStringSafearrayInsideVariantTest.cpp \ CoStringSafearrayInsideVariantTest.h iface.h registry.h CUnknown.h cl $(CPP_FLAGS) CoStringSafearrayInsideVariantTest.cpp @@ -94,6 +97,7 @@ SERVER_OBJS = Server.obj \ CoComtypesDispRecordParamTest.obj \ CoComtypesDispSafearrayParamTest.obj \ CoNumericSafearrayTest.obj \ + CoByteCompatibilityTest.obj \ CoStringSafearrayInsideVariantTest.obj \ CoVariantErrorTest.obj \ Registry.obj \ diff --git a/source/CppTestSrv/SERVER.CPP b/source/CppTestSrv/SERVER.CPP index 90b30795..433b2723 100644 --- a/source/CppTestSrv/SERVER.CPP +++ b/source/CppTestSrv/SERVER.CPP @@ -11,6 +11,7 @@ #include "CoComtypesDispRecordParamTest.h" #include "CoComtypesDispSafearrayParamTest.h" #include "CoNumericSafearrayTest.h" +#include "CoByteCompatibilityTest.h" #include "CoStringSafearrayInsideVariantTest.h" #include "CoVariantErrorTest.h" @@ -57,6 +58,12 @@ CFactoryData g_FactoryDataArray[] = L"Comtypes.NumericSafearrayTest", &LIBID_ComtypesCppTestSrvLib, NULL, 0}, + {&CLSID_CoByteCompatibilityTest, CByteCompatibilityTest::CreateInstance, + L"Comtypes component for byte and narrow-string compatibility tests", + L"Comtypes.ByteCompatibilityTest.1", + L"Comtypes.ByteCompatibilityTest", + &LIBID_ComtypesCppTestSrvLib, + NULL, 0}, {&CLSID_CoStringSafearrayInsideVariantTest, CStringSafearrayInsideVariantTest::CreateInstance, L"String SAFEARRAY inside VARIANT Tester", L"Comtypes.CoStringSafearrayInsideVariantTest.1", diff --git a/source/CppTestSrv/SERVER.IDL b/source/CppTestSrv/SERVER.IDL index 30f26ee5..c261ed3c 100644 --- a/source/CppTestSrv/SERVER.IDL +++ b/source/CppTestSrv/SERVER.IDL @@ -88,6 +88,22 @@ interface INumericSafearrayTest : IUnknown HRESULT GetFloat64Array([in] short dimensions, [in] long count, [out, retval] SAFEARRAY(double)* result); } ; +// A separate interface for scalar input/output and string compatibility. +[ + object, + uuid(047E34C1-33A8-4DAA-A1E5-B941756A8319), + oleautomation +] +interface IByteCompatibilityTest : IUnknown +{ + HRESULT EchoInt8([in] small value, [out, retval] small* result); + HRESULT EchoUint8([in] byte value, [out, retval] byte* result); + HRESULT EchoLpStr([in] LPSTR value, [out, retval] LPSTR* result); + HRESULT ReadCharPointer([in] char* value, [out, retval] byte* result); + // Return one character through a plain char pointer. + HRESULT GetCharacterBytes([in] byte value, [out, retval] char* result); +} ; + // Interface IDispSafearrayParamTest [ uuid(4097A6D0-A111-40E2-BD0B-177B775A9496) @@ -165,6 +181,15 @@ library ComtypesCppTestSrvLib [default] interface INumericSafearrayTest ; } ; + [ + uuid(E07D71E8-C8A5-4B76-9A88-688DB10CF712), + helpstring("Comtypes component for byte and narrow-string compatibility tests.") + ] + coclass CoByteCompatibilityTest + { + [default] interface IByteCompatibilityTest ; + } ; + // Interface IStringSafearrayInsideVariantTest [ odl,