Invoking Method on Object Instantiated From DLL - c#

I am having some trouble with assemblies and DLL's.
instrument_ is declared as an object and I'm creating an instance of "PP150" from the dll whose path is specified by path_.
string className = ContineoProperties.getSingleton().getClassName(path_);
assembly_ = Assembly.LoadFrom(path_);
Type classType = assembly_.GetType("Instrument." + className);
instrument_ = Activator.CreateInstance(classType);
Later I to call the method isntrument_.instrumentCommand(cmd.getCommandName())
The error I get is with when i call the method.
'object' does not contain a definition for 'instrumentCommand'
The isntrument_ is created fine. its just the method call that's giving me a problem. The method does exist in the "PP150.dll". Do I need some DLLImport to allow it to recognize it as a function?
Thanks,
P

If object type is not known in compile time,
To call a method defined on an object, you must use Reflection.
MethodInfo mInfo = classType.GetMethod("instrumentCommand");
mInfo.Invoke(instrument_, new Object[] { _parameters});

The compiler is never going to recognize the methods on a type that you are loading via reflection (e.g. using Assembly.GetType() and Activator.CreateInstance()). Unless you have the type metadata available at build time, you will always get that error if you try to call methods that are not defined on Object itself.
You have two options for making that kind of method call. Both of them require you to give up type safety, the only difference is the amount of work required. In both cases, if you make a mistake, the compiler will not tell you -- you will get a runtime exception instead.
Declare instrument_ as dynamic instead of object. This, obviously, only works in .NET 4.0, but it accomplishes exactly what you're trying to do. The method call will be dispatched at runtime, so as long as the instance that instrument_ references actually has a method call with the appropriate name, it will work.
Use reflection to call the method. You're already using reflection to load the type, so you are halfway there. You would need to add something like this:
// The array of types is the parameter list; assuming instrumentCommand takes
// a string it would look like this:
MethodInfo method = classType.GetMethod("instrumentCommand", new Type[] { typeof(string) });
method.Invoke(instrument_, new object[] { cmd.getCommandName() });

This happens because Activator.CreateInstance returns an object. I would create a separate DLL for the interface which is implemented by the class you want to instantiate. Both the DLL containing this class, and the executable should reference the DLL containing the interface. This way you could cast the object returned by Activator.CreateInstance to the interface, and call its methods:
IInstrument.dll:
interface IInstrument
{
void instrumentCommand(string cmd);
}
Instrument.dll (add IInstrument.dll as reference):
class Instrument : IInstrument
{
public void instrumentCommand(string cmd)
{
// ... implementation ...
}
}
InstrumentApp.exe (add IInstrument.dll as reference):
class Program
{
public static void Main()
{
// ... load Instrument.dll into assembly object ...
// ... load the type from the assembly ...
IInstrument instrument_ = (IInstrument)Activator.CreateInstance(classType);
instrument_.instrumentCommand(cmd.getCommandName());
}
}

