Using native vtable-based DLL interface from C# - c#

Some time ago I wrote a Windows DLL in C++ to encapsulate some functionality that I wanted to share between different C++ and Delphi compilers. I always liked the way that COM allows me to work with objects in contrast to having a flat C API in a DLL. Since for this DLL I did not need the full power of COM and no reference counting, I decided to implement a DLL interface that somewhat mimics COM. I have a single exported C function that allows me to create an object and returns an vtable interface pointer. Via this pointer I can call methods on the object in a compiler neutral way in C++ and Delphi. When I'm done using the interface pointer, I call release on it.
Now I would like to use this DLL from C# but I don't know how to import the interface. Moreover I have a callback interface that the C# client must implement. Following is the C++ interface header file (somewhat shortended).
struct CksReadParams
{
...
};
struct ICksAdapter
{
virtual ~ICksAdapter();
virtual int32_t __cdecl Release() = 0;
virtual int32_t __cdecl ReadValues(CksReadParams* params) = 0;
};
struct ICksAdapterCallback
{
virtual ~ICksAdapterCallback();
virtual void __cdecl OnConnected(int32_t clientID) = 0;
virtual void __cdecl OnData(int32_t clientID, const char* csvLine) = 0;
virtual void __cdecl OnLog(int32_t clientID, const char* message) = 0;
virtual void __cdecl OnError(int32_t clientID, int32_t code, const char* message) = 0;
};
extern "C"
{
typedef int(__cdecl *CA_PCreateAdapter)(ICksAdapterCallback* callback, ICksAdapter** adapter);
__declspec(dllexport) int32_t __cdecl CA_CreateAdapter(ICksAdapterCallback* callback, ICksAdapter** adapter);
}
Can you give me a sketch on how to use this from C#, especially how to implement the callback interface, how to handle the __cdecl calling convention and how to handle char* parameters in the callback which are actually UTF8 encoded C++ strings?

