Accessing JavaScript Objects from vb.net - vb.net

I'm working on a vb.net application that interacts with a (third party provided) web app to provide additional functionality (e.g. removing menu items, pulling information from the pages, etc.). The web app is completely driven by javascript but is hosted in asp.net and is only used with Internet Explorer.
I'm trying to read properties from a javascript object and execute some of it's functions. I've managed to get hold of the javascript object by getting the mshtml.HTMLDocument of the iframe the script resides in and using the following code:
Dim jsObject as Object
jsObject = htmldoc.Script.jsObject
jsObject exists as a {System.__ComObject} and i can use it to execute any of it's functions or read it's properties as follows:
Dim value as String = jsObject.FunctionThatReturnsAString()
jsObject.FunctionTHatDoesSomethingInWebApp("Param1", "Param2")
This works great. However, when I leave the page/frame with jsObject in and return to it, the same code throws an exception when getting the javascript object from the frame again (i.e. executing the following line):
jsObject = htmldoc.Script.jsObject
Exception: Member not found. (Exception from HRESULT: 0x80020003 (DISP_E_MEMBERNOTFOUND))
If I stop debugging and restart, it works again (until i leave the page, etc.). I'm not sure what's happening that's causing the javascript object to disappear as far as my app's concerned. I'm presuming it's due to my app holding a reference to the COM object and i need to release it in some way (particulary as it's got a base type of MarshalByRefObject - which makes sense as it's being passed between app domains).
Why is this happening? Is there a better way of accessing a javascript object, it's properties and functions?

I've found what is, in my case, a better way of achieving what I need. Instead of accessing the jsObject directly as a COM Object (and worrying about Marshaling, etc.), I either use:
execScript to call functions with no return value or
create a hidden div element in the frame i'm working in, set the innerHTML of that div equal to whatever javascript variable/function return value that i'm interested in using execScript and then read that value seperately from the DOM
To read a variable/function return i use the following vb.net function:
Private Function getJScriptVariable(ByVal JScript As String)
Dim command As New StringBuilder()
command.Append("var e = document.getElementById('Matt_JScriptReturn');")
command.Append("if (e == null) {")
command.Append("var e = document.createElement('div');")
command.Append("e.id = 'Matt_JScriptReturn';")
command.Append("e.type = 'hidden';")
command.Append("document.body.appendChild(e);")
command.Append("}")
command.Append("e.innerHTML = ")
command.Append(JScript)
command.Append(";")
'fMaster is the frame containing the javascript's mshtml.IHTMLWindow2
fMaster.execScript(command.ToString(), "JScript")
'N.B. fMaster_Document is the fMaster's mshtml.HTMLDocument
Return fMaster_Document.getElementById("Matt_JScriptReturn").innerHTML
'Optionally execScript to remove element from DOM at this point
End Function
Then i would use that function as follows (respecting my example in the original question):
Dim value as String = getJScriptVariable("jsObject.FunctionThatReturnsAString()")
To execute javascript code without needing to return a value I simply execute it as follows:
fMaster.execScript("jsObject.FunctionTHatDoesSomethingInWebApp('Param1', 'Param2')")
I'd still be interesting in finding out why i had the problem earlier with the javascipt object being unable to access after leaving the page and returning, however this solves my problem so i'm happy for now! I hope this helps someone else at some point.

Related

Using Invoke Method in an IF statement vb.net

I am using a TCP client/server to receive information from a micro controller and using that information inside a Win Forms App, VB.NET. My issue is trying to check a label against a datatable in an IF statement. I have tried different forms of invoke but none of them seem to work.
VB Code
If lblStep.Invoke(New MethodInvoker(Sub() lblStep.Text = lblStep.Text), Nothing) = dt.Rows(0).Item("Step") Then
'Do something
end if
The error I am receiving is a multi thread error.
If you expect to get a value returned then you need to invoke a method that returns a value, which means a function. You could do this:
If CStr(lblStep.Invoke(Function() lblStep.Text)) = dt.Rows(0).Field(Of String)("Step") Then
or this:
If CBool(lblStep.Invoke(Function() lblStep.Text = dt.Rows(0).Field(Of String)("Step"))) Then
The first one gets the String from the UI thread and performs the comparison on the background thread while the second performs the comparison on the UI thread.
Note that I also make sure that everything is the correct data type, which is required if you have set Option Strict On, which you absolutely should have done.

