Skip to content
James edited this page Mar 16, 2019 · 15 revisions

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 (after much experience I now realize I need to include a context type as well, which will come soon). 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 curly brackets like {SomeType} it simply refers to an instance of the type.

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, that's all. Make sure the other x86 and x64 interface and proxy CLR DLLs end up in the target output folder of your host project. They will be dynamically loaded depending on the OS architecture. 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. 8)

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

1.  var V8Engine = new V8Engine(); // (note: you can pass in "false" to create your own context)
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

Command line options for V8 are now supported. You can use '{V8Engine}.SetFlagsFromCommandLine(string flags)' to set one or more flags (space separated).

Definition of options: https://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/flag-definitions.h

Example: {V8Engine}.SetFlagsFromCommandLine("--use_strict")

DLL Files Required For Your Projects

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

  • 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.dll This is the main V8.NET library, and is the only one you need to reference.

    Note: There were 4 CLR libraries, but using a new native loading method the shared library and 2 other interface libraries are no longer required. All code is now consolidated into a single assembly.

How the libraries load

When V8.NET.dll loads, it looks for the native libraries. It has a loader class that searches commonly known locations, such as the current working folder, the DLL location (bin and nuget cache), x64 and x86 sub-folders, and so on.

Tip: Set Loader.AlternateRootSubPath to a sub-directory (defaults to 'V8.NET') to act as an alternate sub-folder to check for. For instance, if using Visual Studio, you could create a "V8.NET" folder in your project, then dump the V8.NET libraries there, and set all files to "Copy if newer". This may help when deploying web solutions, as the IDE will know of the files. The IDE will create 'bin{Debug|Release}\V8.NET{optional:x86|x64}' folder paths in the target output. In this case, using the default 'V8.NET' value, the V8.Net native library loader will consider the nested folder to find the required libraries, if need be.

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.

If needed, you can create a custom global object using an ObjectTemplate. You must first pass false to the engine constructor, create and configure the object template, then call {V8Engine}.CreateContext() with the template. Once you have a context you can assign it as the current context using {V8Engine}.SetContext().

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 (though they both function nearly the same):

  • InternalHandle

    This is a value type that is used internally for wrapping marshalled handles on the native side. This is done so a native handle can quickly be wrapped by a stack-bound value instead of creating objects on the heap for the GC to have to deal with. Stack-bound handles have great speed advantages when V8 interceptors call back into the managed side via large loops executed in script. Outside of call-back methods, it is safe to use the “using(){}” statement, or wrap code in “try..finally” blocks if desired (to force disposal instead of waiting for the GC to do it later). For most cases, where speed is not an issue, it is recommended to use the “Handle” type.

    Exception to the rules: Internal handles used in callback parameters are disposed AUTOMATICALLY upon return from the callback method. Because of this, you should not dispose those yourself.

  • Handle / ObjectHandle
    This is an InternalHandle wrapper (InternalHandle is a value type, while Handle is a class), 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 ObjectHandle to create a variable of type Handle instead (use "ObjectHandle" to represent script objects - it has object-related methods).

    Note: Handle is also the based type for all managed side objects that represent native V8 objects.

Handles in Callback Functions

Callback functions are passed InternalHandle values. InternalHandle parameter values don’t need to be disposed. Anytime a callback is invoked, the internal handle arguments passed to the method are automatically disposed when the method returns. If you need to persist the value, make a copy of it using .Set() or .KeepAlive().

Examples:

The following are two examples of how to use handles.

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.

There are three main ways to forcibly dispose handles (of any type):

1.  var internalHandle = v8Engine.CreateInteger(0);
2.  // (... do something with it [the next 2 below is better in case of errors] ...)
3.  internalHandle.Dispose();
4.
5.  // ... OR ...
6.
7.  using (var internalHandle = v8Engine.CreateInteger(0))
8.  {
9.      // (... do something with it ...)
10. }
11.
12.  // ... OR ...
13.
14.  var internalHandle = InternalHandle.Empty; (optional to initialize)
15.  try
16.  {
17.      internalHandle = v8Engine.CreateInteger(0);
18.      // (... do something with it ...)
19.  }
20.  finally { internalHandle.Dispose(); }

Garbage Collection

