I have a Dll Project with IDL interfaces.
I Want to have to interfaces in my dll that one of them can be derived from other.
I created two interfaces with ATL Simple Object Wizard.
[
object,
uuid(7359AF6C-6E90-4372-991F-556602CB3977),
dual,
nonextensible,
pointer_default(unique)
]
interface IZInterface : IDispatch{
[id(1)] HRESULT ZGetStr([out,retval] BSTR* str);
[id(2)] HRESULT GetSize([in,out] LONG* nSize);
};
[
object,
uuid(8CA6DBF2-E402-464D-96AE-3D6642D91E14),
pointer_default(unique)
]
interface IBClass : IUnknown{
[] HRESULT Method11([out,retval] LONG* l);
};
library DllStandardLib
{
importlib("stdole2.tlb");
[
uuid(491DF659-012F-4C20-90AA-0CBC5BDE5A68)
]
coclass ZInterface
{
[default] interface IZInterface;
};
[
uuid(43CE897F-17F2-4D45-9098-26B7AEE6EC23)
]
coclass BClass
{
[default] interface IBClass;
};
};
now, i right click on CZInterface in Class View then Impelement Interface IBClass .
but in continer that is a C# project:
DllStandardLib.ZInterface dd = new DllStandardLib.ZInterface();
dd.Method11();//---> Error: DllStandardLib.ZInterface' does not contain a definition for 'Method11' and no extension method 'Method11' accepting a first argument of type ...
what is the problem in my project? I want the second (derived) interface knows all methods and properties of base interface.
please help me!
Best thing is not to use the Object Wizard if you want to achive this. First, define your interfaces:
[
object,
uuid(7359AF6C-6E90-4372-991F-556602CB3977),
dual,
nonextensible,
pointer_default(unique)
]
interface IZInterface : IDispatch
{
[id(1)] HRESULT ZGetStr([out,retval] BSTR* str);
[id(2)] HRESULT GetSize([in,out] LONG* nSize);
};
[
object,
uuid(8CA6DBF2-E402-464D-96AE-3D6642D91E14),
pointer_default(unique)
]
interface IBClass : IZInterface // IBClass inherits from IZInterface
{
[id(3)] HRESULT Method11([out,retval] LONG* l);
};
Then define your class, that implements the specialized interface. Do this within your library:
library DllStandardLib
{
importlib("stdole2.tlb");
[
uuid(43CE897F-17F2-4D45-9098-26B7AEE6EC23)
]
coclass BClass
{
[default] interface IBClass;
};
// NOTE: No need for ZInterface coclass.
};
Note that the IZInterface class is not really needed, except you want to provide an separate implementation (= another class).
In order for the QueryInterface-calls to work, you should also add the interface to your COM interface map:
class CBClass :
// ...
{
// ...
BEGIN_COM_MAP(CBClass)
COM_INTERFACE_ENTRY(IBClass)
COM_INTERFACE_ENTRY(IZInterface)
END_COM_MAP()
// ...
// IBClass-Member
STDMETHOD(Method11)(LONG* l); // Implement this in your source file (STDMETHODIMP CBClass::Method11(LONG* l) { return E_NOTIMPL; }
}
To call your IBClass interface members from .NET, you have to create an instance of the right class:
DllStandardLib.BClass bc = new DllStandardLib.BClass();
bc.Method11();
Related
I am working on a legacy project that makes massive use of ATL COM objects. The task is to extend the unit testing (googletest) by the capability of mocking COM objects, or the smart pointers accessing them. To illustrate, I created a small example project with a single ATL simple object, which only exports the method methodB (part of idl file):
object,
uuid(070a4996-d9cf-4434-a823-3ea9043de394),
dual,
nonextensible,
pointer_default(unique)
]
interface IMyATLObj : IDispatch
{
[id(1)] HRESULT methodB([in] SHORT in, [out, retval] SHORT *out);
};
The objects are accessed using smart pointers of base type _com_ptr_t, such as in this example:
#include <comdef.h>
#include ".../ATLExample_i.h"
_COM_SMARTPTR_TYPEDEF(IMyATLObj, __uuidof(IMyATLObj));
IMyATLObjPtr create() {
IMyATLObjPtr objPtr;
objPtr.CreateInstance(__uuidof(MyATLObj));
return objPtr;
}
void use(IMyATLObjPtr objPtr) {
short val = 0;
objPtr->methodB(1, &val);
std::cout << val << std::endl;
}
Now the question is how I can test the function use(...) without actually creating a COM object. Of course I can create a wrapper class as a facade to all COM interactions that I can mock as usual, e.g.:
class MyObjFacade {
public:
MyObjFacade() {
objPtr.CreateInstance(__uuidof(MyATLObj));
}
virtual short methodB(short in) {
short val;
if (objPtr->methodB(in, &val) == S_OK)
return val;
else
throw;
}
IMyATLObjPtr objPtr;
};
class MYObjMock : public MyObjFacade {
public:
MOCK_METHOD1(methodB, short(short in));
};
void use(MyObjFacade *objPtr) {
short val = 0;
val = objPtr->methodB(1);
std::cout << val << std::endl << std::flush;
}
TEST_F(COMMockTest, Test1) {
MYObjMock mock;
EXPECT_CALL(mock, methodB(_)).WillOnce(Return(43));
use(&mock);
}
But that would obviously leave me with lots of boiler plate code and (possibly) unnessessary changes - this solution would probably not be well adopted. I am looking for an elegant solution that, for example, either introduces a mock object into IMyATLObjPtr mocks the pointer object itself. However, I don't really know where to start because all the available interfaces are generated by ATL and are not easily accessible.
Any ideas?
So I asked this question in a C# context and I have set bounty over there.
I have written an equivalent fragment of C++ code (to be housed within an ATL DLL project) to tap C++ developers experience as well.
IDispatch has a method called GetTypeInfo() with which one can acquire a pointer to ITypeInfo. ITypeInfo itself has a method called GetContainingTypeLib which gets the containing ITypeLib (as it says). This is useful when given a CoClass instance once can get to all the other CoClasses in a given DLL, very useful for reflection.
I had wondered if Excel VBA would co-operate in a similar fashion and return a ITypeLib for the containing VBA project and equally allow me to iterate over all the types found therein. So I wrote some test C# code and that is linked. Here I give equivalent C++ code to tap C++ experience.
The IDL...
[
object,
uuid(ddc4e135-49d6-49f8-ad57-ded4180095fd),
dual,
nonextensible,
pointer_default(unique)
]
interface ICoClassGetContainingTypeLib : IDispatch
{
HRESULT GetContainingTypeLib(IDispatch* vbaClass, IUnknown** iunkTypeLib);
};
[
uuid(bc8410a6-802a-4f91-a73b-c03179bb402b)
]
coclass CoClassGetContainingTypeLib
{
[default] interface ICoClassGetContainingTypeLib;
};
The cpp method implementation
STDMETHODIMP CCoClassGetContainingTypeLib::GetContainingTypeLib(IDispatch* vbaClass, IUnknown** iunkTypeLib)
{
CComPtr<IDispatch> itfDispatch(vbaClass);
CComPtr<ITypeInfo> pITypeInfo;
HRESULT hr = S_OK;
HRESULT hr2 = S_OK;
try
{
hr = itfDispatch->GetTypeInfo(0, 0, &pITypeInfo);
if (SUCCEEDED(hr))
{
UINT* idx(0);
CComQIPtr<ITypeLib> pTLib;
HRESULT hr2 = pITypeInfo->GetContainingTypeLib(&pTLib, idx);
return hr2; // throws exception Exception 0x800A88C1
// which ends u[p at 35009
//TODO If successful copy to ByRef param iunkTypeLib
}
}
catch (std::exception ex)
{
return E_FAIL;
}
return S_OK;
}
And the header declaration
STDMETHOD(GetContainingTypeLib)(IDispatch* vbaClass, IUnknown** iunkTypeLib);
And some client VBA
Sub TestGetContainingTypeLib()
Dim oTR As ToolsReferences '* could be ANY VBA class
Set oTR = New ToolsReferences '* could be ANY VBA class
'* instantiate my ATL project
Dim oTest As GetContainingTypeLibTestLib.CoClassGetContainingTypeLib
Set oTest = New GetContainingTypeLibTestLib.CoClassGetContainingTypeLib
Dim iunkTypeLib As IUnknown
Set iunkTypeLib = Nothing '* not strictly required
Call oTest.GetContainingTypeLib(oTR, iunkTypeLib)
End Sub
There is very little on the Internet to explain the exception number of 0x800A88C1 . I think this the Excel team saying no.
The old C++ error module implements one interface:
[
object,
uuid(7286455D-A48B-11D1-8C7E-006097089085),
dual,
helpstring("IDQSEror Interface"),
pointer_default(unique)
]
interface IDQSEror : IDispatch
{
[id(1), helpstring("method getLastError")] HRESULT getLastError([out] BSTR
...
When everything was in C++ this interface could be passed between other COM modules without problem. However, we've converted a middle module to VB.NET and returning the IDQSEror required casting:
NAVInitServr (VB.NET COM)
References
MainModule.PIA (interop assembly #include IDQError)
DQSError.PIA (interop assembly defines IDQError)
NavInit.vb
- Is called by MainModule
- Creates a DQSEror class
- Returns IDQSEror to the MainModule
- Requires casting:
Dim error as DQSError = new DQSEror()
return DirectCast(error, MainModule.PIA.IDQSerror)
Otherwise there is a casting error. We have rewritten DQSError in VB.NET:
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)
Public Interface IDQSEror
DispId(1)
Sub getLastError()
...
End Interface
The interface has the same GUID and DispId as the one in C++. The casting to return the error to the MainModule does not work anymore:
Dim error as DQSError = new DQSEror()
return DirectCast(error, MainModule.PIA.IDQSerror)
I developed an out-of-proc server and trying to marshal input params, but it fails.
My .idl file is:
[
uuid(6a4791c0-f69f-4d78-a591-f549c90c0620),
object,
oleautomation
]
interface IBrowserInterraction : IUnknown
{
HRESULT Unlock([in] BSTR str, [in] int value);
};
[
uuid(4d36f1c6-9ee6-4d9a-b0aa-6b7195cc5666),
version(1.0)
]
library BrowserInterractionInterfaceLibrary
{
[
uuid(cdebf15b-f12c-4559-a6ac-81620c5056c4)
]
coclass BrowserInterractionInterface
{
interface IBrowserInterraction;
};
};
My server code:
m_bpFactory = boost::make_shared<BrowserPluginInterractionClassFactory>(m_synchronizer);
IUnknown* pUnk;
m_bpFactory->QueryInterface(__uuidof(IUnknown), (void**)&pUnk);
CoRegisterClassObject(__uuidof(BrowserInterractionInterface), pUnk, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &magic);
My client code:
std::string data("some value")
std::wstring wstr = std::wstring(password.begin(), password.end());
BSTR bs = SysAllocStringLen(wstr.data(), wstr.size());
IBrowserInterraction* interraction = NULL;
CoCreateInstance(__uuidof(BrowserInterractionInterface), NULL, CLSCTX_ALL, __uuidof(IBrowserInterraction), (void**)&interraction);
interraction->Unlock(bs, 123);
I debugged this client call it sends the right value. But server receives this:
I created a proxy\stub library, registered it with regsvr, and included _i.c and _h.h files in client and server code, and everythong was fine.
And client after call of Unlock functions shows me this error:
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.
The latest version of Hippo Mocks (in its Git repository) looks to have added support for COM interfaces. I've tried mocking an ADO connection object; which took some tweaking of Hippo Mocks to build properly (seems the COM version of the code wasn't updated for changes in the rest of Hippo Mocks). I have it building now, but the following test fails:
MockRepository mocks;
auto pConn = mocks.Mock<ADONS::_Connection>();
mocks.OnCall(pConn, ADONS::_Connection::AddRef).Return(1);
ADONS::_ConnectionPtr conn = pConn;
The very first thing the smart pointer does is AddRef the interface. My mock shouldn't care about reference counting, so I add a call expectation that simply returns 1. However, as soon as AddRef gets called, a HippoMocks::NotImplementedException gets thrown.
Has anyone had success with mocking a COM interface with Hippo Mocks?
I had the same issue and solved it.
The actual version of hippomocks is now published on github:
https://github.com/dascandy/hippomocks
Great appreciation for the provided links, which helped to find an idea for the fix.
UPDATE, Details about my implementation and the added COM support.
First I made it work, which the following test demonstrates
class ICom
{
public:
virtual ~ICom() {}
virtual long __stdcall A(void) = 0;
virtual long __stdcall B(int) = 0;
virtual long __stdcall C(int, int) = 0;
...
};
TEST(checkStdCallBase)
{
MockRepository mocks;
ICom* ic = mocks.Mock<ICom>();
mocks.ExpectCall(ic, ICom::A)
.Return(1);
long actual = ic->A();
EQUALS(1, actual);
}
In order to make it work I had to patch several places in hippomocks.h, the most vital in virtual_function_index method. The correction ensures correct address calculation for the call on an interface.
Second, I added some common setup helpers for COM objects, providing standard behaviour for AddRef, Release and QueryInterface.
The tests show how to use it:
MIDL_INTERFACE("4745C05E-23E6-4c6d-B9F2-E483359A8B89")
COMInterface1 : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE getTObjectCount(
/* [out] */ unsigned long *pCount) = 0;
};
typedef GUID ESTypeID;
MIDL_INTERFACE("356D44D9-980A-4149-A586-C5CB8B191437")
COMInterface2 : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE getMappablePackages(
/* [out] */ long *pSize,
/* [size_is][size_is][out] */ ESTypeID **pIdList) = 0;
};
TEST(CheckThat_AddCommExpectations_Stubs_QueryInterface_AddRef_Release)
{
MockRepository mocks;
COMInterface1* deviceMock = mocks.Mock<COMInterface1>();
AddComExpectations(mocks, deviceMock);
{
CComPtr<IUnknown> pUnk = deviceMock;
CComQIPtr<COMInterface1> pDevice = pUnk;
CHECK(pDevice == pUnk);
IUnknown* p = NULL;
pDevice->QueryInterface(__uuidof(IUnknown), (void**)&p);
CHECK(p == deviceMock);
}
}
TEST(CheckThat_ConnectComInterfaces_Stubs_QueryInterface_ToEachOther)
{
MockRepository mocks;
COMInterface1* deviceMock = mocks.Mock<COMInterface1>();
COMInterface2* devMappingMock = mocks.Mock<COMInterface2>();
ConnectComInterfaces(mocks, deviceMock, devMappingMock);
{
//Com objects can reach each other
CComQIPtr<COMInterface2> pDevMapping = deviceMock;
CHECK(pDevMapping != NULL);
CHECK(pDevMapping == devMappingMock);
CComQIPtr<COMInterface1> pDevNavigate = devMappingMock;
CHECK(pDevNavigate != NULL);
CHECK(pDevNavigate == deviceMock);
}
}
The helper methods AddComExpectations and ConnectComInterfaces are provided in a separate header "comsupport.h". The header is an add-on for Hippomocks:
template <typename T>
void AddComExpectations(HM_NS MockRepository& mocks, T* m)
{
mocks.OnCall(m, T::AddRef)
.Return(1);
mocks.OnCall(m, T::Release)
.Return(1);
mocks.OnCall(m, T::QueryInterface)
.With(__uuidof(T), Out((void**)m))
.Return(S_OK);
mocks.OnCall(m, T::QueryInterface)
.With(__uuidof(IUnknown), Out((void**)m))
.Return(S_OK);
}
template <typename T1, typename T2>
void ConnectComInterfaces(HM_NS MockRepository& mocks, T1* m1, T2* m2)
{
//from T1 to T2
mocks.OnCall(m1, T1::QueryInterface)
.With(__uuidof(T2), Out((void**)m2))
.Return(S_OK);
//from T2 to T1
mocks.OnCall(m2, T2::QueryInterface)
.With(__uuidof(T1), Out((void**)m1))
.Return(S_OK);
AddComExpectations(mocks, m1);
AddComExpectations(mocks, m2);
//no support for interface hierarchies
//no Base IUnknown -> do it yourself if you really need that special case
}