Monday, February 14, 2011

Scripting the Win32 API - F# and IronPython FFI

Mainly a worked example for reminding me of how the syntax goes, reimplementing sn -k in F# and IronPython:

open System
open System.Diagnostics
open System.IO
open System.Runtime.InteropServices
#nowarn "51"
// "The use of native pointers may result in unverifiable .NET IL code" --
// for arguments &&keyBlob, &&generatedSize in the call to StrongNameKeyGenEx
module NativeMethods =
[<DllImport("mscoree.dll")>]
extern int StrongNameErrorInfo()
[<DllImport("mscoree.dll")>]
extern bool StrongNameKeyGenEx(
[<MarshalAs(UnmanagedType.LPWStr)>]String wszKeyContainer,
int dwFlags, int dwKeySize, IntPtr* ppbKeyBlob, int* pcbKeyBlob)
[<DllImport("mscoree.dll")>]
extern void StrongNameFreeBuffer(IntPtr pbMemory)
let GetLastStrongNameError() =
Marshal.GetExceptionForHR(NativeMethods.StrongNameErrorInfo()).Message
let GenerateKeyPair (keyK : int) =
if keyK < 0 || keyK > 2 then
raise (new ArgumentOutOfRangeException("keyK", keyK, "Invalid Key Size -- should be 1 or 2"))
// variables that hold the unmanaged key
let mutable keyBlob = IntPtr.Zero
let mutable generatedSize = 0
// create the key
let createdKey = NativeMethods.StrongNameKeyGenEx(
null, 0, 1024 * keyK,
&&keyBlob, &&generatedSize)
try
// if there was a problem, translate it and report it
if not createdKey || keyBlob = IntPtr.Zero then
raise (new InvalidOperationException(GetLastStrongNameError()))
// make sure the key size makes sense
if generatedSize <= 0 || generatedSize > Int32.MaxValue then
raise (new InvalidOperationException("InternalError"))
// get the key into managed memory
let key = Array.create generatedSize 0uy
Marshal.Copy(keyBlob, key, 0, generatedSize)
key
finally
// release the unmanaged memory the key resides in
if keyBlob <> IntPtr.Zero then
NativeMethods.StrongNameFreeBuffer(keyBlob)
// path to generate new string name key
let keypath = fsi.CommandLineArgs.[1]
// write the key to the specified file if it is not already present
if not (File.Exists(keypath)) then
let key = GenerateKeyPair 1
use snkStream = new FileStream(keypath, FileMode.Create, FileAccess.Write)
use snkWriter = new BinaryWriter(snkStream)
snkWriter.Write(key)
view raw gistfile1.fs hosted with ❤ by GitHub

This about halfway between how you'd just call the APIs naturally in C++/CLI, and the full process of C# P/Invoke. You do need to do your own extern method declarations, whereas IronPython is something else again:

from System import Int32, Byte, IntPtr
import System
from System.IO import *
from System.Runtime.InteropServices import Marshal
from ctypes import *
import sys
mscoree = windll.mscoree
def GetLastStrongNameError():
error = Marshal.GetExceptionForHR(mscoree.StrongNameErrorInfo())
return error.Message
def GenerateKeyPair (keyK):
if keyK < 0 or keyK > 2 :
raise System.ArgumentOutOfRangeException(
"keyK", keyK, "Invalid Key Size -- should be 1 or 2")
# variables that hold the unmanaged key
keyBlob = c_void_p()
generatedSize = c_uint(0)
# create the key
createdKey = mscoree.StrongNameKeyGenEx(
None,
c_uint(0),
c_uint(1024 * keyK),
byref(keyBlob),
byref(generatedSize))
try:
# if there was a problem, translate it and report it
if not createdKey or keyBlob.value == 0:
raise System.InvalidOperationException(GetLastStrongNameError())
#make sure the key size makes sense
generatedSize = generatedSize.value
if generatedSize <= 0 or generatedSize > Int32.MaxValue:
raise System.InvalidOperationException("InternalError")
# get the key into managed memory
key = System.Array.CreateInstance(Byte, generatedSize)
Marshal.Copy(IntPtr(keyBlob.value), key, 0, generatedSize)
return key
finally:
# release the unmanaged memory the key resides in
if keyBlob.value != 0:
mscoree.StrongNameFreeBuffer(keyBlob)
# write the key to the specified file if it is not already present
keypath = sys.argv[1]
if not (File.Exists(keypath)):
print 'Generating key at %s' % (keypath)
key = GenerateKeyPair (1)
# should do this using a 'with'
snkStream = FileStream(keypath, FileMode.Create, FileAccess.Write)
snkWriter = BinaryWriter(snkStream)
snkWriter.Write(key)
snkWriter.Close()
view raw gistfile1.py hosted with ❤ by GitHub

where it was easier to fudge the pointer value for the key buffer into an IntPtr than to try and dereference it via ctypes and make a manual copy.

The code here is an adapted subset of a managed API for strong-name keys; the use-case being a contingent key generation as part of a build process.

No comments :