Get CLSID by PIA interface Type - c#

Edit: Looks like Jon Skeet had some similar questions: How does the C# compiler detect COM types?
How can I get the CLSID for a given interface within a Primary Interop Assembly? Here's what I'm talking about:
// The c# compiler does some interesting magic.
// The following code ...
var app = new Microsoft.Office.Interop.Outlook.Application();
// ... is compiled like so (disassembled with Reflector):
var app =((Microsoft.Office.Interop.Outlook.Application)
Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("0006F03A-0000-0000-C000-000000000046"))));
Microsoft.Office.Interop.Outlook.Application is an interface, and therefore it cannot be instantiated directly. What's interesting here is that c# lets you treat these COM interfaces as if they were classes that you can instantiate with the new keyword.
What I want to know is, given the System.Type for a given interface, how can I get the CLSID?
Note: I ultimately want to be able to create an instance given the interface's System.Type - I don't really care how. I'm assuming here that the easiest way to do this would be to get CLSID given the Type, just as the c# compiler does.

Here's my current solution:
// Get the PIA assemby name, using the GUID of the typlib
string asmName;
string asmCodeBase;
var conv = new System.Runtime.InteropServices.TypeLibConverter();
conv.GetPrimaryInteropAssembly(new Guid("00062FFF-0000-0000-C000-000000000046"), 9, 3, 0, out asmName, out asmCodeBase);
// Load the PIA, and get the interface type
var assembly = System.Reflection.Assembly.Load(asmName);
var type = assembly.GetType("Microsoft.Office.Interop.Outlook.Application");
// Get the coclass
var coClassAttr = (System.Runtime.InteropServices.CoClassAttribute)
type.GetCustomAttributes(typeof(System.Runtime.InteropServices.CoClassAttribute), false)[0];
var coClass = coClassAttr.CoClass;
// Instantiate the coclass
var app = Activator.CreateInstance(coClassAttr.CoClass);
// If needed, the CLSID is also available:
var clsid = coClass.GUID;
I figured this out by disassembling the GAC'd PIA. I noticed that Outlook.Application was decorated with a CoClassAttribute, like so:
[ComImport, Guid("00063001-0000-0000-C000-000000000046"), CoClass(typeof(ApplicationClass))]
public interface Application : _Application, ApplicationEvents_11_Event
{
}
ApplicationClass looks something like:
[ComImport, ClassInterface((short) 0), ComSourceInterfaces("Microsoft.Office.Interop.Outlook.ApplicationEvents_11\0Microsoft.Office.Interop.Outlook.ApplicationEvents\0Microsoft.Office.Interop.Outlook.ApplicationEvents_10\0"), Guid("0006F03A-0000-0000-C000-000000000046"), TypeLibType((short) 11)]
public class ApplicationClass : _Application, Application, ApplicationEvents_11_Event, ApplicationEvents_Event, ApplicationEvents_10_Event
{
//...
}
Let me know what you all think, so I can decide if I should mark this as the chosen answer.

Try the GUID property.

Related

What Ninject convention should I use to bind all interfaces starting with "I" with interfaces having the same name without the "I" prefix for COMObj?