The most simple thing would be to link agains PP150.
If you did link against the dll you must use Assembly.LoadFile or Assembly.Load and not LoadFrom because the last one will cause the assembly load to load your assembly in the LoadFrom loader context which will alter type identity.
Suppose you load the Type T from Assembly A via LoadFrom and you link against A as well.
object CreateTypeFrom()
{
var A = Assembly.LoadFrom(#"xxxx");
return A.CreateInstance("T");
}
void Test()
{
object t = CreateTypeFrom();
T RealT = new T(); // no prob
T Castedt = (T)t; // this will throw an InvalidCastException
T isNull = t as T; // this will result in a null instance
}
As you can see although you did create two times an instance of T they cannot be casted to due to different loader context which will make the type pretty useless.
To get rid of these things you could simply use Reflection to create a proxy type which will forward your calls to the proxy type. If you are using .NET 4 you can take advantage of the DLR to find the best matching methods at runtime. The code below creats a Version object and returns it as dynamic object. Then I do call the Major property to an integer and print it out to console. This does work with no exceptions nor compile time errors if you are using .NET 4 or later.
dynamic CreateTypeFrom()
{
var assembly = typeof(string).Assembly;
return assembly.CreateInstance("System.Version", true, BindingFlags.CreateInstance, null, new object[] { 1, 2, 3, 4 }, null, null);
}
[TestMethod]
public void Test()
{
var t = CreateTypeFrom();
int major = t.Major;
Console.WriteLine(major);
}

Related

ResolveMethod() Returning Completely Different Method

I was resolving methods from .NET DLLs, and noticed that the method being returned by Module.ResolveMethod() is completely different from original method. I am specifying the method's exact MetadataToken, so it makes absolutely no sense to me why I would end up with anything else but the original method.
In the below example, I have the 'Dispose()' method. I grab its metadata token and resolve it, only to find that I now have the 'OnBackColorChanged(System.EventArgs)'method
static void Main(string[] args)
{
Assembly assembly = Assembly.LoadFrom(#"C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Windows.Forms.dll");
MethodInfo method = assembly.GetModules()[0].GetTypes()[300].GetMethods()[362];
Console.WriteLine(method); //Returns 'Void Dispose()'
MethodInfo method2 = (MethodInfo)assembly.GetModules()[0].ResolveMethod(method.MetadataToken);
Console.WriteLine(method2); //Returns 'Void OnBackColorChanged(System.EventArgs)' ...why?
}
Button, through long inheritance chain, inherits from Component class, which implements IDisposable and has void Dispose() method. This is the method you obtain via
assembly.GetModules()[0].GetTypes()[300].GetMethods()[362];
Since this method is declared on type Component, which is located in System assembly - no surprise that using its metadata handle to resolve method from completely different module (System.Windows.Forms) leads to random results.
If you want to get only methods declared on this concrete type - use BindingFlags.DeclaredOnly:
var allMethods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
But note this will return only methods declared on Button, not on any parent type, even if that parent type belongs to the same module.
Alternatively - filter by module:
Assembly assembly = Assembly.LoadFrom(#"C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Windows.Forms.dll");
var module = assembly.GetModules()[0];
var type = module.GetTypes()[300];
var allMethods = type.GetMethods().Where(c => c.Module == module).ToArray();

Make a class instance from string and call a constructor

I'm making a RTS game. Every Unit in RTS game can do some actions, such as Patrol, Attack or Build. In unity, you can easily manually fill-in string and integer arrays for C# scripts.
Because of this, I decided that it would be easiest to have a string[] str_actions array for ever unit and when unit is first initialised, convert this array to Action[] actions.
I can probably do this:
string className = "Attack"
Assembly assembly = Assembly.Load("Actions");
Type t = assembly.GetType("Actions."+className);
Action action = (Action)Activator.CreateInstance(t);
But this doesn't handle two problems:
Action doesn't have constructor that takes 0 arguments
The possibility that className refers to class that is not child of Action
How do I handle them?
To answer the question as posted:
Thats ok! Using this overload of Activator.CreateInstance: MSDN you can pass an object[] in, and it will find the constructor that best fits. Having a default constructor is a good idea though, especially if you are going to utilize serialization.
You can't "handle" it in the sense that you can avoid it from happening. However, your code as written will throw an InvalidCastException if the cast fails. To avoid that, use the as operator:
Action action = Activator.CreateInstance(t) as Action;
Now action will just hold null if the cast is invalid, instead of throwing.
Now for the caveat: Activator.CreateInstance is very rarely the right choice in C#. In general, you want to use direct instantiation or deserialization. Granted, deserialization utilizes reflection; but all the messy details are abstracted away.
So I've figured it out. I'm making it a static method Action.fromString. The thing I was lacking was the Type.GetConstructor method which returns the ConstructorInfo object.
public static Action fromString(string className, string defName, WorldObject actor)
{
//Get the Assembly (namespace)
Assembly assembly = Assembly.Load("Actions");
//Get the exact class Type
Type t = assembly.GetType("Actions." + className);
//Get the info about constructor (using array literal)
// - for every accepted parameter enter typeof(parameterType)
ConstructorInfo constructor = t.GetConstructor(new Type[] { typeof(string), typeof(WorldObject) });
//Initialise the Type instance
System.Object action = constructor.Invoke(new System.Object[] { defName, actor });
//If it's child of the main class
if (action is Action)
return (Action)action;
//Error otherwise
else
{
Debug.LogError("'" + className + "' is not child of Action!");
return null;
}
}

Coldfusion Accessing a .net objects parent methods

For some reason Coldfusion is having an issue with accessing the parent methods of one of the objects it creates.
consider this code:
<cfscript>
variables.sHTML = '<html><head><title></title></head><body><p>Hello <strong>World</strong></p></body></html>';
try{
variables.sAltChunkID = "altChunk1";
variables.sExportDirectory = application.sSecureExportPath&'int'&'\word\';
variables.sDLLPath = 'C:\Program Files (x86)\Open XML SDK\V2.0\lib\DocumentFormat.OpenXml.dll';
variables.sFileName = "testI.docx";
variables.sFileToWrite = variables.sExportDirectory&'#variables.sFileName#';
variables.enumWordProcessingDocumentType = createObject("dotnet","DocumentFormat.OpenXml.WordprocessingDocumentType","#variables.sDLLPath#").init().Document;
variables.oDocument = createObject("dotnet","DocumentFormat.OpenXml.Packaging.WordprocessingDocument","#variables.sDLLPath#").Create(variables.sFileToWrite,variables.enumWordProcessingDocumentType);
variables.oMainDocument = variables.oDocument.AddMainDocumentPart();
variables.oEncoding = createObject("dotnet","System.Text.UTF8Encoding").init();
//variables.oMemoryStream = createObject("dotnet","System.IO.MemoryStream").init(variables.oEncoding.GetBytes(variables.sHTML));
variables.enumAltChunk = createObject("dotnet","DocumentFormat.OpenXml.Packaging.AlternativeFormatImportPartType","#variables.sDLLPath#").html;
variables.oFormatImportPart = variables.oMainDocument.AddAlternativeFormatImportPart(variables.enumAltChunk,variables.sAltChunkID);
writeDump(variables.oFormatImportPart);
variables.oFormatImportPart.FeedData(createObject("dotnet","System.IO.MemoryStream").init(variables.oEncoding.GetBytes(variables.sHTML)));
} catch(Any e) {
writeDump(e);
}
</cfscript>
variables.oFormatImportPart has a parent method of FeedData(System.IO.Stream), however when I get to that line, Coldfusion hits me with an exception of:
Either there are no methods with the specified method name and argument types or the FeedData method is overloaded with argument types that ColdFusion cannot decipher reliably. ColdFusion found 0 methods that match the provided arguments. If this is a Java object and you verified that the method exists, use the javacast function to reduce ambiguity.
But as you can see from my Dump, FeedData does indeed exist as a method:
FeedData is overloaded and expecting a Stream object. You are currently sending it an ambiguous object as it has not be cast to the proper type after your createObject call.

what is the difference between AppDomain.CreateInstance and Activator.CreateInstance?

I want to ask a question to realize the difference between AppDomain and Activator, I loaded my dll via appdomain.CreateInstance. But I realized that more method to create instance. Therefore when or where do I choose this method?
Example1:
// Use the file name to load the assembly into the current
// application domain.
Assembly a = Assembly.Load("example");
// Get the type to use.
Type myType = a.GetType("Example");
// Get the method to call.
MethodInfo myMethod = myType.GetMethod("MethodA");
// Create an instance.
object obj = Activator.CreateInstance(myType);
// Execute the method.
myMethod.Invoke(obj, null);
Example2:
public WsdlClassParser CreateWsdlClassParser()
{
this.CreateAppDomain(null);
string AssemblyPath = Assembly.GetExecutingAssembly().Location;
WsdlClassParser parser = null;
try
{
parser = (WsdlClassParser) this.LocalAppDomain.CreateInstanceFrom(AssemblyPath,
typeof(Westwind.WebServices.WsdlClassParser).FullName).Unwrap() ;
}
catch (Exception ex)
{
this.ErrorMessage = ex.Message;
}
return parser;
}
Example3:
private static void InstantiateMyTypeSucceed(AppDomain domain)
{
try
{
string asmname = Assembly.GetCallingAssembly().FullName;
domain.CreateInstance(asmname, "MyType");
}
catch (Exception e)
{
Console.WriteLine();
Console.WriteLine(e.Message);
}
}
Can you explain why do I need more methods or what are the differences?
From sscli2.0 source code, it looks like the "CreateInstance" method calls in AppDomain class always delegates the call to Activator.
The sole purpose of the (almost static) Activator class is to "Create" instances of various classes, while AppDomain is introduced for quite different (and perhaps more ambitious) purposes, e.g.:
A light-weight unit of application isolation;
Optimize memory consumption because AppDomains can be Unloaded.
...
The 1st and the 3rd example are straightforward, just as zmbq noted. I guess your 2nd example is from this post, where the author showed how to unload an out-of-date proxy using AppDomain.
The first one creates an instance of type Example from the assembly 'example', and calls MethodA on it.
The third one creates an instance of MyType in a different AppDomain
I'm not sure about the second, I don't know what this is, but it seems to create a class in the current app-domain - that is, it's similar to the first.

Object of type 'customObject' cannot be converted to type 'customObject'

I receive the following error when I invoke a custom object
"Object of type 'customObject' cannot be converted to type 'customObject'."
Following is the scenario when I am get this error:
I invoke a method in a dll dynamically.
Load an assembly
CreateInstance....
When calling MethodInfo.Invoke() passing int, string as a parameter for my method works fine => No exceptions are thrown.
But if I try and pass one of my own custom class objects as a parameter, then I get an ArgumentException exception, and it is not either an ArgumentOutOfRangeException or ArgumentNullException.
"Object of type 'customObject' cannot be converted to type 'customObject'."
I am doing this in a web application.
The class file containing the method is in a different project. Also the custom object is a separate class in the same file.
There is no such thing called a static assembly in my code. I am trying to invoke a webmethod dynamically. this webmethod is having the customObject type as an input parameter. So when i invoke the webmethod i am dynamically creating the proxy assembly and all. From the same assembly i am trying to create an instance of the cusotm object assinging the values to its properties and then passing this object as a parameter and invoking the method. everything is dynamic and nothing is created static.. :(
add reference is not used.
Following is a sample code i tried to create it
public static object CallWebService(string webServiceAsmxUrl, string serviceName, string methodName, object[] args)
{
System.Net.WebClient client = new System.Net.WebClient();
//-Connect To the web service
using (System.IO.Stream stream = client.OpenRead(webServiceAsmxUrl + "?wsdl"))
{
//--Now read the WSDL file describing a service.
ServiceDescription description = ServiceDescription.Read(stream);
///// LOAD THE DOM /////////
//--Initialize a service description importer.
ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
importer.ProtocolName = "Soap12"; // Use SOAP 1.2.
importer.AddServiceDescription(description, null, null);
//--Generate a proxy client. importer.Style = ServiceDescriptionImportStyle.Client;
//--Generate properties to represent primitive values.
importer.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;
//--Initialize a Code-DOM tree into which we will import the service.
CodeNamespace nmspace = new CodeNamespace();
CodeCompileUnit unit1 = new CodeCompileUnit();
unit1.Namespaces.Add(nmspace);
//--Import the service into the Code-DOM tree. This creates proxy code
//--that uses the service.
ServiceDescriptionImportWarnings warning = importer.Import(nmspace, unit1);
if (warning == 0) //--If zero then we are good to go
{
//--Generate the proxy code
CodeDomProvider provider1 = CodeDomProvider.CreateProvider("CSharp");
//--Compile the assembly proxy with the appropriate references
string[] assemblyReferences = new string[5] { "System.dll", "System.Web.Services.dll", "System.Web.dll", "System.Xml.dll", "System.Data.dll" };
CompilerParameters parms = new CompilerParameters(assemblyReferences);
CompilerResults results = provider1.CompileAssemblyFromDom(parms, unit1);
//-Check For Errors
if (results.Errors.Count > 0)
{
StringBuilder sb = new StringBuilder();
foreach (CompilerError oops in results.Errors)
{
sb.AppendLine("========Compiler error============");
sb.AppendLine(oops.ErrorText);
}
throw new System.ApplicationException("Compile Error Occured calling webservice. " + sb.ToString());
}
//--Finally, Invoke the web service method
Type foundType = null;
Type[] types = results.CompiledAssembly.GetTypes();
foreach (Type type in types)
{
if (type.BaseType == typeof(System.Web.Services.Protocols.SoapHttpClientProtocol))
{
Console.WriteLine(type.ToString());
foundType = type;
}
}
object wsvcClass = results.CompiledAssembly.CreateInstance(foundType.ToString());
MethodInfo mi = wsvcClass.GetType().GetMethod(methodName);
return mi.Invoke(wsvcClass, args);
}
else
{
return null;
}
}
}
I can't find anything static in what I do.
Any help is greatly appreciated.
Regards,
Phani Kumar PV
Have you looked at what the proxy class looks like that gets generated? You don't need the proxy to call a web service. Just create a class that inherits from SoapHttpClientProtocol and call Invoke(methodName, params).
You are making this SO much more complicated than you need to. Honestly.
EDIT
If you create a class like this:
public class SoapClient : SoapHttpClientProtocol
{
public SoapClient()
{
}
public object[] Invoke(string method, object[] args)
{
return base.Invoke(method, args);
}
}
and call it like this:
SoapClient soapClient = new SoapClient();
soapClient.Url = webServiceAsmxUrl;
soapClient.Invoke(methodName, args);
I think you will see that it has the exact same results as what you are doing.
Let me try to explain the most probable reason for the problem to come in my approach.
When I invoked a method in the assembly called as "methodname" in the webservice I am trying to pass the parameters required for that as args[] to the function "CallWebService"
This args[] when passed will be successfully working when I try to pass a normal parameters like primitive types including string.
But this is what I did when I tried to pass a custom object as a parameter.
Three things that are done in this.
create an object of that type outside the CallWebService function (using reflection). when I did that way what happens is an instance of the customobject created with a temporary dll name internally.
once I set the set the properties of the object and send it across to the CallWebService function as an object in the args array.
I tired to create an instance of the webservice by creating the dynamic dll.
object wsvcClass = results.CompiledAssembly.CreateInstance(foundType.ToString());
When I finally tried to invoke the method with the instance of the dynamic assembly created
I tried to pass the customobject that is created at step 1,2 via args property.
at the time of invocation the CLR tries to see if the customobject that is passed as input and the method that is being invoked are from the same DLL.
which is obviously not from the way the implementation is done.
So following is the approach that should be used to overcome the problem
I need to create the custom object assembly with the same assembly that I used to the create the webservice instance..
I implemented this approach completely and it worked out fine
MethodInfo m = type.GetMethod(methodName);
ParameterInfo[] pm = m.GetParameters();
object ob;
object[] y = new object[1];
foreach (ParameterInfo paraminfo in pm)
{
ob = this.webServiceAssembly.CreateInstance(paraminfo.ParameterType.Name);
//Some Junk Logic to get the set the values to the properties of the custom Object
foreach (PropertyInfo propera in ob.GetType().GetProperties())
{
if (propera.Name == "AppGroupid")
{
propera.SetValue(ob, "SQL2005Tools", null);
}
if (propera.Name == "Appid")
{
propera.SetValue(ob, "%", null);
}
}
y[0] = ob;
}
this can occur when the version of a dll you have referenced in your reflected code is different from the version of that dll in your compiled code.
This is an old thread, but I just had a similar problem. I looked on here, this one popped up, but I saw no useful solutions.
The OP's error was this: Object of type 'customObject' cannot be converted to type 'customObject'.
My very similar error was this: Object of type 'System.String' cannot be converted to type 'System.Windows.Forms.AccessibleRole'.
Here is how I solved my problem:
I performed a Find and Replace (use CRTL+SHIFT+F to bring the dialog box up) search in the Current Project for the term AccessibleRole.
Within one of the Form's Designer's was a place where I was assigning an AccessibleRole value to a String variable using ToString().
I fixed this, and my problem went away.
I hope this provides help to others.

Categories