Added helper classes for working with unmanaged structures (which are used to interface with native code, e.g. MAPI).

This commit is contained in:
Ben Olden-Cooligan 2013-08-19 20:50:19 -04:00
parent 2ff243c9e1
commit 54ee56b639
5 changed files with 287 additions and 72 deletions

View File

@ -49,87 +49,73 @@ namespace NAPS2.Email.Mapi
/// Sends an email described by the given message object.
/// </summary>
/// <param name="message">The object describing the email message.</param>
/// <exception cref="EmailException">Throws an EmailException if an error occurred.</exception>
/// <returns>Returns true if the message was sent, false if the user aborted.</returns>
public bool SendEmail(EmailMessage message)
{
// Translate files & recipients to unmanaged MAPI structures
var files = message.Attachments.Select(attachment => new MapiFileDesc
using (var files = Unmanaged.CopyOf(GetFiles(message)))
using (var recips = Unmanaged.CopyOf(GetRecips(message)))
{
// Create a MAPI structure for the entirety of the message
var mapiMessage = new MapiMessage
{
subject = message.Subject,
noteText = message.BodyText,
recips = recips,
recipCount = recips.Length,
files = files,
fileCount = files.Length
};
// Determine the flags used to send the message
var flags = MapiSendMailFlags.None;
if (!message.AutoSend)
{
flags |= MapiSendMailFlags.Dialog;
}
if (!message.AutoSend || !message.SilentSend)
{
flags |= MapiSendMailFlags.LogonUI;
}
// Send the message
var returnCode = MAPISendMail(IntPtr.Zero, IntPtr.Zero, mapiMessage, flags, 0);
// Process the result
if (returnCode == MapiSendMailReturnCode.UserAbort)
{
return false;
}
if (returnCode != MapiSendMailReturnCode.Success)
{
logger.Error("Error sending email. MAPI error code: {0}", returnCode);
errorOutput.DisplayError(MiscResources.EmailError);
return false;
}
return true;
}
}
private static MapiRecipDesc[] GetRecips(EmailMessage message)
{
return message.Recipients.Select(recipient => new MapiRecipDesc
{
name = recipient.Name,
address = "SMTP:" + recipient.Address,
recipClass = recipient.Type == EmailRecipientType.Cc ? MapiRecipClass.Cc
: recipient.Type == EmailRecipientType.Bcc ? MapiRecipClass.Bcc
: MapiRecipClass.To
}).ToArray();
}
private static MapiFileDesc[] GetFiles(EmailMessage message)
{
return message.Attachments.Select(attachment => new MapiFileDesc
{
position = -1,
path = attachment.FilePath,
name = attachment.AttachmentName
}).ToArray();
var recips = message.Recipients.Select(recipient => new MapiRecipDesc
{
name = recipient.Name,
address = "SMTP:" + recipient.Address,
recipClass = recipient.Type == EmailRecipientType.Cc ? MapiRecipClass.Cc
: recipient.Type == EmailRecipientType.Bcc ? MapiRecipClass.Bcc
: MapiRecipClass.To
}).ToArray();
// Create a MAPI structure for the entirety of the message
var mapiMessage = new MapiMessage
{
subject = message.Subject,
noteText = message.BodyText,
recips = ToUnmanagedArray(recips),
recipCount = recips.Length,
files = ToUnmanagedArray(files),
fileCount = files.Length
};
// Determine the flags used to send the message
var flags = MapiSendMailFlags.None;
if (!message.AutoSend)
{
flags |= MapiSendMailFlags.Dialog;
}
if (!message.AutoSend || !message.SilentSend)
{
flags |= MapiSendMailFlags.LogonUI;
}
// Send the message
var returnCode = MAPISendMail(IntPtr.Zero, IntPtr.Zero, mapiMessage, flags, 0);
// Process the result
if (returnCode == MapiSendMailReturnCode.UserAbort)
{
return false;
}
if (returnCode != MapiSendMailReturnCode.Success)
{
logger.Error("Error sending email. MAPI error code: {0}", returnCode);
errorOutput.DisplayError(MiscResources.EmailError);
return false;
}
return true;
}
/// <summary>
/// Allocates an unmanaged array and populates it with the content of the given managed array.
/// </summary>
/// <typeparam name="T">The type of the array's elements.</typeparam>
/// <param name="managedArray">The array from which to copy the content.</param>
/// <returns>A pointer to the start of the unmanaaged array.</returns>
private IntPtr ToUnmanagedArray<T>(IList<T> managedArray)
{
int elementSize = Marshal.SizeOf(typeof(T));
// Allocate the unmanaged array
int arraySize = managedArray.Count * elementSize;
IntPtr unmanagedArray = Marshal.AllocHGlobal(arraySize);
// Populate it from the content of the managed array
for (int i = 0; i < managedArray.Count; ++i)
{
int ptrOffset = i * elementSize;
Marshal.StructureToPtr(managedArray[i], unmanagedArray + ptrOffset, false);
}
return unmanagedArray;
}
}
}

View File

