v01d has asked for the wisdom of the Perl Monks concerning the following question:

Hi, I've been trying to create an extension to be able to use my libaiml library (c++) from within perl. I tried swig, but the class I define in the main header of the library is inside a namespace and swig doesn't support them.

Well, I tried xs afterwards and I quickly got a module that looked it was going to work. I managed to expose the constructor, destructor and 3 other functions. But I don't know how to expose the most important function.

This function takes 2 const std::string& and 1 std::string& where I put my output. The function returns a boolean, indicating success or failure.

What I don't know how to do is how to create a Perl interface for that. I can think of two versions: return a scalar (which would be undef if the function returns false, otherwise it would contain the value written to the third parameter (in C++)). The other version is the closest one to C++: return a scalar (a boolean), take 2 scalars by copy and the last one by reference.

Now... how do I do this? I would like to come up with a C++ solution by writing C++ code, and not fiddling so much with the code below the "MODULE" line on the .xs file.

So, my current xs file is this one:

#ifdef __cplusplus extern "C" { #endif #include "EXTERN.h" #include "perl.h" #include "XSUB.h" #include "ppport.h" #ifdef __cplusplus } #endif #include <string> #include <libaiml/core.h> using std::string; using aiml::cCore; using aiml::AIMLError; class cAiml : public cCore { public: // Here I could write a function which would call respond() // but doing things more Perl Friendly }; MODULE = AI::AIML PACKAGE = AI::AIML cAiml * cAiml::new() void cAiml::DESTROY() bool cAiml::initialize(const char* file) void cAiml::deinitialize() long cAiml::get_error() const char* cAiml::get_error_str(AIMLError error_num);

my typemap:

TYPEMAP cAiml * O_OBJECT AIMLError T_ENUM

the main header of my library:

/********************************************************************* +****** * This file is part of "libaiml" + * * Copyright (C) 2005 by V01D + * * + * * "libaiml" is free software; you can redistribute it and/or modify + * * it under the terms of the GNU General Public License as published + by * * the Free Software Foundation; either version 2 of the License, or + * * (at your option) any later version. + * * + * * "libaiml" is distributed in the hope that it will be useful, + * * but WITHOUT ANY WARRANTY; without even the implied warranty of + * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * * GNU General Public License for more details. + * * + * * You should have received a copy of the GNU General Public License + * * along with "libaiml"; if not, write to the + * * Free Software Foundation, Inc., + * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * ********************************************************************* +******/ #ifndef __LIBAIML_CORE_H__ #define __LIBAIML_CORE_H__ #define LIBAIML_MAX_LINE_SIZE 16384 // max size a +line of text can be (doesn't apply to .aiml files) #define LIBAIML_POPEN_BUFFER_SIZE 256 + // buffer size used when reading data from popen() #define LIBAIML_VERSION_STRING ("libaiml 0.4") #define LIBAIML_VERSION (0.4) #include <string> #include <fstream> #include <vector> #include <list> #include <map> #include <std_utils/cconfig.h> #include "global.h" #include "errors.h" #include "aiml_parser.h" namespace _aiml { class cCore { public: cCore(void); ~cCore(void); bool initialize(const std::string& file); void deinitialize(void); bool respond(const std::string& input, const std::string& username +, std::string& output); void unregister_user(const std::string& user_id); AIMLError get_error(void); const char* get_error_str(AIMLError error_num); private: friend class cGraphMaster; /** Error handling **/ AIMLError last_error; static const char* error_str[AIMLERR_MAX]; void set_error(AIMLError); /** Utility Functions **/ bool load_substitutions(void); bool load_botvars(void); void load_aiml_files(void); void learn_file(const std::string& filename, bool at_runtime = fal +se); bool load_userlist(void); void save_userlist(void); std::string doSystemCall(const std::string& cmd); std::string doJavaScriptCall(const std::string& cmd); /** ADD: implement this **/ bool load_defvars(void); const std::string& getBotVar(const std::string& key) const; StringMAP botvars_map; /** Internal modules **/ cGraphMaster graphmaster; AIMLparser aiml_parser; /** Configuration options/vars **/ std_util::cConfig cfg; std::ofstream file_gossip_stream; std::string aiml_path, file_substitute, file_gossip, file_botvars, + aiml_files, user_dir; bool should_trim_blanks, allow_system; /** User management **/ typedef std::map<std::string,_aiml::cUser> UserMap; typedef std::list<UserID> UserIDS; UserMap user_map; UserIDS user_ids; bool get_free_uid(UserID* ptr); }; } /** * Public Interface */ namespace aiml { using _aiml::cCore; using _aiml::AIMLError; } #endif // __LIBAIML_CORE_H__

