pass an array of strings from c# to Excel VBA - c#

My situation: I'm creating a library using C# that will be called from Excel using VBA code. There's a C# method "SubscribeCallBack" which will receive an address of a VBA Procedure (Function Pointer) and marshal the function pointer into a C# delegate. Let me explain more:
In the C# library there's a variable to store a typed delegate:
public delegate bool TDelegate([MarshalAs(UnmanagedType.BStr)]string str);
TDelegate CallBack = null;
This delegate will run upon some event:
someObj.SomeEvent += (object sender, EventArgs args) => {
if (CallBack != null)
CallBack(someString);
};
And a method exposed to VBA code to set the CallBack:
public void SetCallBack(int callBackAddress) {
try {
CallBack = (TDelegate)Marshal.GetDelegateForFunctionPointer(
new IntPtr(address), typeof(TDelegate));
}
}
Now after the compiled C# library is loaded as a Excel AddIn, I can call C# methods using Application.Run(). I can write this:
Application.Run("SetCallBack", AddressOf VBACallBack)
Public Function VBACallBack(ByVal str As String) As Boolean
Cells(1, 1).Value = str
MsgBox "VBACallBack Running"
VBACallBack = True
End Function
Now things work as expected: I can see VBACallBack got executed when it should be.
Now finally comes the question: I put [MarshalAs(UnmanagedType.Bstr)]string as C# delegate parameter so that the VBA Function with a single string argument can be Marshaled to this typed delegate. Now I need to pass a Function from VBA like this into a C# delegate:
Function(stringArray() As String) As Boolean
taking a String array (or any other thing containing an ordered list of String to make it work)
How exactly can I do this? I tried with [MarshalAs(UnmanagedType.AsAny)]string[] and [MarshalAs(UnmanagedType.LPArray)]string[] both are not working.

Related

How to get .net managed method pointer by "MethodName" that can be called on native code [duplicate]

This question already has answers here:
C# GetDelegateForFunctionPointer with generic delegate
(6 answers)
Closed 2 years ago.
Precondition
The .net method that I will get its pointer is:
public static method
have no overloads
arguments and return value just ValueType (unsafe pointer, primitive type, unmanaged struct)
Reason
Get the method pointer so I can call in C++ program.
This works for me but I need to declare delegate for every method.
I want to get rid of doing things over and over again.
In .net side:
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void UpdateDelegate(float delta);
public static void* GetUpdatePointer()
{
var delegateInstance = = new UpdateDelegate(Update);
var pfnUpdate = Marshal.GetFunctionPointerForDelegate(delegateInstance);
return (void*)pfnUpdate;
}
public static Update(float delta)=>{...}
In C++ side:
typedef void (_stdcall * FuncPtr)(float);
void foo()
{
//just pseudo-code showing where is the pfnUpdate from.
FuncPtr pfnUpdate = (FuncPtr)GetUpdatePointer();
pfnUpdate(0.01f);
}
what I want
In c#, I export GetMethodPointer for my native code. It will return a function pointer to specified method, and this pointer can be invoked by native program via stdcall calling convention.
//avoid gc collect this object
static List<Delegate> KeepReference = new List<Delegate>();
public unsafe static void* GetMethodPointer(string name)
{
System.Reflection.MethodInfo methodInfo = typeof(PhysicsMain).GetMethod(name);
// also mark this delegate with [UnmanagedFunctionPointer(CallingConvention.StdCall)] attribute
Type delegateType = ConstructDelegateTypeWithMethodInfo(methodInfo);
var delegateInstance = Delegate.CreateDelegate(delegateType, methodInfo);
KeepReference.Add(delegateInstance);
return (void*)Marshal.GetFunctionPointerForDelegate(delegateInstance);
}
I need ConstructDelegateTypeWithMethodInfo to create a delegate with the same signature as the specified method. And mark [UnmanagedFunctionPointer(CallingConvention.StdCall)] attribute for it so that can be marshaled as a function pointer.
I think it may using IL, Reflection, even Asm to do this. Or using IL to write the whole GetMethodPointer method.
Finally, I got a solution these days. Firstly, I came across Expression.GetDelegateType given by this post. But it did't work for me, because Marshal.GetFunctionPointerForDelegate doesn't support generic delegate type generate by Expression.GetDelegateType. I thought there might be a clue in implementation of Expression.GetDelegateType. So, I browsed referencesource and got a internal method called MakeNewCustomDelegate. This link gives code about how to call the internal method. Things are readily solved!
Edit: I forgot to say, the default unmanaged calling convension of a delegate is stdcall, so we don't need to mark the delegate with [UnmanagedFunctionPointer(CallingConvention.StdCall)] explicitly.
In your example you assume that the class of the method is know function (PhysicsMain).
If UpdateDelegate is also known you can use it easily:
Type delegateType = typeof(UpdateDelegate);
var delegateInstance = Delegate.CreateDelegate(delegateType, methodInfo);
But you can also get this type by name only:
Type delegateType = Type.GetType("Namespace.ClassName+UpdateDelegate");
var delegateInstance = Delegate.CreateDelegate(delegateType, methodInfo);
You can look at delegateType.CustomAttributes and verify that the type has UnmanagedFunctionPointer attribute.

