Add cxx{in,out}type typemaps and use them for std::string

This makes using returning strings much simpler to use from C++ code as
the returned pointers don't have to be deleted manually -- although, of
course, this does require an extra allocation and copy and so should be
avoided for the very long strings.

Add a new runtime test showing how simple and convenient it is to use
the functions working with string using the C++ wrappers now.
This commit is contained in:
Vadim Zeitlin 2021-12-06 04:27:23 +01:00
commit fd11a591a3
5 changed files with 135 additions and 30 deletions

View file

@ -476,6 +476,26 @@ area: 7.068583
<td>Type of the result variable used for the return value if the wrapped function is a C++ function
</td>
</tr>
<tr>
<td><tt>cxxintype</tt></td>
<td>Defines the type for the parameters of C++ wrapper functions corresponding to this type. By default is the same as <tt>ctype</tt>, but may sometimes be different to make the functions more convenient to use. For example, <tt>ctype</tt> for <tt>std::string</tt> is <tt>const char*</tt>, but <tt>cxxintype</tt> typemap for it is <tt>std::string const&amp;</tt>, i.e. even though the C++ string passed as a raw pointer via C API, the C++ wrapper still accepts a C++ string. If this typemap is defined, <tt>cxxin</tt> should normally be defined as well. If it is not defined, <tt>ctype</tt> is used.
</td>
</tr>
<tr>
<td><tt>cxxouttype</tt></td>
<td>Similar to <tt>cxxintype</tt>, but is used for the function return values and together with <tt>cxxout</tt> typemap. Also defaults to <tt>ctype</tt> if not defined.
</td>
</tr>
<tr>
<td><tt>cxxin</tt></td>
<td>Defines how to transform <tt>cxxintype</tt> value to <tt>ctype</tt>
</td>
</tr>
<tr>
<td><tt>cxxout</tt></td>
<td>Defines how to transform <tt>ctype</tt> value returned by a function to <tt>cxxouttype</tt>
</td>
</tr>
</table>
<H3>C Typemaps, a Code Generation Walkthrough</H3>

View file

@ -5,11 +5,11 @@
int main(int argc, const char *argv[]) {
{
li_boost_shared_ptr::Klass k("me oh my");
assert( strcmp(k.getValue(), "me oh my") == 0 );
assert( k.getValue() == "me oh my" );
}
{
li_boost_shared_ptr::Klass k{li_boost_shared_ptr_factorycreate()};
assert( strcmp(k.getValue(), "factorycreate") == 0 );
assert( k.getValue() == "factorycreate" );
}
}

View file

@ -0,0 +1,26 @@
#include "li_std_string_wrap.h"
#include <assert.h>
using namespace li_std_string;
int main(int argc, const char *argv[]) {
Structure st;
assert( st.MemberString().empty() );
st.MemberString("bloordyblop");
assert( st.MemberString() == "bloordyblop" );
assert( st.MemberString2() == "member string 2" );
assert( st.ConstMemberString() == "const member string" );
st.StaticMemberString(std::string("static bloordyblop"));
assert( st.StaticMemberString() == "static bloordyblop" );
assert( Structure::StaticMemberString2() == "static member string 2" );
assert( Structure::ConstStaticMemberString() == "const static member string" );
Foo f;
assert( f.test("1+") == "1+1" );
}

View file

@ -27,6 +27,10 @@ public:
};
}
%fragment("include_string", "cxxheader") %{
#include <string>
%}
namespace std {
// use "const string &" typemaps for wrapping member strings
@ -68,4 +72,19 @@ class string;
// taking strings and raw pointers in the generated wrappers.
%typemap(typecheck) string, const string &, string *, string & = char *;
// Define typemaps for wrapping strings back into std::string in C++ wrappers
// and accepting strings directly.
%typemap(cxxintype, fragment="include_string") string, const string & "std::string const&"
%typemap(cxxin) string, const string & "$1.c_str()"
%typemap(cxxouttype, fragment="include_string") string, const string & "std::string"
%typemap(cxxout, noblock="1") string, const string & %{
$result = std::string($cresult);
free(const_cast<char*>($cresult));
%}
}

View file

