Cannot read property 'ccall' of undefined"

47 views
Skip to first unread message

Marco Ippolito

unread,
Dec 28, 2019, 5:07:32 AM12/28/19
to emscripten-discuss
I’m trying to transpose to vue.js this html page which works fine:

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8"/>
      </head>
      <body>
        <input type="button" value="Add" onclick="callAdd()" />

        <script>
          function callAdd() {
            const result = Module.ccall('Add',
                'number',
                ['number', 'number'],
                [1, 2]);
            console.log(`Result: ${result}`);
          }
        </script>
        <script src="js_plumbing.js"></script>
      </body>
    </html>
 

According to the emscripten documentation: https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#calling-compiled-c-functions-from-javascript-using-ccall-cwrap we can use ccall and cwrap functions


This is the Result.vue file:


    <template>
      <div>
        <p button @click="callAdd">Add!</p>
        <p>Result: {{ result }}</p>
      </div>
    </template>

    <script>
        import * as js_plumbing from './js_plumbing'
        export default {
          data () {
            return {
              result: null
            }
          },
          methods: {
            callAdd() {
              const result = js_plumbing.Module.ccall('Add',
                  'number',
                  ['number', 'number'],
                  [1, 2]);
              console.log('Result: ${result}');
              console.log(result);
            }
          }
        }
    </script>


But when executing npm run dev


     npm run dev

    > mypr...@1.0.0 dev /home/marco/cpp/WebAssemblyinAction/Appendix_B/B.1_ccall/startingV
    > cross-env NODE_ENV=development webpack-dev-server --open --hot


I get this error: Cannot read property 'ccall' of undefined"


How to solve it?

Looking forward to your kind help.

Marco

errorsInChome-06.jpg

Mehdi Sabwat

unread,
Dec 28, 2019, 6:19:06 AM12/28/19
to emscripte...@googlegroups.com
Weird, it seems the Module object does not get exported. Sorry it seems vue.js related and I know nothing about it.


--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/emscripten-discuss/650b07f7-efc8-4132-94de-e556320b01ed%40googlegroups.com.

Marco Ippolito

unread,
Dec 28, 2019, 6:33:08 AM12/28/19
to emscripten-discuss
I do not grasp if it is vue.js-related problem, an emscripten-related problem, or a problem related to the interface between vue.-js and emscripten.

I compiled the add.c file with this command:

emcc add.c -o js_plumbing.js -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s ENVIRONMENT='web,worker'

and this is the content of add.c file :

#include <stdlib.h>
#include <emscripten.h>

// If this is an Emscripten (WebAssembly) build then...
#ifdef __EMSCRIPTEN__
  #include <emscripten.h>
#endif

#ifdef __cplusplus
extern "C" { // So that the C++ compiler does not rename our function names
#endif

EMSCRIPTEN_KEEPALIVE
int Add(int value1, int value2)
{
  return (value1 + value2);
}

#ifdef __cplusplus
}
#endif


so, the Add function and the Module function, as far as I know, should be exported.

The very first lines of the js_plumbing.js file are the following:

// Copyright 2010 The Emscripten Authors.  All rights reserved.
// Emscripten is available under two separate licenses, the MIT license and the
// University of Illinois/NCSA Open Source License.  Both these licenses can be
// found in the LICENSE file.

// The Module object: Our interface to the outside world. We import
// and export values on it. There are various ways Module can be used:
// 1. Not defined. We create it here
// 2. A function parameter, function(Module) { ..generated code.. }
// 3. pre-run appended it, var Module = {}; ..generated code..
// 4. External script tag defines var Module.
// We need to check if Module already exists (e.g. case 3 above).
// Substitution will be replaced with actual code on later stage of the build,
// this way Closure Compiler will not mangle it (e.g. case 4. above).
// Note that if you want to run closure, and also to use Module
// after the generated code, you will need to define   var Module = {};
// before the code. Then that object will be used in the code, and you
// can continue to use Module afterwards as well.
var Module = typeof Module !== 'undefined' ? Module : {};

// --pre-jses are emitted after the Module integration code, so that they can
// refer to Module (if they choose; they can also define Module)
// {{PRE_JSES}}

// Sometimes an existing Module object exists with properties
// meant to overwrite the default module functionality. Here
// we collect those properties and reapply _after_ we configure
// the current environment's defaults to avoid having to be so
// defensive during initialization.
var moduleOverrides = {};
var key;
for (key in Module) {
  if (Module.hasOwnProperty(key)) {
    moduleOverrides[key] = Module[key];
  }
}

So, it seems that the Module is object is indeed exported in js_plumbing.js file.
Why isn't it imported in Result.vue file in callAdd() function ?

      methods: {
        callAdd() {
          const result = js_plumbing.Module.ccall('Add',
              'number',
              ['number', 'number'],
              [1, 2]);
          console.log('Result: ${result}');
          console.log(result);
        }
      }


