Cannot cast COM interface converted to VB.NET to a C++ equivalent - c++

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)

Related

C++ (ATL) ITypeInfo.GetContainingTypeLib fails when passed live instance of VBA Class

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.

how to create two ATL interface derived from another?

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();

Need help converting Graphics32 Delphi sample to C++

USING BDS2006:
I'm trying to convert the Graphics32 Resampler_ex example in C++, but i can't even understand what happens in some codes, or how to rewrite that code in C++.
In that sample there's a combobox to choose what resampler to use:
This is the Deplhi code in his OnChange event:
procedure TfmResamplersExample.KernelClassNamesListClick(Sender: TObject);
var
Index: Integer;
begin
Index := KernelClassNamesList.ItemIndex;
if Src.Resampler is TKernelResampler then
with TKernelResampler(Src.Resampler) do
begin
Kernel := TCustomKernelClass(KernelList[Index]).Create;
LbParameter.Visible := (Kernel is TAlbrechtKernel) or
{$IFDEF Ex}
(Kernel is TGaussianKernel) or
(Kernel is TKaiserBesselKernel) or
(Kernel is TNutallKernel) or
(Kernel is TBurgessKernel) or
(Kernel is TBlackmanHarrisKernel) or
(Kernel is TLawreyKernel) or
{$ENDIF}
(Kernel is TSinshKernel);
gbParameter.Visible := LbParameter.Visible;
SetKernelParameter(Kernel);
CurveImage.Repaint;
end;
end;
where:
{ TClassList }
{ This is a class that maintains a list of classes. }
TClassList = class(TList)
protected
function GetItems(Index: Integer): TClass;
procedure SetItems(Index: Integer; AClass: TClass);
public
function Add(AClass: TClass): Integer;
function Extract(Item: TClass): TClass;
function Remove(AClass: TClass): Integer;
function IndexOf(AClass: TClass): Integer;
function First: TClass;
function Last: TClass;
function Find(AClassName: string): TClass;
procedure GetClassNames(Strings: TStrings);
procedure Insert(Index: Integer; AClass: TClass);
property Items[Index: Integer]: TClass read GetItems write SetItems; default;
end;
ResamplerList: TClassList;
My problems are on this line
Kernel := TCustomKernelClass(KernelList[Index]).Create;
How can i convert this line in C++?
EDIT AFTER THE COMMENTS AND THE ANWERS:
Ok, seems beyond my undertanding. For my purposes, it will suffice to be able to replicate what this code do without too much hassle.
Could it be possible to instantiate the right class just using a switch based on the itemindex?
These are the 4 classes i should instantiate:
class DELPHICLASS TNearestResampler;
class PASCALIMPLEMENTATION TNearestResampler : public Gr32::TCustomResampler
{
typedef Gr32::TCustomResampler inherited;
[...]
}
class DELPHICLASS TLinearResampler;
class PASCALIMPLEMENTATION TLinearResampler : public Gr32::TCustomResampler
{
typedef Gr32::TCustomResampler inherited;
[...]
};
class DELPHICLASS TDraftResampler;
class PASCALIMPLEMENTATION TDraftResampler : public TLinearResampler
{
typedef TLinearResampler inherited;
[...]
};
class DELPHICLASS TKernelResampler;
class PASCALIMPLEMENTATION TKernelResampler : public Gr32::TCustomResampler
{
typedef Gr32::TCustomResampler inherited;
[...]
};
I don't ever get how could i assign one of them to "Kernel"....
The Delphi code relies on Delphi virtual constructors. This functionality does not exist in C++.
If you wanted to translate the code literally then you'd need to find a way to instantiate the class by calling the virtual Delphi constructor. You cannot do that in C++ so you'd need some Delphi code to glue it all together. Remy's answer here shows how to use __classid() to obtain a Delphi metaclass. You'd then need to pass that metaclass to a Delphi function to perform the instantiation.
Frankly I would view that as being a rather convoluted solution to the problem. Instead I think I'd replace the class list with a function list. The functions in the list would have the task of instantiating a new instance of a kernel. So instead of adding a class to the list, you add a function that creates a new instance of the class. In fact you might want a map between name and function, it all depends on your needs.
From what I remember from Delphi programming, this will actually instantiate the same type of class, which currently is kept in KernelList[index] and then cast it back to TCustomKernelClass. AFAIK there is no such mechanism in C++, but you can solve it by introducing virtual CreateInstance method:
class TCustomKernelClass
{
public:
virtual TCustomKernelClass * CreateInstance() = 0;
}
class TDerivedKernelClass
{
public:
TCustomKernelClass * CreateInstance()
{
return new TDerivedKernelClass();
}
}
You'll have to introduce some changes in the classes though. I doubt it can be solved directly in C++.
Edit: In response to comments
Ok, I see now, there are class definitions kept in that list. Since RTTI in C++ is not as extensive as in Delphi, I'd change the code to (written from memory, may not compile):
std::vector<std::function<TBaseKernelClass(void)>> KernelList;
KernelList.push_back([]() { return new TDerivedKernelClass(); });
// (...)
Kernel = KernelList[index]();

Wrong marshaling

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.

Program crashes when calling function from DLL when trying to copy an array

Today I tried to make my first DLL and my first application which would use a DLL.
The DLL is made in C++ and this is the code I'm calling:
void Graph::findPath_r(Node* pStart, Node* pEnd, std::vector<cell> &road, cell &costMx)
{
//.....
if(pEnd->getParent() != NULL)
{
while(!path.empty())
{
road.push_back(path.top()->getName());
costMx += path.top()->getGCost();
path.pop();
}
return;
}
return;
}
vector <int>tbcway;
int FUNCTION CalculatePath(int Start, int End, int * Array, int &cost)
{
dgraph->findPath_r(xNode[Start].NodeID ,xNode[End].NodeID,tbcway,cost);
dgraph->reset();
std::copy(tbcway.begin(), tbcway.end(), Array);
tbcway.clear();
return 1;
}
and this is how I declared it in VB.net and called it:
Imports System.Runtime.InteropServices
Public Class Form1
<DllImport("RCP.dll")> _
Public Shared Function LOAD_SYSTEM() As Boolean
End Function
<DllImport("RCP.dll")> _
Public Shared Function GetPluginVersion() As Integer
End Function
<DllImport("RCP.dll")> _
Public Shared Function CalculatePath(ByVal StartNode As Integer, ByVal EndNode As Integer, ByRef Array() As Array, ByRef cost As Integer) As Integer
End Function
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
LOAD_SYSTEM()
MsgBox(GetPluginVersion().ToString())
Dim path(4096) As Array
Dim movecost As Integer
CalculatePath(1702, 27932, path, movecost)
End Sub
End Class
So, what is wrong with this code?
The error I am getting is:
A call to PInvoke function 'RCP GUI!RCP_GUI.Form1::CalculatePath' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature.
This is probably a calling convention mismatch.
Try decorating your DllImport with different calling conventions to see which works (my guess is that it should be cdecl).