I have an instance of a COM object... which is created like this:
Type type = TypeDelegator.GetTypeFromProgID("Broker.Application");
Object application = Activator.CreateInstance(type);
When I try to invoke a method:
type.GetMethod("RefreshAll").Invoke(application, null);
-> type.GetMethod("RefreshAll") returns null.
When I try to get all the methods with type.GetMethods(), there is only these methods:
GetLifetimeService
InitializeLifetimeService
CreateObjRef
ToString
Equals
GetHashCode
GetType
Where is the RefreshAll Method? And how can I invoke it?
You can't use GetMethod on COM objects, you have to use a different
way:
this.application.GetType().InvokeMember("RefreshAll", BindingFlags.InvokeMethod, null, this.application, null);
I am using this way in a old project that uses COM so it should work ok for you.
I realise this is a late answer but c# 4 changes things a bit with the introduction of the dynamic keyword which was designed with COM-interop in mind.
MSDN:
The COM interop scenario that the C# team specifically targeted in the C# 4 release was programming against Microsoft Office applications, such as Word and Excel. The intent was to make this task as easy and natural in C# as it always was in Visual Basic. [1]
Your code now becomes:
Type type = TypeDelegator.GetTypeFromProgID("Broker.Application");
dynamic application = Activator.CreateInstance(type);
application.RefreshAll(); // <---- new in c# 4
Now you won't see RefreshAll() in Visual Studio statement completion so don't be alarmed. It will compile.
[1] Understanding the Dynamic Keyword in C# 4
Related
I’m trying to pass a COM object from C# code to Perl.
At the moment I’m wrapping my Perl code with PerlNET (PDK 9.4; ActiveState) and I have defined a simple subroutine (+ required pod declaration) in Perl to pass objects from C# to the wrapped Perl module.
It seems that the objects I pass are not recognized correctly as COM objects.
An example:
In C# (.NET 4.0), the ScriptControl is used to load a simple class from a file written in VBScript.
var host = new ScriptControl();
host.Language = "VBScript";
var text = File.ReadAllText("TestScript.vbs");
host.AddCode(text);
dynamic obj = host.Run("GetTestClass");
What I get (obj) is of type System.__ComObject. When I pass it to my Perl/PerlNET assembly and try to call method Xyz() in Perl I get the following (runtime) exception:
Can't locate public method Xyz() for System.__ComObject
If, however, I do more or less the same thing in Perl, it works. (In the following case, passing only the contents of my .vbs file as parameter.)
I can even use the script control :
sub UseScriptControl {
my ($self, $text) = #_;
my $script = Win32::OLE->new('ScriptControl');
$script->{Language} = 'VBScript';
$script->AddCode($text);
my $obj = $script->Run('GetTestClass');
$obj->Xyz();
}
Now, calling Xyz() on obj works fine (using Win32::OLE).
In both cases I use:
use strict;
use Win32;
use Win32::OLE::Variant;
Another approach:
I can invoke methods by using InvokeMember of class System.Type if I specify exactly which overload I want to use and which types I’m passing:
use PerlNET qw(typeof);
typeof($obj)->InvokeMember("Xyz",
PerlNET::enum("System.Reflection.BindingFlags.InvokeMethod"),
PerlNET::null("System.Reflection.Binder"),
$obj,
"System.Object[]"->new());
Using this approach would mean rewriting the whole wrapped Perl module. And using this syntax..
Now I am wondering if I am losing both the advantages of the dynamic keyword in .NET 4.0 and the dynamic characteristics of Perl (with Win32::OLE) by using PerlNET with COM objects.
It seems like my preferred solution boils down to some way of mimicking the behaviour of the dynamic keyword in C#/.NET 4.0.
Or, better, finding some way of converting the passed COM object to something that will be recognized as compatible with Win32::OLE. Maybe extract some information of the __ComObject for it to be identified correctly as COM object.
I have to add that I posted to the PDK discussion site too (but didn’t get any response yet): http://community.activestate.com/node/18247
I also posted it to PerlMonks - as I'm not quite sure if this is more a Perl or C#/.NET question:
http://www.perlmonks.org/?node_id=1146244
I would greatly appreciate any help - or advise on where to look further.
I have a ComVisible COM class written in C#. I want to call it from another C# bit of code using COM and pass the default value for the parameter. I can call plenty of other methods without default arguments.
This is the best I can come up with. The first two lines work for all my other methods.
Type mytype = Type.GetTypeFromProgID("MyType");
dynamic myinstance = Activator.CreateInstance(mytype);
object missingValue = System.Reflection.Missing.Value;
myinstance.generatecsvdocument("mystring", ref missingValue);
My method looks like this:
public void generatecsvdocument(string mystring, string rowseperator = "\n")
When I run it I get the error:
The best overloaded method match for 'generatecsvdocument(string,
string)' has some invalid arguments
object missingValue = System.Reflection.Missing.Value;
That cannot work here. It is only valid for a COM method that takes a VARIANT as an argument. Looks like object or dynamic in C#. A very different kind of default argument mechanism than C# supports, it is the callee that determines the default value. In C# it is the caller that determines it, the C# compiler uses metadata to know that default.
Missing.Value turns in a variant of type vtError with the value DISP_E_PARAMNOTFOUND at runtime. Signalling the COM method to use the default value. Not actually that commonly used, usually only implemented in COM servers that support scripting languages. Office Automation is the most common example, probably what inspired you to try this.
But no, your argument is string, not a variant. There is no way to discover the default either when you use late binding, implicit is that you don't know anything about the default value stored in metadata. Otherwise the reason that the vtError mechanism exists, scripting languages have the same problem. The only real way to get ahead is to rewrite the method and test for a null argument, substituting "\n" if that's the case.
I am trying to load the old version of farpoint dll in my project by using below code
System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFile(#"FarPoint.Web.Spread.dll");
System.Type MyDLLFormType = assembly.GetType("FarPoint.Web.Spread.FpSpread");
var c = Activator.CreateInstance(MyDLLFormType);
The problem is after the instance is created, all the available methods of farpoint are not available
[ for example - if i create the object directly the methods like saveExcel or savechanges are available with the instance]
FpSpread fpProxyObject = new FpSpread();
fpProxyObject.SaveExcel();
They are available, just not at compile time. Activator.CreateInstance() returns an object. You could of course cast the object:
var c = Activator.CreateInstance(...)
FpSpread fpProxyObject = (FpSpread)c;
But that would probably beat the whole purpose of using reflection to create the instance.
You can access all members of the result object by using reflection, ie:
MethodInfo saveExcelMethod = c.GetType().GetMethod("SaveExcel");
if (saveExcelMethod == null) throw new ApplicationException("Incorrect version of FarPoint");
saveExcelMethod.Invoke(c);
Intellisense is not working, because, as said #C.Evenhuis, Activator.CreateInstance returns object, so you should cast it to appropriate type.
If type is not known at compile time, but you have access to a code-base, you could try to add interface for it, and implement it by your class. Then cast object to that interface and use it. (I don't know your purpose, but interface could be treated as a contract for all the types, that you will load dynamically).
If type is not known at compile time and you have no access to a code-base, you could use reflection for method invocation or use dynamic instead.
dynamic c = Activator.CreateInstance(MyDLLFormType);
c.SaveExcel(); // this method invocation will be bound in runtime.
By the way be carefull, while using Assembly.LoadFile. You may get more details from this article.
Initially, it was probably not proper to create COM through late binding, and then call its methods via InvokeMember.
But now, perhaps, too late to redo everything from scratch.
Therefore, tell me how to be.
There is a COM object in the DLL. Written in Delphi7.
In C#, it is used as follows:
Type comType = Type.GetTypeFromProgID(ProgID, false);
object comObj = Activator.CreateInstance(comType);
// and then call methods
comType.InvokeMember("DoLongWork", BindingFlags.InvokeMethod, null, comObj, null);
Now we need to add to it the opportunity to call the methods of the server (i.e. the one who keeps to himself this COM object)
For this, in a COM object in its TLB added additional interface
IHookCallback = interface(IDispatch)
procedure ServerHook(DoStuff: integer); safecall;
end;
And also, in its main interface added initialization method callback
ITestDisp = dispinterface
...
procedure SetupHook(const Callback: IHookCallback); safecall;
Then imported into the VS project DLL - with this COMom inside. Thereby gained access to the interface description.
Then (in VS) created a class that implements this interface.
And I try to transfer it to the COM through InvokeMember
comType.InvokeMember("SetupHook", BindingFlags.InvokeMethod, null, comObj, new object[] {SomeClass as IHookCallback});
and so, too, tried
comType.InvokeMember("SetupHook", BindingFlags.InvokeMethod, null, comObj, new object[] {SomeClass});
I receive an error
Exception has been thrown by the target of an invocation.
InnerException
Specified cast is not valid.
Where did I go wrong?
I did.
Write here may be useful to someone my decision.
Initially I have COM object with its own TLB. Written in Delphi 7.
There was also a project written in VS 2010. He created the COM using "Activator Class". And then the work was carried out through late binding. Ie through InvokeMember.
The task was this: were ready COM objects, which could not be replaced. Ie was necessary to ensure backward compatibility and work with existing code as before.
But at the same time, you had to write another 2 COM object with advanced features - support several new methods.
I solved this problem as follows: created a separate TLB on Delphi 7. It announced a completely independent interface (inherited from IDispatch but it does not matter).
Then I created a new COM object that implements the old interface and the new interface.
Then modified the C #. He also created objects through "Activator Class", but after the establishment immediately brought the created object to the second interface (I imported the description of a second TLB in C # project) with the operator "as". If the result was NULL then it was an old COM does not support the new functionality. Otherwise, we have a new COM with enhanced functionality.
Done.
In VB (ASP.NET)
Application("myapp")= Server.CreateObject("APP.Engine")
aa = Application("myapp").myMethod(2)
works.
In C# I tried
Application["myapp"]= Server.CreateObject("APP.Engine")
but
Application["myapp"].myMethod(2)
fails with
'object' does not contain a definition for 'myMethod'
How can I expose the public interface in C#?
If you have access to the defining type (i.e. not a raw COM object), you can simply cast:
((APP.Engine)Application["myapp"]).myMethod(2);
If you are using c# 4.0, you can do:
dymamic myApp = Application["myapp"];
myApp.myMethod(2);
Otherwise you will have to use dynamic method invocation using reflection and Type.InvokeMember(...)
You need to cast to the correct class first, like:
((APP.Engine)Application["myapp"]).myMethod(2)
Cast the result of Application["myapp"] to the correct type, like so:
((APP.Engine)Application["myapp"]).myMethod(2);
Other options include creating a local reference and using that instead.
This basically happens as C# is strongly typed language. When you call Application you are actually calling a collection of type Object. The collection is actually an array of Object. We take it as object because it will enable you to store almost anything to the collection.
Now Application["myapp"] will eventually return an object of System.Object type.
So you need to typecast the object to your type.
App.Engine obj = Application["myapp"] as App.Engine;
if(obj != null)
obj.myMethod(2);
The object obj is a reference to the actual type and the compiler will not throw any exception if you call myMethod(2).
Just remember one thing while converting code from VB to C# is, C# is the strongly typed language and before you access any type, you will need to convert it. However same wasn't required in VB.
So for this example you would need to use App.Engine for type conversion.
((APP.Engine)Application["myapp"]).myMethod(2)