@ -152,9 +152,12 @@
<Compile Include="Scan\Exceptions\ScanDriverUnknownException.cs" />
<Compile Include="Scan\LocalizedDescriptionAttribute.cs" />
<Compile Include="Scan\Stub\StubScanDriver.cs" />
<Compile Include="Unmanaged.cs" />
<Compile Include="Scan\Twain\DibUtils.cs" />
<Compile Include="Scan\Twain\TwainApi.cs" />
<Compile Include="StringWrapper.cs" />
<Compile Include="UnmanagedArray.cs" />
<Compile Include="UnmanagedBase.cs" />
<Compile Include="WinForms\FTwainGui.cs">
<SubType>Form</SubType>
</Compile>

60
NAPS2/Unmanaged.cs Normal file
View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace NAPS2
{
public static class Unmanaged
{
public static Unmanaged<T> CopyOf<T>(T value)
{
return new Unmanaged<T>(value);
}
public static UnmanagedArray<T> CopyOf<T>(T[] value)
{
return new UnmanagedArray<T>(value);
}
}
public class Unmanaged<T> : UnmanagedBase<T>
{
public Unmanaged()
: this(default(T))
{
}
public Unmanaged(T value)
{
if (!ReferenceEquals(value, null))
{
Size = Marshal.SizeOf(typeof(T));
Pointer = Marshal.AllocHGlobal(Size);
Marshal.StructureToPtr(value, Pointer, false);
}
}
~Unmanaged()
{
Dispose();
}
protected override T GetValue()
{
if (Pointer == IntPtr.Zero)
{
// T must be a reference type, so this returns null
return default(T);
}
return (T)Marshal.PtrToStructure(Pointer, typeof(T));
}
protected override void DestroyStructures()
{
Marshal.DestroyStructure(Pointer, typeof(T));
}
}
}

99
NAPS2/UnmanagedArray.cs Normal file
View File

@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace NAPS2
{
public class UnmanagedArray<T> : UnmanagedBase<T[]>
{
public UnmanagedArray(IEnumerable<T> array)
{
ElementSize = Marshal.SizeOf(typeof(T));
if (array != null)
{
T[] arrayVal = array as T[] ?? array.ToArray();
Length = arrayVal.Length;
Size = ElementSize * Length;
Pointer = Marshal.AllocHGlobal(Size);
// Populate the contents of the unmanaged array
for (int i = 0; i < Length; ++i)
{
Marshal.StructureToPtr(arrayVal[i], this[i], false);
}
}
}
~UnmanagedArray()
{
Dispose();
}
/// <summary>
/// Gets the size of each element in the unmanaged array in bytes.
/// </summary>
public int ElementSize { get; private set; }
/// <summary>
/// Gets the number of elements in the unmanaged array.
/// </summary>
public int Length { get; private set; }
/// <summary>
/// Gets the pointer offset to access the element of the unmanaged array at the given index.
/// </summary>
/// <param name="index">The index of the element to access.</param>
/// <returns>The offset, relative to the Pointer.</returns>
public int Offset(int index)
{
if (index < 0 || index >= Length)
{
throw new ArgumentOutOfRangeException("index");
}
return index * ElementSize;
}
/// <summary>
/// Gets the pointer to the element of the unmanaged array at the given index.
/// </summary>
/// <param name="index">The index of the item to access.</param>
/// <returns>The pointer to the element.</returns>
public IntPtr PointerWithOffset(int index)
{
return Pointer + Offset(index);
}
protected override T[] GetValue()
{
var result = new T[Length];
for (int i = 0; i < Length; ++i)
{
result[i] = (T)Marshal.PtrToStructure(this[i], typeof(T));
}
return result;
}
protected override void DestroyStructures()
{
for (int i = 0; i < Length; ++i)
{
Marshal.DestroyStructure(this[i], typeof(T));
}
}
/// <summary>
/// Gets the pointer to the element of the unmanaged array at the given index.
/// </summary>
/// <param name="index">The index of the item to access.</param>
/// <returns>The pointer to the element.</returns>
public IntPtr this[int index]
{
get
{
return PointerWithOffset(index);
}
}
}
}

67
NAPS2/UnmanagedBase.cs Normal file
View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace NAPS2
{
public abstract class UnmanagedBase<T> : IDisposable
{
private bool disposed;
/// <summary>
/// Gets the size of the unmanaged structure in bytes. If the structure is null, this is zero.
/// </summary>
public int Size { get; protected set; }
/// <summary>
/// Gets a value indicated whether the unmanaged structure is null.
/// </summary>
public bool IsNull
{
get
{
return Pointer == IntPtr.Zero;
}
}
/// <summary>
/// Gets a pointer to the unmanaged structure. If the provided value was null, this is IntPtr.Zero.
/// </summary>
public IntPtr Pointer { get; protected set; }
/// <summary>
/// Gets a managed copy of the unmanaged structure.
/// </summary>
public T Value
{
get
{
if (disposed)
{
throw new ObjectDisposedException("unmanaged");
}
return GetValue();
}
}
public void Dispose()
{
if (Pointer != IntPtr.Zero)
{
DestroyStructures();
Marshal.FreeHGlobal(Pointer);
}
disposed = true;
}
protected abstract T GetValue();
protected abstract void DestroyStructures();
public static implicit operator IntPtr(UnmanagedBase<T> unmanaged)
{
return unmanaged.Pointer;
}
}
}