Reputation: 1047
I'm doing a project that does some work with certificates and I need to convert the CTL_USAGE structure to C#. The original structure is as follows:
typedef struct _CTL_USAGE {
DWORD cUsageIdentifier;
LPSTR *rgpszUsageIdentifier;
} CTL_USAGE, *PCTL_USAGE, CERT_ENHKEY_USAGE, *PCERT_ENHKEY_USAGE;
According to P/Invoke website, the C# structure should be this:
[StructLayout(LayoutKind.Sequential)]
public struct CTL_USAGE
{
public int cUsageIdentifier;
public IntPtr rgpszUsageIdentifier;
}
To use this code, I convert each string that I want to add to the structure to a byte array using Encoding.ASCII.GetBytes(). I then turn that byte array into a GCHandle using GCHandle.Alloc(byteArray, GCHandleType.Pinned). I add that value to an array of IntPtrs and then create a GCHandle to the IntPtr array and assign it to rgpszUsageIdentifier. The call to CryptEncodeObjectEx doesn't throw an error, but the resulting data is garbage and cannot be unencrypted using CryptDecodeObject. My encoding is as follows:
//EnhancedUsage is a List<String> containing the Enhanced Usage OIDs
CTL_USAGE usage = new CTL_USAGE()
{
cUsageIdentifier = EnhancedUsage.Count,
};
List<IntPtr> usageList = new List<IntPtr>();
foreach (string s in EnhancedUsage)
usageList.Add(new PinnedHandle(Encoding.ASCII.GetBytes(s)));
usage.rgpszUsageIdentifier = new PinnedHandle(usageList.ToArray());
IntPtr data = Marshal.AllocHGlobal(Marshal.SizeOf(usage));
Marshal.StructureToPtr(usage, data, false);
int encodedSize = 0;
if (!Crypt32.CryptEncodeObjectEx((int)CertEncoding.X509Asn, CertOid.szOID_ENHANCED_KEY_USAGE, data, 0, IntPtr.Zero, null, ref encodedSize))
throw new Win32Exception();
byte[] buffer = new byte[encodedSize];
if (!Crypt32.CryptEncodeObjectEx((int)CertEncoding.X509Asn, CertOid.szOID_ENHANCED_KEY_USAGE, data, 0, IntPtr.Zero, buffer, ref encodedSize))
throw new Win32Exception();
The PinnedHandle class is a wrapper for the GCHandle. It looks like this:
public class PinnedHandle : IDisposable
{
private GCHandle handle;
public PinnedHandle(object value)
{
handle = GCHandle.Alloc(value, GCHandleType.Pinned);
}
public static implicit operator IntPtr(PinnedHandle value)
{
return value.handle.AddrOfPinnedObject();
}
public void Dispose()
{
try
{
handle.Free();
}
catch
{
}
}
}
I don't think it's causing any problem here as I've used it in other similar situations and they're working properly. Any idea how to make this work correctly?
Upvotes: 0
Views: 293
Reputation: 43311
Allocate unmanaged memory block with AllocHGLobal
, copy all IntPtr
instances from usageList
to it, and pass this block handle to unmanaged function. Use Marshal.WriteIntPtr Method (IntPtr, Int32, IntPtr)
for this.
http://msdn.microsoft.com/en-us/library/ms146679.aspx
In my interoperability code I avoid pinning managed objects and prefer to build native memory regions, using different Marshal methods.
Upvotes: 1