Noobish C++ Question - invalid argument types

Apprentice
Posts: 14
Joined: 2008.08
Post: #1
Well, only a few days into using c++ I've run into an unsolvable (to me) error. Its probably a something simple that I've done wrong, but I can't for the life of me figure out what it is!

The troublesome files:

JSManager.h

Code:
/*
*  JSManager.h
*  GameEngine
*
*  Created by Simon on 26/02/2009.
*  Copyright 2009 ProScripts. All rights reserved.
*
*/

#ifndef _JSMANAGER_
#define _JSMANAGER_

#include "/usr/local/include/js/jsapi.h"


//#include <libmozjs.dylib/jsapi.h>

class JSManager {
public:
    bool initJS();
    bool destroyJS();
    bool execFile(char* filename);
    
    JSBool js_print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval);

private:
    
    JSRuntime* JSRun;
    JSContext* JSCon;
    JSClass* JSCls;
    JSObject* JSGlb;
    
    
    
};

#endif

JSManager.cpp
Code:
/*
*  JSManager.cpp
*  GameEngine
*
*  Created by Simon on 26/02/2009.
*  Copyright 2009 ProScripts. All rights reserved.
*
*/

#include "JSManager.h"


#include <iostream>


bool JSManager::initJS() {
    
    JSClass JSCls = {
        "global", JSCLASS_GLOBAL_FLAGS,
        JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
        JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
        JSCLASS_NO_OPTIONAL_MEMBERS
    };
    
    JSRun = JS_NewRuntime(8L * 1024L * 1024L);
    if (JSRun == NULL)
        return 0;
    
    JSCon = JS_NewContext(JSRun, 8192);
    if (JSCon == NULL)
        return 0;
    
    JSGlb = JS_NewObject(JSCon, &JSCls, NULL, NULL);
    if (JSGlb == NULL)
        return 0;
    
    
    if (!JS_InitStandardClasses(JSCon, JSGlb))
        return 0;
    
    static JSFunctionSpec global_functions[] = {
        JS_FS("print", this->js_print, 1, 0, 0),
        JS_FS_END
    };

    if (!JS_DefineFunctions(JSCon, JSGlb, global_functions))
        return 0;
    
    
    
    return 1;
}

bool JSManager::destroyJS() {
    
    JS_DestroyContext(JSCon);
    JS_DestroyRuntime(JSRun);
    JS_ShutDown();    
    
    return 1;
}

bool JSManager::execFile(char* filename){
    
    std::cout << "Testing";
    
    char *source = "print('face');";
    JSBool ok;
    jsval rval;
    
    ok = JS_EvaluateScript(JSCon, JSGlb, source, strlen(source),
                           "face", 22, &rval);
    
    
    return 1;
}

JSBool JSManager::js_print(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){
    const char *printval;
    int rc;
    
    if (!JS_ConvertArguments(JSCon, argc, argv, "s", &printval))
        return JS_FALSE;
    
    std::cout << printval;
    rc = 1;
    if (rc != 0) {
        /* Throw a JavaScript exception. */
        JS_ReportError(JSCon, "Command failed with exit code %d", rc);
        return JS_FALSE;
    }
    
    *rval = JSVAL_VOID;  /* return undefined */
    return JS_TRUE;
}


The problem is in this line here, according to the compiler:

Code:
    static JSFunctionSpec global_functions[] = {
        JS_FS("print", this->js_print, 1, 0, 0),
        JS_FS_END
    };

and the error is:

Code:
/Users/***/Projects/GameEngine/JSManager.cpp:44: error: argument of type 'JSBool (JSManager::)(JSContext*, JSObject*, uintN, jsval*, jsval*)' does not match 'JSBool (*)(JSContext*, JSObject*, uintN, jsval*, jsval*)'


Any idea what the problem is?


(Also, if anyone has any experience with Mach-O libraries, do you have any ideas of how to use them in XCode?)


Cheers Grin
Quote this message in a reply
Member
Posts: 67
Joined: 2006.07
Post: #2
The problem is that you are trying to pass a pointer to js_print(), which is a member function, to JS_FS(). Remember, member functions have to operate within the context of an object. But if you pass a pointer to a member function, you're just passing a pointer to the member function itself -- the function you're calling wouldn't know what object it was referring to. (This is essentially what the compiler error is trying to say.)

As for how to go about it, a quick fix is to declare your member function as a static function (put a "static" in front of its declaration), so that it doesn't operate within the context of an object. Of course, be aware that, as a static function, it can no longer access instance variables or other member functions.

In general, though, I think it's best to avoid using member functions in this manner, as complications can arise.

