php extention used as lib in QT?
-
Hello Guys,
i have an extention (its SAP RFC connector)
https://gkralik.github.io/php7-sapnwrfc/installation.html- so of course i have already compiled php_sapnwrfc.dll/so
- and of course i have all libs necesarry from official SAP RFC SDK
which i then can use in very simple way:
<?php $parameters = [ 'ashost' => 'server_ip', 'sysnr' => 'system_number', 'client' => 'client_number', 'user' => 'username', 'passwd' => 'password', // if you need to connect through saprouter, uncomment the following line //'saprouter' => '/H/my.saprouter.local/H/', ]; // connect $connection = new SAPNWRFC\Connection($parameters); // call RFC function with parameters try { $function = $connection->getFunction("RFC_READ_TABLE"); // RFC_READ_TABLE BBP_RFC_READ_TABLE $parms = array( 'QUERY_TABLE' => "MSEG", 'DELIMITER' => "", 'ROWCOUNT' => 999, 'OPTIONS' => array(array( 'TEXT' => "MATNR = '614-20227-04'" )) ); $results = $function->invoke($parms); // print function return as array echo "<pre>".print_r($results)."</pre>"; } catch (Exception $error) { echo "Caught exception: ".$error->getMessage(); } // close the connection $connection->close(); ?>
so my quesiton is... can i load this dll/so php extention in QT and then somehow run use it? idealy not in php, but rewrite it to call the function from QML, c++ to run the code, and return the result/error back to QML?
can I please ask for the example if this is possible?
Thank you
-
Hi,
Might be a silly question but wouldn't it be simpler to use their C/C++ SDK since you write a C++ application ?
-
@SGaist hello,
sorry, but i dont understand what you mean.Im having even harder time to setup SAP official SDK to use it in my QT project,
so thinking of using the dll from php connector and knowing how it works, to me it sounds much easier to use that extention php dll connector and only rewrite the code form php to c++...tho i might be wrong of course, so im very much open to easier suggestions
-
Hi @shokarta,
so thinking of using the dll from php connector and knowing how it works, to me it sounds much easier to use that
Nope, that will not be easier. Definitely possible, but it will be a lot of work to implement, and maintain, so I'd consider that the last resort. To use that DLL, you would essentially have to embed the PHP runtime into your app. I've actually done this, in C++ (not Qt), commercially, very successfully (after a a lot of work), but that business had a large existing PHP codebase that needed to be shared (between existing apps, and a set of new high-speed AMQP message processors, using Apache Qpid... it was actually all very interesting, but I digress). But since you asked, I'll give you some code samples below to demonstrate the kinds of effort you'd need to do.
And if you did, you'd end with a chain like:
your-app -> php_embed -> php7-sapnwrfc -> official-NW-RFC-SDK -> NetWeaver
As @SGaist suggested, you would much, much better off skipping the middle-components, and just doing:
your-app -> official-NW-RFC-SDK -> NetWeaver
Im having even harder time to setup SAP official SDK to use it in my QT project,
Yeah, it can be tricky using third party SDKs in any language, but I'd focus your attention here. What trouble are you seeing trying to use the official SDK? If it's Qt specific, we're likely to be able to help, or if its general C/C++. Of course, if its a NW RFC SDK specific issue, then you should try SAP's support channels.
Now that I've said "don't do the PHP approach"... here's a bit of what that would be involved (partly because this is knowledge I've never has reason to share ;) and partly to put you off it when you have likely better options to persue).
php_embed
PHP does provide an "php_embed" SAPI. See, for example, the
libphp-embed
package in Ubuntu. However, it is undocumented, and you need to figure things out by reading the source. I, for example, had to figure out the the init and de-init functions must be called from the same thread, or you'll get seemingly random crashes.Initialise
Initialisation looks something like (of course this is using Boost, you'd want to refactor to use Qt or C++17+ equivalents):
boost::unique_lock<boost::mutex> lock(initCountMutex); logTrace("begin initCount=" << initCount); // Initialise the global PHP module (if not already). if (initCount == 0) { logDebug("initialising PHP..."); if (initThreadId) { throw Exception("initCount is non-zero, but initThreadId is already valid", EXCEPTION_LOCATION); } php_embed_module.ub_write = php::global::ub_write; php_embed_module.log_message = php::global::log_message; php_embed_module.sapi_error = php::global::sapi_error; if (php_embed_init(argc, argv PTSRMLS_CC) != SUCCESS) { throw Exception("Failed to initialise global PHP module.", EXCEPTION_LOCATION); } initThreadId = boost::this_thread::get_id(); } // Increment the initialisation requests count. initCount++; logTrace("end initCount=" << initCount);
And de-init:
boost::unique_lock<boost::mutex> lock(initCountMutex); logTrace("begin initCount=" << initCount); // If this was the last instance, shutdown the PHP embed SAPI. if (initCount == 1) { logDebug("shutting down PHP..."); if (!initThreadId) { throw Exception("shutting down PHP, but initThreadId is not set", EXCEPTION_LOCATION); } else if (*initThreadId != boost::this_thread::get_id()) { throw Exception("final shutdown must be called from the " "same thread as the first initialise call", EXCEPTION_LOCATION); } php_embed_shutdown(TSRMLS_C); initThreadId.reset(); // This thread is no longer the initialisation thread. } else if (initCount < 1) { throw Exception("PHP shutdown requested when PHP not initialised", EXCEPTION_LOCATION); } // Decrement the initialisation requests count. initCount--; logTrace("end initCount=" << initCount);
Execute PHP
You'll need to marshall the C++/PHP types (will get to that in a minute), but the overall execution looks like:
// Setup the PHP arguments. zval *param; TSRMLS_FETCH(); MAKE_STD_ZVAL(param); try { phpTypes::variantToZval(param, &in TSRMLS_CC); } catch (Exception &ex) { zval_dtor(param); FREE_ZVAL(param); ex.addLocation(EXCEPTION_LOCATION); throw ex; } zval *params[1] = { param }; // Call the PHP function. zval *functionNameZval, returnValue; MAKE_STD_ZVAL(functionNameZval); INIT_ZVAL(returnValue); int callStatus = FAILURE; std::string error; zend_first_try { ZVAL_STRING(functionNameZval, functionName.c_str(), 0); logDebug("Calling PHP function: " << Z_STRVAL_P(functionNameZval)); callStatus = call_user_function(EG(function_table), NULL, functionNameZval, &returnValue, 1, params TSRMLS_CC); } zend_catch { std::stringstream stream; stream << "PHP error with exit status " << EG(exit_status) << " while calling " << functionName; error = stream.str(); // It's not clear whether its safe to throw here, so we don't. } zend_end_try(); zval_dtor(param); // variantToZval will have copied the string's contents, so we free it here. FREE_ZVAL(param); //zval_dtor(functionNameZval); // functionNameZval has a reference to functionName.c_str(), so don't free it! FREE_ZVAL(functionNameZval); // If any errors occurred, throw an exception. if (!error.empty()) { if (callStatus != FAILURE) { // This should not happen, but just in case, log and error and prevent memory leaks. logNotice("a PHP error occurred, yet call_user_function returned SUCCESS"); zval_dtor(&returnValue); exit(0); } throw Exception(error, EXCEPTION_LOCATION); } // If call_user_function failed, also throw an exception. if (callStatus == FAILURE) { throw Exception("call_user_function returned FAILURE", EXCEPTION_LOCATION); } // Convert result to a variant. { zval * returnValuePointer = &returnValue; out = phpTypes::zvalToVariant(&returnValuePointer TSRMLS_CC); } zval_dtor(&returnValue);
Now, hopefully you've already been put off the idea by this stage - there's an awful lot old-C API cruft going on here. Which is ok, if you have no other choice, but for a new app, I would 100% avoid.
But just to round out the picture, the thing that's still missing is those
phpTypes::variantToZval()
andphpTypes::zvalToVariant()
functions - those are some large functions which, in the case above, convert betweenqpid::type::variant
(very similar toboost::variant
) types and PHP'szval
types. In your case, you'd want to marshall betweenQVariant
andzval
instead, and this is a lot of work too (and absolutely needs good unit tests!). Here's a very small taste for how that looks:#define ADD_TO_ARRAY(type, ...) { \ if (key == NULL) \ add_next_index_##type(output , ##__VA_ARGS__); \ else if (keyLength == (uint)-1) \ add_assoc_##type(output, key , ##__VA_ARGS__); \ else \ add_assoc_##type##_ex(output, key, keyLength , ##__VA_ARGS__); \ } #define SET_OR_ADD(TYPE, type, ...) \ if (add) { \ ADD_TO_ARRAY(type, ##__VA_ARGS__); \ } else { \ ZVAL_##TYPE(output , ##__VA_ARGS__); \ } void variantToZval( zval * const output, const qpid::types::Variant * const variant TSRMLS_DC, // Zend thread-safety arguments. const bool add, // Is output an array we should add to? const char *key, // If non-NULL, the associative array key. const uint keyLength // If non-default, the length of key. ) { switch (variant->getType()) { case qpid::types::VAR_VOID: SET_OR_ADD(NULL, null); break; case qpid::types::VAR_BOOL: SET_OR_ADD(BOOL, bool, variant->asBool()); break; case qpid::types::VAR_UINT8: case qpid::types::VAR_UINT16: case qpid::types::VAR_UINT32: case qpid::types::VAR_UINT64: { const uint64_t value = variant->asUint64(); if (value > LONG_MAX) { throw Exception("unsigned integer too large for PHP", EXCEPTION_LOCATION); } SET_OR_ADD(LONG, long, (long)value); } break; case qpid::types::VAR_INT8: case qpid::types::VAR_INT16: case qpid::types::VAR_INT32: case qpid::types::VAR_INT64: // this is not even half-way...
So, in conclusion, I highly recommend you skip all the PHP stuff (even though it is technically possible, and actually kinda interesting), and focus your efforts on working with the official SAP NW RFC C/C++ SDK directly. You'll have less code, less bugs, less to maintain, and less dependencies if you go direct :)
Cheers!