On Saturday, 28 December 2019 12:19:06 UTC+1, Mehdi Sabwat wrote:
Weird, it seems the Module object does not get exported. Sorry it seems vue.js related and I know nothing about it.


To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

Mehdi Sabwat

unread,
Dec 28, 2019, 6:45:59 AM12/28/19
to emscripte...@googlegroups.com
If I were you I'd try to load a handmade js file with vuejs, before trying this. Maybe something is missing in your vuejs setup?

To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/emscripten-discuss/2b7fb9b9-7d4e-42e5-ac66-f9539b920fd6%40googlegroups.com.

Marco Ippolito

unread,
Dec 28, 2019, 8:57:50 AM12/28/19
to emscripten-discuss
Just to check how to correctly import javascript files into vue files, I imported this jsFunct.js : 

    export function Add(x, y) {
      var z = x + y;
      return z;
    }

into Result2.vue :  

    <template>
      <div>
        <p button @click="callAdd">Add!</p>
        <p>Result: {{ result }}</p>
      </div>
    </template>

    <script>
        import * as jsFunct from './jsFunct'

        export default {
          data () {
            return {
              result: 0
            }
          },
          methods: {
            callAdd() {
              const result = jsFunct.Add(1, 2);
              this.result = result;
            }
          }
        }
    </script>

   And it works fine:

So.. I tried to do the same with emscripten's generated js_plumbing.js file but got errors: 

    <template>
      <div>
        <p button @click="callAdd">Add!</p>
        <p>Result: {{ result }}</p>
      </div>
    </template>

    <script>
        import * as js_plumbing from './js_plumbing'
        export default {
          data () {
            return {
              result: null
            }
          },
          methods: {
            callAdd() {
              const result = js_plumbing.Add(1,2);
              this.result = result;
            }
          }
        }
    </script>


This could mean that we have to pass through Module to load and use Add() function.
But how?



On Saturday, 28 December 2019 12:45:59 UTC+1, Mehdi Sabwat wrote:
If I were you I'd try to load a handmade js file with vuejs, before trying this. Maybe something is missing in your vuejs setup?

To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

Mehdi Sabwat

unread,
Dec 28, 2019, 9:10:43 AM12/28/19
to emscripte...@googlegroups.com

I see you also posted there, but yeah ccall is a Module object method. So you need to be able to export a module from vuejs.

To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/emscripten-discuss/6c34513a-d675-46c0-b39c-f87891115b28%40googlegroups.com.

Mehdi Sabwat

unread,
Dec 28, 2019, 9:44:06 AM12/28/19
to emscripte...@googlegroups.com
Sorry I mean you need to be able to import the Module object into your vuejs environment.

Marco Ippolito

unread,
Dec 28, 2019, 3:02:47 PM12/28/19
to emscripten-discuss
I "solved" through  a sort of an hack, which I do not like at all.


This is the Result.vue file:

    <template>
      <div>
        <p button @click="callAdd">Add!</p>
        <p>Result: {{ result }}</p>
      </div>
    </template>

    <script>
        import * as js_plumbing from './js_plumbing'
        import Module  from './js_plumbing'

        export default {
          data () {
            return {
              result: null
            }
          },
          methods: {
            callAdd () {
              const result = js_plumbing.Module.ccall('Add',
                'number',
                ['number', 'number'],
                [1, 2]);
              this.result = result;
            }
          }
        }
    </script>

which is exactly the same as the one used before

The only thing I've done to make it working, is to add export to the definition of Module in js_plumbing.js : 

js_plumbing.js 

    // Copyright 2010 The Emscripten Authors.  All rights reserved.
    // Emscripten is available under two separate licenses, the MIT license and the
    // University of Illinois/NCSA Open Source License.  Both these licenses can be
    // found in the LICENSE file.

    // The Module object: Our interface to the outside world. We import
    // and export values on it. There are various ways Module can be used:
    // 1. Not defined. We create it here
    // 2. A function parameter, function(Module) { ..generated code.. }
    // 3. pre-run appended it, var Module = {}; ..generated code..
    // 4. External script tag defines var Module.
    // We need to check if Module already exists (e.g. case 3 above).
    // Substitution will be replaced with actual code on later stage of the build,
    // this way Closure Compiler will not mangle it (e.g. case 4. above).
    // Note that if you want to run closure, and also to use Module
    // after the generated code, you will need to define   var Module = {};
    // before the code. Then that object will be used in the code, and you
    // can continue to use Module afterwards as well.
    export var Module = typeof Module !== 'undefined' ? Module : {};


But, as I said, I do not like this hack.
Any suggestions on how to make the Module exportable, thus importable, without manually adding 'export' in js_plumbing.js file?

On Saturday, 28 December 2019 15:44:06 UTC+1, Mehdi Sabwat wrote:
Sorry I mean you need to be able to import the Module object into your vuejs environment.

