-
Notifications
You must be signed in to change notification settings - Fork 851
Implementation of 'com_record' as a subclassable Python type. #2437
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
ebfaa62
f3bc21a
010a6b3
b4618a9
0f95786
6b16620
2b84fde
ca4f490
f92cc97
b1e55d2
c9aeec2
cb675ab
61662ab
aed5d91
13bc5d7
bd5efdc
dfeec71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,10 @@ | ||
| #include <new> | ||
| #include "stdafx.h" | ||
| #include "PythonCOM.h" | ||
| #include "PyRecord.h" | ||
|
|
||
| extern PyObject *g_obPyCom_MapRecordGUIDToRecordClass; | ||
|
|
||
| // @doc | ||
|
|
||
| // The owner of the record buffer - many records may point here! | ||
|
|
@@ -31,7 +34,7 @@ class PyRecordBuffer { | |
| long ref; | ||
| }; | ||
|
|
||
| BOOL PyRecord_Check(PyObject *ob) { return ((ob)->ob_type == &PyRecord::Type); } | ||
| BOOL PyRecord_Check(PyObject *ob) { return PyObject_IsInstance(ob, (PyObject *)&PyRecord::Type); } | ||
|
|
||
| BOOL PyObject_AsVARIANTRecordInfo(PyObject *ob, VARIANT *pv) | ||
| { | ||
|
|
@@ -91,7 +94,7 @@ PyObject *PyObject_FromSAFEARRAYRecordInfo(SAFEARRAY *psa) | |
| hr = info->RecordCopy(source_data, this_dest_data); | ||
| if (FAILED(hr)) | ||
| goto exit; | ||
| PyTuple_SET_ITEM(ret_tuple, i, new PyRecord(info, this_dest_data, owner)); | ||
| PyTuple_SET_ITEM(ret_tuple, i, PyRecord::new_record(info, this_dest_data, owner)); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should probably add a null check? It should have already been there for
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're right. The question is where to put this null check? Should we instead raise a Python exception in PyRecord::new_record itself in every place where it currently returns a null without raising an exception?
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IIUC, it will only currently return null when OOM. In this patch we have a more complicated situation - sometimes when it returns null there will be an exception set, whereas OOM will not. Having those OOM cases call
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| this_dest_data += cb_elem; | ||
| source_data += cb_elem; | ||
| } | ||
|
|
@@ -141,7 +144,7 @@ PyObject *PyObject_FromRecordInfo(IRecordInfo *ri, void *data, ULONG cbData) | |
| delete owner; | ||
| return PyCom_BuildPyException(hr, ri, IID_IRecordInfo); | ||
| } | ||
| return new PyRecord(ri, owner->data, owner); | ||
| return PyRecord::new_record(ri, owner->data, owner); | ||
| } | ||
|
|
||
| // @pymethod <o PyRecord>|pythoncom|GetRecordFromGuids|Creates a new record object from the given GUIDs | ||
|
|
@@ -200,14 +203,50 @@ PyObject *pythoncom_GetRecordFromTypeInfo(PyObject *self, PyObject *args) | |
| return ret; | ||
| } | ||
|
|
||
| PyRecord::PyRecord(IRecordInfo *ri, PVOID data, PyRecordBuffer *owner) | ||
| // This function creates a new 'com_record' instance with placement new. | ||
| // If the particular Record GUID belongs to a registered subclass | ||
| // of the 'com_record' base type, it instantiates this subclass. | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this comment needs an update for the param. Or maybe a new function
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The tp_new method is the only code path that would use such a |
||
| PyRecord *PyRecord::new_record(IRecordInfo *ri, PVOID data, PyRecordBuffer *owner) | ||
| { | ||
| GUID structguid; | ||
| OLECHAR *guidString; | ||
| PyObject *guidUnicode, *recordType; | ||
| // By default we create an instance of the base 'com_record' type. | ||
| PyTypeObject *type = &PyRecord::Type; | ||
| // Retrieve the GUID of the Record to be created. | ||
| HRESULT hr = ri->GetGuid(&structguid); | ||
| if (FAILED(hr)) { | ||
| PyCom_BuildPyException(hr); | ||
| return NULL; | ||
| } | ||
| if (S_OK != StringFromCLSID(structguid, &guidString)) | ||
| return NULL; | ||
| if (!(guidUnicode = PyUnicode_FromWideChar(guidString, -1))) | ||
| return NULL; | ||
| ::CoTaskMemFree(guidString); | ||
| recordType = PyDict_GetItem(g_obPyCom_MapRecordGUIDToRecordClass, guidUnicode); | ||
| Py_DECREF(guidUnicode); | ||
| // If the Record GUID is registered as a subclass of com_record | ||
| // we return an object of the subclass type. | ||
| if (recordType && PyObject_IsSubclass(recordType, (PyObject *)&PyRecord::Type)) { | ||
| type = (PyTypeObject *)recordType; | ||
| } | ||
| // Finally allocate the memory for the the appropriate | ||
| // Record type and construct the instance with placement new. | ||
| char *buf = (char *)PyRecord::Type.tp_alloc(type, 0); | ||
| if (buf == NULL) { | ||
| delete owner; | ||
| return NULL; | ||
| } | ||
| return new (buf) PyRecord(ri, owner->data, owner); | ||
| } | ||
|
|
||
| PyRecord::PyRecord(IRecordInfo *ri, PVOID data, PyRecordBuffer *buf_owner) | ||
| { | ||
| ob_type = &PyRecord::Type; | ||
| _Py_NewReference(this); | ||
| ri->AddRef(); | ||
| pri = ri; | ||
| pdata = data; | ||
| this->owner = owner; | ||
| owner = buf_owner; | ||
| owner->AddRef(); | ||
| }; | ||
|
|
||
|
|
@@ -217,44 +256,81 @@ PyRecord::~PyRecord() | |
| pri->Release(); | ||
| } | ||
|
|
||
| PyObject *PyRecord::tp_new(PyTypeObject *type, PyObject *args, PyObject *kwds) | ||
| { | ||
| PyObject *item, *obGuid, *obInfoGuid; | ||
| int major, minor, lcid; | ||
| GUID guid, infoGuid; | ||
| if (type == &PyRecord::Type) | ||
| // If the base 'com_record' type was called try to get the | ||
| // information required for instance creation from the call parameters. | ||
| { | ||
| if (!PyArg_ParseTuple(args, "OiiiO:__new__", | ||
| &obGuid, // @pyparm <o PyIID>|iid||The GUID of the type library | ||
| &major, // @pyparm int|verMajor||The major version number of the type lib. | ||
| &minor, // @pyparm int|verMinor||The minor version number of the type lib. | ||
| &lcid, // @pyparm int|lcid||The LCID of the type lib. | ||
| &obInfoGuid)) // @pyparm <o PyIID>|infoIID||The GUID of the record info in the library | ||
| return NULL; | ||
| if (!PyWinObject_AsIID(obGuid, &guid)) | ||
| return NULL; | ||
| if (!PyWinObject_AsIID(obInfoGuid, &infoGuid)) | ||
| return NULL; | ||
| } | ||
| // Otherwise try to get the information from the class variables of the derived type. | ||
| else if (!(item = PyDict_GetItemString(type->tp_dict, "GUID")) || !PyWinObject_AsIID(item, &infoGuid) || | ||
| !(item = PyDict_GetItemString(type->tp_dict, "TLBID")) || !PyWinObject_AsIID(item, &guid) || | ||
| !(item = PyDict_GetItemString(type->tp_dict, "MJVER")) || ((major = PyLong_AsLong(item)) == -1) || | ||
| !(item = PyDict_GetItemString(type->tp_dict, "MNVER")) || ((minor = PyLong_AsLong(item)) == -1) || | ||
| !(item = PyDict_GetItemString(type->tp_dict, "LCID")) || ((lcid = PyLong_AsLong(item)) == -1)) | ||
| return NULL; | ||
| IRecordInfo *ri = NULL; | ||
| HRESULT hr = GetRecordInfoFromGuids(guid, major, minor, lcid, infoGuid, &ri); | ||
| if (FAILED(hr)) | ||
| return PyCom_BuildPyException(hr); | ||
| PyObject *ret = PyObject_FromRecordInfo(ri, NULL, 0); | ||
| ri->Release(); | ||
| return ret; | ||
| } | ||
|
|
||
| PyTypeObject PyRecord::Type = { | ||
| PYWIN_OBJECT_HEAD "com_record", | ||
| sizeof(PyRecord), | ||
| 0, | ||
| PyRecord::tp_dealloc, /* tp_dealloc */ | ||
| 0, /* tp_print */ | ||
| 0, /* tp_getattr */ | ||
| 0, /* tp_setattr */ | ||
| 0, /* tp_compare */ | ||
| &PyRecord::tp_repr, /* tp_repr */ | ||
| 0, /* tp_as_number */ | ||
| 0, /* tp_as_sequence */ | ||
| 0, /* tp_as_mapping */ | ||
| 0, /* tp_hash */ | ||
| 0, /* tp_call */ | ||
| 0, /* tp_str */ | ||
| PyRecord::getattro, /* tp_getattro */ | ||
| PyRecord::setattro, /* tp_setattro */ | ||
| 0, /* tp_as_buffer */ | ||
| Py_TPFLAGS_DEFAULT, /* tp_flags */ | ||
| 0, /* tp_doc */ | ||
| 0, /* tp_traverse */ | ||
| 0, /* tp_clear */ | ||
| PyRecord::tp_richcompare, /* tp_richcompare */ | ||
| 0, /* tp_weaklistoffset */ | ||
| 0, /* tp_iter */ | ||
| 0, /* tp_iternext */ | ||
| PyRecord::methods, /* tp_methods */ | ||
| 0, /* tp_members */ | ||
| 0, /* tp_getset */ | ||
| 0, /* tp_base */ | ||
| 0, /* tp_dict */ | ||
| 0, /* tp_descr_get */ | ||
| 0, /* tp_descr_set */ | ||
| 0, /* tp_dictoffset */ | ||
| 0, /* tp_init */ | ||
| 0, /* tp_alloc */ | ||
| 0, /* tp_new */ | ||
| (destructor)PyRecord::tp_dealloc, /* tp_dealloc */ | ||
| 0, /* tp_print */ | ||
| 0, /* tp_getattr */ | ||
| 0, /* tp_setattr */ | ||
| 0, /* tp_compare */ | ||
| &PyRecord::tp_repr, /* tp_repr */ | ||
| 0, /* tp_as_number */ | ||
| 0, /* tp_as_sequence */ | ||
| 0, /* tp_as_mapping */ | ||
| 0, /* tp_hash */ | ||
| 0, /* tp_call */ | ||
| 0, /* tp_str */ | ||
| PyRecord::getattro, /* tp_getattro */ | ||
| PyRecord::setattro, /* tp_setattro */ | ||
| 0, /* tp_as_buffer */ | ||
| Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ | ||
| 0, /* tp_doc */ | ||
| 0, /* tp_traverse */ | ||
| 0, /* tp_clear */ | ||
| PyRecord::tp_richcompare, /* tp_richcompare */ | ||
| 0, /* tp_weaklistoffset */ | ||
| 0, /* tp_iter */ | ||
| 0, /* tp_iternext */ | ||
| PyRecord::methods, /* tp_methods */ | ||
| 0, /* tp_members */ | ||
| 0, /* tp_getset */ | ||
| 0, /* tp_base */ | ||
| 0, /* tp_dict */ | ||
| 0, /* tp_descr_get */ | ||
| 0, /* tp_descr_set */ | ||
| 0, /* tp_dictoffset */ | ||
| 0, /* tp_init */ | ||
| 0, /* tp_alloc */ | ||
| (newfunc)PyRecord::tp_new, /* tp_new */ | ||
| }; | ||
|
|
||
| static PyObject *PyRecord_reduce(PyObject *self, PyObject *args) | ||
|
|
@@ -435,6 +511,15 @@ PyObject *PyRecord::getattro(PyObject *self, PyObject *obname) | |
| char *name = PYWIN_ATTR_CONVERT(obname); | ||
| if (name == NULL) | ||
| return NULL; | ||
| if (strcmp(name, "__record_type_name__") == 0) { | ||
| BSTR rec_name; | ||
| HRESULT hr = pyrec->pri->GetName(&rec_name); | ||
| if (FAILED(hr)) | ||
| return PyCom_BuildPyException(hr, pyrec->pri, IID_IRecordInfo); | ||
| PyObject *res = PyWinCoreString_FromString(rec_name); | ||
| SysFreeString(rec_name); | ||
| return res; | ||
| } | ||
| if (strcmp(name, "__members__") == 0) { | ||
| ULONG cnames = 0; | ||
| HRESULT hr = pyrec->pri->GetFieldNames(&cnames, NULL); | ||
|
|
@@ -496,7 +581,7 @@ PyObject *PyRecord::getattro(PyObject *self, PyObject *obname) | |
| // Short-circuit sub-structs and arrays here, so we don't allocate a new chunk | ||
| // of memory and copy it - we need sub-structs to persist. | ||
| if (V_VT(&vret) == (VT_BYREF | VT_RECORD)) | ||
| return new PyRecord(V_RECORDINFO(&vret), V_RECORD(&vret), pyrec->owner); | ||
| return PyRecord::new_record(V_RECORDINFO(&vret), V_RECORD(&vret), pyrec->owner); | ||
| else if (V_VT(&vret) == (VT_BYREF | VT_ARRAY | VT_RECORD)) { | ||
| SAFEARRAY *psa = *V_ARRAYREF(&vret); | ||
| if (SafeArrayGetDim(psa) != 1) | ||
|
|
@@ -531,7 +616,7 @@ PyObject *PyRecord::getattro(PyObject *self, PyObject *obname) | |
| // in the last parameter, i.e. 'sub_data == NULL'. | ||
| this_data = (BYTE *)psa->pvData; | ||
| for (i = 0; i < nelems; i++) { | ||
| PyTuple_SET_ITEM(ret_tuple, i, new PyRecord(sub, this_data, pyrec->owner)); | ||
| PyTuple_SET_ITEM(ret_tuple, i, PyRecord::new_record(sub, this_data, pyrec->owner)); | ||
| this_data += element_size; | ||
| } | ||
| array_end: | ||
|
|
@@ -645,4 +730,8 @@ PyObject *PyRecord::tp_richcompare(PyObject *self, PyObject *other, int op) | |
| return ret; | ||
| } | ||
|
|
||
| void PyRecord::tp_dealloc(PyObject *ob) { delete (PyRecord *)ob; } | ||
| void PyRecord::tp_dealloc(PyRecord *self) | ||
| { | ||
| self->~PyRecord(); | ||
| Py_TYPE(self)->tp_free((PyObject *)self); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,6 +30,7 @@ extern PyObject *pythoncom_IsGatewayRegistered(PyObject *self, PyObject *args); | |
| extern PyObject *g_obPyCom_MapIIDToType; | ||
| extern PyObject *g_obPyCom_MapGatewayIIDToName; | ||
| extern PyObject *g_obPyCom_MapInterfaceNameToIID; | ||
| extern PyObject *g_obPyCom_MapRecordGUIDToRecordClass; | ||
|
|
||
| PyObject *g_obEmpty = NULL; | ||
| PyObject *g_obMissing = NULL; | ||
|
|
@@ -2195,6 +2196,13 @@ PYWIN_MODULE_INIT_FUNC(pythoncom) | |
| PYWIN_MODULE_INIT_RETURN_ERROR; | ||
| } | ||
|
|
||
| // Initialize the dictionary for registering com_record subclasses. | ||
| g_obPyCom_MapRecordGUIDToRecordClass = PyDict_New(); | ||
| if (g_obPyCom_MapRecordGUIDToRecordClass == NULL) { | ||
| PYWIN_MODULE_INIT_RETURN_ERROR; | ||
| } | ||
| PyDict_SetItemString(dict, "RecordClasses", g_obPyCom_MapRecordGUIDToRecordClass); | ||
|
|
||
| // XXX - more error checking? | ||
| PyDict_SetItemString(dict, "TypeIIDs", g_obPyCom_MapIIDToType); | ||
| PyDict_SetItemString(dict, "ServerInterfaces", g_obPyCom_MapGatewayIIDToName); | ||
|
|
@@ -2248,6 +2256,10 @@ PYWIN_MODULE_INIT_FUNC(pythoncom) | |
| PyType_Ready(&PyRecord::Type) == -1) | ||
| PYWIN_MODULE_INIT_RETURN_ERROR; | ||
|
|
||
| // Add the PyRecord type as a module attribute | ||
| if (PyModule_AddObject(module, "com_record", (PyObject *)&PyRecord::Type) != 0) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should it have "type" in the name? (I've no idea about idiomatic python tbh!)
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, in the example code on python.org it has the same name as in the tp_name slot. So the module attribute is and the type of an instance is which, I would say, makes sense. Although more precisely, in the example code, the tp_name slot also specifies the module name of the new type. Of course the name of the module attribute could be changed to Up to you. |
||
| PYWIN_MODULE_INIT_RETURN_ERROR; | ||
|
|
||
| // Setup our sub-modules | ||
| if (!initunivgw(dict)) | ||
| PYWIN_MODULE_INIT_RETURN_ERROR; | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.