Test for jQuery-DataTables API object

I'm working with a mix of legacy code that uses the jQuery DataTables plugin (version 1.10. Some of the old code passes around jQuery object, and some of the old code passes around DataTables API objects. I'd like to make all new code accept either a jQuery object or a DataTables API object, but I haven't been able to detect when a variable refers to a DataTables API object.
The only detection method I could find in the API, isDataTable, is not suitable - it take a string selector for a table and returns whether the selection has been turned into a DataTable.
The "class detection" methods in this answer don't work; typeof returns "object", constructor.name returns "Object", and I don't know of a constructor to use for instanceof or isPrototypeOf.
As suggested in this answer (to same question), Object.prototype.toString.call(var) returns "[object Object]"
In Chrome, console.log(var) produces a summary that looks like it has some other class information:
▶ _Api {context: Array[1], selector: Object, ajax: Object}
I imagine that means there is something I could use somewhere but this question about where Chrome gets that name has no useful answers.
How can I detect when a variable refers to a jQuery-DataTables API object?
You can dig around more by using a breakpoint in Chrome. If you set a breakpoint just after a DataTable variable, and evaluate the variable, you'll get a complete map. I did that, and found this:
var myDataTable = $('#myTable').DataTable(myOptions);
alert(myDataTable.$.__dt_wrapper);
The alert returns true. (myOptions is an object containing all the options for the DataTable.) So, I looked at an object (I used myOptions) to see what I could test for. You can't test directly for whether myOptions.$.__dt_wrapper) is false, because myOptions.$ evaluates to undefined. So:
if(undefined != myObjectVariable.$ && myObjectVariable.$.__dt_wrapper) {
alert('A DataTable Object')
} else {
alert('Not a DataTable Object')
}
Googling "__dt_wrapper" doesn't show any links that are unrelated to DataTables. Looking at datatables.js, it occurs twice. Both cases suggest that it's an internal attribute used to denote a DataTable object, or more specifically whether an object is a DataTable wrapper.
Since this is undocumented there's no guarantee that it will work in all future versions of DataTables. But this works in DataTables 1.10.11.
Edit: I asked about this on the DataTables forum (link), and there indeed is a cleaner way to do this:
myObjectVariable instanceof $.fn.DataTable.Api
Is true if the object is a DataTable. (This also works for the lower case dataTable object.)

Calling PythonFunction's from a VB application hosting Iron Python

