I'm new to marshaling and will be happy for any advise.
In Windows 8 and Windows Server 2012 we have a new Windows API function: DnsQueryEx
It allows DNS queries to be made asynchronously.
Using C#, I am trying to call it in order to receive MX host records.
As standard, System.Net.DNS is missing support for retrieving MX records with UDP.
I've successfully called DnsQuery API function before using of this example:
http://www.pinvoke.net/default.aspx/dnsapi.DnsQuery
Unfortunately, there are no examples with DnsQueryEx, and i spend some couple of time plaing with possible parameters, but i think i can't find out how I need build DnsAddressArray param.
Why I need to call it asynchronously - because I had developed asynchronous SMTP server, with great socket performance, and I were using DnsQuery function for retrieving MX records, but time to time I am getting DNS error exception there, during call from async context.
Right now, when i am calling DnsQueryEx app process terminates unexpectally, before i were getting error 87 also, which means - wrong parameters.
Also i can't find out how to marshal string to unmanaged code as part of structure?
Here is my code:
private enum QueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}
private enum QueryTypes
{
DNS_TYPE_MX = 15
}
[StructLayout(LayoutKind.Sequential)]
private struct QueryContextStruct
{
public int RefCount;
public IntPtr QueryName;
public short QueryType;
public ulong QueryOptions;
public DnsQueryResult QueryResults;
public CancelHandle QueryCancelContext;
public IntPtr QueryCompletedEvent;
}
[StructLayout(LayoutKind.Sequential)]
private struct DNSQueryRequest
{
public uint Version;
//public string QueryName;
public QueryTypes QueryType;
public QueryOptions QueryOptions;
public IntPtr pDnsServerList;
public uint InterfaceIndex;
public IntPtr pQueryCompletionCallback;
public IntPtr pQueryContext;
}
[StructLayout(LayoutKind.Sequential)]
public struct DnsAddr {
IntPtr MaxSa;
IntPtr DnsAddrUserDword;
}
[StructLayout(LayoutKind.Sequential)]
public struct DnsAddrArray
{
public uint MaxCount;
public uint AddrCount;
public uint Tag;
public ushort Family;
public ushort WordReserved;
public uint Flags;
public uint MatchFlag;
public uint Reserved1;
public uint Reserved2;
public IntPtr AddrArray;
}
[StructLayout(LayoutKind.Sequential)]
private class DnsQueryResult
{
public uint Version;
public int QueryStatus;
public ulong QueryOptions;
public IntPtr pQueryRecords;
public IntPtr reserved;
}
[StructLayout(LayoutKind.Sequential)]
private class CancelHandle
{
public byte Handle = 32;
}
[DllImport("dnsapi")]
private static extern int DnsQueryEx(IntPtr pQueryRequest, IntPtr pQueryResults, IntPtr pCancelHandle);
delegate void DnsReceivedResultDelegate(IntPtr parameter);
public static void ReceiveMxAsync(string domain)
{
// Ensure that this thread's identity is mapped, 1-to-1, with a native OS thread.
Thread.BeginThreadAffinity();
GCHandle requestHandle = default(GCHandle);
GCHandle resultHandle = default(GCHandle);
GCHandle dnsListHandle = default(GCHandle);
GCHandle dnsHandle = default(GCHandle);
GCHandle cancelHandle = default(GCHandle);
DnsReceivedResultDelegate changeDelegate = ReceivedStatusChangedEvent;
IntPtr hSCM = IntPtr.Zero;
IntPtr hService = IntPtr.Zero;
try
{
//request
DNSQueryRequest queryRequest = new DNSQueryRequest();
queryRequest.Version = 0x1;
queryRequest.QueryType = QueryTypes.DNS_TYPE_MX;
queryRequest.QueryOptions = QueryOptions.DNS_QUERY_BYPASS_CACHE;
DnsAddr address = new DnsAddr();
dnsHandle = GCHandle.Alloc(address, GCHandleType.Pinned);
IntPtr DnsStructure = dnsHandle.AddrOfPinnedObject();
//getting server list
DnsAddrArray addressArray = new DnsAddrArray();
addressArray.MaxCount = 1;
addressArray.AddrCount = 1;
addressArray.AddrArray = DnsStructure;
dnsListHandle = GCHandle.Alloc(addressArray, GCHandleType.Pinned);
IntPtr pinnedDnsStructure = dnsListHandle.AddrOfPinnedObject();
queryRequest.pDnsServerList = pinnedDnsStructure;
queryRequest.InterfaceIndex = 0;
queryRequest.pQueryCompletionCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
requestHandle = GCHandle.Alloc(queryRequest, GCHandleType.Pinned);
IntPtr pinnedRequestStructure = requestHandle.AddrOfPinnedObject();
//result
DnsQueryResult result = new DnsQueryResult();
result.Version = 0x1;
resultHandle = GCHandle.Alloc(result, GCHandleType.Pinned);
IntPtr pinnedResultStructure = resultHandle.AddrOfPinnedObject();
//cancel handle
CancelHandle cancel = new CancelHandle();
cancelHandle = GCHandle.Alloc(cancel, GCHandleType.Pinned);
IntPtr pinnedCancleStructure = cancelHandle.AddrOfPinnedObject();
var res = DnsQueryEx(pinnedRequestStructure, pinnedResultStructure, pinnedCancleStructure);
//timeout
//SleepEx(5000, true);
if (res > 0)
return;
}
catch (Exception ex)
{
if (ex != null)
return;
}
finally
{
GC.KeepAlive(changeDelegate);
if (dnsListHandle != default(GCHandle))
{
dnsListHandle.Free();
}
if (dnsHandle != default(GCHandle))
{
dnsHandle.Free();
}
if (requestHandle != default(GCHandle))
{
requestHandle.Free();
}
if (resultHandle != default(GCHandle))
{
resultHandle.Free();
}
if (cancelHandle != default(GCHandle))
{
cancelHandle.Free();
}
Thread.EndThreadAffinity();
}
}
private static void DnsReceivedResultEvent(IntPtr parameter, DnsQueryResult result)
{
}
//==================================================
UPDATE: I have changed code as this:
private enum QueryOptions
{
DNS_QUERY_ACCEPT_TRUNCATED_RESPONSE = 1,
DNS_QUERY_BYPASS_CACHE = 8,
DNS_QUERY_DONT_RESET_TTL_VALUES = 0x100000,
DNS_QUERY_NO_HOSTS_FILE = 0x40,
DNS_QUERY_NO_LOCAL_NAME = 0x20,
DNS_QUERY_NO_NETBT = 0x80,
DNS_QUERY_NO_RECURSION = 4,
DNS_QUERY_NO_WIRE_QUERY = 0x10,
DNS_QUERY_RESERVED = -16777216,
DNS_QUERY_RETURN_MESSAGE = 0x200,
DNS_QUERY_STANDARD = 0,
DNS_QUERY_TREAT_AS_FQDN = 0x1000,
DNS_QUERY_USE_TCP_ONLY = 2,
DNS_QUERY_WIRE_ONLY = 0x100
}
private enum QueryTypes
{
DNS_TYPE_MX = 15
}
[StructLayout(LayoutKind.Sequential)]
private struct QueryContextStruct
{
public int RefCount;
public IntPtr QueryName;
public short QueryType;
public ulong QueryOptions;
public DnsQueryResult QueryResults;
public CancelHandle QueryCancelContext;
public IntPtr QueryCompletedEvent;
}
[StructLayout(LayoutKind.Sequential)]
private struct DNSQueryRequest
{
public uint Version;
[MarshalAs(UnmanagedType.LPWStr)]
public string QueryName;
public QueryTypes QueryType;
public QueryOptions QueryOptions;
public IntPtr pDnsServerList;
public uint InterfaceIndex;
public IntPtr pQueryCompletionCallback;
public IntPtr pQueryContext;
}
[StructLayout(LayoutKind.Sequential)]
public struct DnsAddr
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 32500)] //todo: DNS_ADDR_MAX_SOCKADDR_LENGTH
public byte[] MaxSa;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public uint[] DnsAddrUserDword;
}
[StructLayout(LayoutKind.Sequential)]
public struct DnsAddrArray
{
public uint MaxCount;
public uint AddrCount;
public uint Tag;
public ushort Family;
public ushort WordReserved;
public uint Flags;
public uint MatchFlag;
public uint Reserved1;
public uint Reserved2;
public DnsAddr AddrArray;
}
[StructLayout(LayoutKind.Sequential)]
private class DnsQueryResult
{
public uint Version;
public int QueryStatus;
public ulong QueryOptions;
public IntPtr pQueryRecords;
public IntPtr reserved;
}
public unsafe struct CancelHandle
{
private fixed byte Reserved[32];
}
[DllImport("dnsapi")]
private static extern int DnsQueryEx(IntPtr pQueryRequest, IntPtr pQueryResults, IntPtr pCancelHandle);
delegate void DnsReceivedResultDelegate(IntPtr parameter);
public static void ReceiveMxAsync(string domain)
{
// Ensure that this thread's identity is mapped, 1-to-1, with a native OS thread.
Thread.BeginThreadAffinity();
GCHandle requestHandle = default(GCHandle);
GCHandle resultHandle = default(GCHandle);
GCHandle dnsListHandle = default(GCHandle);
GCHandle dnsHandle = default(GCHandle);
GCHandle cancelHandle = default(GCHandle);
DnsReceivedResultDelegate changeDelegate = ReceivedStatusChangedEvent;
IntPtr hSCM = IntPtr.Zero;
IntPtr hService = IntPtr.Zero;
try
{
//request
DNSQueryRequest queryRequest = new DNSQueryRequest();
queryRequest.Version = 0x1;
queryRequest.QueryName = domain;
queryRequest.QueryType = QueryTypes.DNS_TYPE_MX;
queryRequest.QueryOptions = QueryOptions.DNS_QUERY_BYPASS_CACHE;
queryRequest.pDnsServerList = Marshal.AllocHGlobal(Marshal.SizeOf(queryRequest.pDnsServerList));
queryRequest.InterfaceIndex = 0;
queryRequest.pQueryCompletionCallback = Marshal.GetFunctionPointerForDelegate(changeDelegate);
var dnsServerAddrList = new DnsAddrArray();
dnsServerAddrList.MaxCount = (uint)Marshal.SizeOf(dnsServerAddrList);
dnsServerAddrList.AddrArray = new DnsAddr();
Marshal.StructureToPtr(dnsServerAddrList, queryRequest.pDnsServerList, true);
IntPtr pinnedRequestStructure = Marshal.AllocHGlobal(Marshal.SizeOf(queryRequest));
//result
DnsQueryResult result = new DnsQueryResult();
result.Version = 0x1;
resultHandle = GCHandle.Alloc(result, GCHandleType.Pinned);
IntPtr pinnedResultStructure = resultHandle.AddrOfPinnedObject();
//cancel handle
CancelHandle cancel = new CancelHandle();
cancelHandle = GCHandle.Alloc(cancel, GCHandleType.Pinned);
IntPtr pinnedCancleStructure = cancelHandle.AddrOfPinnedObject();
//getting server list
var res = DnsQueryEx(pinnedRequestStructure, pinnedResultStructure, pinnedCancleStructure);
//timeout
//SleepEx(5000, true);
if (res > 0)
return;
}
catch (Exception ex)
{
if (ex != null)
return;
}
finally
{
GC.KeepAlive(changeDelegate);
if (dnsListHandle != default(GCHandle))
{
dnsListHandle.Free();
}
if (dnsHandle != default(GCHandle))
{
dnsHandle.Free();
}
if (requestHandle != default(GCHandle))
{
requestHandle.Free();
}
if (resultHandle != default(GCHandle))
{
resultHandle.Free();
}
if (cancelHandle != default(GCHandle))
{
cancelHandle.Free();
}
Thread.EndThreadAffinity();
}
}
private static void DnsReceivedResultEvent(IntPtr parameter, DnsQueryResult result)
{
//SUCESS!
}
//==================================================
Without luck, still error 87 in response... I think i need to init AddrArray structure with right server's parameters, i would appreciate any help... Here we have C++ working example:
It should be possible to use this as C++ lib, but i would like to avoid one more gateway, and unfortunately i am week in C++...