I'm working on integrating an accounting system which objects are COM objects.
When binding one to one as follows, it works just fine.
IKernel kernel = new StandardKernel();
kernel.Bind<IAcoSDKX>().ToMethod(_ => { return new AcoSDKX(); }).InSingletonScope();
The situation I'm having is that both IAcoSDKX and AcoSDKX are interfaces, and the AcoSDKClass is inaccessible to the consumer.
So I'm looking for a way to bind both interfaces together as only their spelling differ. Ont starts with an 'I', and other doesn't. So I'd like to come up with a conventional binding where even though I keep using unbound interfaces, Ninject knows what to bind it to when activating the objects through constructor injection.
Here's the try I came up with so far with no success.
kernel.Bind(services => services
.FromAssembliesMatching("AcoSDK.dll")
.SelectAllTypes()
.Where(t => t.Name.StartsWith("I") && t.IsInterface)
.BindDefaultInterfaces());
So I wonder, how to configure a convention binding using Ninject that could suit the actual need?
Basically the convention is to bind all interfaces starting with "I" with interfaces having the same name without the "I" prefix.
EDIT
After further searches, I found out that the types of AcoSDK.dll are embedded into my own assembly. Only types that are early loaded are bindable.
Besides, although I can new a COM Object interface, the Activator.CreateInstance won't initialize it under pretext that it is an interface. See objects declaration as follows:
namespace AcoSDK {
[ComImport]
[Guid("00000114-0000-000F-1000-000AC0BA1001"]
[TypeLibType(4160)]
public interface IAcoSDKX { }
[ComImport]
[Guid("00000114-0000-000F-1000-000AC0BA1001")]
[CoClass(typeof(AcoSDKClass))]
public interface AcoSDKX : IAcoSDKX { }
[ComImport]
[Guid("00000115-0000-000F-1000-000AC0BA1001")]
[TypeLibType(2)]
[ClassInterface(0)]
public class AcoSDKXClass : IAcoSDKX, AcoSDKX { }
}
public class Program() {
// This initializes the type.
IAcoSDKX sdk = new AcoSDKX();
// This also does.
IKernel kernel = new StandardKernel();
kernel.Bind<IAcoSDKX>().ToMethod(_ => new AcoSDKX());
// This won't activate because type is an interface.
IAcoSDKX sdk = Activator.CreateInstance(typeof(AcoSDKX));
// This says : Interop type AcoSDKXClass cannot be embedded. Use the applicable interface instead.
IAcoSDKX sdk = Activator.CreateInstance(typeof(AcoSDKXClass));
// Same message occurs when newing.
IAcoSDKX sdk = new AcoSDKClass();
}
Given this new information, anyone has a similar experience with COM Objects? I tried to new the AcoSDKClass or to access it in any fashion, and I just don't seem to get a hook on it.
A lot of this answer depends on how you are referencing the COM object. If you use the 'Add Reference' in Visual Studio to a COM class it will add the reference and generate an Interop.*.dll file. By default, if you view properties on the reference, Embed Interop Types = True. This is how you are configured given the compiler error Interop type AcoSDKXClass cannot be embedded. you note above. If you can change this to False it can save a lot of pain, but you need to ship the Interop.*.dll file.
First, I will answer the question as if you specified Embed Interop Types = False because it is simple. For this code, I'm using a console application with .NET 4.8. Then add a reference to a COM object - I'm using 'Microsoft Speech Object Library', which will generate a new reference SpeechLib which generates an interop Interop.SpeechLib.dll assembly which is equal in concept to your AcoSDK.dll. On the SpeechLib reference, select Properties and set Embed Interop Types = False. Add the Ninject nuget package.
IKernel kernel = new StandardKernel();
var assemblyOfEmbeddedComTypes = typeof(ISpVoice).Assembly;
var allTypes = assemblyOfEmbeddedComTypes.GetTypes();
var comClassTypes = allTypes.Where(
x => x.IsClass &&
x.IsCOMObject &&
x.GetInterfaces().Any()).ToList();
foreach (var iface in allTypes.Where(t =>
t.IsInterface &&
t.Name.StartsWith("I") &&
t.GetCustomAttributes<ComImportAttribute>(false).Any()))
{
var impl = comClassTypes.FirstOrDefault(x => x.GetInterfaces().Contains(iface));
if (impl != null)
kernel.Bind(iface).To(impl);
}
var sv2 = kernel.Get<ISpVoice>();
sv2.Speak("Hello World", 0, out _);
If you MUST work with Embed Interop Types = True, you would still need to have the full interop assembly available at runtime, because in order to create an instance of the COM class, you need to be able to get either the CLSID or ProgId of the COM class. You can use Type.GetTypeFromCLSID(Guid) or Type.GetTypeFromProgID(string) that will give you a Type that will work with Activator.CreateInstance(type). The CLSID is shown in your example in the GuidAttribute on the AcoSDKClass. Unfortunately, with the interop types embedded, this class will not be included in your assembly therefore cannot be found this way.
For this code, set Embed Interop Types = True and erase the previous code. Since it is embedded, the interop is in the obj rather than the bin folder. The following looks in that interop assembly for all interfaces with your pattern, finds the first class that implements it (that has a Guid attribute which is the COM CLSID), then we add the interface name to a dictionary along with the CLSID. You don't need to technically need to use ReflectionOnlyLoadFrom here as it makes getting the GuidAttribute value a bit harder, but (spoiler alert) we can't use Types from this assembly anyway.
var assembly = Assembly.ReflectionOnlyLoadFrom("..\\..\\obj\\Debug\\Interop.SpeechLib.dll");
var types = assembly.GetTypes();
var interfaces = types.Where(t => t.IsInterface && t.Name.StartsWith("I") &&
t.GetCustomAttributesData().Any(c => c.AttributeType == typeof(ComImportAttribute)));
var classes = types.Where(t => t.IsClass && t.IsCOMObject &&
t.GetCustomAttributesData().Any(c=> c.AttributeType == typeof(GuidAttribute))).ToArray();
var typeNameToClsidDict = new Dictionary<string, Guid>();
foreach (var iface in interfaces)
{
var c = classes.FirstOrDefault(x => x.GetInterfaces().Contains(iface));
if (c != null)
{
var guidAtt= c.GetCustomAttributesData()
.First(ad => ad.AttributeType == typeof(GuidAttribute));
var clsid = new Guid((string)guidAtt.ConstructorArguments.First().Value);
typeNameToClsidDict.Add(iface.FullName, clsid);
}
}
This is where the real fun is. Since Embed Interop Types = true, the COM interface types are from YOUR assembly and NOT the interop assembly - effectively they are different types because they are from different assemblies. So now you need to use reflection to find the embedded interfaces from your assembly - these are the types you need to register with Ninject - then lookup the CLSID from the dictionary we built from the interop assembly. Add the following code to what we did above:
IKernel kernel = new StandardKernel();
var assemblyOfEmbeddedComTypes = typeof(ISpVoice).Assembly;
foreach (var iface in assemblyOfEmbeddedComTypes.GetTypes().Where(
t => t.IsInterface &&
t.Name.StartsWith("I") &&
t.GetCustomAttributes<ComImportAttribute>(false).Any()))
{
if (typeNameToClsidDict.TryGetValue(iface.FullName, out var clsid))
{
var type = Type.GetTypeFromCLSID(clsid);
kernel.Bind(iface).ToMethod(_ => Activator.CreateInstance(type));
}
}
var sv2 = kernel.Get<ISpVoice>();
sv2.Speak("Hello World", 0, out _);
I don't think there is a way to structure the Ninject conventions binding to do it fluently for either approach, though I may be wrong here.

Roslyn scripting with enforced script interface

I have simple IScript interface. And I want enforce that all scripts implement it.
public interface IScript<T>
{
T Execute(object[] args);
}
I want to use Roslyn scripting API to achive this. Something like this is possible with CSScript (see Interface Alignment).
var code = #"
using System;
using My.Namespace.With.IScript;
public class Script : IScript<string>
{
public string Execute()
{
return ""Hello from script!"";
}
}
";
var script = CSharpScript.Create(code, ScriptOptions.Default); // + Load all assemblies and references
script.WithInterface(typeof(IScript<string>)); // I need something like this, to enforce interface
script.Compile();
string result = script.Execute(); // and then execute script
Console.WriteLine(result); // print "Hello from script!"
Type safety is a static thing enforced a compile time (of your application). Creating and running a CSharpScript is done at runtime. So you cannot enforce type safety at runtime.
Maybe CSharpScript is not the right way to go. By using this SO answer,
You can compile a piece of C# code into memory and generate assembly bytes with Roslyn.
You would then change the line
object obj = Activator.CreateInstance(type);
to
IScript<string> obj = Activator.CreateInstance(type) as IScript<string>;
if (obj != null) {
obj.Execute(args);
}

Mobile Broadband C# interface to manage Profiles

I am trying to use the C# interface to the mobile broadband API. The code below compiles and intellisense displays all of the COM methods, but the code does not execute correctly.
MbnInterfaceManager mbnInfMgr = new MbnInterfaceManager();
IMbnConnectionProfile conProfile = (IMbnConnectionProfile)mbnInfMgr;
string xmlBuff = conProfile.GetProfileXmlData();
The following error is produced:
Unable to cast COM object of type 'System.__ComObject' to interfacetype
'MbnApi.IMbnConnectionProfile'.
This operation failed because the QueryInterface call on the COM component
for the interface with IID '{DCBBBAB6-2010-4BBB-AAEE-338E368AF6FA}' failed
due to the following error:
No such interface supported(Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
Microsoft lists the calls as below:
IMbnConnectionProfile Interface Method C# Signature
Delete public void Delete();
GetProfileXmlData public string GetProfileXmlData();
UpdateProfile public void UpdateProfile( string strProfile);
It looks as if I need to specify the interface but can't seem to find out how to do this.
Can any one show me how to do this please?
By calling IMbnInterfaceManager::GetInterface or IMbnInterfaceManager::GetInterfaces methods.
E.g.
MbnInterfaceManager mbnInfMgr = new MbnInterfaceManager();
IMbnInterfaceManager infManager = (IMbnInterfaceManager)mbnInfMgr;
//obtain the IMbnInterface passing interfaceID
string interfaceID = “{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}”;
IMbnInterface mbnInterface= infMgr.GetInterface(interfaceID);
MbnConnectionProfileManager mbnProfMgr = new MbnConnectionProfileManager();
IMbnConnectionProfileManager profileManager =
(IMbnConnectionProfileManager)mbnProfMgr;
IMbnConnectionProfile[] profArr =
(IMbnConnectionProfile[])profileManager.GetConnectionProfiles(mbnInterface);

Is it posible to perform QueryInterface on behalf of VBScript

I'm trying to create a COM class with one method that will cast an object to a specific interface on behalf of VBScript.
This is the method signature I'm using:
public object GetInterface(object unknown, string iid)
I thought this would be possible because if the method explicitly declares the return type as :
public IRequestedInterface GetInterface(object unknown, string iid)
Then VBScript gets the reference to the desired interface.
So I tried just casting to the interface
return (IRequestedInterface)unknown;
Unfortunately, VBScript gets a reference to the default interface instead of the requested interface.
I have tried getting round this by creating a custom marshaller using ICustomMarshaler.
I thought this would work because the method MarshalManagedToNative returns a IntPtr.
Because of this I thought that if i just returned the IntPtr to the interface
return Marshal.GetComInterfaceForObject(unknown, typeof(IRequestedInterface));
it would work. But, obviously, it didn't have the desired effect :(
So does anybody know if it is posible and how you would do it?
EDIT:
I thought it would be helpful to add a concrete example (although it is contrived) to explain why I haven't accepted that VBScript will always get the default interface. I'm still clinging to my hope.
Below you will find the contents of 3 files, 'TestLib.cs', 'Build.cmd' and 'Test.vbs'. These hopefully demonstrate why I still think it 'should' be possible.
Note: I have tested this on Windows XP SP3 (x86).
TestLib.cs
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
[assembly: ComVisible(false)]
[assembly: Guid("64e20009-c664-4883-a6e5-1e36a31a0fd8")]
[assembly: AssemblyVersion("2012.06.*")]
[ComVisible(true)]
[Guid("EB77C7B1-D1B9-4BB3-9D63-FBFBD56C9ABA")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IPerformQi
{
[DispId(1000)]
object GetInterface(object unknown, string iid);
[DispId(2000)]
IRequested GetIRequested(object unknown);
}
[ComVisible(true)]
[Guid("7742BC0A-8719-483E-B1DF-AE9CD9A958DC")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IDefault
{
[DispId(1000)]
void SayHello(string name);
}
[ComVisible(true)]
[Guid("FFF34296-2A06-47D4-B09C-B93B63D5CC53")]
[InterfaceType(ComInterfaceType.InterfaceIsDual)]
public interface IRequested
{
[DispId(1000)]
void SayGoodbye(string name);
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IPerformQi))]
[Guid("222BB88D-B9FA-4F23-8DB3-BA998F4E668B")]
[ProgId("TestLib.PerformQi")]
public class PerformQi : IPerformQi
{
object IPerformQi.GetInterface(object unknown, string iid)
{
if(iid == "FFF34296-2A06-47D4-B09C-B93B63D5CC53")
return (IRequested)unknown;
throw new Exception("Unable to find inteface");
}
IRequested IPerformQi.GetIRequested(object unknown)
{
return (IRequested)unknown;
}
}
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IDefault))]
[Guid("174ABED6-3325-4878-89E3-BF8BD1107488")]
[ProgId("TestLib.Test")]
public class Test : IDefault, IRequested
{
void IDefault.SayHello(string name)
{
MessageBox.Show(string.Format("Hello '{0}'", name));
}
void IRequested.SayGoodbye(string name)
{
MessageBox.Show(string.Format("Goodbye '{0}'", name));
}
}
Build.cmd
"%windir%\Microsoft.Net\Framework\v4.0.30319\csc.exe" /out:TestLib.dll /target:library /r:System.Windows.Forms.dll TestLib.cs
"%windir%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" TestLib.dll /codebase /tlb:TestLib.tlb
PAUSE
Test.vbs
Dim oPerformQi 'As TestLib.PerformQi
Dim oTest 'As TestLib.Test
Dim oTest2 'As IRequested
Dim oTest3 'As IRequested
Set oPerformQi = CreateObject("TestLib.PerformQi")
Set oTest = CreateObject("TestLib.Test")
Call oTest.SayHello("Robert")
Set oTest2 = oPerformQi.GetIRequested(oTest)
'Note: This works
Call oTest2.SayGoodbye("Robert")
Set oTest3 = oPerformQi.GetInterface(oTest, "FFF34296-2A06-47D4-B09C-B93B63D5CC53")
'Note: This does not work
Call oTest3.SayGoodbye("Robert")
Using the call oPerformQi.GetIRequested(oTest) makes the call to oTest3.SayGoodbye("Robert") work. This makes me think you are not limited to just the default interface in VBS.
Perhaps .Net is not capable of returning the specified interface because of an implicit cast on the return value? Ideally I would use generics for this, but as we all know COM does not support genrics.
Under this restriction is there any other way that you can think of to achieve this?
EDIT 2:
I have found that I can achieve this using VB6, below is the code for the class.
Option Explicit
Public Function GetInterface(ByVal oUnknown As Object, ByVal IID As String) As Variant
Dim oIRequested As IRequested
If IID = "FFF34296-2A06-47D4-B09C-B93B63D5CC53" Then
Set oIRequested = oUnknown
Set GetInterface = oIRequested
Else
Err.Raise 1, , "Unable to find inteface"
End If
End Function
I would still like to find a C# version if anybody can shed some light on the subject i would appreciate it.
In order to have multiple IDispatch-derived interfaces implemented on a single object, to be accessible from scripting environment you should rather implement IDispatchEx and have its methods called once a call from script is taking place.
The problem you are facing is caused by the fact that script queries for your IDispatch first, and both your IDispatch-derived interfaces return the same "main" IDispatch leaving no chance for methods of other interfaces to be accessible.
When VBS host is about to call a method on your object, it first queries IDispatchEx. If found, the calls are delivered via IDispatchEx::InvokeEx and your COM Class can internally route the call to the proper IDispatch implementation, both private or forward to external/inner object.
In case IDispatchEx is not found, it looks for IDispatch and there you are in trouble because it sees only your "main" interface. That is, the workaround for you is to implement IDispatchEx. You can do it either way: implement right on your COM class, or instead create a proxy class to accept scripting calls via IDispatchEx::InvokeEx and forward to correct IDispatch in your code.
Example: Both A and B classes implement IX and IY interfaces, B additionally implements IDispatchEx. Interface methods are IX::X1, IY::Y1.
On Error Resume Next
Set A = CreateObject("Test.A")
WScript.Echo A.X1 ' Success, via IX::Invoke
WScript.Echo A.Y1 ' Failure, A's IDispatch is IX's parent and does not have Y1 method
Set B = CreateObject("Test.B")
WScript.Echo B.X1 ' Success, via IDispatchEx::InvokeEx
WScript.Echo B.Y1 ' Success, via IDispatchEx::InvokeEx

