powershelldlldelegatesinteropservices

Pass function pointer into a DLL from PowerShell


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.


Solution

  • 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 use Add-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 imported P/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.