It works pretty well like this, just that I can't use respond() with that .xs.

Any help apreciated, I've been googling and reading and man'ning a lot, and can't find anything that clears this for me.

Replies are listed 'Best First'.
Re: C++ strings, references and XS (char*)
by tye (Sage) on Dec 20, 2005 at 07:44 UTC

    If you don't need to handle '\0' characters in your strings, then this is nearly trivial to handle.

    char* cAiml::_respond( input, user ) char* input char* user

    ...does most of the work. Sorry, I never do OO via XS and I'm a bit rusty regarding little XS details so I won't try to fill in the CODE: block for above but C++ turns char* into std::string for you automatically so all you need to do is return undef (or the empty list) for one case or grab output.c_str() for the XS to copy into the returned PV for the other.

    Let us know how far you get and someone will likely give you more hints...

    - tye        

      I thought about doing that, the problem is that I don't know how would that affect memory handling. I mean, If I'd had a function like this in C++:

      string bleh(void);

      I could easily have a C wrapper that would do:

      const char* blehC(void) { return bleh(void).c_str(); }

      But... this has a big problem: you are returning a pointer to a local variable (the returned string inside blehC is local to blehC).

      So, what I would need is to tell XS/perl/whatever to give me a piece of memory that it considers a scalar where I can write to (wether it is a scalar being returned or a reference to a scalar which I receive). I guess that I don't have an alternative to write something in the CODE: section, but I really wouldn't know how to do that nor how the C/C++ function should look like. I mean, in C I don't have references, so I would be taking a char*, but... how do I know how much space do I have to write? When I write to a std::string I'm indirectly allocating memory and returning to something a user gave me. So, at C level, how does XS/perl/whatever knows how to free the memory I return? assuming that I do something like this:

      char* respondC(const char* input, const char* username) { char* ret = NULL; if (respond(input, username, cpp_ret)) { string cpp_ret; ret = new char[cpp_ret.length()+1]; strncpy(ret, cpp_ret.c_str(), cpp_ret.length()); } return ret; }
      (ignoring possible fencepost errors)

      So, that is what is haunting me... Maybe there's a way to not even write at CODE: but just write a C function that takes a scalar reference (SV* or something like that) where I can copy (as a string) the result of the C++ function. Anyway, that would be ideal, not sure if possible.

        the problem is that I don't know how would that affect memory handling

        That is why I said (emphasis added):

        or grab output.c_str() for the XS to copy into the returned PV for the other

        You write a C++ function that has a lexical std::string that it passes to the method for the method to fill in. Then you copy the value from output.c_str() to a Perl PV (part of an SV). The handling of a RETVAL of type "char*" will do this copying automatically.

        I suppose it becomes a problem if you can't get the XS code to be treated as C++ such that the XS routine is where you declare the output std::string. Though I'm pretty sure this is possible, I also realize that it at least used to be somewhat difficult, requiring some undocumented fiddling.

        If I wanted to avoid that complication, then I'd have the middle-man C++ function take two input "char*"s (still) and one output SV* (see your typemap file for the simple incantation required to copy a '\0'-terminated "char*" into the PV of an SV).

        - tye        

Re: C++ strings, references and XS
by reneeb (Chaplain) on Dec 20, 2005 at 02:42 UTC

      I will look into that, but I would really like to know how what I'm trying to acomplish is usually done. I don't think that is a very uncommon problem.

      Besides... I feel so close just needed that function alone =S

      Thank you