On Sat, Dec 28, 2019, 15:10 Mehdi Sabwat <mehdi...@gmail.com> wrote:

I see you also posted there, but yeah ccall is a Module object method. So you need to be able to export a module from vuejs.

To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-discuss+unsub...@googlegroups.com.

Floh

unread,
Dec 30, 2019, 11:08:31 AM12/30/19
to emscripten-discuss
The behaviour of ccall() together with emscripten's dead code elimination and JS minification may be a bit confusing, and (I think) the default behaviour also has changed a couple of times.

Basically, what (probably) happens is that the ccall name gets renamed to something else by emscripten's minification pass (for instance from Module.ccall() to Module.a()). The minification tool will then also change all calls to ccall() from Module.ccal() to Module.a(), BUT it can do this only in the source code it can "see" during the linking stage. If you try to call Module.ccall() from a .html or .js file that's not part of the build process it fails with the 'undefined' error you're seeing, because there isn't a Module.ccall() function anymore, only a minimized version called Module.a() (or similar).

That's at least my theory of what's happening, and what I've seen in my own code.

The emscripten documentation mentions this at the end of the second blue "NOTE box" here:


quote:

~~~
The compiler will remove code it does not see is used, to improve code size. If you use ccall in a place it sees, like code in a --pre-js or --post-js, it will just work. If you use it in a place the compiler didn’t see, like another script tag on the HTML or in the JS console like we did in this tutorial, then because of optimizations and minification you should export ccall from the runtime, using EXTRA_EXPORTED_RUNTIME_METHODS, for example using -s 'EXTRA_EXPORTED_RUNTIME_METHODS=["ccall", "cwrap"]', and call it on Module (which contains everything exported, in a safe way that is not influenced by minification or optimizations).
~~~

Marco Ippolito

unread,
Dec 30, 2019, 12:34:18 PM12/30/19
to emscripte...@googlegroups.com
Thank you Floh for your kind explanation pointing out that only functions that have been built can be called at runtime.

In order to make ccall and cwrap callable, I then compiled add.c in this way:

    emcc add.c -o js_plumbing.js -s EXPORTED_FUNCTIONS="['_Add']" -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s EXPORT_ES6=1 -s MODULARIZE=1

But now I get this error:

Failed to compile.

./src/components/js_plumbing.js Module build failed (from ./node_modules/babel-loader/lib/index.js): SyntaxError: Unexpected token, expected ( (3:25) 1 | 2 | var Module = (function() { > 3 | var _scriptDir = import.meta.url; | ^ 4 | 5 | return ( 6 | function(Module) {


image.png


If, I compile add.c in this way:
    emcc add.c -o js_plumbing.js -s MODULARIZE=1
and modify Result.vue as follows:


<template>
  <div>
    <p button @click="callAdd">Add!</p>
    <p>Result: {{ result }}</p>
  </div>
</template>

<script>

    import Module from './js_plumbing'

    export default {
      data () {
        return {
          result: null
        }
      },
      methods: {
        callAdd() {

          const instance = Module({
            onRuntimeInitialized() {
              console.log(instance._Add(1,2));
              this.result = instance._Add(1,2);
            }
          })
        }
      }
    }
</script>

then the console.log actually gives the correct output,
but this output is not instantiated within the html part of Result.vue

image.png



--
You received this message because you are subscribed to the Google Groups "emscripten-discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to emscripten-disc...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/emscripten-discuss/71327de2-b015-46b4-b554-2bcd215b2f12%40googlegroups.com.

Marco Ippolito

unread,
Dec 30, 2019, 1:11:25 PM12/30/19
to emscripte...@googlegroups.com
I also tried to compile add.c defining the EXPORTED_FUNCTIONS together with the EXPORTED_RUNTIME_METHODs:

emcc add.c -o js_plumbing.js -s EXPORTED_FUNCTIONS="['_Add']" -s EXTRA_EXPORTED_RUNTIME_METHODS=['ccall','cwrap'] -s MODULARIZE=1

I modified the Result.vue in order to call _Add function through ccall:

    <template>
      <div>
        <p button @click="callAdd">Add!</p>
        <p>Result: {{ result }}</p>
      </div>
    </template>

    <script>
        import Module from './js_plumbing'
        export default {
          data () {
            return {
              result: null
            }
          },
          methods: {
            callAdd() {
              const instance = Module({
                onRuntimeInitialized () {
                  console.log(instance._Add(1,2));
                  this.result = instance.ccall(_Add(1,2));
                }
              })
            }
          }
        }
    </script>

But now I get this error message, when clicking on Add! button: Result.vue?2a14:21 Uncaught (in promise) ReferenceError: _Add is not defined

image.png


Why it says "_Add is not defined" if it was included in the EXPORTED_FUNCTIONS="['_Add']" during compiling phase?

Sam Clegg

unread,
Jan 8, 2020, 9:13:42 PM1/8/20
to emscripte...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages