COM interop: how to use ICustomMarshaler to call 3rd party component - c++

I want to call a method in a COM component from C# using COM interop. This is the methods signature:
long GetPrecursorInfoFromScanNum(long nScanNumber,
LPVARIANT pvarPrecursorInfos,
LPLONG pnArraySize)
and this is sample code (which I checked is really working) to call it in C++:
struct PrecursorInfo
{
double dIsolationMass;
double dMonoIsoMass;
long nChargeState;
long nScanNumber;
};
void CTestOCXDlg::OnOpenParentScansOcx()
{
VARIANT vPrecursorInfos;
VariantInit(&vPrecursorInfos);
long nPrecursorInfos = 0;
m_Rawfile.GetPrecursorInfoFromScanNum(m_nScanNumber,
&vPrecursorInfos,
&nPrecursorInfos);
// Access the safearray buffer
BYTE* pData;
SafeArrayAccessData(vPrecursorInfos.parray, (void**)&pData);
for (int i=0; i < nPrecursorInfos; ++i)
{
// Copy the scan information from the safearray buffer
PrecursorInfo info;
memcpy(&info,
pData + i * sizeof(MS_PrecursorInfo),
sizeof(PrecursorInfo));
}
SafeArrayUnaccessData(vPrecursorInfos.parray);
}
And here's the corresponding C# signature after importing the typelib of the COM component:
void GetPrecursorInfoFromScanNum(int nScanNumber, ref object pvarPrecursorInfos, ref int pnArraySize);
If I'm not mistaken, I need to pass in null for pvarPrecursorInfos to make COM interop marshal it as the expected VT_EMPTY variant. When I'm doing it, I get a SafeArrayTypeMismatchException - not really surprising, looking at how the result is expected to be handled in the sample. So I was trying to use a custom marshaler. Since a cannot alter the component itself, I tried to introduce it this way:
[Guid("06F53853-E43C-4F30-9E5F-D1B3668F0C3C")]
[TypeLibType(4160)]
[ComImport]
public interface IInterfaceNew : IInterfaceOrig
{
[DispId(130)]
int GetPrecursorInfoFromScanNum(int nScanNumber, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(MyMarshaler))] ref object pvarPrecursorInfos, ref int pnArraySize);
}
The TypeLibType and DispID attribute are the same as in the original version. This works as far as that the MyMarshaller.GetInstance() method is called, but I do not get a callback in MyMarshaller.NativeToManaged. Instead, an access violation is reported. So is this a reliable approach? If yes - how can I make it work? If no: are there any alternatives?
(Just a footnote: in theory I could try to use managed C++ to call the component natively. However, there are lots of other methods in it that work fine with COM interop, so I would very much like to stick with C# if there is any way.)

Since someone asked for it, here's my solution in Managed C++.
array<PrecursorInfo^>^ MSFileReaderExt::GetPrecursorInfo(int scanNumber)
{
VARIANT vPrecursorInfos;
VariantInit(&vPrecursorInfos);
long nPrecursorInfos = -1;
//call the COM component
long rc = pRawFile->GetPrecursorInfoFromScanNum(scanNumber, &vPrecursorInfos, &nPrecursorInfos);
//read the result
//vPrecursorInfos.parray points to a byte sequence
//that can be seen as array of MS_PrecursorInfo instances
//(MS_PrecursorInfo is a struct defined within the COM component)
MS_PrecursorInfo* pPrecursors;
SafeArrayAccessData(vPrecursorInfos.parray, (void**)&pPrecursors);
//now transform into a .NET object
array<PrecursorInfo^>^ infos = gcnew array<PrecursorInfo^>(nPrecursorInfos);
MS_PrecursorInfo currentPrecursor;
for (int i=0; i < nPrecursorInfos; ++i)
{
currentPrecursor = pPrecursors[i];
infos[i] = safe_cast<PrecursorInfo^>(Marshal::PtrToStructure(IntPtr(&currentPrecursor), PrecursorInfo::typeid));
}
SafeArrayUnaccessData(vPrecursorInfos.parray);
return infos;
}

I look at the github code mzLib, which I believe is related to this topic. The code looks good, where it calls
pin_ptr<const wchar_t> wch = PtrToStringChars(path);
I think it may cause some problem, better use
pin_ptr<const wchar_t> pathChar = static_cast<wchar_t*>(System::Runtime::InteropServices::Marshal::StringToHGlobalUni(path).ToPointer());
The code then seems to be worked just fine when compiles. However, it might run in problem when imported as dll. I worked on that by adding a constructor,such as
public ref class ThermoDLLClass
{
public:
ThermoDLLClass();
PrecursorInfo GetPrecursorInfo(int scanNum, String^ path);
};
Then, it seems to get precursorInfo and parameters appropriately.

Related

ComPtr<ID3D11Device> to IntPtr

I'm an absolute zero at C++. But I need to write a small c++ class for managing a d3ddevice.
My C# code is:
public class HCPPUtils
{
[DllImport("HSpectrum\\Assets\\HCPPUtils.dll")]
private static extern int Getd3Device(ICanvasResourceCreator resourceCreator);}
HCPPUtils hcp = new HCPPUtils();
var pnt = hcp.HGetOrCreate(ResourceCreator);
var d3dDevice = SharpDX.Direct3D11.Device.FromPointer<SharpDX.Direct3D11.Device>(new System.IntPtr(pnt));
My C++ code is:
extern "C"
{
__declspec(dllexport) int Getd3Device
(Microsoft::Graphics::Canvas::ICanvasResourceCreator^ canvasDevice)
{
ComPtr<ID3D11Device> m_device;
__abi_ThrowIfFailed(Windows::Graphics::DirectX::Direct3D11::GetDXGIInterface(canvasDevice->Device,m_device.GetAddressOf()));
return m_device???
}
}
How can i return a IntPtr from C++ code; so, how can i get IntPtr from ComPtr < ID3D11Device >?
[edited]
What I'm doing is...
I have a win2d canvasandimatedcontrol in my c# project. I want to draw direct3d object in it using sharpdx. But I found out that I need to have the d3ddevice object from win2d canvas. And there isn't a c# method to get it.
So the only solution I can imagine is to build a simple c++ project to which I can pass the canvas control and get the d3ddevice. The only problem is how to pass back the d3d device to c#. Sharp DX seems to have just a method Device.FormIntPtr to create it. But I'm not able to pass back the intptr to the c# object.
I tried to implement what Rook wrote, but I cannot understand how it could be useful for my scenario. I mean it could be usueful, but I need to pass the IDirect3DDevice object from a c++ project anyway.
I suspect what you need to do is to read the docs for things like this: http://microsoft.github.io/Win2D/html/M_Microsoft_Graphics_Canvas_CanvasDevice_CreateFromDirect3D11Device.htm
CanvasDevice implements ICanvasResourceCreator, so you could return it directly once you've created it using the static factory method.
Be careful with the scope and lifetime of m_device here, because you don't want its refcount to be decremented when Getd3Device returns and the ComPtr goes out of scope. I'm assuming that it is actually part of a class that will look after its lifetime, but it bears repeating just in case.
I've been trying to access the unity3d device today. This is how I passed the pointer back into unity/managed code:
cpp:
/*
delegate to pass directx device/context back to managed code
see https://forum.unity3d.com/threads/communicating-c-with-c.89930/#post-586885
*/
typedef int(__stdcall *ANSWERCB)(ID3D11Device* ptr);
static ANSWERCB cb;
extern "C" __declspec(dllexport) int GetDevice(ANSWERCB fp)
{
cb = fp;
if (cb)
{
return cb(s_CurrentAPI->GetDevice());
}
return 0;
}
cs:
[DllImport("RenderingPlugin")]
private static extern void GetDevice(Action<IntPtr> callback);
later I call:
GetDevice(devicePtr =>
{
Debug.Log(devicePtr);
if (devicePtr == IntPtr.Zero) return;
device = SharpDX.Direct3D11.Device.FromPointer<SharpDX.Direct3D11.Device>(devicePtr);
...
works fine in the editor as well as the built in the new 2017.1 beta version (as long as you copy the necessary 64bit system dlls to unitys plugin folder)

Segmentation fault when creating custom tcl channel driver

I am trying to create custom tcl channel and use it to get the output of tcl Interpreter. I added the implementation of few function of Tcl_ChannelType but I am getting segfault.
#include <tcl.h>
#include <iostream>
int driverBlockModeProc(ClientData instanceData, int mode) {
std::cout << "driverBlockModeProc\n";
return 0;
}
int driverCloseProc(ClientData instanceData, Tcl_Interp *interp) {
std::cout << "driverCloseProc\n";
return 0;
}
int driverInputProc(ClientData instanceData, char* buf, int bufSize, int *errorCodePtr) {
std::cout << "driverInputProc\n";
return 0;
}
int driverOutputProc(ClientData instanceData, const char* buf, int toWrite, int *errorCodePtr) {
std::cout << "driverOutputProc\n";
return 0;
}
int main() {
Tcl_ChannelType *typePtr = new Tcl_ChannelType;
typePtr->blockModeProc = driverBlockModeProc;
typePtr->outputProc = driverOutputProc;
typePtr->closeProc = driverCloseProc;
typePtr->inputProc = driverInputProc;
typePtr->seekProc = NULL;
typePtr->setOptionProc = NULL;
typePtr->getOptionProc = NULL;
typePtr->watchProc = NULL;
typePtr->getHandleProc = NULL;
typePtr->close2Proc = NULL;
typePtr->blockModeProc = NULL;
typePtr->flushProc = NULL;
typePtr->handlerProc = NULL;
typePtr->wideSeekProc = NULL;
typePtr->threadActionProc = NULL;
ClientData data = new char[200];
Tcl_CreateChannel(typePtr, "im_chanel", data, TCL_WRITABLE | TCL_READABLE);
}
I cant debug the segfault because its source are not available. I think the segfault is because a function is called which is NULL. I only need to use the channel to get the output of interpreter. Which functions I needn't implement here and is it right direction to solve the problem.
You're advised to download the source to Tcl when working at this level. I'm not sure what version you're using, but all official distributions of the source going back a very long way are on the SourceForge file distribution system; pick the exact match for the version you've got.
Creating a custom channel driver is not easy. There's a significant amount of complexity involved, and it isn't especially well-documented what “methods” within the channel driver type are mandatory and what are optional. (They're not C++ methods in a class — Tcl is pure C code for reasons too long to go into here — but they function in a conceptually similar way.)
If we look at the documentation for Tcl_CreateChannel, we see (quite a long way down that page) a definition of the channel type structure. The channel type structure should be statically allocated (Tcl's implementation assumes very strongly that it never changes location) and the following fields must be set to something meaningful:
typeName — This is the name of the channel type, useful for debugging!
version — This is the version of the channel type; you should set it to the latest version supported by your target source level. (You're recommended to use at least TCL_CHANNEL_VERSION_2 or things get rather more complex.)
closeProc or close2Proc — Channels must be closeable, but you have two choices for ways to do it. Bidirectional channels ought to use the close2Proc, but aren't strictly required to.
inputProc — Only needed if you're reading; take care to handle this correctly.
outputProc — Only needed if you're writing; take care to handle this correctly.
watchProc — Called to tell the channel driver to install itself into the event system so as to receive suitable events (as instructed by the supplied bitmask). Channels that don't have backing OS handles use timer events, or simply never actually generate events (in which case they'll never become readable or writable from the perspective of fileevent).
Looking at your code, I see that you're missing a watchProc. I know it's hard to see (not many people write channel drivers, to be honest, so the documentation isn't very hard “tested”) but it's really necessary.

When to use SafeArrayAccessData to lock a SAFEARRAY

I'm having a question about when it is necessary to use SafeArrayAccessData to lock a SAFEARRAY, which is passed by managed code.
Here is our code. The VARIANT is passed by managed code, with a string array. During code review, somebody suggest to use SafeArrayAccessData/SafeArrayUnAccessData. But he is not sure about why and what's the benefit. Can you share some of your experiences? Thanks!
STDMETHODIMP Base::Method1(VARIANT values, VARIANT_BOOL result)
{
CComSafeArray<BSTR> ids;
ids.Attach(values.parray);
unsigned int size = ids.GetCount();
for(unsigned int i = 0; i < size; ++i)
{
// use ids[i] here
}
// ...
}
Well, always :) You need it to get a reference to the array content.
But you use a friendly C++ wrapper class. The CComSafeArray<> template already does this for you so you should not help. It uses SafeArrayLock() in the Attach() method, that also returns a pointer to the array content like SafeArrayAccessData() does. And automatically unlocks with its destructor, it runs at the end of your method. Locking otherwise ensures that the array access is thread-safe and cannot be deleted while you are accessing it. There is little danger of that in your existing code, but this squarely fits the better-safe-than-sorry principles of Automation.

Allow managed code in hosted environment to call back unmanaged code

I have C++ code that hosts a clr in order to make use of Managed.dll, written in c#.
This .net has a method like the following that allows code to register for notification of events:
public void Register(IMyListener listener);
The interface looks something like this
public interface IMyListener
{
void Notify(string details);
}
I'd like to do stuff in the C++ part of the program, triggered by the events in the .net world. I would not even mind creating another managed dll for the sole purpose of making Managed.dll more C++-friendly, if that is necessary.
What are my options here? The only one I am sure I could implement is this:
Write another managed dll that listens for those events, queues them and lets the C++ code access the queue via polling
This would of course change from an 'interrupt' style to a 'polling' style with all its advantages and disadvantages and the need to provide for queuing. Can we do without polling? Could I somehow call managed code and provide it a function pointer into the C++ world as the argument?
Update
Thanks to stijn's answer and comments I hope I moved a bit in the right direction, but I guess the main problem still open is how to pass a fn pointer from unmanaged land into the clr hosted environment.
Say I have an "int fn(int)" type of function pointer that I want to pass to the managed world, here are the relevant parts:
Managed code (C++/CLI)
typedef int (__stdcall *native_fun)( int );
String^ MyListener::Register(native_fun & callback)
{
return "MyListener::Register(native_fun callback) called callback(9): " + callback(9);
}
Unmanaged code
typedef int (__stdcall *native_fun)( int );
extern "C" static int __stdcall NativeFun(int i)
{
wprintf(L"Callback arrived in native fun land: %d\n", i);
return i * 3;
}
void callCLR()
{
// Setup CLR hosting environment
...
// prepare call into CLR
variant_t vtEmpty;
variant_t vtRetValue;
variant_t vtFnPtrArg((native_fun) &NativeFun);
SAFEARRAY *psaMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 1);
LONG index = 0;
SafeArrayPutElement(psaMethodArgs, &index, &vtFnPtrArg);
...
hr = spType->InvokeMember_3(bstrMethodName, static_cast<BindingFlags>(
BindingFlags_InvokeMethod | BindingFlags_Static | BindingFlags_Public),
NULL, vtEmpty, psaMethodArgs, &vtRetValue);
if (FAILED(hr))
wprintf(L"Failed to invoke function: 0x%08lx\n", hr);
The spType->InvokeMember_3 call will lead to a 0x80131512 result.
Something seems to be wrong with the way I pass the pointer to NativeFun over to the managed world, or how my functions are defined. When using a String^ param instead of the fn ptr, I can call the CLR function successfully.
You can write a seperate dll in C++/CLI and implement the interface there, and forward the logic to C++. From my experience with mixing managed/unmanaged I can say using an intermediate C++/CLI step is the way to go. No fiddling with DllImport and functions only, but a solid bridge between both worlds. It just takes some getting used to the syntax and marshalling, but once you have that it's practically effortless. If you need to hold C++ objects in the managed class, best way is to use something like clr_scoped_ptr.
Code would look like this:
//header
#using <Managed.dll>
//forward declare some native class
class NativeCppClass;
public ref class MyListener : public IMylIstener
{
public:
MyListener();
//note cli classes automatically implement IDisposable,
//which will call this destructor when disposed,
//so used it as a normal C++ destructor and do cleanup here
~MyListener();
virtual void Notify( String^ details );
private:
clr_scoped_ptr< NativeCppClass > impl;
}
//source
#include "Header.h"
#include <NativeCppClass.h>
//here's how I marshall strings both ways
namespace
{
inline String^ marshal( const std::string& i )
{
return gcnew String( i.data() );
}
inline std::string marshal( String^ i )
{
if( i == nullptr )
return std::string();
char* str2 = (char*) (void*) Marshal::StringToHGlobalAnsi( i );
std::string sRet( str2 );
Marshal::FreeHGlobal( IntPtr( str2 ) );
return sRet;
}
}
MyListener::MyListener() :
impl( new NativeCppClass() )
{
}
MyListener::~MyListener()
{
}
void MyListener::Notify( String^ details )
{
//handle event here
impl->SomeCppFunctionTakingStdString( marshal( details ) );
}
update
Here's a simple solution to call callbacks in C++ from the managed world:
pubic ref class CallbackWrapper
{
public:
typedef int (*native_fun)( int );
CallbackWrapper( native_fun fun ) : fun( fun ) {}
void Call() { fun(); }
CallbackWrapper^ Create( ... ) { return gcnew CallbackWrapper( ... ); }
private:
native_fun fun;
}
you can also wrap this in an Action if you want.
Another way is using GetDelegateForFunctionPointer, for example as in this SO question
If someone still needs a better way for this , you can simply pass c++ function to CLR using intptr_t in variant and long in managed , then use Marshall and delegate to invoke your native function , super easy and works like charm.
if you need a code snippet , let me know.

Passing an array using COM?

I am a COM object written in ATL that is used from a C++ application, and I want to pass an array of BYTEs between the two. My experience of COM/IDL so far is limited to passing simple types (BSTRs, LONGs, etc.).
Is there a relatively easy way to have the COM object pass an array to the caller? For example, I want to pass a raw image (TIFF) instead of messing with temporary files.
Try passing a safearray variant to the COM Object. Something like this to put a BYTE array inside a safearray variant....
bool ArrayToVariant(CArray<BYTE, BYTE>& array, VARIANT& vtResult)
{
SAFEARRAY FAR* psarray;
SAFEARRAYBOUND sabounds[1];
sabounds[0].lLbound=0;
sabounds[0].cElements = (ULONG)array.GetSize();
long nLbound;
psarray = SafeArrayCreate(VT_UI1, 1, sabounds);
if(psarray == NULL)
return false;
for(nLbound = 0; nLbound < (long)sabounds[0].cElements ; nLbound++){
if(FAILED(SafeArrayPutElement(psarray, &nLbound, &array[nLbound]))){
SafeArrayDestroy(psarray);
return false;
}
}
VariantFree(vtResult);
vtResult.vt = VT_ARRAY|VT_UI1;
vtResult.parray = psarray;
return true;
}
SAFEARRAYs are the way to go if you want OLE-Automation compliance, and maybe use the COM interface from other languages such as VB6. But there is an alternative in IDL, for example: -
void Fx([in] long cItems, [in, size_is(cItems)] BYTE aItems[]);
This describes a method where the marshalling code can infer the number of bytes to be transfered by inspecting the value of the first parameter.
This is fine if your clients are all written in C/C++, but i think that an interface containing this would not be automation-compliant, so not usable from VB6, and possibly the standard marshaler will not be able to do the marshaling, so you'd need to generate your own proxy/stub DLL from the IDL. Not hard to do, but a bit harder than using SAFEARRAYs.
Check out using safearrays. Here's some example code:
The safearray is returned as a pointer to a VARIANT
[id(1), helpstring("LogCache")] HRESULT LogCache([out,retval] VARIANT* logCache);
Safearrays are pretty easy to use. Here's some example code which is a cache of the latest 1000 log messages of some application:
safearray_t<bstr_t> m_logCache;
...
if (m_logCache.size() > 1000)
{
m_logCache.pop_back();
}
m_logCache.push_front(Msg.str(), 0);
variant_t LogCache()
{
if (!m_logCache.is_empty())
{
variant_t cache(m_logCache);
return cache;
}
}
Note that the syntax in your case is almost certainly going to be different since I'm using the comet COM library, but the ideas/concepts are the same.
You can use BSTR to pass an array of bytes.
BYTE array[buffer_size];
...
BSTR toBePassed = SysAllocStringByteLen((OLECHAR*)array,length);
YourCOMMethod(toBePassed);
SysFreeString(toBePassed);
In your method:
BYTE* pData = (BYTE*)bstrPassed;
DWORD dataLength = SysStringByteLen(bstrPassed);