Overview

It’s important to understand a few things before using V8.NET.  The original premise to creating V8.NET was to create a wrapper that allows the user to control V8 from the managed side – at least to the degree that allows the user to easily integrate their own managed classes. The idea was not to wrap ALL V8 objects with managed ones, but only key ones, such as “ObjectTemplate”, “FunctionTemplate”, “Function”, “Object”, and the V8 handles.  While many methods on the V8 objects also have managed side counterparts (with the same names), many new methods are added to support the managed-side specific implementation.  All that said, the goal does include progressively exposing more and more of the native V8 side to the managed world over time.

The source code comments and this documentation will use the terms “Managed Object” to describe managed ‘IV8NativeObject’ objects, and “Native V8 Object” to describe the objects within the V8 engine.

Note: The source and documentation comments expect you to understand basic V8 concepts, so it is encouraged to read through the Google V8 Embedder’s Guide.  Also, if you see “{V8Engine}” it simply refers to an instance of the engine.

 

Quick Start

To begin using V8.NET, you must do the following:

  1. First, if using Windows, you may need to unblock the downloaded file (right click, and select properties).  If you don't, you will run into security issues trying to load and run the DLLs.
  2. Add a reference to the V8.Net.dll and V8.Net.SharedTypes.dll to your project, and make sure any other accompanying CLR DLLs are also in the same folder (currently there are only those two CLR DLLs). The x86 and x64 dlls should remain in their respective folders (they are not CLR [except the interface one]).  They will be dynamically loaded depending on the OS architecture.  Those files are (or related to) native compiled DLLS, not C# libraries.  The v8.Net.dll (compiled with "any CPU") will decide which one to load.
  3. In your source file, add “using V8.Net;”
  4. Done. Hot smile

To start using it, just create an instance of “V8Engine” and execute commands within a V8 scope. Here’s a simple “Console” application example.

Example
  1. var v8Engine = newV8Engine();
  2. Handle result = v8Engine.Execute("/* Some JavaScript Code Here */", "My V8.NET Console");
  3. Console.WriteLine(result.AsString); // (or "(string)result")
  4. Console.WriteLine("Press any key to continue ...");
  5. Console.ReadKey();
 
 
Another quick start example can be found here: https://gist.github.com/rjamesnw/5ee5a0a2a769b321e1d0

 

DLL Files Required For Your Projects

All of these files MUST exist in your "bin\Debug|Release" folders.

  • v8-ia32.dll / v8-x64.dll
    The native Google V8 engine binaries.
  • V8_Net_Proxy_x86.dll / V8_Net_Proxy_x64.dll
    Native libraries use used to support P/Invokes (contains native proxy wrappers). These are pure native wrappers only (no mixed CLR code).
  • V8.Net.Proxy.Interface.x86.dll / V8.Net.Proxy.Interface.x64.dll
    Managed libraries that contain only interface methods for calling into the native proxy libraries.  V8.NET.dll will “decide” which one to load based on the platform bit depth it is running in.
  • V8.Net.SharedTypes.dll
    Managed library that contains only types needed between the various managed libraries.  This is needed if you access certain enum and struct types (such as property type enums).
  • V8.Net.dll
    This is the main V8.NET library, and is usually the only one you need to reference (though it will be a good idea to reference “V8.Net.SharedTypes.dll” as well).

How the libraries load

When V8.NET.dll loads, it is designed to expect “V8.Net.Proxy.Interface.dll”, which will never exist because it is deleted after the source compiles (via a post build step).  This forces a hook into the assembly resolver to locate the file, at which time the correct interface for the platform is loaded.

 

Thread Safety and Scopes

While the Google V8 engine is not thread safe, it does have "isolates" to block calls while another is in progress (so many threads can use it, but only one thread can call in at a time). The "scopes" have also been abstracted, which makes everything a bit easier on the CLR side.

The Global Object