I'm a C++ programming who was tapped to write a small application in Visual Basic. The application hosts an IronPython runtime and I am attempting to define some function in python and then call them from VB. I have written a simple test function in python
def test():
print "Test was Called"
Then I use the iron python to create a ScriptSource from the python file. I am able to look up the "test" variable through object operations but I can not figure out how to call the object that. For example (in VB):
pyScope = engine.CreateScope()
pySource = engine.CreateSourceFromFile("C:\Some\File\path\test.py")
pySource.Execute(pyScope)
' Now I expect the function test() to be defined in pyScope
Dim tmp as Object
pyScope.TryGetVariable("test", tmp)
At this point in my code tmp is defined as an object of type PythonFunction. I can't figure out how to call this function.
tmp()
Is not valid VB syntax. I've gotten this far, now how do I perform this seemingly simple task?
Edit: By calling
pyEngine.Operations.Invoke(tmp)
I am able to call the function and I see the expected output at stdout. I am still under the impression that there is some function-pointer-like type that I can cast objects of type PythonFunction to which will let me invoke temp directly without calling to the Python engine.
Not sure this will work, but try casting it to an Action type:
DirectCast(tmp, Action)()
Based on the comment, try this:
engine.ObjectOperations.Invoke(tmp, Nothing)
VB in .NET 4 should have the same dynamic support as C#. According to http://msdn.microsoft.com/en-us/library/ee461504.aspx#Y5108 (near the bottom), you should be able to do:
Dim tmp As Object = scope.GetVariable("test")
... which is what you're already doing, so make sure you're targeting .NET 4.
If that doesn't work you should be able to cast it with the generic version of GetVariable:
Dim tmp As Action = scope.GetVariable(Of Action)("test")
Finally, you already discovered Invoke on ObjectOperations.
(You may need to tweak the syntax, since I don't know VB.)

ExtJS4 store mapping NPE

When using the Ext.data.Store 'mapping' config property, of 'x.y', and the mapped model does not contain an 'x' property, the store throws an exception, which, prevents the store data from rendering into the grid view on data store load.
If the store source is out of your control, is it possible to avoid/catch the exception when the root of the mapping path does not exist. I've tried using a 'convert' function for the target property of the data store. The mapping path into the JSON document is only determined from the run context [e.g. this.mappingPath]. Dynamically generating the convert function (to catch the exception) seems to slow down the page a bit.
Is there a solution to null results along the model's mapping path within the ExtJS API, or is catching the exception from within the convert function the way to go? Or possibly another solution...
I ended up just using a convert function with a call to a 'followPath' type function anywhere that this was the case. Follow path breaks up the mapping component into it's parts (split on '.') and iterates through the list readjusting the context to context = context[part] along the way. so the call is followPath(item.data,path). This performs well, and gets the job done.

In VB6, how do I call a COM object requiring a pointer to an object?

I'm having trouble with a .NET Assembly that is com visible, and calling certain methods from VB6.
What I have found is that if the parameters are well defined types, (e.g. string), calls work fine. If they are higher level objects, it raises a runtime error '438' suggesting that the property or method is not present. I suspect that this is a question of having the correct signature on the call, but I can't see how to do this correctly.
I believe that I've done everything correct on the .NET side (ComVisible, public interfaces, etc. and even have it down to a simple enough case).
Looking at the output from the typelib viewer, I have the following:
dispinterface ISimple {
properties:
methods:
[id(0x60020000)]
void Add([in] ISimpleMember* member);
[id(0x60020001)]
ISimpleMember* Create();
};
OK. So I have 2 methods in my ISimple interface. One takes an ISimpleMember (Add), whilst the other, returns an ISimpleMember.
The corresponding code in VB looks like this:
Dim item As ISimpleMember
Dim simple As simple
Set item = New SimpleMember
item.S1 = "Hello"
item.S2 = "World"
Set simple = New simple
simple.Add (item) <---- This raised the run time error 438
Set item = simple.Create <---- This works fine, returning me an ISimpleMember
I've tried a couple of things:
1. Dim item as SimpleMember (makes no difference)
2. simple.Add(ObjPtr(item)) - Syntax error
3. simple.Add(ByRef item) - Syntax error
Basically, The run time error is the same as if I had
simple.AMethodThatIHaventWritten()
Also, If I browse References in the VB6 Environment, The Add method is well defined:
Sub Add(member As SimpleMember)
I've found the answer I believe. It was very simple:
When calling a SubRoutine, I shouldn't put the name in braces. the call should have been:
simple.add member
rather than
simple.add(member)
If I change it to a function (i.e. return a value rather than void) the braces are necessary
This seems to work
(Probably) The top 3 VB6 coding mistakes made by devs who now mainly code in C#, Javascript etc. Are:-
Placing ; at the end of lines. Its a syntax error very easily spotted and picked up the compiler.
Not placing Then on the other side of an If condition expression. Again its a syntax error.
Calling a method without retrieving a value and yet using ( ) to enclose the parameter list. With multiple parameters this is a syntax error and easily found. With only one parameter the use of ( ) is interpreted as an expression. Its the result of the ( ) expression which is passed as parameter. This causes problems when ByRef is expected by the callee.