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);
Related
I'm trying to integrate a Brother QL-700 label printer into a C# application that I'm writing. I've created a console application to test the printer with and added the following code (taken from the SDK on the Brother website):
class Program
{
private const string TEMPLATE_DIRECTORY = #"c:\program files\brother bpac3 sdk\templates\";
private const string TEMPLATE_FRAME = #"NamePlate2.LBX";
static void Main(string[] args)
{
string templatePath = TEMPLATE_DIRECTORY;
templatePath += TEMPLATE_FRAME;
IDocument doc = new bpac.Document();
if (doc.Open(templatePath) != false)
{
doc.GetObject("objCompany").Text = "Company";
doc.GetObject("objName").Text = "Your name here...";
doc.StartPrint("", PrintOptionConstants.bpoDefault);
doc.PrintOut(1, PrintOptionConstants.bpoDefault);
doc.EndPrint();
doc.Close();
}
else
{
Console.WriteLine("Open() error: " + doc.ErrorCode);
Console.ReadLine();
}
}
}
The application stops on the line where I create a new instance of bpac.Document and it throws the following error message:
System.Runtime.InteropServices.COMException: 'Retrieving the COM class factory for component with CLSID {B940C105-7F01-46FE-BF41-E040B9BDA83D} failed due to the following error: 80040154 Class not registered (Exception from HRESULT: 0x80040154 (REGDB_E_CLASSNOTREG)).'
There are several other posts on Stack Overflow about this issue and I have tried some of the commonly suggested fixes:
The DLL in question is definitely in the Windows registry
I have changed the application architecture on the console application to x86
I'm still unable to make any progress. Does anyone else have any other ideas?
I have a COM object called Test and the .rgs files looks as follows. I do see that this class is registered in the registery at the location HKEY_LOCAL_MACHINE>>SOFTWARE>>Classes>>CLSID.
HKCR
{
NoRemove CLSID
{
ForceRemove {17899D17-698D-4781-894C-3BAA80F3D4BB} = s 'Test Class'
{
ForceRemove 'Programmable'
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Apartment'
}
val AppID = s '{D2A47892-4C98-11D6-8C6D-00105A1790CD}'
'TypeLib' = s '{9A887890-4C99-11d6-8C6D-00105A1790CD}'
}
}
}
When I create an instance of this object inside a C++ project it works fine. The code used is:
ATLTRACE( _T( " | CoCreateInstance Test\n" ) );
hr = m_Test.CoCreateInstance(CLSID_Test, NULL, CLSCTX_INPROC_SERVER);
if (FAILED(hr))
CREATE_COM_OBJECT_ERROR(_T("Test"));
However when I try to call this object from .NET application it errors out saying "An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in PatternEditor.exe
Additional information: Catastrophic failure (Exception from HRESULT: 0x8000FFFF (E_UNEXPECTED))". The code used is as follows:
public partial class MainWindow : Window
{
Main m_Main = new Main();
Test m_Test;
public MainWindow()
{
InitializeComponent();
//This line errors
m_Test = m_Main.Test;
}
}
Usually, the easiest way to use COM objects inside .NET is creating a reference in the project (the tab COM). It will generate a "stub" and you can use as simple .NET code.
Other way is through reflection. This answer has an idea: C# COM and attaching events
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
I have a working CLI interface between C++ and C# code. The code has a C++ abstract interface like:
-------------C++ Interface---------------
namespace cppns
{
class cppInterface
{
public:
virtual bool Start(const char *pcDir) = 0;
};
}
------Implementation of abstract C++ interface in same dll---------
namespace cppns
{
class cppimp : public cppInterface
private:
gcroot<MyInternalCSharpClass^> mInternalClassAccess;
public:
cppimp::cppimp()
{
mInternalClassAccess = gcnew MyInternalCSharpClass();
}
virtual bool cppimp::Start(const char *pcDir)
{
System::AppDomain ^appDom = AppDomain::CurrentDomain::get();
System::String ^strDomainName = appDom->FriendlyName;
mInternalClassAccess->Initalize(pcDir);
}
}
---------Method to create an instance of the class in a factory--------------
cppns::cppInterface *GetImplObject()
{
return new cppns::cppimp();
}
----------Factory class .h to allow C++ to get an instance of the cppimp class------
------The C++ code knows about the abstract interface by including the header file--
------FactoryExport is __declspec(dllexport) when compiled in dll and---------------
----- __declspec(dllimport) when used as a header file in exe that uses header------
class FactoryExport ClassFactory
{
public:
static cppns::cppInterface *CreateImpl();
};
----------Factory class .cpp to allow C++ to get an instance of the cppimp class------
cppns::cppInterface *ClassFactory::CreateImpl()
{
return GetImplObject();
}
This code correctly allows me to call CreateImpl to get an implementation of the interface that contains the Start method. My issue is that I'm trying to force the whole CLR/.NET loading and executing into an AppDomain that is not the default AppDomain. I can create a secondary AppDomain using the following code:
CComPtr<ICorRuntimeHost> pRuntimeHost;
//Retrieve a pointer to the ICorRuntimeHost interface
HRESULT hr = CorBindToRuntimeEx(
L"v2.0.50727", //Retrieve last version before 4.0.
// NULL, //Retrieve latest version by default
L"wks",
STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN | STARTUP_CONCURRENT_GC,
CLSID_CorRuntimeHost,
IID_ICorRuntimeHost,
(void**)&pRuntimeHost.p
);
hr = pRuntimeHost->Start();
DWORD dwAppDomainId = 22;
WCHAR domainName[80 + 1];
swprintf(domainName, 80, L"%s-%ld",L"NoDefaultDomain", dwAppDomainId);
CComPtr<IUnknown> pUnknownAppDomain;
hr = pRuntimeHost->CreateDomainEx(domainName, NULL, NULL, &pUnknownAppDomain);
CComPtr<_AppDomain> pAppDomain;
hr = pUnknownAppDomain->QueryInterface(__uuidof(_AppDomain), (VOID**)&pAppDomain.p);
BSTR bstrFriendlyName;
hr = pAppDomain->get_FriendlyName(&bstrFriendlyName);
if (SUCCEEDED(hr))
{
_bstr_t bstrFriendlyNameWrap(bstrFriendlyName, false);
}
_bstr_t bstrAssemblyName("InteropCode");
CComPtr<_Assembly> pAssembly;
hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly);
BSTR bstrFullName;
hr = pAssembly->get_FullName(&bstrFullName);
if (SUCCEEDED(hr))
{
_bstr_t bstrFullNameWrap(bstrFullName, false);
std::cout << "Assembly name is: " << bstrFullNameWrap << "\n";
}
Every attempt of getting the factory to return to me an interface to cppns::cppInterface within this secondary application domain has failed. I have even attempted to create a secondary factory that is a C# class that returns the pointer to the implemented interface so that an Invoke call on the Assembly would hopefully cause the rest of the code to execute in the AppDomain that I loaded the Assembly into but the Invoke returns an IDispatch pointer that I can't seem to map back into any type of C++ pointer on my interface.
namespace cppns
{
public ref class NetFactory
{
public:
NetFactory()
{
}
cppInterface *CreateInterop()
{
return GetImplObject();;
}
};
}
Is there another way to get everything to run in a secondary AppDomain or is the IDispatch pointer usable in calling the Start method?
I have managed to get most of the .NET stuff running in another domain. It seems like there is no way to get the CLI layer to run in anything other than the default AppDomain.
To make this work I needed to make the class that sits within both appdomains derive from MarshalByRefObject. In my example above that meant I had to change MyInternalCSharpClass so that it derived from MarshalByRefObject. It was also nessary to made the objects sent and returned from MyInternalCSharpClass also derive from MarshalByRefObject. Finally I these same objects that were passed and returned had to have the [Serializable] property and to also mark all their private variables public. Note if the classes being transferred though the AppDomains are already using the Serializable attribute you can use [XmlIgnore] on each formally private variable to avoid changing the serialization that is being done.
Now that everything can be moved between the AppDomains I created a second AppDomain by doing the following:
bool CreateInstanceInAppDomain(const char *pcAppDomainName)
{
bool bRtn = false;
gcroot<String^> csStrAppDomainName (gcnew String(pcAppDomainName));
mAppDomain = AppDomain::CreateDomain(csStrAppDomainName);
delete csStrAppDomainName;
Object^ MyInternalObject = mAppDomain->CreateInstanceAndUnwrap("AssemblyName", "ClassNameSpace.MyInternalCSharpClass");
mInternalClassAccess = dynamic_cast<MyInternalCSharpClass^>(MyInternalObject);
if (mInternalClassAccess)
{
bRtn = true;
}
return bRtn;
}
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.