Passing an IDispatch parameter from C# - c#

I'm writing a C# COM dll that will be used by both Managed C# as well as Delphi and C++ programs and javascript. The COM dll includes a monitor part where the application registers a function that is supposed to return a string value to the dll. I have done this before for javascript 'applications' where you would simply pass a function as a parameter to the dll. When the dll needs to query the javascript 'application' the following is run:
Type t = theScript.GetType();
object ret = t.InvokeMember("", BindingFlags.InvokeMethod, null, theScript, new object[] { });
'theScript' is stored as an Object in the C# dll. As far as I understand this is accomplished by IDispatch. My theory now is that I should be able to use the same approach for the other languages. So I made a COM visible method with this signature:
void RegisterQuery(object method);
However I can't figure out how to pass a parameter to this method from C#. I've tried using a delegate but calling the delegate simply returns the ToString() method from the delegate (the one that returns the calss name). Nothing else that I have tried even compiles.
So I have two questions:
How shall I pass a method parameter to this dll from C# so that the dll will be able to call the methods, in the manner specified above, when it needs to?
Is this really IDispatch or is there another reason why it works for javascript?
Thanks in advance

Turns out that this was really simple:
Type t = theScript.GetType();
object ret = t.InvokeMember(theObject, BindingFlags.InvokeMethod, null, theScriptName, new object[] { });

Related

Is It Possible To Use Reflection To Pass A COM Object From C# to C++?