Cross-platform C code and preventing garbage collection

I have a set of C functions that I need to use on an ARM target, in C++ and in C#. I can successfully wrap up the C into a C++ DLL and then into a C# DLL and use all the C functions I've bound successfully. However, I have a debug function that I want to be able to print to the C# GUI and the delegate it uses is being garbage collected rather than left in place for the duration.
Managed Debugging Assistant 'CallbackOnCollectedDelegate' has detected a
problem in 'C:\utm\pc\utm_win32_app\bin\Debug\utm_win32_app.vshost.exe'.
Additional Information: A callback was made on a garbage collected delegate of
type
'utm_dll_wrapper_cs!MessageCodec.MessageCodec_dll+guiPrintToConsoleCallback::
Invoke'. This may cause application crashes, corruption and data loss. When
passing delegates to unmanaged code, they must be kept alive by the managed
application until it is guaranteed that they will never be called.
Here's the snippet of C code that uses and sets up the callback mp_guiPrintToConsole:
#ifdef WIN32
static void (* mp_guiPrintToConsole) (const char*) = NULL;
void logMsg (const char * pFormat, ...)
{
char buffer[MAX_DEBUG_MESSAGE_LEN];
va_list args;
va_start (args, pFormat);
vsnprintf (buffer, sizeof (buffer), pFormat, args);
va_end (args);
#ifdef WIN32
if (mp_guiPrintToConsole)
{
(*mp_guiPrintToConsole) (buffer);
}
#else
// Must be on ARM
printf (buffer);
#endif
}
void initDll (void (*guiPrintToConsole) (const char *))
{
#ifdef WIN32
mp_guiPrintToConsole = guiPrintToConsole;
// This is the signal to the GUI that we're done with initialisation
logMsg ("ready.\r\n");
#endif
}
Here's the C++ code, built into a DLL along with the C code, that can be called from C# and passes in the function pointer printToConsole:
void msInitDll (void (*printToConsole) (const char *))
{
initDll (printToConsole);
}
Here's the snippet code from the C# DLL that calls msInitDll(), passing in guiPrintToConsole(), and defines the delegate onConsoleTrace, which I guess is the thing that is disappearing:
[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
public delegate void _msInitDll([MarshalAs (UnmanagedType.FunctionPtr)] guiPrintToConsoleCallback callbackPointer);
public _msInitDll msInitDll;
public delegate void ConsoleTrace(string data);
public event ConsoleTrace onConsoleTrace;
public void guiPrintToConsole(StringBuilder data)
{
if (onConsoleTrace != null)
{
onConsoleTrace (data.ToString ());
}
}
public void bindDll(string dllLocation)
{
IntPtr ptrDll = LoadLibrary (dllLocation);
if (ptrDll == IntPtr.Zero) throw new Exception (String.Format ("Cannot find {0}", dllLocation));
//...
// All the other DLL function bindings are here
//...
msInitDll = (_msInitDll)bindItem(ptrDll, "msInitDll", typeof(_msInitDll));
msInitDll(guiPrintToConsole);
}
I've looked at the various answers here and the most promising seemed to be to create a static variable in the C# code:
static GCHandle gch;
...and then use that to reference onConsoleTrace in the C# bindDll() function:
gch = GCHandle.Alloc(onConsoleTrace);
However, that doesn't do me any good. I've tried a few other attempts at declaring things static but nothing seems to get me where I want to be. Can anyone suggest another approach to fixing the problem? I have a bug that I need to fix and the lack of any debug is proving quite annoying.
Rob
The following line uses some syntactic sugar:
msInitDll(guiPrintToConsole);
The full syntax is:
msInitDll(new guiPrintToConsoleCallback(guiPrintToConsole));
Hopefully now you see why the delegate can get garbage-collected.
One simple workaround:
var callback = new guiPrintToConsoleCallback(guiPrintToConsole);
msInitDll(callback);
// ... some other code
GC.KeepAlive(callback);
Now the delegate is guaranteed to be alive up to the GC.KeepAlive call.
But you most probably need the delegate to stay alive for longer. As the error message says, simply keep a reference to it. If you need it for the full C# app lifetime duration, turn the callback local to a static field in your class. Static fields are treated as GC roots as their values are always reachable.
And the answer was, in the C# DLL code, add the static variable:
public static guiPrintToConsoleCallback debugCallback;
...and then, in C# bindDLL(), change:
msInitDll(guiPrintToConsole);
...to
debugCallback = new guiPrintToConsoleCallback(guiPrintToConsole);
msInitDll(debugCallback);
Simple when you know how.

C++/Cx passing delegates to native c++ exception thrown

I have a C++/Cx library to which I need to pass a callback function from C# as a pointer to it.
The delegate is declared in WinRt layer as follows:
public delegate void del(int, enumType, uint, string, int);
C#
void callbackFunction(
int a,
enumType b, //this is public enum class
uint c,
String d,
int e)
{
tb.Text() = d;
}
Wrc_Component.wrc w = new Wrc_Component.wrc();
del d = new d(callbackFunction);
IntPtr p = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(d);
w.func(p.ToInt32(), "string");
And again in WinRt layer definition of func():
void wrc::func(int ptrToCallback, Platform::String^ str){
void*p = (void*)ptrToCallBack;
callBackFunction cb = (callBackFunction)p;
cb(0,enumType(0),0,"aaaa",0); //here is exception thrown
}
Of course the code is simplified a little.
The line in which following exception is thrown is marked in a code:
An exception of type 'System.Runtime.InteropServices.MarshalDirectiveException' occurred in App1.exe but was not handled in user code
Additional information: Windows Runtime delegates may not be used for PInvoke interop.
I would like to mention also that if I am calling a function retrieved in the same way but much simpler it does not throw any exceptions.
What is causing this exception? Is this because of user defined enum type?
EDIT
And what if I need to pass it even further down to C++ native library, I can't retrieve the pointer to function from delegate in WinRt as far as i know.
As robwirving stated, you can’t just pass the function pointer to Windows Runtime component as a delegate.
The Windows Runtime delegate is different from the .NET delegate. To pass the handler from the .NET to Windows Runtime, you need to have a Windows Runtime wrapper. In your case, you can wrap through delegate del.
del handler = new del((a, b, c, d, e) =>
{
// to change the UI element in a handler, you may need to consider using the CoreDispatcher.
tb.Text = d;
});
w.func(handler, "string");
You don't pass the delegate functions around as native pointers. You pass them as the delegate type. Try this in your C#:
void callbackFunction(
int a,
enumType b, //this is public enum class
uint c,
String d,
int e)
{
tb.Text() = d;
}
Wrc_Component.wrc w = new Wrc_Component.wrc();
del d = new del(callbackFunction);
w.func(d, "string");
And for your WinRT change to this:
void wrc::func(del^ myDel, Platform::String^ str){
if(myDel)
myDel->Invoke(0, enumType(0),0,"aaaa",0);
}
I believe I covered everything but I have a full blog post on this topic here: http://www.robwirving.com/2014/07/21/calling-c-methods-c-winrt-components/
I managed to fix that exception...
The cause of this exception was that the delegate that is declared in Windows Runtime cannot be used in C#, therefore declaring and using C# delegate solved the problem.
Additionally with the construction as in a question (except for delegate of course) I am able to pass and store function pointers in all three layers I am using.
However, if anyone sees some possible hazards in this implementation please, share with us for the better code!
Thanks to other answers provided by #Jeffrey Chen and #robwirving.

Port code to vb.net

I found some code online. It is in C# and I am trying to port it to vb.net. I need some help with calling the evaluator function from within the Log subroutine. In C#, evaluator does not appear to expect any parameters when it gets called in Log. However, VB keeps asking for the Match parameter. What is the magic and how should I get it to work in VB.NET? Thanks.
private string evaluator(Match match)
{
Pri pri = new Pri(match.Groups[1].Value);
return pri.ToString()+" ";
}
private void Log(EndPoint endPoint, string strReceived)
{
...
string strMessage = string.Format("{0} : {1}",
endPoint, m_regex.Replace(strReceived, evaluator));
...
}
The C# version is using the (string, MatchEvaluator) overload of Regex.Replace(), and using the implicit conversion of the method name to the MatchEvaluator delegate type. See the MSDN documentation on that overload.
On the MSDN page, this is how they call it:
Dim result As String = rx.Replace(text, AddressOf RegExSample.CapText)
So make sure to use the AddressOf keyword.

Implement callback C# functions for C++ DLL

I'm writing a DLL wrapper for my C++ library, to be called from C#. This wrapper should also have callback functions called from the library and implemented in C#. These functions have for instance std::vector<unsigned char> as output parameters. I don't know how to make this. How do I pass a buffer of unknown size from C# to C++ via a callback function?
Let's take this example
CallbackFunction FunctionImplementedInCSharp;
void FunctionCalledFromLib(const std::vector<unsigned char>& input, std::vector<unsigned char>& output)
{
// Here FunctionImplementedInCSharp (C# delegate) should somehow be called
}
void RegisterFunction(CallbackFunction f)
{
FunctionImplementedInCSharp = f;
}
How should CallbackFunction be defined and what is the code inside FunctionCalledFromLib?
One of the things that dumb me is: how do I delete a buffer created by C# inside C++ code?
As of Visual Studio 2013 at least, there is a safe way to pass callbacks from C# to C++ and have C++ store them and invoke them asynchronously later from unmanaged code. What you can do is create a managed C++/CX class (e.g., named "CallbackManager") to hold the callback delegate references in a map, keyed off an enum value for each. Then your unmanaged code can retrieve a managed delegate reference from the managed C++/CX CallbackManager class via the delegate's associated enum value. That way you don't have to store raw function pointers and so you don't have to worry about the delegate being moved or garbage-collected: it stays in the managed heap throughout its lifecycle.
On the C++ side in CallbacksManager.h:
#include <unordered_map>
#include <mutex>
using namespace Platform;
namespace CPPCallbacks
{
// define callback IDs; this is what unmanaged C++ code will pass to the managed CallbacksManager class to retrieve a delegate instance
public enum class CXCallbackType
{
cbtLogMessage,
cbtGetValueForSetting
// TODO: add additional enum values as you add more callbacks
}
// defines the delegate signatures for our callbacks; these are visible to the C# side as well
public delegate void LogMessageDelegate(int level, String^ message);
public delegate bool GetValueForSettingDelegate(String^ settingName, String^* settingValueOut);
// TODO: define additional callbacks here as you need them
// Singleton WinRT class to manage C# callbacks; since this class is marked 'public' it is consumable from C# as well
public ref class CXCallbacksManager sealed
{
private:
CXCallbacksManager() { } // this is private to prevent incorrect instantiation
public:
// public methods and properties are all consumable by C# as well
virtual ~CXCallbacksManager() { }
static property CXCallbacksManager^ Instance
{
CXCallbacksManager^ get();
}
bool UnregisterCallback(CXCallbackType cbType);
void UnregisterAllCallbacks();
Delegate^ GetCallback(CXCallbackType cbType);
// define callback registration methods
RegisterLogMessageCallback(LogMessageDelegate^ cb) { RegisterCallback(CXCallbackType::cbtLogMessage, cb); }
RegisterGetValueForSettingCallback(GetValueForSettingDelegate^ cb) { RegisterCallback(CXCallbackType::GetValueForSetting, cb); }
// TODO: define additional callback registration methods as you add more callbacks
private:
void RegisterCallback(CXCallbackType cbType, Delegate^ rCallbackFunc);
typedef unordered_map<CXCallbackType, Delegate^> CALLBACK_MAP;
typedef pair<CXCallbackType, Delegate^> CBType_Delegate_Pair;
// Note: IntelliSense errors shown for static data is a Visual Studio IntellSense bug; the code below builds fine
// See http://social.msdn.microsoft.com/Forums/windowsapps/en-US/b5d43215-459a-41d6-a85e-99e3c30a162e/about-static-member-of-ref-class?forum=winappswithnativecode
static mutex s_singletonMutex;
static CXCallbacksManager^ s_rInstance;
mutex m_callbackMapMutex;
CALLBACK_MAP m_callbacksMap; // key=CallbackType, value = C# delegate (function) pointer
};
}
In CallbacksManager.cpp we implement the managed C++/CX class accessed by both C# and our unmanaged C++ code:
#include <assert.h>
#include "CXCallbacksManager.h"
using namespace Platform;
namespace CPPCallbacks
{
// define static class data
CXCallbacksManager^ CXCallbacksManager::s_rInstance;
mutex CXCallbacksManager::s_singletonMutex;
// Returns our singleton instance; this method is thread-safe
CXCallbacksManager^ CXCallbacksManager::Instance::get()
{
s_singletonMutex.lock();
if (s_rInstance == nullptr)
s_rInstance = ref new CXCallbacksManager(); // this lives until the application terminates
s_singletonMutex.unlock();
return s_rInstance;
}
// Register a C# callback; this method is thread-safe
void CXCallbacksManager::RegisterCallback(const CXCallbackType cbType, Delegate^ rCallbackFunc)
{
_ASSERTE(rCallbackFunc);
m_callbackMapMutex.lock();
m_callbacksMap.insert(CBType_Delegate_Pair(cbType, rCallbackFunc));
m_callbackMapMutex.unlock();
}
// Unregister a C# callback; this method is thread-safe
// Returns: true on success, false if no callback was registered for callbackType
bool CXCallbacksManager::UnregisterCallback(const CXCallbackType cbType)
{
m_callbackMapMutex.lock();
const bool bRemoved = (m_callbacksMap.erase(cbType) > 0);
m_callbackMapMutex.unlock();
return bRemoved;
}
// Unregister all callbacks; this method is thread-safe
void CXCallbacksManager::UnregisterAllCallbacks()
{
// must lock the map before iterating across it
// Also, we can't change the contents of the map as we iterate across it, so we have to build a vector of all callback types in the map first.
vector<CXCallbackType> allCallbacksList;
m_callbackMapMutex.lock();
for (CALLBACK_MAP::const_iterator it = m_callbacksMap.begin(); it != m_callbacksMap.end(); it++)
allCallbacksList.push_back(it->first);
for (unsigned int i = 0; i < allCallbacksList.size(); i++)
{
CALLBACK_MAP::const_iterator it = m_callbacksMap.find(allCallbacksList[i]);
if (it != m_callbacksMap.end()) // sanity check; should always succeed
UnregisterCallback(it->first);
}
m_callbackMapMutex.unlock();
}
// Retrieve a registered C# callback; returns NULL if no callback registered for type
Delegate^ CXCallbacksManager::GetCallback(const CXCallbackType cbType)
{
Delegate^ rCallbackFunc = nullptr;
m_callbackMapMutex.lock();
CALLBACK_MAP::const_iterator it = m_callbacksMap.find(cbType);
if (it != m_callbacksMap.end())
rCallbackFunc = it->second;
else
_ASSERTE(false); // should never happen! This means the caller either forgot to register a callback for this cbType or already unregistered the callback for this cbType.
m_callbackMapMutex.unlock();
return rCallbackFunc;
}
}
The delegate instances remain stored in the managed heap by our CXCallbacksManager class, so now it's easy and safe to store callbacks on the C++ side for unmanaged code to invoke later asynchronously. Here is the C# side registering two callbacks:
using CPPCallbacks;
namespace SomeAppName
{
internal static class Callbacks
{
// invoked during app startup to register callbacks for unmanaged C++ code to invoke asynchronously
internal static void RegisterCallbacks()
{
CPPCallbacks.CXCallbacksManager.Instance.RegisterLogMessageCallback(new LogMessageDelegate(LogMessageDelegateImpl));
CPPCallbacks.CXCallbacksManager.Instance.RegisterGetValueForSettingCallback(new GetValueForSettingDelegate(GetValueForSettingDelegateImpl));
// TODO: register additional callbacks as you add them
}
//-----------------------------------------------------------------
// Callback delegate implementation methods are below; these are invoked by C++
// Although these example implementations are in a static class, you could also pass delegate instances created
// from inside a non-static class, which would maintain their state just like any other instance method (i.e., they have a 'this' object).
//-----------------------------------------------------------------
private static void LogMessageDelegateImpl(int level, string message)
{
// This next line is shown for example purposes, but at this point you can do whatever you want because
// you are running in a normal C# delegate context.
Logger.WriteLine(level, message);
}
private static bool GetValueForSettingDelegateImpl(String settingName, out String settingValueOut)
{
// This next line is shown for example purposes, but at this point you can do whatever you want because
// you are running in a normal C# delegate context.
return Utils.RetrieveEncryptedSetting(settingName, out settingValueOut);
}
};
}
Lastly, here is how to invoke your registered C# callbacks from unmanaged C++ code:
#include <assert.h>
#include <atlstr.h> // for CStringW
#include "CXCallbacksManager.h"
using namespace CPPCallbacks;
// this is an unmanaged C++ function in the same project as our CXCallbacksManager class
void LogMessage(LogLevel level, const wchar_t *pMsg)
{
_ASSERTE(msg);
auto rCallback = static_cast<LogMessageDelegate^>(CXCallbacksManager::Instance->GetCallback(CXCallbackType::cbtLogMessage));
_ASSERTE(rCallback);
rCallback(level, ref new String(pMsg)); // invokes C# method
}
// this is an unmanaged C++ function in the same project as our CXCallbacksManager class
// Sets settingValue to the value retrieved from C# for pSettingName
// Returns: true if the value existed and was set, false otherwise
bool GetValueForSetting(const wchar_t *pSettingName, CStringW &settingValue)
{
bool bRetCode = false;
auto rCallback = static_cast<GetValueForSettingDelegate^>(CXCallbacksManager::Instance->GetCallback(CXCallbackType::cbtGetValueForSetting));
_ASSERTE(rCallback);
if (rCallback) // sanity check; should never be null
{
String^ settingValueOut;
bRetCode = rCallback(ref new String(pSettingName), &settingValueOut);
// store the retrieved setting value to our unmanaged C++ CStringW output parameter
settingValue = settingValueOut->Data();
}
return bRetCode;
}
This all works because although you cannot store a managed delegate reference as a member variable inside an unmanaged class, you can still retrieve and invoke a managed delegate from unmanaged code, which is what the above two native C++ methods do.
There are some things you should be aware of. The first is that if you are calling a .NET delegate from unmanaged code, then unless you follow some pretty narrow constraints, you will be in for pain.
Ideally, you can create a delegate in C# pass it into managed code, marshal it into a function pointer, hold onto it for as long as you like, then call it with no ill effects. The .NET documentation says so.
I can tell you that this is simply not true. Eventually, part of your delegate or its thunk will get garbage collected and when you call the function pointer from unmanaged code you will get sent into oblivion. I don't care what Microsoft says, I've followed their prescription to the letter and watched function pointers get turned into garbage, especially in server-side code behinds.
Given that, the most effective way to use function pointers is thus:
C# code calls unmanaged code, passing in delegate.
Unmanaged code marshals the delegate to a function pointer.
Unmanaged code does some work, possible calling the function pointer.
Unmanaged code drops all references to the function pointer.
Unmanaged code returns to managed code.
Given that, suppose we have the following in C#:
public void PerformTrick(MyManagedDelegate delegate)
{
APIGlue.CallIntoUnamangedCode(delegate);
}
and then in managed C++ (not C++/CLI):
static CallIntoUnmanagedCode(MyManagedDelegate *delegate)
{
MyManagedDelegate __pin *pinnedDelegate = delegate;
SOME_CALLBACK_PTR p = Marshal::GetFunctionPointerForDelegate(pinnedDelegate);
CallDeepIntoUnmanagedCode(p); // this will call p
}
I haven't done this recently in C++/CLI - the syntax is different - I think it ends up looking like this:
// This is declared in a class
static CallIntoUnamangedCode(MyManagedDelegate ^delegate)
{
pin_ptr<MyManagedDelegate ^> pinnedDelegate = &delegate;
SOME_CALLBACK_PTR p = Marshal::GetFunctionPointerForDelegate(pinnedDelegate);
CallDeepIntoUnmanagedCode(p); // This will call p
}
When you exit this routines, the pinning gets released.
When you really, really need to have function pointers hanging around for a while before calling, I have done the following in C++/CLI:
Made a hashtable that is a map from int -> delegate.
Made register/unregister routines that add new delegates into the hashtable, bumping up a counter for the hash int.
Made a single static unmanaged callback routine that is registered into unmanaged code with an int from the register call. When this routine is called, it calls back into managed code saying "find the delegate associated with <int> and call it on these arguments".
What happens is that the delegates don't have thunks that do transitions anymore since they're implied. They're free to hang around in limbo being moved by the GC as needed. When they get called, the delegate will get pinned by the CLR and released as needed. I have also seen this method fail, particularly in the case of code that statically registers callbacks at the beginning of time and expects them to stay around to the end of time. I've seen this fail in ASP.NET code behind as well as server side code for Silverlight working through WCF. It's rather unnerving, but the way to fix it is to refactor your API to allow late(r) binding to function calls.
To give you an example of when this will happen - suppose you have a library that includes a function like this:
typedef void * (*f_AllocPtr) (size_t nBytes);
typedef void *t_AllocCookie;
extern void RegisterAllocFunction(f_AllocPtr allocPtr, t_AllocCookie cookie);
and the expectation is that when you call an API that allocates memory, it will be vectored off into the supplied f_AllocPtr. Believe it or not, you can write this in C#. It's sweet:
public IntPtr ManagedAllocMemory(long nBytes)
{
byte[] data = new byte[nBytes];
GCHandle dataHandle = GCHandle.Alloc(data, GCHandleType.Pinned);
unsafe {
fixed (byte *b = &data[0]) {
dataPtr = new IntPtr(b);
RegisterPointerHandleAndArray(dataPtr, dataHandle, data);
return dataPtr;
}
}
}
RegisterPointerHandleAndArray stuffs the triplet away for safe keeping. That way when the corresponding free gets called, you can do this:
public void ManagedFreeMemory(IntPtr dataPointer)
{
GCHandle dataHandle;
byte[] data;
if (TryUnregister(dataPointer, out dataHandle, out data)) {
dataHandle.Free();
// do anything with data? I dunno...
}
}
And of course this is stupid because allocated memory is now pinned in the GC heap and will fragment it to hell - but the point is that it's doable.
But again, I have personally seen this fail unless the actual pointers are short lived. This typically means wrapping your API, so that when you call into a routine that accomplishes a specific task, it registers callbacks, does the task, and then pulls the callbacks out.
As it turns out, the answer to the original question is rather simple, once you know it, and the whole callback issue was no issue. The input buffer parameter is replaced with parameter pair unsigned char *input, int input_length, and the output buffer parameter is replaced with parameter pair unsigned char **output, int *output_length. The C# delegate should be something like this
public delegate int CallbackDelegate(byte[] input, int input_length,
out byte[] output, out int output_length);
And wrapper in C++ should be something like this
void FunctionCalledFromLib(const std::vector<unsigned char>& input, std::vector<unsigned char>& output)
{
unsigned char *output_aux;
int output_length;
FunctionImplementedInCSharp(
&input[0], input.size(), &ouput_aux, &output_length);
output.assign(output_aux, output_aux + output_length);
CoTaskMemFree(output_aux); // IS THIS NECESSARY?
}
The last line is the last part of the mini-puzzle. Do I have to call CoTaskMemFree, or will the marshaller do it for me automagically?
As for the beautiful essay by plinth, I hope to bypass the whole problem by using a static function.
There is no point to using C++/cli.
And here is a real world example from my project.
public ImageSurface(byte[] pngData)
: base(ConstructImageSurfaceFromPngData(pngData), true)
{
offset = 0;
}
private static int offset;
private static IntPtr ConstructImageSurfaceFromPngData(byte[] pngData)
{
NativeMethods.cairo_read_func_t func = delegate(IntPtr closure, IntPtr out_data, int length)
{
Marshal.Copy(pngData, offset, out_data, length);
offset += length;
return Status.Success;
};
return NativeMethods.cairo_image_surface_create_from_png_stream(func, IntPtr.Zero);
}
That is used to transfer PNG data from C# to the native cairo API.
You can see how the C function pointer cairo_read_func_t is implemented in C# and then used as a callback for cairo_image_surface_create_from_png_stream.
Here is a similar example.

Categories