[Mono-list] to wrapp mono with C

Jonathan Pryor jonpryor at vt.edu
Sun Oct 2 11:36:57 EDT 2005

On Thu, 2005-09-29 at 14:39 +0200, pa-alran at tele2.fr wrote:
> I want to use the hash function of openssl with my code in 
> mono.
> The file .h in C is :
> #define SHA_BLOCK	16
> typedef struct SHAstate_st
> {
> 	unsigned long h0,h1,h2,h3,h4;
> 	unsigned long Nl,Nh;
> 	unsigned long data[SHA_LBLOCK];
> 	int num;
> } SHA_CTX;
> void SHA1_Init(SHA_CTX *c);
> void SHA1_Update(SHA_CTX *c, unsigned char *data, unsigned 
> long len);
> void SHA1_Final(unsigned char *md, SHA_CTX *c);

And this is where your first problem shows up.  The size of "unsigned
long" is platform-dependent.  In particular, it will be 32 bits for
32-bit ILP32 platforms, 64-bits for LP64 platforms (anything Unix-like),
and 32-bits for P64 platforms (Win64).

If you plan on sticking to ILP32 platforms or P64 platforms, then using
uint for ``unsigned long'' is correct.  If you ever need to use an LP64
platform, such as SuSE or Fedora Core for AMD64, then this will be
incorrect.  From this you might think that using UIntPtr would work,
since it varies with the size of the pointer, but this would break on
Win64, for which `long' doesn't match the pointer size.

The safest thing you can do is to follow the model of Mono.Posix.dll and
libMonoPosixHelper.so: define an ABI-specific set of functions and
structures to expose from your managed code, and "thunk" to the
platform-specific representation.

> ---------------------------
> I have write my wrapper like this :
> protected int SHA_BLOCK = 16;
> [StructLayout(LayoutKind.Sequential, 
> CharSet=CharSet.Ansi)]
> public struct SHA_CTX
> {
>      public uint h0;
>      public uint h1;
>      public uint h2;
>      public uint h3;
>      public uint h4;
>      public uint Nl;
>      public uint Nh;
>      [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
>      public uint [] data ;
>      public int num;
> } 
> //chargement de la dll SHA1.so "C"
> [DllImport("SHA1")]
> public static extern void SHA1_Init(ref SHA_CTX c);
> //public static extern void SHA1_Init(IntPtr c);
> //chargement de la dll SHA1.dll "C"
> [DllImport("SHA1")]
> public static extern void SHA1_Update(ref SHA_CTX c, ref 
> byte [] data, uint len);

This is incorrect.  `unsigned char*' should be a byte[], not a `ref
byte[]'.  When going from managed to unmanaged code, a [] becomes a *,
and `ref' and `out' become *, and class types add another *.  So:

	Managed                 Unmanaged
	-------                 ---------
	// value type
	byte                    unsigned char
        ref byte                unsigned char*
	byte[]			unsigned char*
	ref byte[]              unsigned char**

	// class type
	string                  const char*
	ref string              const char**
	string[]                const char**
	ref string[]            const char***

I'm not sure `ref string' and `ref string[]' are actually supported by
the marshaler, but if they were the above is what the unmanaged side
would receive.

There is also the (already mentioned) issue that `unsigned long' might
not be a `uint'.

> //public static extern void SHA1_Update(ref SHA_CTX c, 
> IntPtr data, uint len);
> //public static extern void SHA1_Update(ref SHA_CTX c, 
> byte * data, uint len);

The above two declarations would also be correct (except for the
`unsigned long' -> uint mapping).
> //chargement de la dll SHA1.dll "C"
> [DllImport("SHA1")]
> public static extern void  SHA1_Final(ref byte [] md, ref 
> SHA_CTX c);

Again, `md' should be a `byte[]', not a `ref byte[]'.

> //public static extern void  SHA1_Final(IntPtr md, ref 
> SHA_CTX c);
> //public static extern void  SHA1_Final(byte* md, ref 
> SHA_CTX c);
> -----------------------
> With the structure, i can acces to the array data ?
> And i don't know how to wrapp " unsigned char * " ? my 
> solutions don't work.

Your solutions didn't work because you had too many pointer
indirections.  One of your commented declarations would have worked, but
would also have required more work in the caller's code to invoke.

As for exposing an ABI-neutral API, you'd have to effectively duplicate
OpenSSL's API, e.g.

	/* unmanaged code: */
	#include <glib.h>
	#define SHA_BLOCK 16
	typedef struct your_sha_state_st {
		guint64 h0,h1,h2,h3,h4;
		guint64 Nl,Nh;
		guint64 data[SHA_BLOCK];
		int num;
	} your_sha_ctx;

	static void
	to_openssl (SHA_CTX *c, your_sha_ctx *y)
		int i;

		/* todo: unsigned long overflow checking */
		c->h0 = y->h0;
		c->h1 = y->h1;
		c->h2 = y->h2;
		c->h3 = y->h3;
		c->h4 = y->h4;
		c->Nl = y->Nl;
		c->Nh = y->Nh;
		c->num = y->num;
		for (i = 0; i < SHA_BLOCK; ++i)
			c->data[i] = y->data [i];

	static void
	from_openssl (your_sha_ctx *y, SHA_CTX *c)
		int i;

		/* todo: unsigned long overflow checking */
		y->h0 = c->h0;
		y->h1 = c->h1;
		y->h2 = c->h2;
		y->h3 = c->h3;
		y->h4 = c->h4;
		y->Nl = c->Nl;
		y->Nh = c->Nh;
		y->num = c->num;
		for (i = 0; i < SHA_BLOCK; ++i)
			y->data[i] = c->data [i];

	void your_sha1_init (your_sha_ctx *c)
		SHA_CTX o;
		SHA1_Init (&o);
		from_openssl (c, &o);

	void your_sha1_update (your_sha_ctx *c, unsigned char* data,
		guint64 len)
		SHA_CTX o;
		to_openssl (&o, c);
		SHA1_Update (&o, data, (unsigned long) len);
		from_openssl (c, &o);

	void your_sha1_final (unsigned char *md, your_sha1_ctx *c)
		SHA_CTX o;
		to_openssl (&o, c);
		SHA1_Final (md, &o);
		from_openssl (c, &o);

	// managed code would use ulong's instead of uints everywhere.

Suffice it to say, it's potentially a lot of work, much of it tedious
and potentially error prone.  It's probably far easier to just use a
managed equivalent, such as the System.Security.Cryptography.SHA1 class.

 - Jon

More information about the Mono-list mailing list