Problem
I have a DLL with the following function exported.
extern void Finalize(void (*WriteEntry)(const char* entry));
I am using P-Invoke to call into the DLL from PowerShell.
In PowerShell scripts, I have a function WriteEntry defined, which I need to be invoked whenever Finalize invokes the WriteEntry function pointer.
My approach so far
It seems like the right approach is to create a C-style wrapper around the WriteEntry PowerShell function, where the wrapper is passed to Finalize as the function pointer.
After doing some investigation, I found out about creating a delegate which can interoperate with unmanaged code (See How to pass function pointer from C# to a C++ Dll?). I created the following CallbackDelegate type. I was able to pass it from C# code to the DLL. Now, I need a way for a PowerShell application to be able to construct the delegate and pass it to the DLL.
$delegateTypeDefinition = @'
[System.Runtime.InteropServices.UnmanagedFunctionPointer(System.Runtime.InteropServices.CallingConvention.Cdecl)]
public delegate void CallbackDelegate([System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.LPTStr)] string entry);
'@
Where I'm stuck
I'm not sure how to wrap the CallbackDelegate delegate around the WriteEntry PowerShell function.
So, the question that I believe that I need answered is, how do we construct a delegate from a PowerShell function. If that turns out to be an XY problem, I hope that I have provided enough details to be pointed in the correct direction.
Unfortunately I don't believe you can do what you're asking with PowerShell alone, at least not without jumping through a series of hoops to create a class inheriting from
MulticastDelegateand then attempting to register it. PowerShell doesn't have an equivalent of thedelegatekeyword for defining one easily.We can instead define the C# source for doing so, and ultimately perform the invocation from PowerShell itself. We already need to
P/Invokefrom PowerShell, so we'll just keep yourdelegatedefinition provided in C# form.A
delegateis functionally equivalent (no pun intended) in C# to a callback in C++. AMethodcan be used as adelegate. AnActioncan be used as adelegate. AFunccan be used as adelegate. At its core, adelegateis an executable block of code, in some form. So how does this translate to PowerShell?PowerShell code will use a
ScriptBlock{}in order to define executable blocks of code. These are used quite often, from function definitions, to method definitions on a class, to looping constructs, and more. AScriptBlockcan even be used and executed on its own using the call operator&. AScriptBlockis just a schmancy name for an unnamed function. Since aScriptBlockis a function, we can use that as the delegate value.The kicker here is that
ScriptBlockneeds to be converted to yourdelegatetype. And while we're at it, let's fix that mouthful of qualified type-names with a using statementNormally you would then call
$callbackDelegate.Invoke("some string")to run yourC#-defined delegate function. But since you want to pass it to
Finalize, we'll do that instead.In theory, this should work for passing in the callback.
That said,
delegatetypes, in particular when accessing members which have adelegatetype, can behave somewhat funky in PowerShell. In addition, I don't have or know offhand of a native DLL to test this with myself. I can only guarantee$callbackDelegate.Invoke("some value")will work as far as setting assigning aScriptBlockto adelegatetype and invoking it.If you run into more issues, or there's a "gotcha" with what I provided above in your use case, you said you got this working in C#. You may want to consider implementing this piece entirely in C# while additionally creating a public helper function on your helper class to take the
entrystring and runFinalizewithin the C# code. UseAdd-Typeto either compile the source from your script or load a pre-compiled DLL. From there you can run your helper function within PowerShell to execute theFinalizefunction. It's a bit of a cop-out, butdelegatetypes are an area where PowerShell can be difficult to work with at times.