I would like to extend the functionality of vbscript with the code I have written in c#. I have written some classes to automate the SAP GUI and would like to use these classes in all the vbscript files I have.
I have hundreds of vbscript files and know it will take years to convert all them to C#. So I think it will be faster to expose my c# classes to vbscript.
Do you know how to do this or know any references online I can study?
I don't know whether you're running your VBScript from the command-line or from within something like Office.
If the former, you could create one or more command-line apps that you can call from any scripting language and into which you pass parameters & action specifiers just like any other command-line tool. (Also consider moving to PowerShell in this case - it exponentially better than VBScript for command-line scripting & has great integration with .NET).
If the latter, you'll likely need to register your C# classes using RegAsm and then create instances of your C# types as per any other COM type. See this post for more details: How do I call .NET code (C#/vb.net) from vbScript?
VB script runs on the client inside the browser run-time.
The only C# solution I am aware of to run inside the browser, is silverlight. It is still just c# though.
You can access c# code from scripting languages like VB- of java-script, by decorating them with the [ScriptableMember] attribute, like so:
/// <summary>
/// Members that can be called from javascript. (or vbscript)
/// </summary>
public sealed class LINEARVIEWER_SL_SCRIPTS {
[ScriptableMember]
public void ChangeNetwork(string pNetworkFilterId, string pNetworkFilter) {
MainViewModel MainVM = (MainViewModel)((MainPage)Application.Current.RootVisual).DataContext;
long SectionID;
if (long.TryParse(pNetworkFilterId, out SectionID) == false) {
throw new FormatException("'" + pNetworkFilterId + "' not a valid section / network ID.");
}
MainVM.RoadFilterViewModel.SelectSectionAsync(SectionID, /* completed handler = */ null);
}
}
You have to register these classes when the silverligh (c#) application starts up, like so:
private void Application_Startup(object sender, StartupEventArgs e) {
...
HtmlPage.RegisterScriptableObject("LINEARVIEWER_SL_SCRIPTS", new LINEARVIEWER_SL_SCRIPTS());
}
From the java (or vb) script, you can then simply call those methods like so:
function DoAddToLIV(pNetworkFilterId, pNetworkFilter) {
...
gObjLIV.Content.LINEARVIEWER_SL_SCRIPTS.ChangeNetwork(pNetworkFilterId, pNetworkFilter);
...
}
where gObjLIB.Content is the id of the silverlight object inside the html page.
var gObjLIV = null;
function onSilverlightPluginLoaded(sender, args) {
gObjLIV = sender.getHost();
}
You can hook that function to the silverlight object in the html of ASPX page, using this parameter:
<param name="onLoad" value="onSilverlightPluginLoaded" />
Let me know if I missed anything or if you need more examples. I don't mind.
Related
I have been given a library written in C# and I need to use it in a C++ project. The C# library has been exported to a .tlb type library, which I can successfully import into my C++ project by using the #import directive.
Being utterly unfamiliar with COM I can't for the life of me figure out how to get at static member functions on any classes. Here's how I access it in C#:
void Function()
{
StaticClass.StaticMethod();
}
And then you get into the C++ side, what gets generated in the .tlh file is:
struct __declspec(uuid("some big long thing"))
/* dual interface */_StaticClass;
//long while later
_COM_SMARTPTR_TYPEDEF(_StaticClass, __uuidof(_StaticClass));
So I'm trying to figure out how to get use of the static class and haven't had any luck with Google. The only example anywhere else in any other project I have access to gives me something similar to this:
_StaticClassPtr s = _StaticClassPtr(__uuidof(_StaticClass));
but the example I have isn't for a static class anyway.
Basically I'm stuck with nowhere to even really start. This fails with "Unhandled exception at in <executable>: Microsoft C++ exception: _com_error at memory location <location>"
Edit: Since #dxiv informed me static methods aren't usable with COM interop, there's another option marked 'obsolete' that does not use static members -- problem is I get exactly the same exception when I construct the instance with similar syntax:
IInstanceClassPtr p = _IInstanceClassPtr(__uuidof(_InstanceClass));
The same exception is thrown, "_com_error at memory location"
Reading your question you would like to use C# using COM Interop. What I done is something like this. Starting from the assumption that COM is the acronym of Component Object Model and to use it you need an instantiable, local or remote, object. The example below creates an "in-proc" instance of the CLR object.
Create an interface which is exposed to COM:
namespace MyNamespace
{
/// <summary>
/// Provides an entry point for COM clients.
/// </summary>
[ComVisible(true)]
[Guid("A9E6D7FE-34FD-4A6B-9EB2-DC91F4AE567B")]
public interface IMyAccessor
{
void ExecuteStaticMethod();
// add anything else like methods, property,
}
}
then implement the interface in a C# class:
namespace MyNamespace
{
/// <summary>
/// The implementation of IMyAccessor.
/// </summary>
[ComVisible(true)]
[Guid("C65A7F81-641C-4F17-B34A-DEB88B4158E8")]
[ProgId("MyCompany.MyAccessor")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IMyAccessor))]
public sealed class MyAccessor: IMyAccessor
{
public void ExecuteStaticMethod() { StaticClass.StaticMethod(); }
}
}
export the TLB and import it in C++ project (MyAccessor is only a name I used here) in the header file of your C++ class using the following clause:
#import "MyAccessor.tlb"
Within the class header add a line like the following:
MyNamespace::IMyAccessorPtr m_IMyAccessor;
And in the class implementation use the following:
HRESULT hr = m_IMyAccessor.CreateInstance(__uuidof(MyNamespace::MyAccessor));
if (FAILED(hr))
{
// do something if failed
}
m_IMyAccessor->ExecuteStaticMethod(); // this will execute your static method in C#
NOTE: when exporting the TLB use the correct switchs. In an x64 environment (/win64) must be used to have the right pointer size: normally tlbexp returns pointer usable in a 32bit environment. This is important if you want to extend the class with more sofistcated methods.
NOTE 2: if the returned HRESULT from CreateInstance is something like "class not registered", remember to execute the registration of the TLB wih REGTLIB.
*Please note that this is not for a web based application, it's windows based.
I'm building an application where I will need the user to submit simple javascripts that will be run by the application.
The scripts will call functions that are part of the c# build.
An example:
C# code:
public void helloWorld()
{
Debug.WriteLine("hello world");
}
Javascript submitted by user:
helloWorld();
The JavaScript would be parsed by the application at runtime and then call the required functions in my C# code.
Why?..
My app will be used by people with very little programming experience, they enter very simple JavaScripts and the app will attempt to automate a few tasks on the users computer. So my reason for using JavaScript is because it's simple and very easy to learn for someone with little experience.
It sounds like you want a JavaScript parser for your application. To be honest, I dont think what you're doing is possible, considering the context of the script and your code is different. However, this project seems to be doing something that may get you to the right place:
http://javascriptdotnet.codeplex.com/
Personally, I would think making some kind of XML format would be useful (like how UrlRewriter.net makes rewriting URLs easy):
<xml>
<commands>
<!-- Expose a Set of Condition Objects to Select From -->
<if condition="YourApplication.Conditions.RightClickOnDesktop">
<print text="HelloWorld" />
</if>
</commands>
Here is an example running a javascript code which, in turn, invokes a c# method
[System.Runtime.InteropServices.ComVisible(true)]
public class CSharpClass
{
public void MsgBox(string s)
{
MessageBox.Show(s);
}
}
-
Type scriptType = Type.GetTypeFromCLSID(Guid.Parse("0E59F1D5-1FBE-11D0-8FF2-00A0D10038BC"));
dynamic obj = Activator.CreateInstance(scriptType, false);
obj.Language = "Javascript";
obj.AddObject("mywindow", new CSharpClass(), true);
var result = obj.Eval(
#"
function test(){
mywindow.MsgBox('hello');
}
test();
"
);
Why do you "need the user to submit simple javascripts"? What is your application and what do users need it to do? Why have you decided a scripting language is the way to do this? I'm not saying that is the wrong answer, but that you have not justified this conclusion.
If your app will be used by "people with very little programming experience" I do not recommend implementing a scripting language. Basic concepts like source code and variables are very difficult for non-programmers to understand.
I suggest first investigating macro recording for user scripting. For .NET there is UI Automation and the White automation framework.
I'm exposing a C# class to COM using these attributes:
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.AutoDual)]
[GuidAttribute("2325EBEB-DB5F-4D29-B220-64845379D9C5")]
[ComSourceInterfaces(typeof(WrapperEvents))]
in this class I have a function:
public void shutdownService()
This function is meant to be called just once from a VB6 client via COM Interop. Everything works fine. But somehow, it's being called more than once. My C# codes doesn't call this function directly. So I'm guessing the problem is in VB6 code. Unfortunately, that's not what the VB6 team thinks.
Is there a way to determine the caller of this function, ie. from my C#code or the VB6 code?
Right now I'm using a simple function to get the stacktrace:
public void LogStack()
{
var trace = new System.Diagnostics.StackTrace();
foreach (var frame in trace.GetFrames())
{
var method = frame.GetMethod();
if (method.Name.Equals("LogStack")) continue;
logger.Debug(string.Format("LogStack: {0}::{1}",
method.ReflectedType != null ? method.ReflectedType.Name : string.Empty, method.Name));
}
}
Obviously, I got somthing like this on the log:
2011-12-23 08:28:40,067 1 DEBUG (null) LogStack: Service::shutdownService
Since the only line of LogStack is the COM exposed function, I assume it's being called from vb6. But that's not enough proof for the VB6 team. Any idea how to really prove where function ?
You can try several things:
set a breakpoint in your code to trigger the debugger, then look at the call stack.
You could do an application dump here from visual studio and send it to them or screenshot the stack.
ex. Debugger.Break
http://www.netsplore.com/PublicPortal/blog.aspx?EntryID=12
Dump with "Savre Dump As"
http://msdn.microsoft.com/en-us/library/d5zhxt22.aspx
Use the com tracing
from a system level see
http://support.microsoft.com/kb/926098
I also recall a tool being installed with visual studio 6 do to this as well
I'm embedding IronPython (2.6.1) in a C# assembly and exposing several objects to scripts which are executed with PythonEngine.ExecuteFile. I expose them either with
scope.SetVariable("SomeObject", new SomeObject())
or
engine.Execute("from MyNamespace import SomeObject", scope)
depending on how the scripts use them. My application assembly is added to the engine with
engine.Runtime.LoadAssembly(Assembly.GetExecutingAssembly())
Now a script can execute help(SomeObject) and dump the nice little help info(*), however it's incomplete. None of the object's events or properties (public of course) show up and many of the 'built-in' members are missing as well.
Here's the odd part; If I fire up ipy.exe and execute the following:
import sys
sys.path.append('<location of my app>')
import clr
clr.AddReferenceToFile('myapp.exe')
from MyNamespace import SomeObject
help(SomeObject)
I get a different dump, complete with all the missing members!
Why do the two differ?
Bonus question: Assuming I get it working correctly, is it possible to add descriptive text on my CLR objects to the output of help()? Like you can from within the script, on your python-native types? My first guess was the DescriptionAttribute, but that didn't work.
(*) Obviously a final, working script wouldn't do this but it is exceedingly helpful while writing/testing the script.
Answered
Here is a complete console program that illustrates how to import the site which replaces the usless internal help() with the standard python library help().
using System;
using System.Collections.Generic;
using System.Reflection;
using IronPython.Hosting;
using IronPython.Runtime;
using Microsoft.Scripting.Hosting.Providers;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
// Work around issue w/ pydoc - piping to more doesn't work so instead indicate that we're a dumb terminal
if (Environment.GetEnvironmentVariable("TERM") == null)
Environment.SetEnvironmentVariable("TERM", "dumb");
var engine = Python.CreateEngine();
// Add standard Python library path (is there a better way to do this??)
PythonContext context = HostingHelpers.GetLanguageContext(engine) as PythonContext;
ICollection<string> paths = context.GetSearchPaths();
paths.Add(#"C:\Program Files (x86)\IronPython 2.6\Lib");
context.SetSearchPaths(paths);
// Import site module
engine.ImportModule("site");
engine.Runtime.LoadAssembly(Assembly.GetEntryAssembly());
var scope = engine.CreateScope();
scope.SetVariable("SomeObject", new SomeObject());
engine.Execute("help(SomeObject)", scope);
}
}
/// <summary>
/// Description of SomeObject.
/// </summary>
public class SomeObject
{
/// <summary>
/// Description of SomeProperty.
/// </summary>
public int SomeProperty { get; set; }
/// <summary>
/// Description of SomeMethod.
/// </summary>
public void SomeMethod() { }
/// <summary>
/// Description of SomeEvent.
/// </summary>
public event EventHandler SomeEvent;
}
}
My guess is that in your app you're not importing the standard library. IronPython includes a built-in help function and the standard library includes a help function which gets installed by site.py. If you make sure the standard library is available when you host and then import site.py when you create the engine then you should get the standard library help. That being said it's probably a bug or missing feature that the built-in help isn't documenting events and properties.
Regarding the documentation - yes, you just need to use C#'s doc comments and build with the /doc:MyAssemblyName.xml option. If the XML file is in the same directory as the assembly IronPython will read the doc strings and provide them for doc attributes which help() then reads.
IronRuby and VS2010 noob question:
I'm trying to do a spike to test the feasibility of interop between a C# project and an existing RubyGem rather than re-invent that particular wheel in .net. I've downloaded and installed IronRuby and the RubyGems package, as well as the gem I'd ultimately like to use.
Running .rb files or working in the iirb Ruby console is without problems. I can load the both the RubyGems package, and the gem itself and use it, so, at least for that use case, my environment is set up correctly.
However, when I try to do the same sort of thing from within a C# (4.0) console app, it complains about the very first line:
require 'RubyGems'
With the error:
no such file to load -- rubygems
My Console app looks like this:
using System;
using IronRuby;
namespace RubyInteropSpike
{
class Program
{
static void Main(string[] args)
{
var runtime = Ruby.CreateRuntime();
var scope = runtime.ExecuteFile("test.rb");
Console.ReadKey();
}
}
}
Removing the dependencies and just doing some basic self-contained Ruby stuff works fine, but including any kind of 'requires' statement seems to cause it to fail.
I'm hoping that I just need to pass some additional information (paths, etc) to the ruby runtime when I create it, and really hoping that this isn't some kind of limitation, because that would make me sad.
Short answer: Yes, this will work how you want it to.You need to use the engine's SetSearchPaths method to do what you wish.
A more complete example
(Assumes you loaded your IronRuby to C:\IronRubyRC2 as the root install dir)
var engine = IronRuby.Ruby.CreateEngine();
engine.SetSearchPaths(new[] {
#"C:\IronRubyRC2\Lib\ironruby",
#"C:\IronRubyRC2\Lib\ruby\1.8",
#"C:\IronRubyRC2\Lib\ruby\site_ruby\1.8"
});
engine.Execute("require 'rubygems'"); // without SetSearchPaths, you get a LoadError
/*
engine.Execute("require 'restclient'"); // install through igem, then check with igem list
engine.Execute("puts RestClient.get('http://localhost/').body");
*/
Console.ReadKey();