The global object is the root executing environment where global properties are stored.  This can be found on the managed side in two ways:

  •  {V8Engine}.GlobalObject – This is a reference of type IV8NativeObject, which provides methods for editing properties.  This is a simple wrapper around the native V8 context object. 
  • {V8Engine}.DynamicGlobalObject – This is the same reference as the first one, EXCEPT it is type-cast to “dynamic” for you!  You can simply store properties on it like “{V8Engine}.DynamicGlobalObject.x = 0”.
    Note: When accessing dynamic properties, they will usually be of type “InternalHandle”;  however, if the handle represents a managed object, the managed object will be returned instead.  If you know this in advance, you can simply type cast directly to the managed object type.  If not, then no worries, since the manage types implemented by V8.NET implicitly convert to “InternalHandle” types anyhow.

Handles

Handles wrap native V8 handles and are used to keep track of them.  V8 handles are never disposed (cached) until they are no longer in use, so it is important to make sure to dispose of handles when they are no longer needed.  Fortunately, you can use the “Handle” type and let the garbage collector take care of it for you, or call “Dispose()” yourself if you’d like to release it back more quickly.  There are two types of handles (thought they both function nearly the same):

  • InternalHandle 


    This is a value type that is used internally for wrapping marshalled handle proxies.  This is done so a proxy can quickly be wrapped by a stack created value instead of creating objects within the heap for the GC to have to deal with.  This has great speed advantages when V8 interceptors call back into the managed side via a large loops executed in script.  While this provides a huge performance boost, it comes at the cost of requiring its users to ensure “Dispose()” is called on it in order to reduce reference counts.  It is safe to use the “using(){}” statement, or wrap code in “try..finally” blocks.  For most cases, where speed is not an issue, it is recommended to use the “Handle” type.

    Warning:
    If using this type, you should never use the “=” operator to set a value.  Since C# doesn’t support copy constructors, you have to call “{InternalHandle}.Set()” instead.  If you use the “=” operator to copy this handle type, the system will not be aware of the  copy, and the native handle may become disposed before it gets used.  As well, when calling “Set()” to set a handle value, failing to call “Dispose()” will result in memory leaks.

    Note:
    If you pass an internal handle (for example, one returned by calling “{V8Engine}.CreateInteger()”) directly as an argument to another V8Engine method, it will be detected and dispose automatically, so upon return that handle will no longer be valid.  To prevent this, simple make a copy of the handle value using “Set()” or “Clone()”.

  •  Handle / ObjectHandle

    This is an InternalHandle wrapper (“InternalHandle” is a value type), and is the type most developers should be using, unless speed is an issue.  If at any time you get an “InternalHandle“ type returned, you should either type-cast it to “Handle”, or create a variable of type “Handle” instead (or cast to "ObjectHandle" to represent script objects - "ObjectHandle" has object-related methods).  This allows developers to use the garbage collector to clean up their objects for them.  You don’t have to keep creating these objects – instead, feel free to create them only once and call “Set()” instead (same rule of thumb for “InternalHandle” values).

    Warning:
    If using this type, you should never use the “=” operator to make copies of the handle, unless you know what you are doing.  Since C# doesn’t support copy constructors, you have to call “{Handle}.Set()” instead.   If you use the “=” operator to copy this handle type you will be copying only the reference to the handle object, and V8.NET will not be aware of it.  If many references point to the same handle instance, calling “Dispose()” on any of them will dispose all of them.

There is a special case where “InternalHandle” values don’t need to be disposed.  Anytime a “callback” is executed, the internal handle arguments passed to the method are automatically disposed when the method returns (unless you make copies).

Examples:

The following are two examples of how to use handles.

Example
  1. Handle handle = v8Engine.CreateInteger(0);
  2.  var handle = (Handle)v8Engine.CreateInteger(0);

In both cases, the “InternalHandle” value returned is converted to a handle object so that the native handles can be disposed when they are no longer needed.   This is the recommended way to use handles.  That said, if speed is important, or there’s a need to create an enormous number of handles, you can use the stack created handles “InternalHandle” which will prevent creating objects for the garbage collector to deal with; however, they have to be manually disposed.

Example
  1. var handle = v8Engine.CreateInteger(0);

