I am extending an existing InstallScript project and need to call a method in an assembly that targets .NET 4.0.
The project already calls a method in another class in the same assembly: so I know that much works.
Mirroring what I see for the existing .NET method call, here is what I have so far:
//////////////////////////////////////////////////////////////////////////////
// SomeFunction
//
// Wraps SomeAssembly.SomeClass.SomeMethod.
//////////////////////////////////////////////////////////////////////////////
prototype number SomeFunction();
function number SomeFunction()
string szDLL, szAssemblyandClassName;
OBJECT oSomeClass;
number nResult;
begin
ChangeDirectory(SRCDIR); // changed from ChangeDirectory(SUPPORTDIR), which yielded error number -2147219705 in the try-catch below
szDLL = "SomeAssembly.dll";
szAssemblyAndClassName = "SomeAssembly.SomeClass";
try
SprintfBox(INFORMATION, "Debug", "Calling DotNetCoCreateObject(\"%s\", \"%s\", \"\")..."), szDLL, szAssemblyAndClassName); // FORNOW
set oSomeClass = DotNetCoCreateObject(szDLL, szAssemblyAndClassName, "");
catch
SprintfBox(SEVERE, "Error", "Error %i:\n\n%s\n\n%s", Err.Number, Err.Description, Err.LastDllError);
abort;
endcatch;
SprintfBox(INFORMATION, "Debug", "Calling oSomeClass.SomeMethod()..."); // FORNOW
nResult = oSomeClass.SomeMethod();
SprintfBox(INFORMATION, "Debug", "oSomeClass.SomeMethod() returned %i.", nResult); // FORNOW
return nResult;
end;
When SomeFunction() executes in the setup I have built, I see the following output...
Calling DotNetCoCreateObject("SomeAssembly.dll", "SomeAssembly.SomeClass", "")...
Calling oSomeClass.SomeMethod()...
..., but then the setup immediately exits without any apparent error. Getting no indication of what goes wrong makes this frustrating to troubleshoot. Searching for a likely cause, I have found nothing so far.
Why would oSomeClass.SomeMethod() cause the setup to exit immediately with no apparent error?
EDIT:
Per #MichaelUrman's comment asking for more details about what differs between the existing class (OrigClass) that works and the new one (SomeClass) that does not:
Both classes are public sealed.
Both classes have an implicit default constructor - no explicit constructors.
Both methods (OrigMethod and SomeMethod) are public.
Neither class or method is marked with the ComVisible attribute; but their assembly (SomeAssembly) has [assembly: ComVisible(true)] in its AssemblyInfo.cs.
The differences between Existing.rul (that successfully integrates with SomeAssembly.OrigClass.OrigMethod) and New.rul (that does not successfully integrate with SomeAssembly.SomeClass.SomeMethod) are as follows (using a patch file):
2c2
< // OrigFunction
---
> // SomeFunction
4c4
< // Wraps SomeAssembly.OrigClass.OrigMethod.
---
> // Wraps SomeAssembly.SomeClass.SomeMethod.
6,7c6,7
< prototype number OrigFunction();
< function number OrigFunction()
---
> prototype number SomeFunction();
> function number SomeFunction()
9c9
< OBJECT oOrigClass;
---
> OBJECT oSomeClass;
14c14
< szAssemblyAndClassName = "SomeAssembly.OrigClass";
---
> szAssemblyAndClassName = "SomeAssembly.SomeClass";
18c18
< set oOrigClass = DotNetCoCreateObject(szDLL, szAssemblyAndClassName, "");
---
> set oSomeClass = DotNetCoCreateObject(szDLL, szAssemblyAndClassName, "");
24,26c24,26
< SprintfBox(INFORMATION, "Debug", "Calling oOrigClass.OrigMethod()..."); // FORNOW
< nResult = oOrigClass.OrigMethod();
< SprintfBox(INFORMATION, "Debug", "oOrigClass.OrigMethod() returned %i.", nResult); // FORNOW
---
> SprintfBox(INFORMATION, "Debug", "Calling oSomeClass.SomeMethod()..."); // FORNOW
> nResult = oSomeClass.SomeMethod();
> SprintfBox(INFORMATION, "Debug", "oSomeClass.SomeMethod() returned %i.", nResult); // FORNOW
OrigFunction and SomeFunction use a try-catch-endcatch to respectively ensure that oOrigClass and oSomeClass are set to a valid reference. The InstallScript Language Reference's DotNetCoCreateObject documentation explains that "[the] function throws an exception if the object cannot be created".
The problem turned out to have a simple explanation.
There was a mismatch between SomeMethod in oSomeClass.SomeMethod() and the actual name of the .NET SomeClass method available to call - i.e. oSomeClass.SomeMethod() should have been oSomeClass.SomeOtherMethod().
In my case, the mismatch was not so drastic and simply due to pluralization I initially overlooked in the defined .NET method's name - e.g. oSomeClass.SomeStringMethod() (singular) where oSomeClass.SomeStringsMethod() (plural) was in order.
That the setup immediately exited without any apparent error made it a booger to identify the simple cause of the problem.
Related
I have to instrument any given code (without directly changing given code ) at the beginning and end of every thread. Simply speaking , how can I print something at entry and exit points of any thread.
How can I do that using javassist ?
Short Answer
You can do this by creating an ExprEditor and use it to modify MethodCalls that match with start and join of thread objects.
(very) Long answer (with code)
Before we start just let me say that you shouldn't be intimidated by the long post, most of it is just code and once you break things down it's pretty easy to understand!
Let's get busy then...
Imagine you have the following dummy code:
public class GuineaPig {
public void test() throws InterruptedException {
Thread t = new Thread(new Runnable() {
#Override
public void run() {
for (int i = 0; i < 10; i++)
System.out.println(i);
}
});
t.start();
System.out.println("Sleeping 10 seconds");
Thread.sleep(10 * 1000);
System.out.println("Done joining thread");
t.join();
}
}
When you run this code doing
new GuineaPig().test();
You get an output like (the sleeping system.out may show up in the middle of the count since it runs in the main thread):
Sleeping 10 seconds
0
1
2
3
4
5
6
7
8
9
Done joining thread
Our objective is to create a code injector that will make the output change for the following:
Detected thread starting with id: 10
Sleeping 10 seconds
0
1
2
3
4
5
6
7
8
9
Done joining thread
Detected thread joining with id: 10
We are a bit limited on what we can do, but we are able to inject code and access the thread reference. Hopefully this will be enough for you, if not we can still try to discuss that a bit more.
With all this ideas in mind we create the following injector:
ClassPool classPool = ClassPool.getDefault();
CtClass guineaPigCtClass = classPool.get(GuineaPig.class.getName());
guineaPigCtClass.instrument(new ExprEditor() {
#Override
public void edit(MethodCall m) throws CannotCompileException {
CtMethod method = null;
try {
method = m.getMethod();
} catch (NotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
String classname = method.getDeclaringClass().getName();
String methodName = method.getName();
if (classname.equals(Thread.class.getName())
&& methodName.equals("start")) {
m.replace("{ System.out.println(\"Detected thread starting with id: \" + ((Thread)$0).getId()); $proceed($$); } ");
} else if (classname.equals(Thread.class.getName())
&& methodName.equals("join")) {
m.replace("{ System.out.println(\"Detected thread joining with id: \" + ((Thread)$0).getId()); $proceed($$); } ");
}
}
});
guineaPigCtClass
.writeFile("<Your root directory with the class files>");
}
So what's happening in this small nifty piece of code? We use an ExprEdit to instrument our GuineaPig class (without doing any harm to it!) and intercept all method calls.
When we intercept a method call, we first check if the declaring class of the method is a Thread class, if that's the case it means we are invoking a method in a Thread object. We then proceed to check if it's one of the two particular methods start and join.
When one of those two cases happen, we use the javassist highlevel API to do a code replacement. The replacement is easy to spot in the code, the actual code provided is where it might be a bit tricky so let's split one of those lines, let's take for example the line that will detect a Thread starting:
{ System.out.println(\"Detected thread starting with id: \" + ((Thread)$0).getId()); $proceed($$); } "
First all the code is inside curly brackets, otherwise javassist won't accept it
Then you have a System.out that references a $0. $0 is a special parameter that can be used in javassist code manipulations and represents the target object of the method call, in this case we know for sure it will be a Thread.
$proceed($$) This probably is the trickiest instruction if you're not familiar with javassist since it's all javassist special sugar and no java at all. $proceed is the way you have to reference the actual method call you are processing and $$ references to the full argument list passed to the method call. In this particular case start and join both will have this list empty, nevertheless I think it's better to keep this information.
You can read more about this special operators in Javassist tutorial, section 4.2 Altering a Method Body (search for MethodCall subsection, sorry there's no anchor for that sub-section)
Finally after all this kung fu we write the bytecode of our ctClass into the class folder (so it overwrites the existing GuinePig.class file) and when we execute it... voila, the output is now what we wanted :-)
Just a final warning, keep in mind that this injector is pretty simple and does not check if the class has already been injected so you can end up with multiple injections.
The following code works correctly - output: You chose Test 1
package main
import (
"fmt"
)
type TNameMap map[int]string
var nameMap TNameMap
func init() {
nameMap = make(TNameMap)
nameMap[1] = "You chose Test 1"
nameMap[2] = "You chose Test 2"
nameMap[3] = "You chose Test 3"
}
func main() {
fmt.Println(nameMap[1])
}
If I comment out the first line in init() i.e //nameMap = make(TNameMap) , I get a panic when main() runs, because nameMap was never initialized:
panic: runtime error: assignment to entry in nil map
But - if in init() I write nameMap := make(TNameMap)
instead of nameMap = make(TNameMap) , I get no panic, but also no output - main() simply runs and process terminates.
I understand that if I use the Initialization operator - nameMap := make(TNameMap) - I have declared a new variable nameMap that is scoped only to the init() function and so only the package level variable var nameMap TNameMap is in scope for main(), resulting in no output, because the package level var holds no map data.
But, I am confused: Why don't I get the panic in that situation? If main() is making the call on the package var, it was never initialized - so why no panic?
According to the Go spec:
A nil map is equivalent to an empty map except that no elements may be
added.
This means that you can read from a nil map, but not write. Just like the panic says "assignment to entry in nil map". If you comment out just the line nameMap = make(TNameMap) it will crash because you attempt to write to it in init (which is where the panic happens). If you comment out the entirety of init the Println will not crash because you're permitted to access (read from) a nil map.
Changing the assignment to a declaration is just masking the real issue here, what's happening is it's making all the assignments valid, and then discarding the result. As long as you make the assignments valid (either by removing them or making a temporary variable), then you will observe the same behavior in Println.
The value returned by a nil map is always the zero value of the value type of the map. So a map[T]string returns "", a map[T]int returns 0, and so on. (Of course, if you check with val,ok := nilMap[key] then ok will be false).
In the code below I would like to replace Windows WinSock error WSAEINTR=10004 with a generic boost system error code, but how do I map the code I found in the debugger, with the generic enums?
timeout_.expires_from_now(posix_time::seconds(15));
timeout_.async_wait( boost::bind(&cancel_socket_fn,this,_1) );
asio::streambuf rd_buf;
unsigned length = read_until( socket_, rd_buf,delimiter_string, error );
timeout_.cancel();
if(error)
{
// how do I make this portable?
if(error.value()==WSAEINTR) throw request_timeout_exception()
else throw my_asio_exception(error,"Unable to read header");
}
...
cancel_socket_fn(system::error_code e) { socket_.cancel(); }
if (error == boost::asio::error::interrupted)...
And I think here is a design error because if this code is called from the thread where io_service::run() (or similar) is called then cancel_socket_fn() will not be called until read_until() finishes. Or if they are in different threads then here are synchronization problems because timer methods are not thread-safe.
I'm doing an experiment with wxWebConnect test application, incorporating the xpcom tutorial at "http://nerdlife.net/building-a-c-xpcom-component-in-windows/"
I adapt MyComponent class as necessary to compile together with testapp.exe (not as separate dll), and on MyApp::OnInit I have the following lines:
ns_smartptr<nsIComponentRegistrar> comp_reg;
res = NS_GetComponentRegistrar(&comp_reg.p);
if (NS_FAILED(res))
return false;
ns_smartptr<nsIFactory> prompt_factory;
CreateMyComponentFactory(&prompt_factory.p);
nsCID prompt_cid = MYCOMPONENT_CID;
res = comp_reg->RegisterFactory(prompt_cid,
"MyComponent",
"#mozilla.org/mycomp;1",
prompt_factory);
Those lines are copied from GeckoEngine::Init(), using the same mechanism to register PromptService, etc. The code compiles well and testapp.exe is running as expected.
I put javascript test as below :
try {
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
const cid = "#mozilla.org/mycomp;1";
obj = Components.classes[cid].createInstance();
alert(typeof obj);
// bind the instance we just created to our interface
alert(Components.interfaces.nsIMyComponent);
obj = obj.QueryInterface(Components.interfaces.nsIMyComponent);
} catch (err) {
alert(err);
return;
}
and get the following exception:
Could not convert JavaScript argument arg 0 [nsISupport.QueryInterface]
The first alert says "object", so the line
Components.classes[cid].createInstance()
is returning the created instance.
The second alert says "undefined", so the interface nsIMyComponent is not recognized by XULRunner.
How to dynamically registering nsIMyComponent interface in wxWebConnect environment ?
Thx
I'm not sure what is happening here. The first thing I would check is that your component is scriptable (I assume it is, since the demo you copy from is). The next thing I would check is whether you can instantiate other, standard XULRunner components and get their interface (try something like "alert('Components.interfaces.nsIFile');" - at least in my version of wxWebConnect this shows an alert box with string "nsIFile".
Also, I think it would be worth checking the Error Console to make sure there are no errors or warnings reported. A magic string to do that (in Javascript) is:
window.open('chrome://global/content/console.xul', '', 'chrome,dialog=no,toolbar,resizable');
Ok it looks likes like I have stumbled upon a strange timing issue... I made a quick SQL wrapper class for executing sql statements. However after .execute() is called, the SQLEvent.RESULT event is never fired, but the new entry in the DB is created as it should be. The really really odd part is if I put a setTimeout() just after calling execute() the event fires as expected.. I hope I'm missing something really obvious here... Here is a link to an example air app:
http://www.massivepoint.com/airsqltest/AIRSQL.zip
And here is the code to the wrapper class:
if you look down at line 51 in the SQLRequest class, you will see the commented out setTimeout() method. To make everything work, just uncomment that line.. but to me this doesn't make any sense...
anyone have any thoughts? I'm totally stumped here...
package com.jac.sqlite
{//Package
import flash.data.SQLConnection;
import flash.data.SQLStatement;
import flash.events.EventDispatcher;
import flash.events.SQLErrorEvent;
import flash.events.SQLEvent;
import flash.utils.setTimeout;
public class SQLRequest extends EventDispatcher
{//SQLRequest Class
private var _callback:Function;
private var _dbConn:SQLConnection;
private var _query:String;
private var _params:Object;
private var _statement:SQLStatement;
public function SQLRequest(callback:Function, connection:SQLConnection, query:String, parameters:Object=null):void
{//SQLRequest
trace("Creating new SQL Request");
_callback = callback;
_dbConn = connection;
_query = query;
_params = parameters;
_statement = new SQLStatement();
_statement.sqlConnection = _dbConn;
_statement.text = _query;
if (_params != null)
{//assign
for (var param:String in _params)
{//params
trace("Setting Param: " + param + " to: " + _params[param]);
_statement.parameters[param] = _params[param];
}//params
}//assign
//setup events
_statement.addEventListener(SQLEvent.RESULT, handleResult, false, 0, true);
_statement.addEventListener(SQLErrorEvent.ERROR, handleError, false, 0, true);
}//SQLRequest
public function startLoad():void
{//execute
_statement.execute();
//setTimeout(handleTimeOut, 10000);
}//execute
//TEMP
private function handleTimeOut():void
{//handleTimeOut
trace("Executing: " + _statement.executing + " / " + executing);
}//handleTimeOut
private function handleResult(e:SQLEvent):void
{//handleResult
trace("Good SQL Request");
_callback(e);
dispatchEvent(e);
}//handleResult
private function handleError(e:SQLErrorEvent):void
{//handleError
trace("SQL Error: " + e.errorID + ": " + e.error);
//dispatchEvent(e);
}//handleError
public function get executing():Boolean
{//get executing
return _statement.executing;
}//get executing
public function get query():String { return _query; }
public function get statement():SQLStatement { return _statement; }
}//SQLRequest Class
}//Package
I think what you're missing here is garbage collection.
Haven't tested your code, but this could certainly be the source of the problem.
var sqlReq:SQLRequest = new SQLRequest(handleResult, _dbConn, sql);
sqlReq.startLoad();
The reference sqlReq is local to the function and becomes unreacheable when the function returns. That makes it collectable. I guess there must be some code in the AIR runtime that collects garbage more agressively when there are sql connections involved. Because generally, you'll get away with not storing a ref to your object (at least in a web based environment, in my experience; this is a bug in such code, nevertheless; you just have to be in a bad day to experience it).
The setTimeout masks this problem (or almost solves it, although in an unintended way), because the setTimeout function uses a Timer internally. Running timers are not collected. So, the timer is alive and kicking and has a reference to your SQLRequest instance, which makes it reacheable, and so, not elligible for collection. If your DB call takes longer than the timeout though, you're back in the same situation.
To solve this, store a ref to the object and dispose it properly when you're done.
Edit
Another option, if you don't want to change the way you calling code works, is storing a ref to the instance in a class-scoped (i.e. static) dictionary for the duration of the call (this dictionary shoul not use weak referenced keys for obvious reasons).
You are adding a hidden side effect to your method, which is not a sign of good design in general, but as long as you remove it when the call to the DB is finished (whether it succeded or not), you're safe, so I think the problem is more of style than anything else.
What I mean is something like this:
private static var _dict:Dictionary = new Dictionary();
public function startLoad():void
{//execute
_statement.execute();
// add a self reference to dict so the instance won't be collected
// do this in the last line, so if we have an exception in execute, this
// code will not run (or add a try/catch if you want, but this is simpler
// and cleaner, IMO
addToDict();
}//execute
private function handleResult(e:SQLEvent):void
{//handleResult
// remove the self reference before running any other code
// (again, this is in case the code that follows throws)
removeFromDict();
trace("Good SQL Request");
_callback(e);
dispatchEvent(e);
}//handleResult
private function handleError(e:SQLErrorEvent):void
{//handleError
// same comment as handleResult
removeFromDict();
trace("SQL Error: " + e.errorID + ": " + e.error);
//dispatchEvent(e);
}//handleError
private function addToDict():void {
_dict[this] = true;
}
private function removeFromDict():void {
if(_dict[this]) {
delete _dict[this];
}
}