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.
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);
'@
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 MulticastDelegate
and then attempting to register it. PowerShell doesn't have an equivalent of the delegate
keyword 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/Invoke
from PowerShell, so we'll just keep your delegate
definition provided in C# form.
A delegate
is functionally equivalent (no pun intended) in C# to a callback in C++. A Method
can be used as a delegate
. An Action
can be used as a delegate
. A Func
can be used as a delegate
. At its core, a delegate
is 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. A ScriptBlock
can even be used and executed on its own using the call operator &
. A ScriptBlock
is just a schmancy name for an unnamed function. Since a ScriptBlock
is a function, we can use that as the delegate value.
The kicker here is that ScriptBlock
needs to be converted to your delegate
type. And while we're at it, let's fix that mouthful of qualified type-names with a using statement 😋
Note: If your C# code is defined in a source file, you can instead read it in with
Get-Content -Raw
. If it is already compiled into a DLL, you can skip that part and useAdd-Type
providing the path to the DLL.
$delegateTypeDefinition = @"
using System.Runtime.InteropServices;
# Here is the callback delegate you provided
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void CallbackDelegate([MarshalAs(UnmanagedType.LPTStr)] string entry);
public static class DelegateHelper
{
# Use your actual DLL
[DllImport("DllName.dll", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
public static extern void Finalize(CallbackDelegate del);
}
"@
# Compile your C# code
Add-Type $delegateTypeDefinition
# Define your delegate function with the correct parameters
[CallbackDelegate]$callbackDelegate = {
Param(
[string]$entry
)
# Your callback code here
}
Normally you would then call $callbackDelegate.Invoke("some string")
to run your
C#-defined delegate function. But since you want to pass it to Finalize
, we'll do that instead.
Note: This is why we put the
DllImport
inside of a class. It does not have to be defined as a static class or member but importedP/Invoke
functions have to be defined in a class like a method.
# Call Finalize
[DelegateHelper]::Finalize($callbackDelegate)
In theory, this should work for passing in the callback.
That said, delegate
types, in particular when accessing members which have a delegate
type, 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 a ScriptBlock
to a delegate
type 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 entry
string and run Finalize
within the C# code. Use Add-Type
to 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 the Finalize
function. It's a bit of a cop-out, but delegate
types are an area where PowerShell can be difficult to work with at times.