Garbage collection is a challenge to deal with, as normally you try never touch the GC and work around it. However, exposing managed objects to the native side means the managed side GC has no idea about native side V8 handles. V8 does have a GC itself, but as well, it also has no idea about managed objects. To make this work it is necessary to hook into the GC finalization process on both sides in order to coordinate the disposal of objects. One rule of thumb is that the managed side owns ALL wrappers to native proxy data, so disposal must start on the manage side. These are the usual rules/steps:

  1. The InternalHandle type has a reference to a shared tracker object (when returned outside the engine), and the Handle type tracks itself. All handles have an internal native field Disposed set to 0. The {V8Engine}.Handles_Active property will return a list of these handles (no need to dispose the handles returned from this list).

  2. When a CLR handle loses all references to it, the Finalizing() method will get called by the GC (indirectly) to have itself added to a disposal queue (only if necessary, otherwise nothing happens [eg. 'Handle' objects always finalize immediately, and add an InternalHandle value to the disposal queue instead]). This is required, as calling into V8 from the finalizer thread to dispose native side V8 handles can cause deadlocks (Google V8 does not support multiple threads at the same time). Once in the disposal queue, an internal proxy field 'Disposed' is set to 1, which indicates that the managed side is now ready to dispose of the managed object. These handles can be inspected through {V8Engine}.Handles_ManagedSideDisposeReady.

  3. The worker thread reads from the disposal queue and checks the '{IV8Disposable}.CanDispose' property. If true, it will call '{IV8Disposable}.Dispose()' on the object (in essence, acting like a finalizer). If false, then it is assumed that the object is still needed by the native V8 side. In such case it is assumed that the object was "abandoned" on the manage side (no more CLR references), and is then added to an "abandoned" queue. If this is found to be a 'V8NativeObject', then the native V8 handle is marked as "weak", and the proxy ''Disposed' value is set to 2, which means that the native V8 engine will call back to dispose the object later on when no other V8 handle exists (note: just because there's no JavaScript reference doesn't mean V8 or a native side proxy object doesn't still have a reference for some reason). These handles can be inspected through '{V8Engine}.Handles_NativeSideWeak'. Items that dispose successfully (usually InternalHandle values) can be inspected through {V8Engine}.Handles_DisposedAndCached.

  4. The queue of abandoned objects are scanned periodically (in timed short stages) to see if they can be disposed by checking the '{IV8Disposable}.CanDispose' property. Most objects that make it here are 'ObjectTemplate' and 'FunctionTemplate' objects (though others are possible). Those template objects must remain as long as any objects created from them still exist, otherwise those objects will no longer be valid. As template created objects are disposed, a reference counter is updated on the template object. When this is 0, the template object will then dispose successfully.

Integration into ASP.NET:

  1. Create a folder specifically named "V8.NET", or change the name using 'Loader.AlternateRootSubPath'.

    Note: There is a test project in the source with the source which uses a PRE-Build event that copies files to "$(ProjectDir)V8.NET" - this must match 'Loader.AlternateRootSubPath'.

    Note: Loader.AlternateRootSubPath is set to "V8.NET" by default, but you could change it to "lib"/"libs" for example, if you have other libraries all in one folder.

  2. For all DLLS in the "$(ProjectDir)V8.NET" folder, change "Copy to Output Directory" to "Copy if newer". This should also tell Visual Studio that this content is required for the application.

  3. Set any "$(ProjectDir)V8.NET*.DLL" files that you have REFERENCED to "Do not copy" - the build action will copy referenced DLLs by default.

    When setup correctly, Visual Studio will replicate the folder structure for the "V8.NET" folder into the 'bin' folder, and the referenced DLLs will end up in the ROOT of the 'bin' folder. Thus, you will have this folder structure:

    • bin\V8.Net.dll
    • bin\V8.Net*.dll The V8.Net.dll Loader class will look for the "V8.NET" folder (or other if specified) in the 'bin' folder for the other DLLs, and if found, will use that instead to locate the dependent libraries.

    Note: ASP.NET may shadow copy some DLLs in the 'bin' folder (http://goo.gl/vXbwGp). This means that those DLLs may end up elsewhere in a temporary folder during runtime.

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. There are some very neat examples of integration with the .Net side. 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 the 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.

In addition, there are example projects under a solution folder called "Test Projects" (note: due to recent changes some may be missing or not be working as of March 6, 2019):

  • V8.Net-Console: A demo console I use mainly for unit-testing, but contains may examples as well.
  • V8.NET-Console-ObjectExamples: Examples on how to integrate your own types.
  • ASPNetTest: Example ASP.NET project.
  • WCFServiceTest: Example WCF service project.
Clone this wiki locally