codekingpro/portable-devtools
114k
1"""Utility functions for writing out gateway C++ files2 3 This module will generate a C++/Python binding for a specific COM4 interface.5 6 At this stage, no command line interface exists. You must start Python, 7 import this module, change to the directory where the generated code should8 be written, and run the public function.9 10 This module is capable of generating both 'Interfaces' (ie, Python11 client side support for the interface) and 'Gateways' (ie, Python12 server side support for the interface). Many COM interfaces are useful13 both as Client and Server. Other interfaces, however, really only make14 sense to implement one side or the other. For example, it would be pointless15 for Python to implement Server side for 'IRunningObjectTable', unless we were16 implementing core COM for an operating system in Python (hey - now there's an idea!)17 18 Most COM interface code is totally boiler-plate - it consists of19 converting arguments, dispatching the call to Python, and processing20 any result values.21 22 This module automates the generation of such code. It has the ability to23 parse a .H file generated by the MIDL tool (ie, almost all COM .h files)24 and build almost totally complete C++ code.25 26 The module understands some of the well known data types, and how to27 convert them. There are only a couple of places where hand-editing is28 necessary, as detailed below:29 30 unsupported types -- If a type is not known, the generator will31 pretty much ignore it, but write a comment to the generated code. You32 may want to add custom support for this type. In some cases, C++ compile errors33 will result. These are intentional - generating code to remove these errors would34 imply a false sense of security that the generator has done the right thing.35 36 other return policies -- By default, Python never sees the return SCODE from37 a COM function. The interface usually returns None if OK, else a COM exception38 if "FAILED(scode)" is TRUE. You may need to change this if:39 * EXCEPINFO is passed to the COM function. This is not detected and handled40 * For some reason Python should always see the result SCODE, even if it41 did fail or succeed. For example, some functions return a BOOLEAN result42 in the SCODE, meaning Python should always see it.43 * FAILED(scode) for the interface still has valid data to return (by default,44 the code generated does not process the return values, and raise an exception45 to Python/COM46 47"""48 49import re50 51from . import makegwparse52 53 54def make_framework_support(55 header_file_name, interface_name, bMakeInterface=1, bMakeGateway=156):57 """Generate C++ code for a Python Interface and Gateway58 59 header_file_name -- The full path to the .H file which defines the interface.60 interface_name -- The name of the interface to search for, and to generate.61 bMakeInterface = 1 -- Should interface (ie, client) support be generated.62 bMakeGatewayInterface = 1 -- Should gateway (ie, server) support be generated.63 64 This method will write a .cpp and .h file into the current directory,65 (using the name of the interface to build the file name.66 67 """68 fin = open(header_file_name)69 try:70 interface = makegwparse.parse_interface_info(interface_name, fin)71 finally:72 fin.close()73 74 if bMakeInterface and bMakeGateway:75 desc = "Interface and Gateway"76 elif bMakeInterface and not bMakeGateway:77 desc = "Interface"78 else:79 desc = "Gateway"80 if interface.name[:5] == "IEnum": # IEnum - use my really simple template-based one81 import win32com.makegw.makegwenum82 83 ifc_cpp_writer = win32com.makegw.makegwenum._write_enumifc_cpp84 gw_cpp_writer = win32com.makegw.makegwenum._write_enumgw_cpp85 else: # Use my harder working ones.86 ifc_cpp_writer = _write_ifc_cpp87 gw_cpp_writer = _write_gw_cpp88 89 fout = open("Py%s.cpp" % interface.name, "w")90 try:91 fout.write(92 """\93// This file implements the %s %s for Python.94// Generated by makegw.py95 96#include "shell_pch.h"97"""98 % (interface.name, desc)99 )100 # if bMakeGateway:101 # fout.write('#include "PythonCOMServer.h"\n')102 # if interface.base not in ["IUnknown", "IDispatch"]:103 # fout.write('#include "Py%s.h"\n' % interface.base)104 fout.write(105 '#include "Py%s.h"\n\n// @doc - This file contains autoduck documentation\n'106 % interface.name107 )108 if bMakeInterface:109 ifc_cpp_writer(fout, interface)110 if bMakeGateway:111 gw_cpp_writer(fout, interface)112 finally:113 fout.close()114 fout = open("Py%s.h" % interface.name, "w")115 try:116 fout.write(117 """\118// This file declares the %s %s for Python.119// Generated by makegw.py120"""121 % (interface.name, desc)122 )123 124 if bMakeInterface:125 _write_ifc_h(fout, interface)126 if bMakeGateway:127 _write_gw_h(fout, interface)128 finally:129 fout.close()130 131 132###########################################################################133#134# INTERNAL FUNCTIONS135#136#137 138 139def _write_ifc_h(f, interface):140 f.write(141 """\142// ---------------------------------------------------143//144// Interface Declaration145 146class Py%s : public Py%s147{148public:149 MAKE_PYCOM_CTOR(Py%s);150 static %s *GetI(PyObject *self);151 static PyComTypeObject type;152 153 // The Python methods154"""155 % (interface.name, interface.base, interface.name, interface.name)156 )157 for method in interface.methods:158 f.write(159 "\tstatic PyObject *%s(PyObject *self, PyObject *args);\n" % method.name160 )161 f.write(162 """\163 164protected:165 Py%s(IUnknown *pdisp);166 ~Py%s();167};168"""169 % (interface.name, interface.name)170 )171 172 173def _write_ifc_cpp(f, interface):174 name = interface.name175 f.write(176 """\177// ---------------------------------------------------178//179// Interface Implementation180 181Py%(name)s::Py%(name)s(IUnknown *pdisp):182 Py%(base)s(pdisp)183{184 ob_type = &type;185}186 187Py%(name)s::~Py%(name)s()188{189}190 191/* static */ %(name)s *Py%(name)s::GetI(PyObject *self)192{193 return (%(name)s *)Py%(base)s::GetI(self);194}195 196"""197 % (interface.__dict__)198 )199 200 ptr = re.sub("[a-z]", "", interface.name)201 strdict = {"interfacename": interface.name, "ptr": ptr}202 for method in interface.methods:203 strdict["method"] = method.name204 f.write(205 """\206// @pymethod |Py%(interfacename)s|%(method)s|Description of %(method)s.207PyObject *Py%(interfacename)s::%(method)s(PyObject *self, PyObject *args)208{209 %(interfacename)s *p%(ptr)s = GetI(self);210 if ( p%(ptr)s == NULL )211 return NULL;212"""213 % strdict214 )215 argsParseTuple = (216 argsCOM217 ) = (218 formatChars219 ) = codePost = codePobjects = codeCobjects = cleanup = cleanup_gil = ""220 needConversion = 0221 # if method.name=="Stat": import win32dbg;win32dbg.brk()222 for arg in method.args:223 try:224 argCvt = makegwparse.make_arg_converter(arg)225 if arg.HasAttribute("in"):226 val = argCvt.GetFormatChar()227 if val:228 f.write("\t" + argCvt.GetAutoduckString() + "\n")229 formatChars = formatChars + val230 argsParseTuple = (231 argsParseTuple + ", " + argCvt.GetParseTupleArg()232 )233 codePobjects = (234 codePobjects + argCvt.DeclareParseArgTupleInputConverter()235 )236 codePost = codePost + argCvt.GetParsePostCode()237 needConversion = needConversion or argCvt.NeedUSES_CONVERSION()238 cleanup = cleanup + argCvt.GetInterfaceArgCleanup()239 cleanup_gil = cleanup_gil + argCvt.GetInterfaceArgCleanupGIL()240 comArgName, comArgDeclString = argCvt.GetInterfaceCppObjectInfo()241 if comArgDeclString: # If we should declare a variable242 codeCobjects = codeCobjects + "\t%s;\n" % (comArgDeclString)243 argsCOM = argsCOM + ", " + comArgName244 except makegwparse.error_not_supported as why:245 f.write(246 '// *** The input argument %s of type "%s" was not processed ***\n// Please check the conversion function is appropriate and exists!\n'247 % (arg.name, arg.raw_type)248 )249 250 f.write(251 "\t%s %s;\n\tPyObject *ob%s;\n" % (arg.type, arg.name, arg.name)252 )253 f.write(254 "\t// @pyparm <o Py%s>|%s||Description for %s\n"255 % (arg.type, arg.name, arg.name)256 )257 codePost = (258 codePost259 + "\tif (bPythonIsHappy && !PyObject_As%s( ob%s, &%s )) bPythonIsHappy = FALSE;\n"260 % (arg.type, arg.name, arg.name)261 )262 263 formatChars = formatChars + "O"264 argsParseTuple = argsParseTuple + ", &ob%s" % (arg.name)265 266 argsCOM = argsCOM + ", " + arg.name267 cleanup = cleanup + "\tPyObject_Free%s(%s);\n" % (arg.type, arg.name)268 269 if needConversion:270 f.write("\tUSES_CONVERSION;\n")271 f.write(codePobjects)272 f.write(codeCobjects)273 f.write(274 '\tif ( !PyArg_ParseTuple(args, "%s:%s"%s) )\n\t\treturn NULL;\n'275 % (formatChars, method.name, argsParseTuple)276 )277 if codePost:278 f.write("\tBOOL bPythonIsHappy = TRUE;\n")279 f.write(codePost)280 f.write("\tif (!bPythonIsHappy) return NULL;\n")281 strdict["argsCOM"] = argsCOM[1:]282 strdict["cleanup"] = cleanup283 strdict["cleanup_gil"] = cleanup_gil284 f.write(285 """ HRESULT hr;286 PY_INTERFACE_PRECALL;287 hr = p%(ptr)s->%(method)s(%(argsCOM)s );288%(cleanup)s289 PY_INTERFACE_POSTCALL;290%(cleanup_gil)s291 if ( FAILED(hr) )292 return PyCom_BuildPyException(hr, p%(ptr)s, IID_%(interfacename)s );293"""294 % strdict295 )296 codePre = codePost = formatChars = codeVarsPass = codeDecl = ""297 for arg in method.args:298 if not arg.HasAttribute("out"):299 continue300 try:301 argCvt = makegwparse.make_arg_converter(arg)302 formatChar = argCvt.GetFormatChar()303 if formatChar:304 formatChars = formatChars + formatChar305 codePre = codePre + argCvt.GetBuildForInterfacePreCode()306 codePost = codePost + argCvt.GetBuildForInterfacePostCode()307 codeVarsPass = codeVarsPass + ", " + argCvt.GetBuildValueArg()308 codeDecl = codeDecl + argCvt.DeclareParseArgTupleInputConverter()309 except makegwparse.error_not_supported as why:310 f.write(311 '// *** The output argument %s of type "%s" was not processed ***\n// %s\n'312 % (arg.name, arg.raw_type, why)313 )314 continue315 if formatChars:316 f.write(317 '%s\n%s\tPyObject *pyretval = Py_BuildValue("%s"%s);\n%s\treturn pyretval;'318 % (codeDecl, codePre, formatChars, codeVarsPass, codePost)319 )320 else:321 f.write("\tPy_INCREF(Py_None);\n\treturn Py_None;\n")322 f.write("\n}\n\n")323 324 f.write("// @object Py%s|Description of the interface\n" % (name))325 f.write("static struct PyMethodDef Py%s_methods[] =\n{\n" % name)326 for method in interface.methods:327 f.write(328 '\t{ "%s", Py%s::%s, 1 }, // @pymeth %s|Description of %s\n'329 % (method.name, interface.name, method.name, method.name, method.name)330 )331 332 interfacebase = interface.base333 f.write(334 """\335 { NULL }336};337 338PyComTypeObject Py%(name)s::type("Py%(name)s",339 &Py%(interfacebase)s::type,340 sizeof(Py%(name)s),341 Py%(name)s_methods,342 GET_PYCOM_CTOR(Py%(name)s));343"""344 % locals()345 )346 347 348def _write_gw_h(f, interface):349 if interface.name[0] == "I":350 gname = "PyG" + interface.name[1:]351 else:352 gname = "PyG" + interface.name353 name = interface.name354 if interface.base == "IUnknown" or interface.base == "IDispatch":355 base_name = "PyGatewayBase"356 else:357 if interface.base[0] == "I":358 base_name = "PyG" + interface.base[1:]359 else:360 base_name = "PyG" + interface.base361 f.write(362 """\363// ---------------------------------------------------364//365// Gateway Declaration366 367class %s : public %s, public %s368{369protected:370 %s(PyObject *instance) : %s(instance) { ; }371 PYGATEWAY_MAKE_SUPPORT2(%s, %s, IID_%s, %s)372 373"""374 % (gname, base_name, name, gname, base_name, gname, name, name, base_name)375 )376 if interface.base != "IUnknown":377 f.write(378 "\t// %s\n\t// *** Manually add %s method decls here\n\n"379 % (interface.base, interface.base)380 )381 else:382 f.write("\n\n")383 384 f.write("\t// %s\n" % name)385 386 for method in interface.methods:387 f.write("\tSTDMETHOD(%s)(\n" % method.name)388 if method.args:389 for arg in method.args[:-1]:390 f.write("\t\t%s,\n" % (arg.GetRawDeclaration()))391 arg = method.args[-1]392 f.write("\t\t%s);\n\n" % (arg.GetRawDeclaration()))393 else:394 f.write("\t\tvoid);\n\n")395 396 f.write("};\n")397 f.close()398 399 400def _write_gw_cpp(f, interface):401 if interface.name[0] == "I":402 gname = "PyG" + interface.name[1:]403 else:404 gname = "PyG" + interface.name405 name = interface.name406 if interface.base == "IUnknown" or interface.base == "IDispatch":407 base_name = "PyGatewayBase"408 else:409 if interface.base[0] == "I":410 base_name = "PyG" + interface.base[1:]411 else:412 base_name = "PyG" + interface.base413 f.write(414 """\415// ---------------------------------------------------416//417// Gateway Implementation418"""419 % {"name": name, "gname": gname, "base_name": base_name}420 )421 422 for method in interface.methods:423 f.write(424 """\425STDMETHODIMP %s::%s(426"""427 % (gname, method.name)428 )429 430 if method.args:431 for arg in method.args[:-1]:432 inoutstr = "][".join(arg.inout)433 f.write("\t\t/* [%s] */ %s,\n" % (inoutstr, arg.GetRawDeclaration()))434 435 arg = method.args[-1]436 inoutstr = "][".join(arg.inout)437 f.write("\t\t/* [%s] */ %s)\n" % (inoutstr, arg.GetRawDeclaration()))438 else:439 f.write("\t\tvoid)\n")440 441 f.write("{\n\tPY_GATEWAY_METHOD;\n")442 cout = 0443 codePre = codePost = codeVars = ""444 argStr = ""445 needConversion = 0446 formatChars = ""447 if method.args:448 for arg in method.args:449 if arg.HasAttribute("out"):450 cout = cout + 1451 if arg.indirectionLevel == 2:452 f.write("\tif (%s==NULL) return E_POINTER;\n" % arg.name)453 if arg.HasAttribute("in"):454 try:455 argCvt = makegwparse.make_arg_converter(arg)456 argCvt.SetGatewayMode()457 formatchar = argCvt.GetFormatChar()458 needConversion = needConversion or argCvt.NeedUSES_CONVERSION()459 460 if formatchar:461 formatChars = formatChars + formatchar462 codeVars = (463 codeVars + argCvt.DeclareParseArgTupleInputConverter()464 )465 argStr = argStr + ", " + argCvt.GetBuildValueArg()466 codePre = codePre + argCvt.GetBuildForGatewayPreCode()467 codePost = codePost + argCvt.GetBuildForGatewayPostCode()468 except makegwparse.error_not_supported as why:469 f.write(470 '// *** The input argument %s of type "%s" was not processed ***\n// - Please ensure this conversion function exists, and is appropriate\n// - %s\n'471 % (arg.name, arg.raw_type, why)472 )473 f.write(474 "\tPyObject *ob%s = PyObject_From%s(%s);\n"475 % (arg.name, arg.type, arg.name)476 )477 f.write(478 '\tif (ob%s==NULL) return MAKE_PYCOM_GATEWAY_FAILURE_CODE("%s");\n'479 % (arg.name, method.name)480 )481 codePost = codePost + "\tPy_DECREF(ob%s);\n" % arg.name482 formatChars = formatChars + "O"483 argStr = argStr + ", ob%s" % (arg.name)484 485 if needConversion:486 f.write("\tUSES_CONVERSION;\n")487 f.write(codeVars)488 f.write(codePre)489 if cout:490 f.write("\tPyObject *result;\n")491 resStr = "&result"492 else:493 resStr = "NULL"494 495 if formatChars:496 fullArgStr = '%s, "%s"%s' % (resStr, formatChars, argStr)497 else:498 fullArgStr = resStr499 500 f.write('\tHRESULT hr=InvokeViaPolicy("%s", %s);\n' % (method.name, fullArgStr))501 f.write(codePost)502 if cout:503 f.write("\tif (FAILED(hr)) return hr;\n")504 f.write(505 "\t// Process the Python results, and convert back to the real params\n"506 )507 # process the output arguments.508 formatChars = codePobjects = codePost = argsParseTuple = ""509 needConversion = 0510 for arg in method.args:511 if not arg.HasAttribute("out"):512 continue513 try:514 argCvt = makegwparse.make_arg_converter(arg)515 argCvt.SetGatewayMode()516 val = argCvt.GetFormatChar()517 if val:518 formatChars = formatChars + val519 argsParseTuple = (520 argsParseTuple + ", " + argCvt.GetParseTupleArg()521 )522 codePobjects = (523 codePobjects + argCvt.DeclareParseArgTupleInputConverter()524 )525 codePost = codePost + argCvt.GetParsePostCode()526 needConversion = needConversion or argCvt.NeedUSES_CONVERSION()527 except makegwparse.error_not_supported as why:528 f.write(529 '// *** The output argument %s of type "%s" was not processed ***\n// %s\n'530 % (arg.name, arg.raw_type, why)531 )532 533 if formatChars: # If I have any to actually process.534 if len(formatChars) == 1:535 parseFn = "PyArg_Parse"536 else:537 parseFn = "PyArg_ParseTuple"538 if codePobjects:539 f.write(codePobjects)540 f.write(541 '\tif (!%s(result, "%s" %s))\n\t\treturn MAKE_PYCOM_GATEWAY_FAILURE_CODE("%s");\n'542 % (parseFn, formatChars, argsParseTuple, method.name)543 )544 if codePost:545 f.write("\tBOOL bPythonIsHappy = TRUE;\n")546 f.write(codePost)547 f.write(548 '\tif (!bPythonIsHappy) hr = MAKE_PYCOM_GATEWAY_FAILURE_CODE("%s");\n'549 % method.name550 )551 f.write("\tPy_DECREF(result);\n")552 f.write("\treturn hr;\n}\n\n")553 554 555def test():556 # make_framework_support("d:\\msdev\\include\\objidl.h", "ILockBytes")557 make_framework_support("d:\\msdev\\include\\objidl.h", "IStorage")558 559 560# make_framework_support("d:\\msdev\\include\\objidl.h", "IEnumSTATSTG")561 