I wrote the following code that seems to do what I need. It was mainly inspired by this blog post Implementing an unmanaged C++ interface callback. The code builds mainly on Marshal.GetDelegateForFunctionPointer and Marshal.GetFunctionPointerForDelegate which are available starting with .NET 4.5 which is acceptable in my case. To sum up, you need to know the vtable of the C++ object and you need to fake a vtable in unmanaged memory for the callback interface.
If it's worth the effort I will leave to the reader. In my case it at least allows me to reuse my DLL. Next time I will probably stick with a flat C API or use COM in the first place.
class CksAdapterCallback
{
private IntPtr instance;
public CksAdapterCallback()
{
instance = Marshal.AllocHGlobal(IntPtr.Size * 6);
IntPtr vtblPtr = IntPtr.Add(instance, IntPtr.Size);
Marshal.WriteIntPtr(instance, vtblPtr);
Marshal.WriteIntPtr(vtblPtr, IntPtr.Zero); //dummy entry for the destructor
OnConnectedInternal = new OnConnectedDelegate(OnConnected);
Marshal.WriteIntPtr(IntPtr.Add(vtblPtr, IntPtr.Size), Marshal.GetFunctionPointerForDelegate(OnConnectedInternal));
OnDataInternal = new OnDataDelegate(OnData);
Marshal.WriteIntPtr(IntPtr.Add(vtblPtr, 2*IntPtr.Size), Marshal.GetFunctionPointerForDelegate(OnDataInternal));
...
}
~CksAdapterCallback()
{
Marshal.FreeHGlobal(instance);
}
public IntPtr Instance
{
get { return instance; }
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void OnConnectedDelegate(IntPtr instance, Int32 clientID);
OnConnectedDelegate OnConnectedInternal;
void OnConnected(IntPtr instance, Int32 clientID)
{
...
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate void OnDataDelegate(IntPtr instance, Int32 clientID, IntPtr data);
OnDataDelegate OnDataInternal;
void OnData(IntPtr instance, Int32 clientID, IntPtr data)
{
...
}
...
}
class CksAdapterProxy
{
private IntPtr instance;
public CksAdapterProxy(IntPtr instance)
{
this.instance = instance;
IntPtr vtblPtr = Marshal.ReadIntPtr(instance, 0);
IntPtr funcPtr = Marshal.ReadIntPtr(vtblPtr, 1 * IntPtr.Size);
ReleaseInternal = Marshal.GetDelegateForFunctionPointer<ReleaseDelegate>(funcPtr);
...
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate Int32 ReleaseDelegate(IntPtr instance);
ReleaseDelegate ReleaseInternal;
...
public Int32 Release()
{
return ReleaseInternal(instance);
}
...
}
public class CksAdapterTest
{
[DllImport("CKS\\CksAdapter.dll")]
[return: MarshalAs(UnmanagedType.I4)]
static extern int CA_CreateAdapter(IntPtr adapterCallback, out IntPtr adapter);
CksAdapterProxy adapter = null;
CksAdapterCallback adapterCallback = new CksAdapterCallback();
public CksAdapterTest()
{
IntPtr nativeAdapter = IntPtr.Zero;
int result = CA_CreateAdapter(adapterCallback.Instance, out nativeAdapter);
if(result == 0)
{
adapter = new CksAdapterProxy(nativeAdapter);
...
adapter.Release();
}
}
}

Related

using nested structure pointers in C/C++ DLL in C#

let say we have following code in a dll written in C-lang, where i try to map some functions defined in the dll as functionpointers, map to their actual functions, i follow following link to get till here
https://learn.microsoft.com/en-us/dotnet/framework/interop/marshaling-different-types-of-arrays
Dlltest.h
typedef struct VersionInfo
{
UINT uMajor;
UINT uMinor;
UINT uMRevision;
} STRUCT_VERSION_INFO;
typedef struct DllTestFPStruct
{
int(*Close) (void);
int(*Init) (STRUCT_VERSION_INFO *sVersInfo);
int(*RegisterClient) (int id);
} STRUCT_DLL_TEST_FP_STRUCT;
typedef struct DllTestMgr
{
STRUCT_DLL_TEST_FP_STRUCT *psItf;
int iDummy;
} STRUCT_DLL_TEST_MGR;
extern "C"
{ __declspec(dllexport) void GetDllTestFP(STRUCT_DLL_TEST_MGR *psFP); }
Dlltest.c
static int Close(void);
static int Init(STRUCT_VERSION_INFO *sVersInfo);
static int RegisterClient(int id);
STRUCT_DLL_TEST_FP_STRUCT sFP =
{
&Close,
&Init,
&RegisterClient,
};
DLLTESTC_API void GetDllTestFP(STRUCT_DLL_TEST_MGR *psFP)
{ psFP->psItf = &sFP; }
static int Close(void)
{ printf("Closed called.\n"); }
static int Init(STRUCT_VERSION_INFO *sVersInfo)
{ printf("Init called.\n");}
static int RegisterClient(STRUCT_VERSION_INFO *sVersInfo)
{ printf("RegisterClient called.\n");}
Now i want to write a c# application which uses this DLL, specially it should make use of the "GetDllTestFP"-Function which maps the functionpointers to their actuall function. right now my C#-Application is as follow:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int FP_DLL_TEST_CLOSE(ref VersionInfo sVersInfo);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int FP_DLL_TEST_INIT(ref VersionInfo sVersInfo);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int FP_DLL_TEST_RC(ref VersionInfo sVersInfo);
[StructLayout(LayoutKind.Sequential)]
public struct DllTestFPStruct
{
public FP_DLL_TEST_CLOSE Close;
public FP_DLL_TEST_INIT Init;
public FP_DLL_TEST_RC RegisterClient;
}
[StructLayout(LayoutKind.Sequential)]
public struct DllTestMgr
{
public IntPtr psItf;
public int iDummy;
}
[DllImport("DllTestC.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
public static extern void GetDllTestFP(ref DllTestMgr ps);
static void Main(string[] args)
{
VersionInfo vInfo = new VersionInfo();
DllTestFPStruct dllFPs = new DllTestFPStruct();
DllTestMgr dllTest = new DllTestMgr();
IntPtr buffer = Marshal.AllocCoTaskMem(Marshal.SizeOf(dllFPs));
Marshal.StructureToPtr(dllFPs, buffer, false);
dllTest.psItf = buffer;
GetDllTestFP(ref dllTest); // Funtionpointers are not mapped, still null
dllFPs.Close(ref vInfo);
}
The problem is, that the functions do not get mapped to their actuall functions in the dll.
Any idea how can i achieve my goal?
Thanks
The C# code is:
DllTestMgr dllTest = new DllTestMgr();
GetDllTestFP(ref dllTest);
DllTestFPStruct dllFPs = (DllTestFPStruct)Marshal.PtrToStructure(dllTest.psItf, typeof(DllTestFPStruct));
VersionInfo vInfo = new VersionInfo();
dllFPs.Close(ref vInfo);
You don't need to allocate dllTest.psItf becase in GetDllTestFP you do:
DLLTESTC_API void GetDllTestFP(STRUCT_DLL_TEST_MGR *psFP)
{
psFP->psItf = &sFP;
}
So you copy the address of sFP.
Note that in general this is a bad idea, because you are giving to the "client" direct access to your data (the sFP struct). The alternative is that the client passes the memory (as you wrote before) and then you do:
(*psFP->psItf) = sFP;
(but then remember to free the allocated memory!)
Third alternative, the C-side allocates a block of memory through a shared allocator (one that can be used by C#, so no malloc/new here) and then the C# has to deallocate it.
wrong solution is
STRUCT_DLL_TEST_FP_STRUCT sFP2 = sFP;
psFP->psItf = &sFP2;
The lifetime of sFP2 ends when the method returns. psFP->psItf is now pointing to a piece of stack that doesn't exist anymore. don't do it!
Ah... as written by #Hans Passant, depending on who allocates the memory, the GetDllTestFP can be ref or out. If the memory is allocated by C# then it must be ref, if it isn't allocated (as is now) or is allocated by C++, then out is ok and you'll save on the marshaling in one direction.

In C# how to access DLL methods written in C++ [duplicate]

I have an unmanaged DLL that exports only a C style factory method that returns a new instance of a class (simplified here to look simple).
hello.h
#if defined(HWLIBRARY_EXPORT) // inside DLL
# define HWAPI __declspec(dllexport)
#else // outside DLL
# define HWAPI __declspec(dllimport)
#endif
struct HelloWorld{
public:
virtual void sayHello() = 0;
virtual void release() = 0;
};
extern "C" HWAPI HelloWorld* GetHW();
hello.cpp
#include "hello.h"
struct HelloWorldImpl : HelloWorld
{
void sayHello(){
int triv;
std::cout<<"Hello World!";
std::cin>>triv;
};
void release(){
this->HelloWorldImpl::~HelloWorldImpl();
};
HelloWorld* GetHW(){
HelloWorld* ptr = new HelloWorldImpl();
return ptr;
};
Now, I can use dllimport to access GetHW() but is there a way to access the member functions of the returned 'struct'... ie, sayHello and release?
I was also stuck with the same problem. This question was asked a while before. I commented to it for any better solution but didn't get any reply yet. So, reposting it.
When i googled, able to find out two solutions.
Solution1: Expose all the member functions in the C-style for the existing dll. Which i cant do, as it is a 3rd party dll.
Solution2: Write a managed C++ dll exposing the functionality of native C++ dll, which later can be used in your C# dll. Here many classes/functions are present. So, creating would take most of the time.
i got the above solutions from the link below.
How To Marshall
Please let me know if there is any better solution other than the above two solutions?
i have the source code for C++ solution. But what i though was not to touch C++ dll. If there is any possibility to do it in C#, it would be great.
If there is no alternative, i need to follow any one of the specified two solutions.
The C++ code is using the way abstract classes are implemented by the Visual C++ compiler. http://blogs.msdn.com/b/oldnewthing/archive/2004/02/05/68017.aspx. This memory layout is "fixed" because it is used for implementing COM interfaces. The first member of the struct in memory will be a pointer to a vtable containing the function pointers of your methods. So for a
struct HelloWorldImpl : public HelloWorld
{
public:
int value1;
int value2;
}
the "real" layout in memory would be:
struct HelloWorldImpl
{
HelloWorldVtbl *vtbl;
int value1;
int value2;
}
where vtbl would be:
struct HelloWorldVtbl
{
void *sayHello;
void *release;
}
Just for the sake of doing a complete response, I'm writing the example for this signatures:
struct HelloWorld {
public:
virtual int sayHello(int v1, int v2, int v3) = 0;
virtual void release() = 0;
};
C# code:
[DllImport("NativeLibrary.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr GetHW();
[StructLayout(LayoutKind.Sequential)]
struct HelloWorldVtbl
{
public IntPtr sayHello;
public IntPtr release;
}
Your functions are void Func(void) or int Func(int, int, int), but in truth they have a hidden parameter, this, so you can write them as:
int sayHello(HelloWorld*, int, int, int);
void release(HelloWorld*);
so in C# the delegate is
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate int Int32MethodInt32Int32Int32(IntPtr ptr, int v1, int v2, int v3);
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
public delegate void VoidMethodVoid(IntPtr ptr);
Then you can use
IntPtr ptr = GetHW();
IntPtr vtbl = Marshal.ReadIntPtr(ptr, 0);
HelloWorldVtblhw = (HelloWorldVtbl)Marshal.PtrToStructure(vtbl, typeof(HelloWorldVtbl));
Int32MethodInt32Int32Int32 sayHello = (Int32MethodInt32Int32Int32)Marshal.GetDelegateForFunctionPointer(hw.sayHello, typeof(Int32MethodInt32Int32Int32));
int res = sayHello(ptr, 1, 2, 3);
Console.WriteLine(res);
VoidMethodVoid release = (VoidMethodVoid)Marshal.GetDelegateForFunctionPointer(hw.release, typeof(VoidMethodVoid));
release(ptr);

Function pointer in DLL call - how to handle in C#?

I'm using a native DLL that is called out of my C# application. One of the DLL-functions is defined like this:
int set_line(const int width,fct_line_callback callback)
fct_line_callback itself is defined as
typedef int (*fct_line_callback)(double power,void *userData);
So how can I use this function out of my C#-application? Is there a way to define a C# method to be used as callback-function for this DLL-call?
Thanks!
You have to declare a delegate type that matches the native function pointer. It probably should look like this:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
delegate int fct_line_callback(double power, IntPtr userdata);
Now you can write the pinvoke declaration:
[DllImport("foo.dll", CallingConvention = CallingConvention.Cdecl)]
extern static int set_line(int width, fct_line_callback callback);
If the callback can only be made while set_line() is executing then calling the native function is simple:
public void SetLine(int width) {
set_line(width, MyCallback);
}
private void MyCallback(double power, IntPtr userdata) {
// etc...
}
However, if the callback can be made after set_line() is executed, in other words when the native code stores the function pointer, then you have to make sure that the garbage collector cannot collect the delegate object. Simplest way to do so is by storing the object in a static variable:
static class Wrapper {
private static fct_line_callback callback = MyCallback;
public static void SetLine(int width) {
set_line(width, callback);
}
private static int MyCallback(double power, IntPtr userdata) {
// etc...
}
}

How do I turn a C++ class into managed class and call the pure virtual function inside?

It looks likes this
#include <stdio.h>
#define __dllexport extern "C" __declspec(dllexport)
class PureVirtual
{
public:
virtual void Foo() = 0;
};
class ImplPureVirtual : public PureVirtual
{
public:
void Foo()
{
printf("Moo");
}
};
__dllexport PureVirtual* GetPV();
In implementation:
#include "ManagedClass.h"
PureVirtual* impl = new ImplPureVirtual;
__dllexport PureVirtual* GetPV()
{
return impl;
}
It compiled as TestFoo.dll, Then in C#
class Program
{
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string strFileName);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string strProcName);
static void Main(string[] args)
{
var ptrLib = LoadLibrary("TestFoo.dll");
if (ptrLib != IntPtr.Zero)
{
var ptrAddr = GetProcAddress(ptrLib, "GetPV");
if (ptrAddr != IntPtr.Zero)
// Now we have the pointer object, what should I do next?
}
Console.ReadLine();
}
}
I think it's a pretty good problem, that if a software company released their unmanaged API to the public(with only pure virtual method), and if we could control the wisdom of C++ abstractive and polymorphism, then we could make it into managed and have several benefit! Such as Garbage control, generics, and a wide choices of references and libraries.
You would also need to define an extern "C" function for each virtual member; managed code cannot call unmanaged C++ exports in an easy or portable way due to C++ name mangling. For example:
__dllexport void PureVirtual_Foo(PureVirtual * pv)
{
pv->Foo();
}
Also, consider that you need to catch C++ exceptions in the extern "C" wrappers, because an uncaught exception can skip over managed stack frames, causing the CLR to get into a bad state.
I would strongly suggest that you look into using SWIG. It is able to automatically generate these wrappers for you.
Note that you do not need to use LoadLibrary() etc., you can just P/Invoke the function:
[DllImport("TestFoo")]
public static extern IntPtr GetPV();
Likewise, to call the virtual method given my example wrapper:
[DllImport("TestFoo")]
public static extern void PureVirtual_Foo(IntPtr pv);
Then you could call the virtual method like this:
PureVirtual_Foo(GetPV());

What's wrong with this interop C#/managed/unmanaged C++ parameter passing?

I'm trying to make a non-static vendor C++ DLL accessible via C#. In order to do this, I'm writing a managed C++ wrapper DLL which basically creates static variables for the vendor DLL and makes those accessible to the C# application.
Here's an example:
typedef void(__stdcall *LPLISTENER_FUNC)(VENDORHANDLE hModule, VENDORWPARAM wParam, VENDORLPARAM lParam);
public delegate void VENDOR_Delegate(VENDORHANDLE hModule,
VENDORWPARAM wParam, VENDORLPARAM lParam);
public class VENDORWrapper
{
private:
static VENDORHSTORAGE _hStorage;
static VENDOR_Delegate^ _hOpenCallback;
void static Initialize()
{
_hStorage=storage_initialize();
}
void static registerCallback(unsigned int type, VENDOR_Delegate^ callback)
{
if (type == 2)
{
_hOpenCallback = callback;
::storage_register_callback(_hStorage, type, (LPLISTENER_FUNC)&_hOpenCallback);
}
}
bool static Open(String^ file)
{
bool retval=false;
filePath = file;
IntPtr ip = Marshal::StringToHGlobalAuto(filePath);
LPCWSTR str = static_cast<LPCWSTR>(ip.ToPointer());
//ERROR OCCURS HERE
retval = storage_open(_hStorage, str);
Marshal::FreeHGlobal( ip );
return retval;
}
void static Close()
{
storage_close(_hStorage);
}
}
The C# is skeletal:
public static VENDORStorageWrapper.VENDOR_Delegate openCallback
= new VENDORStorageWrapper.VENDOR_Delegate(fileOpened);
static void Main(string[] args)
{
VENDORStorageWrapper.VENDORStorageWrapper.Initialize();
Debug.WriteLine("DLL initalized");
VENDORStorageWrapper.VENDORStorageWrapper.registerCallback(2,
openCallback);
Debug.WriteLine("Callback registered");
VENDORStorageWrapper.VENDORStorageWrapper.Open("blah_file");
Debug.WriteLine("File opened");
}
public static void fileOpened(System.Int32 hstorage, System.UInt32 wParam, System.Int32 lParam)
{
Debug.WriteLine("file opened");
}
The vendor DLL's functions are specified as __stdcall, so I think I'm compliant on that front. The vendor's initialize call (_storage_initialize above) seems to be properly setting the handle, which is statically scoped. The storage_open call that's leading into the exception accepts a VENDORHANDLE (really a long) and an LPCWSTR, which I'm trying to convert the string passed from C# to. I think that's where the problem is...
When run, the app throws an unhandled exception "System.Runtime.InteropServices.SEHException" at the commented line above. The exception's coming from inside the vendor DLL, which I have no source code for. The vendor library works perfectly when called in an unmanaged C++ context and the file is known to be good. I think I'm missing something obvious in how I'm handling the parameters, but I can't see what it is.
I also don't think I have the callback set up properly, but I'm not the point where I can test that yet. Any ideas?
I'm not sure of the true big picture, but my experience using native DLLs with .net c# or vb, create a simple c# wrapper (just declarations) to the native DLL, instead of a c++ wrapper. Maybe this will help if the vendor DLL is really a vanilla DLL, as most are, like Windows api calls.
namespace MyNameSpace
{
public class MyWrapper
{
// passing an int to the native DLL
[DllImport("Vendor.DLL")]
public static extern int DllFunc1(int hModule, int nData);
// passing a string to a native DLL expecting null terminated raw wide characters //
[DllImport("Vendor.DLL", CharSet=CharSet.Unicode )]
public static extern int Dllszset(int hModule, string text);
}
}
Then .net will handle it for you and you call your vendor function as...
MyNameSpace.MyWrapper.Dllszset(h, "hello");
Hope this helps you or someone.

Categories

Resources