Since when was "Fred" a placeholder variable?
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #3
You can still use a member function pointer, but you have to make sure that you keep the object as well. Check out this site for some info on how to use member function pointers. Note that you need to keep both the object and the function pointer around. If you give us more information on what you want to accomplish with member functions pointers, we can give you some tips on how best to continue.
Quote this message in a reply
Apprentice
Posts: 14
Joined: 2008.08
Post: #4
Thanks for the help so far!

I'm attempting to implement some basic native javascript functions for scripting in the program I'm creating. The base functions, things which don't need support from other objects/classes, are going to be member functions of the JSManager class. The member functions need to have access to the instance variables of the object, so it can get the Context and Runtime variables when called by Javascript.

Any ideas on how to go about this?
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #5
Since you know the class and instance ahead of time, it's a bit easier to handle. There are two ways I can see to handle this. If you only need one instance of JSManager, you can just have a static variable for your single JSManager instance, then have an array of member function pointers. If you need multiple instances of JSManager, then you can have an array of structures which contain a JSManager instance and the corresponding function pointer. The link I gave earlier should give you all the details you need to do either.

Somewhat related, am I right in assuming that jsapi.h is part of your project? It's generally a bad idea to put your headers under the /usr directory when building your project, and even worse to give absolute paths to your header. You should have a directory in your project folder (that is in some arbitrary location) and use relative paths to that header directory so your project is relocatable. You can also add a header lookup path to the compiler arguments so you can have a cleaner #include. For example, if the relative path to your includes is "../include", you can add the lookup path of "../include" then instead of #include ../include/file.h" you can do #include "file.h" or #include <file.h>.
Quote this message in a reply
Apprentice
Posts: 14
Joined: 2008.08
Post: #6
I know about the absolute paths, I was more concerned on just getting it working first so I could get something to work from! Thanks for the advice though, it will be useful when I finally try to pack the code up to work somewhere else.

Thanks for the tips, I'll give them a go and see if it works Smile

EDIT: Could you give some advice on how to implement member function pointers in my situation? I've had a go and my limited experience with C++ is hindering me yet again.
Quote this message in a reply
Member
Posts: 63
Joined: 2005.12
Post: #7
Simie Wrote:Could you give some advice on how to implement member function pointers in my situation? I've had a go and my limited experience with C++ is hindering me yet again.
I don't know very much about the JS API, but in general, where a non-member function pointer is expected (as appears to be the case here), there's no way to substitute a member function pointer in its place. This is for the reasons explained earlier: to invoke a member function via a member function pointer, you need an object on which to invoke it, and there's no provision for that here.

Modern C++ provides various means of treating member and non-member function pointers (and in fact any function object) as being essentially interchangeable (see: Boost Bind), but that won't be of any help to you here. I don't know enough about the context to give you any more specific advice than that, but unless I'm missing something, you'll have to do something like what was suggested earlier (that is, find a way to use a static member or global non-member function).

One other tip: although it's unlikely to cause any problems in this case, symbols that begin with an underscore followed by a capital letter are technically reserved by the implementation, so it would probably be a good idea to drop the leading underscores from your header guards.
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #8
Here are a couple of code snippets. First, an example on how to use a member function pointer if you need only one instance of JSManager.
Code:
//declare type to make syntax prettier
typedef void (JSManager::*FunctionType)(JSContext *, JSObject *, uintN, jsval *, jsval *)

//singleton for JSManager
static JSManager globalManager;

...

//initialize at some point
globalManager.initJS();

//get the function pointer
FunctionType function = &JSManager::js_print;
//and call it
(globalManager.*function)(cx, obj, argc, argv, rval);

If you need to have multiple instances of JSManager, then you can use a helper structure to hold both for you.

Code:
//declare type to make syntax prettier
typedef void (JSManager::*FunctionType)(JSContext *, JSObject *, uintN, jsval *, jsval *)

//helper structure to store both a JSManager instance and the function you wish to call
struct FunctionPair
{
    FunctionPair() :
        manager(NULL), function(NULL) {}
    FunctionPair(JSManager *m, FunctionType f) :
        manager(m), function(f) {}
    JSManager *manager;
    FunctionType function;
    void call(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
    {
        (manager->*function)(cx, obj, argc, argv, rval);
    }
};

...

//create the the manager instance used and initialize it
JSManager manager;
manager.initJS();

//create the helper structure instance and call the function
FunctionPair pair(&manager, &JSManager::js_print);
pair.call(cx, obj, argc, argv, rval);
Note that in the last example, you need to make sure the JSManager instance you pass into the FunctionPair object stays alive as long as the helper structure is used, otherwise it will try to call the member function on an invalid object and crash.

To integrate this into your code, your table of functions can either hold an array of FunctionType function pointers or FunctionPair structures, depending on if you need one or many JSManager objects.
Quote this message in a reply
Apprentice
Posts: 14
Joined: 2008.08
Post: #9
Thank you both for the replies Smile

I've moved the JS functions to a JSFunction namespace, to keep them separate from everything else. However, I now have another problem.

The JSManager object is created from main.cp, and it had to be accessed in JSFunctions.cp, which is included from JSManager.cpp.

I'm guessing that since main.cp and JSManager are compiled separately, the variables from one can't be accessed from another?

Is there any way to go about doing this?

Thanks for the help so far :-)
Quote this message in a reply
Member
Posts: 245
Joined: 2005.11
Post: #10
You can just declare the variable as "extern" in JSFunctions.cp in order to access it.
Code:
extern static JSManager globalManager;
Don't change the decalration in main.cp (is .cp actually recognised as C++, or should those be .cpp?)