In the case above, the handle is of type “InternalHandle”.  There are two main things to keep in mind: 1. The handle must be manually disposed, and 2. The handle is marked as a “first” handle, which means it will be destroyed automatically if passed directly into another V8Engine method that accepts those handle types. There are three main ways to dispose handles (of any type):

Example
  1. var handle = v8Engine.CreateInteger(0);
  2. // (... do something with it ...)
  3. handle.Dispose();
  4.  
  5. // ... OR ...
  6.  
  7. using (var handle = v8Engine.CreateInteger(0))
  8. {
  9.     // (... do something with it ...)
  10. }
  11.  
  12. // ... OR ...
  13.  
  14. InternalHandle handle = InternalHandle.Empty;
  15. try
  16. {
  17.     handle = v8Engine.CreateInteger(0);
  18.     // (... do something with it ...)
  19. }
  20. finally { handle.Dispose(); }

If a copy of a handle is needed, then you must “set” it, and not “assign” it.

Example
  1. handle.Set(anotherHandle);
  2. // ... OR ...
  3. var handle = anotherHandle.Clone(); // (note: this is only valid when initializing a variable)

In both these cases, a proper copy of the handle is made.  A counter on the native side keeps track of the number of handles, and is only released when all handles on the managed side are disposed.  As long as one handle copy exists, disposing one will not release the native handle.
Note: On line 1, when “Set()” is called, if “anotherHandle” is a value directly returned from a V8Engine method (such as “CreateInteger()”), making a copy of it will immediately dispose the returned handle value.

The following is an example of what NOT to do:

Example
  1. var handle = v8Engine.CreateInteger(0);
  2. var handle2 = handle;

This example makes a copy of the handle (an “InternalHandle” type); however, because “Set()” was not used, disposing one will immediately invalidate the other as well, and the native handle will no longer be valid. As a rule of thumb, never use the “=” operator on ANY handle type, unless this behaviour is desired. The same issue will occur for “Handle” types as well for the same reason.

In regards to “InternalHandle” types, here’s another example of what NOT to do:

Example
  1. handle.Set(anotherHandle.Clone());

This creates a new copy of “anotherHandle” (incrementing an internal handle counter in the process) and passes the new copy to the “Set()” method.  At this point there are now two handles (the original and the copy).  The “Set()” method will first dispose of any existing handle reference in “handle”, and then make another copy of the copy passed in, effectively resulting in 2 copies (besides the original one).  The first copy is never disposed, causing a memory leak.  It’s important to use either one or the other as needed, but not both.

 

More Help

There is much more documentation on all classes and methods throughout the assemblies (and source).  As well, please open the program.cs file of the Console project and review it for examples.  I encourage you to also run the console app and watch what it outputs to the screen, then take a look at the source behind he outputs.  It will show examples, and allow you to play around with the environment to get a handle on it.  If you still need more help, feel free to start a discussion.

 

Last edited Jan 27 at 5:06 PM by jamesnw, version 53

Comments

jamesnw Dec 19, 2013 at 4:20 AM 
Yes, correct, thanks. :)

danielearwicker Oct 21, 2013 at 10:02 AM 
The first example doesn't compile: Console.WriteLine(result); is ambiguous due to multiple overloads of WriteLine that Handle can convert for.

jamesnw Oct 8, 2013 at 6:41 PM 
Hi @neurospeech, I didn't see your comment until now (never got any notices for some reason). There's a discussion on this now that should help clarify things a bit. https://v8dotnet.codeplex.com/discussions/456763 I'll update the doc asap. :) (please feel free to open a discussion and ask me how to do a specific thing, and I'll type out some example code for you. :) )

neurospeech Sep 8, 2013 at 6:50 AM 
How about accessing & calling CLR objects & methods?

dvela1 Jul 1, 2013 at 2:21 AM 
Nice!

jamesnw Jun 19, 2013 at 5:47 AM 
Ok, I've added some more handle examples with more detailed explanations.

jamesnw Jun 17, 2013 at 7:07 PM 
Sure thing. Was going to do it last night but ran out of time. ;)

dvela1 Jun 17, 2013 at 2:25 PM 
Can we get some example code. Maybe different ways on how to use the Handles?