I'm using clearscript as a vbscript execution engine.
I have exposed the following C# object (I appreciate it doesn't actually execute a query, I'm just testing for now):
public class SCB
{
public ADODB.Recordset executeQuery(string q)
{
ADODB.Recordset _recordset = new ADODB.Recordset();
_recordset.Fields.Append("Id", ADODB.DataTypeEnum.adInteger);
_recordset.Fields.Append("Name", ADODB.DataTypeEnum.adVarChar, 20);
_recordset.Open(System.Reflection.Missing.Value
, System.Reflection.Missing.Value
, ADODB.CursorTypeEnum.adOpenStatic
, ADODB.LockTypeEnum.adLockOptimistic, 0);
_recordset.AddNew(Type.Missing, Type.Missing);
_recordset.Fields["Name"].Value = "Test";
return _recordset;
}
}
}
I have created a vbscript host using:
_engine = new VBScriptEngine(WindowsScriptEngineFlags.EnableDebugging);
And added the class as an object with this line:
_engine.AddHostObject("SCB", HostItemFlags.GlobalMembers, new SCB());
If I run some vbscript code via the following call I receive an error (described below):
_engine.ExecuteCommand(code);
The code I am executing is the following:
Function GetUniversalMessage
dim lRS, sMessage, sColour,sTimeout
Set Lrs = SCB. executeQuery ("SELECT MESSAGE, TIMEOUT, COLOUR, ENABLED FROM U_SCROLLER WHERE SCROLLER_ID=1 AND ENABLED='Y' AND (DISABLE_TIME IS NULL OR DISABLE_TIME>GETDATE())")
if not lRS.EOF then
End If
End Function
I receive an exception that lrs.eof is not a valid field ... but it is valid for an ado com object, and if I check the object created in executeQuery before it returns to the script engine, the EOF field is present.
When I debug by attaching the debugger and calling the stop command, I can see that the lrs object in vbscript does not contain EOF, or most of the valid Recordset fields.
Can someone assist me in explaining what clearscript is doing to the object before it is inserted into the vbscript engine?
Thanks,
Rob
Check whether you're using embedded Interop types. That feature is problematic for scripting because the embedded types are stripped of any members you don't use in managed code.
For example, if you don't use EOF in managed code, the metadata for the EOF property will be left out of the embedded version of Recordset.
In Visual Studio, try setting the Embed Interop Types property of your ADODB reference to False.
Another thing to try would be to change the executeQuery return type to object, or add the attribute [ScriptMember(ScriptMemberFlags.ExposeRuntimeType)].
Related
I have a VBScript which automates a little step of a SAPGUI Application.
The point is, it is needed to be converted to a dll due to internal reasons. So I created a .Net Class Library and added the Microsoft.VisualBasic.dll as assembly reference. Now I am able to get the application object, but I'm unable to execute the following methods on the object itself.
I tried through getting the type of the application and then calling getMethod() to access the function, but this didn't work out.
object application = SapGuiAuto.GetType().GetMethod("GetScriptingengine");
And I tried accessing the function through the Microsoft.VisualBasic.Interaction.Methods but these do not provide the function needed.
Here my vbs:
Set SapGuiAuto = GetObject("SAPGUI")
Set application = SapGuiAuto.GetScriptingEngine
If Not IsObject(connection) Then
Set connection = application.Children(0)
End If
If Not IsObject(session) Then
Set session = connection.Children(0)
End If
If IsObject(WScript) Then
WScript.ConnectObject session, "on"
WScript.ConnectObject application, "on"
End If
session.findById("wnd[0]").maximize
session.findById("wnd[0]/usr/cntlIMAGE_CONTAINER/shellcont/shell/shellcont[0]/shell").selectedNode = "0000000003"
session.findById("wnd[0]/usr/cntlIMAGE_CONTAINER/shellcont/shell/shellcont[0]/shell").doubleClickNode "0000000003"
session.findById("wnd[0]/usr/cntlIMAGE_CONTAINER/shellcont/shell/shellcont[0]/shell").selectedNode = "0000000004"
session.findById("wnd[0]/usr/cntlIMAGE_CONTAINER/shellcont/shell/shellcont[0]/shell").nodeContextMenu "0000000004"
session.findById("wnd[0]/usr/cntlIMAGE_CONTAINER/shellcont/shell/shellcont[0]/shell").selectContextMenuItem "XXEXEC"
session.findById("wnd[0]/usr/cntlSINWP_CONTAINER/shellcont/shell/shellcont[0]/shell").selectedNode = " 2"
session.findById("wnd[0]/usr/cntlSINWP_CONTAINER/shellcont/shell/shellcont[0]/shell").selectedNode = " 3"
session.findById("wnd[0]/usr/cntlSINWP_CONTAINER/shellcont/shell/shellcont[1]/shell/shellcont[0]/shell").pressToolbarButton "CREA_RAW"
Sadly my C# Script is quite small, since it's the first thing I'm getting stuck on:
public static void RunScriptParameterless()
{
object SapGuiAuto = Interaction.GetObject("SAPGUI", "");
MethodInfo application = SapGuiAuto.GetType().GetMethod("GetScriptingengine");
object session = application.Invoke(SapGuiAuto, null);
}
I just get a NullPointerException on the application declaration line.
Actually I want an object just like in the VBS which is a connection on which I can operate on.
I'm using the following code to run a VBA macro via C# Excel interop:
public void macroTest()
{
Excel.Application xlApp = new Excel.Application();
xlApp.Visible = true;
string bkPath = #"C:\somePath\someBk.xlsm";
Excel.Workbook bk = xlApp.Workbooks.Open(bkPath);
string bkName = bk.Name;
string macroName = "testThisMacro_m";
string runString = "'" + bkName + "'!"+macroName;
xlApp.Run(runString);
bk.Close(false);
xlApp.Quit();
}
testThisMacro_m is in a module testMacro, and this runs successfully. When I replace it with:
string macroName = "testThisMacro_s";
where testThisMacro_s has its code in Sheet1, the xlApp.Run() line gives the following COM Exception:
Cannot run the macro ''someBk.xlsm'!testThisMacro_s'.
The macro may not be available in this workbook or all macros may be disabled.
I checked macro security settings, and they are indeed set to "Disable with notification", but being able to run a macro from a module and not from a worksheet seems to indicate that this is a different issue than application-level macro security.
Is there something different that I have to do when making an interop call to a macro in a worksheet?
UPDATE: I was able to get the macro to execute by changing the call to:
string macroName = "Sheet1.testThisMacro_s"
but it seems that this hands control back to C# before the macro completes, so now I need to figure out how to check for macro completion (probably a different question).
A Worksheet object is an object - and objects are defined with class modules. Worksheets, workbooks, user forms; they're all objects. And you can't just call a method on an object, if you don't have an instance of that object.
Macros work off standard modules, which aren't objects, and don't need to be instantiated.
Application.Run can't call methods of an object, that's why macros need to be in standard modules.
I was able to get the macro to execute by changing the call to:
string macroName = "Sheet1.testThisMacro_s"
Wouldn't a helper sub solve both of your problems, re: Mat's Mug's reply concerning instantiation?
In some standard module:
Sub testHelperSubToBeCalledFromInterop
Call Sheet1.testThisMacro_s
End Sub
EDIT:
I want to translate the following VB6 code into C#
If optHost(0).Value Then
Set m_oScpiAccess = New IcSCPIActiveX.IcSCPIAccess
Else
sHost = txtHost.Text
Set m_oScpiAccess = CreateObject("Exfo.IcSCPIActiveX.IcSCPIAccess", sHost)
End If
I used TlbImp.exe to create wrappers for the COM classes, and I tried:
if (string.IsNullOrEmpty(host))
{
// this works
IcSCPIAccess = new IcSCPIAccess();
}
else
{
// throws MissingMethodException
IcSCPIAccess = (IcSCPIAccess)Activator.CreateInstance(
typeof(IcSCPIAccessClass),
host);
}
But there is no constructor which accepts the host parameter
It is not a constructor call. The sHost variable contains the name of a machine, the one that provides the out-of-process COM server. The equivalent functionality is provided by Type.GetTypeFromProgId(), using the overload that allows specifying the server name:
var t = Type.GetTypeFromProgID("Exfo.IcSCPIActiveX.IcSCPIAccess", sHost, true);
obj = (IcSCPIAccess)Activator.CreateInstance(t);
I named it "obj", do avoid giving variables the same name as the interface type. A gazillion things can still go wrong, having the COM server properly registered both on the client and server machine and setting the DCOM security correctly is essential to make this code work. Don't try this until you are sure that the original code works properly.
I am writing a C# client for a Corba server and I am using IIOP.NET, going by the example on the following page: http://iiop-net.sourceforge.net/rmiAdderDNClient.html
I have gotten this far without errors:
// Register channel
IiopClientChannel channel = new IiopClientChannel();
ChannelServices.RegisterChannel(channel, false);
// Access COS naming context
CorbaInit init = CorbaInit.GetInit();
NamingContext context = init.GetNameService(host, port);
The variable "host" is a string with the computer name of the server and "port" is an int representing the port number. The values for these are currently used by other systems to connect to the server so I can confirm that they are correct.
However, trying to connect to the trader service yields an exception in runtime. Here is the code I use to do that:
// Looking up VB Trader
NameComponent[] names = new NameComponent[] { new NameComponent("TraderInterface") };
object obj = context.resolve(names);
And here is the error message I'm getting:
"CORBA system exception : omg.org.CORBA.INV_OBJREF, completed: Completed_No minor: 10102."
This seems to suggest an invalid object reference, but what does that mean? Is the string I am passing to the resolve method incorrectly formatted? I have tried many different names for this service as used in other systems, but I always get the same error, which makes me wonder whether I am even interpreting it correctly.
Incidentally, in my desperation, I have also attempted to obtain an object reference from the IOR, but this again throws a different exception (namely omg.org.CORBA.ORB_package.InvalidName).
OrbServices orb = OrbServices.GetSingleton();
object obj = orb.resolve_initial_references(traderIOR);
Any advice is welcome.
I was never able to reach my server with any of the above methods, however the following code is what finally got the communication working:
Hashtable props = new Hashtable();
props[IiopChannel.BIDIR_KEY] = true;
props[IiopServerChannel.PORT_KEY] = port;
// register corba services
IiopChannel channel = new IiopChannel(props);
ChannelServices.RegisterChannel(channel, false);
MyInterface obj = (MyInterface)RemotingServices.Connect(typeof(MyInterface), ior);
I'm not entirely sure why I had to use this (seemingly) unconventional way. Perhaps it is due to the lack of a naming service running on the server. Whatever the cause, I hope this helps somebody out there.
Is it possible to do this task using C#?
Global Const COMPLUS_SERVER As String = "http://myserver"
Sub Test()
Set objRDS = CreateObject("RDS.Dataspace")
Set objCLS = objRDS.CreateObject("MY_System", COMPLUS_SERVER)
Set ListNames = objCLS.LstOBSReasons("databaseserver", "databasename", 5)
End Sub
I've tried with Activator.CreateInstance(Type.GetTypeFromProgID("")); with no success, besides I would like to know in another way that I can connect to my business object.
Thanks in advance!
It should be possible, first you need to add the Microsoft Remote Data Services Library to your project's references. You will find it under the COM tab. You can then create the RDS.DataSpace class by doing:
DataSpaceClass objRDS = new RDS.DataSpaceClass();
dynamic objCLS = objRDS.CreateObject("MY_System", "http://myserver");
dynamic listNames = objCLS.LstOBSReasons("databaseserver", "databasename", 5);
The only tricky part is you might not be able to call the resulting object through the dynamic invocation (and I assume you are using C#4). If you can't you will need to also import the types for your business object. For example look at something like this for more information on implementing COM interop.