@ -611,35 +611,49 @@ public:
}
}
for (; p; p = nextSibling(p)) {
// Static variables use fully qualified names, so we can't just use the name directly.
scoped_dohptr name_ptr;
String* name = Getattr(p, "name");
if (!name) {
// Parameters can also not have any names at all, in which case we use auto-generated one.
name = Getattr(p, "lname");
} else if (Strstr(name, "::")) {
name_ptr = Swig_scopename_last(name);
name = name_ptr.get();
if (p) {
// We want to use readable parameter names in our wrappers instead of the autogenerated arg$N if possible, so do it, and do it before calling
// Swig_typemap_attach_parms(), as this uses the parameter names for typemap expansion.
for (Parm* p2 = p; p2; p2 = nextSibling(p2)) {
String* name = Getattr(p, "name");
if (!name) {
// Can't do anything for unnamed parameters.
continue;
}
// Static variables use fully qualified names, so we need to strip the scope from them.
scoped_dohptr name_ptr;
if (Strstr(name, "::")) {
name_ptr = Swig_scopename_last(name);
name = name_ptr.get();
}
Setattr(p, "lname", name);
}
const type_desc ptype_desc = lookup_cxx_parm_type(n, p);
if (!ptype_desc.type()) {
Swig_warning(WARN_C_TYPEMAP_CTYPE_UNDEF, Getfile(p), Getline(p),
"No ctype typemap defined for the parameter \"%s\" of %s\n",
name,
Getattr(n, "sym:name")
);
return;
Swig_typemap_attach_parms("cxxin", p, NULL);
for (; p; p = nextSibling(p)) {
String* const name = Getattr(p, "lname");
const cxx_ptype_desc ptype_desc = lookup_cxx_parm_type(n, p);
if (!ptype_desc.type()) {
Swig_warning(WARN_C_TYPEMAP_CTYPE_UNDEF, Getfile(p), Getline(p),
"No ctype typemap defined for the parameter \"%s\" of %s\n",
name,
Getattr(n, "sym:name")
);
return;
}
if (Len(parms_cxx))
Append(parms_cxx, ", ");
Printv(parms_cxx, ptype_desc.type(), " ", name, NIL);
if (Len(parms_call))
Append(parms_call, ", ");
Append(parms_call, ptype_desc.get_param_code(name));
}
if (Len(parms_cxx))
Append(parms_cxx, ", ");
Printv(parms_cxx, ptype_desc.type(), " ", name, NIL);
if (Len(parms_call))
Append(parms_call, ", ");
Append(parms_call, ptype_desc.get_param_code(name));
}
// Avoid checking for exceptions unnecessarily. Note that this is more than an optimization: we'd get into infinite recursion if we checked for exceptions
@ -1006,6 +1020,9 @@ private:
// The logic here is somewhat messy because we use the same "$resolved_type*" typemap for pointers/references to both enums and classes, but we actually
// need to do quite different things for them. It could probably be simplified by changing the typemaps to be distinct, but this would require also updating
// the code for C wrappers generation in substituteResolvedTypeSpecialVariable().
//
// An even better idea might be to try to define this using cxx{in,out} typemaps for the various types and let the generic SWIG machinery do all the
// matching instead of doing it in the code here.
scoped_dohptr resolved_type(SwigType_typedef_resolve_all(type));
scoped_dohptr base_resolved_type(SwigType_base(resolved_type));
@ -1182,11 +1199,23 @@ private:
// Ensure our own replaceSpecialVariables() is used for $typemap() expansion.
temp_ptr_setter<cxx_ptype_desc*> set(&ptype_desc_, &ptype_desc);
if (String* type = Swig_typemap_lookup("ctype", p, "", NULL)) {
bool use_cxxin = true;
String* type = Swig_typemap_lookup("cxxintype", p, "", NULL);
if (!type) {
use_cxxin = false;
type = Swig_typemap_lookup("ctype", p, "", NULL);
}
if (type) {
ptype_desc.set_type(type);
do_resolve_type(n, Getattr(p, "type"), ptype_desc.type(), &ptype_desc, NULL);
}
if (use_cxxin) {
if (String* in_tm = Getattr(p, "tmap:cxxin"))
ptype_desc.apply_in_typemap(Copy(in_tm));
}
return ptype_desc;
}
@ -1194,13 +1223,24 @@ private:
// As above, ensure our replaceSpecialVariables() is used.
temp_ptr_setter<cxx_rtype_desc*> set(&rtype_desc_, &rtype_desc);
String* type = Swig_typemap_lookup("ctype", n, "", NULL);
bool use_cxxout = true;
String* type(Swig_typemap_lookup("cxxouttype", n, "", NULL));
if (!type) {
use_cxxout = false;
type = Swig_typemap_lookup("ctype", n, "", NULL);
}
if (!type)
return false;
rtype_desc.set_type(type);
do_resolve_type(n, Getattr(n, "type"), rtype_desc.type(), NULL, &rtype_desc);
if (use_cxxout) {
if (String* out_tm = Swig_typemap_lookup("cxxout", n, "", NULL))
rtype_desc.apply_out_typemap(out_tm);
}
return true;
}