[Mono-list] Catching C-callbacks through InteropServices

Jonathan Pryor jonpryor@vt.edu
Fri, 28 May 2004 18:17:14 -0400


On Fri, 2004-05-28 at 14:14, Simon Ask Ulsnes wrote:
<snip/>
> So far, it works fine, I can retrieve keys and encrypt/sign. However, 
> the GPGME API is constructed in such a way that in order to retrieve a 
> passphrase for use with decryption, you have to give it a callback 
> function returning the passphrase as a const char*.

Hmm...  The `const char*' return type can be problematic, as it implies
that the return value won't be freed by the caller.  This in turn means
that the managed return value for your callback should be an IntPtr. 
Then, you'll need to store the IntPtr as a class member so you can
properly free the memory, which begs the question: when do you free the
memory?

See the sample code below for more information.

> What I would do is to make a property in my GnuPGEngine class (which 
> manages communication with the C layer through InteropServices) which 
> will then be given to the real GnuPG engine (a GpgmeCtx, which means a 
> GPGME Context object) through an internal callback.
> 
> How is this best done?

Delegates. :-)

> The API syntax in C for setting the callback function for the context is:
> gpgme_set_passphrase(ctx, &get_passphrase, NULL);
> 
> where get_passphrase:
> static const char* get_passphrase(void *HOOK, const char *DESC, void 
> **R_HD);
> 
> With the above, the get_passphrase function never gets called. What to do?

I'm not sure what you mean here.

Regardless, and shooting from the hip (i.e., completely uncompiled code
follows), here's a basic idea of how to do it:

        using System;
        using System.Runtime.InteropServices;
        
        class GnuPgEngine : IDisposable {
        	private delegate 
        	IntPtr GetPassphrase (IntPtr hook, string desc, ref IntPtr R_HD);
        
        
        	[DllImport ("the library")]
        	void gpgme_set_passphrase (IntPtr ctx, GetPassphrase gph, string something);
        
        	private IntPtr passphrase;
        
        	private IntPtr MyGetPassphrase (IntPtr h, string d, ref IntPtr r)
        	{
        		passphrase = Marshal.StringToHGlobalAnsi ("some string");
        		return passphrase;
        	}
        
        	public void Dispose ()
        	{
        		if (passphrase != IntPtr.Zero) {
        			Marshal.FreeHGlobal (passphrase);
        			passphrase = IntPtr.Zero;
        		}
        		GC.SuppressFinalize (this);
        	}
        
        	~GnuPgEngine ()
        	{
        		Dispose ();
        	}
        
        	public void SetPassphrase ()
        	{
        		GetPassphrase gp = new GetPassphrase (MyGetPassphrase);
        		gpgme_set_passphrase (IntPtr.Zero, gp, null);
        	}
        }

 - Jon