Access host class from IronPython script

How do I access a C# class from IronPython script?
C#:
public class MyClass
{
}
public enum MyEnum
{
One, Two
}
var engine = Python.CreateEngine(options);
var scope = engine.CreateScope();
scope.SetVariable("t", new MyClass());
var src = engine.CreateScriptSourceFromFile(...);
src.Execute(scope);
IronPython script:
class_name = type(t).__name__ # MyClass
class_module = type(t).__module__ # __builtin__
# So this supposed to work ...
mc = MyClass() # ???
me = MyEnum.One # ???
# ... but it doesn't
UPDATE
I need to import classes defined in a hosting assembly.
You've set t to an instance of MyClass, but you're trying to use it as if it were the class itself.
You'll need to either import MyClass from within your IronPython script, or inject some sort of factory method (since classes aren't first-class objects in C#, you can't pass in MyClass directly). Alternatively, you could pass in typeof(MyClass) and use System.Activator.CreateInstance(theMyClassTypeObject) to new up an instance.
Since you also need to access MyEnum (note you're using it in your script without any reference to where it might come from), I suggest just using imports:
import clr
clr.AddReference('YourAssemblyName')
from YourAssemblyName.WhateverNamespace import MyClass, MyEnum
# Now these should work, since the objects have been properly imported
mc = MyClass()
me = MyEnum.One
You might have to play around with the script source type (I think File works best) and the script execution path to get the clr.AddReference() call to succeed.

Categories

Resources