I have a C# assembly that does some work and sends the results of the work back to a C++ core. I am trying to use Reflection to pass it back since the C# assembly runs on a different thread than the one it was initialized by from the C++ core. I have tried using the COM interface as the parameter type.
IDL:
HRESULT SendEvent([in] IEventData *pEventData);
C#:
WECOInspectionCoreIDL.IEventData eventData = new EventData() as WECOInspectionCoreIDL.IEventData;
var parameters = new object[1];
parameters[0] = eventData;
_piInspectionCore.GetType().InvokeMember("SendEvent", BindingFlags.InvokeMethod, null, _piInspectionCore, parameters);
This gets the error "0x80020005 Type mismatch" in the atlcom.h hRes = m_pInfo->Invoke(...) call which apparently eventually gets converted to "Unable to cast COM object of type 'System.__ComObject' ... No such interface supported.
I've also tried making the parameter an IDispatch* and then the call goes through to C++, but it doesn't seem to be the real object.
IDL:
HRESULT SendEvent([in] IDispatch *pEventData);
C++:
STDMETHODIMP CInspectionCore::SendEvent(IDispatch *pEventData)
{
IEventData *pIEventData = (IEventData *)pEventData;
Even calling pIEventData->GetIDsOfNames() fails.
Is there a way to pass a COM object created in C# to C++ when the C# object is called from a different C++ thread?
With COM, you should never cast a COM interface into another COM interface like this:
STDMETHODIMP CInspectionCore::SendEvent(IDispatch *pEventData)
{
IEventData *pIEventData = (IEventData *)pEventData; // wrong!
}
Instead you must use QueryInterface, this works fine:
STDMETHODIMP CInspectionCore::SendEvent(IDispatch *pEventData)
{
IEventData* pIEventData;
HRESULT hr = pEventData->QueryInterface(&pIEventData);
if (FAILED(hr)) // etc.
}
In some cases (in fact often), raw casting may work which can give the false impression that it's ok.
In your case, it doesn't work because you use different threads which creates implicit proxies (COM apartments, etc.). You can see that if you breakpoint in SendEvent have a look at the call stack when it's called, it's all COM marshaling stuff.

Pass a callback function from Visual Basic to C#

I have a .dll written in C# and I use it in Visual Basic for making a Com object. I call C# functions from my Visual Basic code. I'm quite new to this technology and I encountered with such a problem. In C# code I have a method, that receives a callback function as a parameter, gets data from server then calls that callback function. Now I need to to call that function from my Visual Basic code and pass to it a callback.
I think it should look something like this
// C# dll
public bool GetDataFromServer(int someData1, Action<MyCustomType> callback, int someData2)
{
// request to server, get data, create an object of MyCustomType
// call callback and pass MyCustomType object to it as a parameter
}
// Visual Basic part
Public Sub DisplayData(ByRef resp As My_Dll.MyCustomType)
' do something with resp
End Sub
// call Dll method
My_Dll.GetDataFromServer(1, DisplayData, 2) ' I get a compile error
But I can't get it work, it event does not compile. I get a compilation error saying "Argument not optional". I've also tried to pass callback function with AddressOf, but I get an error again saying - "Invalid use of AddressOf operator".
Now my question is - what am I doing wrong? What is the correct way of passing a callback function from Visual Basic to C# and then get it invoked.
Thank you for your time!
Anything containing generics is not visible to COM, so your GetDataFromServer with Action<MyCustomType> is not COM visible. You must define a method on a non-generic class without generic type arguments of itself and without generic parameter or return types.
The usual way of passing a callback in COM is to pass an interface pointer with a method to be called. Avoid the AddressOf approach, however feasible, it's really bad practice once you want the callback to work out-of-process.
A common trick with IDispatch objects is to define a class with a default method with DispId 0 (DISPID_VALUE), which may be invoked by the COM server through IDispatch::Invoke. In some languages, such as JScript, objects may be callable through this technique.
To see how you can do this in VB6/VBA, search for VB_UserMemId. Essentially, you must edit the class file in its raw format (in VBA, you must export it) and add an attribute, such as:
Sub Call()
Attribute Call.VB_UserMemId = 0
End Sub
In VBA, you'd delete the class and import from the edited file. The effect is that the Call method now has DispId 0. You may call it whatever you want, but remember to edit both the Sub name and the Attribute.
In C#, I believe the only ways to achieve such call is with a dynamic variable with:
obj[0]
or through Type.InvokeMember or similar IDispatch::Invoke approaches:
obj.GetType().InvokeMember("", BindingFlags.InvokeMethod, null, obj, null);
The latter is more reliable and you pass the arguments you want, while the former uses a misleading syntax and forces you to pass an argument.
In general, if you control the whole situation and you don't need callable objects, just go for a plain method.
Regarding the AddressOf error, this operator works on module procedures and functions, so the error you see is probably because DisplayOptionQuotes is a method.

How do I handle var (or ref) parameters in COM Interop?

I have a stand-alone application which exposes COM Interfaces/CoClasses/Enums, etc. One of those interfaces exports the following function:
procedure FindTask(const TaskId: WideString; var Task: OleVariant); safecall;
Note: Task is also exposed as a CoClass.
Note: Task is [in, out]
So far, my legacy application, which is also written in Delphi, had been able to communicate with the stand-alone application. Consider that stand-alone app as an out-of-proc COM server and consider this legacy app as a client app.
As I am rewriting the old Delphi app (client) in C#, I have to use COM Interop in C# to communicate with that application. Therefore, I made use of tlbimp.exe to translate those COM interfaces to C# interfaces.
That method was translated as following:
void FindTask(string, TaskId, ref object Task);
How can I call FindTask and receive the result of an already existent task with this signature?
I've tried this: (Task is the CoClass type)
ScTask target = new CsTask();
scheduler.FindTask("A GUID value", ref target);
But the compiler yelled about the type, since there is no overload with that class type (the translated method receives an object)
Could I do this, instead?
object target = new object();
scheduler.FindTask("A GUID value", ref target);
ScTask translated = (ScTask) target;
How can I call a method that expects an var (in, out) Variant *, therefore translated as object, but that actually have a more precise type?
It rather looks to me as though the parameter is really an out parameter. You don't actually appear to be attempting to pass anything in. The method appears to be returning a different object. So in the C# it ought to be:
void FindTask(string TaskId, out object Task);
You'd then call it like this:
You'd need to change the type library to specify out semantics.
If it really is in/out then you'd keep the use of ref and call the method like this:
object target = null;
scheduler.FindTask("A GUID value", ref target);
ScTask translated = (ScTask) target;
Or possibly pass in an actual object with meaningful state if that's how it is designed to work.

How to pass C# class object to managed c++ without unsafe modifier?

I am passing a C# object (PSObject) to managed C++ in the following way. But it crashes in the commented statement. Am I missing something?
Am I passing PSObject correctly to managed C++?
or am I accessing it wrongly?
I am using clr:oldsyntax.
Actually C# will pass a PSObject to managed C++,
in managed C++ I want to examine the properties present in PSObject. (An object returned by running PowerShell commands)
In C#, using delegate concept I am calling managed C++ method to pass PSObject.
The delegate is declared as below:
delegate bool CFuncDelegate(PSObject Arg);
funcObj (a IntPtr) is a pointer to addData function in C++ (I didn't write that code here, since it is not relevant) and I am calling addData in csharp as:
CFuncDelegate func = (CFuncDelegate)Marshal.GetDelegateForFunctionPointer(funcObj, typeof(CFuncDelegate));
bool isCompleted = func(psoObject); //psoObject is a PSObject
and in managed C++,
static bool __clrcall addData(System::Management::Automation::PSObject* curPsObj)
{
log(Marshal::StringToHGlobalUni(curPsObj->AdaptedMemberSetName));
//prints psadapted
System::Object* value = curPsObj->Properties->get_Item("DisplayName");
//crashes
}
It would be better if someone just post two lines of code to pass object from C# and accessing it in managed C++. Thanks in advance.
I think it's really time that you abandoned oldsyntax and moved to C++/CLI. Anyway, even doing that is not going to solve your problem. You define a delegate like this:
delegate bool CFuncDelegate(PSObject Arg);
And then you assign a delegate variable like this:
CFuncDelegate func = (CFuncDelegate)Marshal.GetDelegateForFunctionPointer(
funcObj, typeof(CFuncDelegate));
The documentation for Marshal.GetDelegateForFunctionPointer says:
Converts an unmanaged function pointer to a delegate.
So, your code can only work if funcObj is an unmanaged function. And your addData method is certainly not that. You need to stop using GetDelegateForFunctionPointer here. It is simply not compatible with calling the managed function addData.
I've no idea where funcObj comes from. You said:
funcObj (a IntPtr) is a pointer to addData function in cpp (i didnt write that code here, since it is not relevant)
In fact it is not only relevant to the problem, it is the root cause of the problem. What I would expect to see here would be for you to add the C++ assembly to your C# project as a reference, at which point addData could be referenced directly.
Of course, none of this even mentions the fact that your calling conventions are mis-matched. The managed code uses clrcall and your unmanaged function pointer is taken to be stdcall.
Update
There's some more information in the comments. Pass addData as a delegate. You'll need to declare the delegate type in the C# code which I believe you reference from your C++ assembly.

Calling C++/CLI Method with System::DateTime parameter requires 'ValueType' as a parameter?

I'm attempting to call a method written in C++/CLI from C#. The C++/CLI code is used to update a TIMESTAMP column in an Oracle database, given a record ID and the System::DateTime object which is the .NET compatible data type for Oracle's TIMESTAMP type.
The method I am calling has the following prototype:
bool ChangeJobUpdateDate (int jobIdIn, System::DateTime^ updateDateIn)
I've added a reference to this DLL project in a test project that I made; I'm writing the tests in C#. However, when I try to call this method from the C# unit test project, the function appears to have the following method declaration (via intellisense):
bool ChangeJobUpdateDate (int jobIdIn, ValueType updateDateIn)
I'm admittedly not that familiar with C++/CLI, so is there something I'm missing?
I suspect it's because you're using DateTime^ instead of just DateTime. It's a value type, so why are you trying to use a reference?
C# doesn't have any way of representing the boxed reference type associated with a value type, so the best it can do is ValueType - I suspect that's what's happening, although I can't say for sure due to my lack of experience with C++. Try just DateTime and see how that looks...

Categories

Resources