<edit>
Alternatively, you can create a function which returns a pointer to globalManager in main.cp. This is probably the preferred approach for object-oriented programming (I prefer it, anyway!)
</edit>
Quote this message in a reply
Apprentice
Posts: 14
Joined: 2008.08
Post: #11
I miss PHP, haha.

What I have:

In main.cp:
Code:
JSManager* returnJSManager(){
    return js;
}

In JSFunctions.cp:
Code:
extern JSManager* returnJSManager();
JSManager *js = returnJSManager();
..
..

namespace JSFunctions {

    JSBool js_include(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){
        
        const char *filename;
...
        
        ::js.execFile(filename);
...
        
    }
}

The error is fussing about it not being declared. I tried it without the leading ::, without declaring it as extern, and directly using it like this:

returnJSManager.execFile()


I'm betting I did something wrong here, but I'm just not used to this way of doing things. Sad I come from using PHP, in which there is only one file which everything is included into.

Edit:

There is more to the js_include function which I removed, as it doesn't have much to do with this, as far as I can tell.
Quote this message in a reply
Member
Posts: 245
Joined: 2005.11
Post: #12
"extern" is for global variables. If you declare a variable in main.cpp and then re-declare it as extern in JSFunctions.cpp you can access it in both files.
For the other approach you need to put the prototype for returnJSManager() in a header file and include that in JSFunctions.cp then call it as normal.
Like this:
Code:
//main.h
JSManager *returnJSManager;

Code:
//main.cpp
JSManager *js;

JSManager *returnJSManager()
{
return js;
}

Code:
//JSFunctions.cpp
#include "main.h"

void someFunction()
{
JSManager *jsm = returnJSManager();
}

It may not be a good idea to call returnJSManager outside of a function as it is hard to be sure js has been initialised already. Besides, if you want to use it like that you may as well just go with the extern approach.
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #13
Alternatively, if you want to access js directly without having to call a function to get it, then this is what you want.

Code:
//main.h
extern JSManager *js;
Code:
//main.cpp
JSManager *js;

Then any file that includes main.h has access to js. In the end, it's more elegant to use a function, but it's still good to know how the extern keyword works.

Note that if you declare a function without a definition such as this
Code:
void someFunc();
it has an implicit extern added, so there's no need to use extern. It's assume to be declared somewhere else, whether it's later in the same file or in another file that's linked in.

Oh, and backslash, you didn't want to use the static keyword in your extern declaration. Rasp
Quote this message in a reply
Apprentice
Posts: 14
Joined: 2008.08
Post: #14
It seems to be insisting that globalJSInstance is not a JSManager instance.

Any ideas on why?

main.h
Code:
extern JSManager *globalJSInstance;

JSFunctions.m
Code:
#include "main.h"
stuff
...
    JSBool js_include(JSContext *cx, JSObject *obj, uintN argc, jsval *argv, jsval *rval){
        
        const char *filename;
        
        if (!JS_ConvertArguments(cx, argc, argv, "s", &filename))
            return JS_FALSE;

        std::cout << "\nIncluding file: " << filename;
        
        globalJSInstance.execFile(filename);
        
        
        *rval = JSVAL_TRUE;
        return JS_TRUE;
        
    }

Error:
JSFunctions.cp:47: error: request for member 'execFile' in 'globalJSInstance', which is of non-class type 'JSManager*'


(I know it seems I'm asking before googling, but I am actually trying to solve these problems myself, I'm just really not used to C++ D: )


Thanks so much for the help so far Smile
Quote this message in a reply
Moderator
Posts: 1,140
Joined: 2005.07
Post: #15
You need to include JSManager.h within JSFunctions.cpp so it knows the definition of the class.
Quote this message in a reply
Post Reply 

Possibly Related Threads...
Thread: Author Replies: Views: Last Post
  invalid display CactusJack 2 2,931 May 12, 2009 03:39 AM
Last Post: Robin Forder
  Quicktime image importer file types? kelvin 2 2,662 Mar 15, 2005 08:19 PM
Last Post: arekkusu