Reputation: 79
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++...
Upvotes: 2
Views: 1058
Reputation: 613242
tl;dr use C++/CLI to call this exceedingly complex API.
I don't have Windows 8 at hand, so I cannot execute anything. However, I can find a number of errors in the code. I'll list them below. I expect that there will be more errors than I have found, but hopefully this helps.
DNS_QUERY_CANCEL
typedef struct _DNS_QUERY_CANCEL {
CHAR Reserved[32];
} DNS_QUERY_CANCEL, *PDNS_QUERY_CANCEL;
This is a struct containing an array of char
, of length 32. Your translation is incorrect. A literal translation is tricky because you must create types that can be pinned. If you could use an unsafe
context then you could use a fixed size buffer.
public unsafe struct DnsQueryCancel
{
private fixed byte Reserved[32];
}
Otherwise you could just put some fields in to give a struct the right length. That's fine because you do not need to read or write to this struct.
public struct DnsQueryCancel
{
private long Reserved1;
private long Reserved2;
private long Reserved3;
private long Reserved4;
}
DNS_ADDR_ARRAY
typedef struct _DnsAddrArray {
DWORD MaxCount;
DWORD AddrCount;
DWORD Tag;
WORD Family;
WORD WordReserved;
DWORD Flags;
DWORD MatchFlag;
DWORD Reserved1;
DWORD Reserved2;
DNS_ADDR AddrArray[ ];
} DNS_ADDR_ARRAY, *PDNS_ADDR_ARRAY;
Your translation is incorrect. The final field is not a pointer. It is an inline array. The struct is therefore variable sized. You cannot declare that in a way that allows it to be pinned. You'd need to do the marshal manually, to unmanaged memory. If you only need to support structs with AddrCount
of 1
then you can declare the struct like this:
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;
}
Note that MaxCount
is the size in bytes of the struct. You are setting that incorrectly.
DNS_ADDR
typedef struct _Dns_Addr {
CHAR MaxSa[DNS_ADDR_MAX_SOCKADDR_LENGTH];
DWORD DnsAddrUserDword[8];
} DNS_ADDR, *PDNS_ADDR;
Again, you get this badly wrong in the C#. Assuming the use of fixed this would be:
public unsafe struct DnsAddr
{
public fixed byte MaxSa[DNS_ADDR_MAX_SOCKADDR_LENGTH];
public fixed uint DnsAddrUserDword[8];
}
However, fixed buffers are very inconvenient to work with. So I would probably do this as:
public struct DnsAddr
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = DNS_ADDR_MAX_SOCKADDR_LENGTH)]
public byte[] MaxSa[];
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public uint[] DnsAddrUserDword;
}
Then you'd need to use Marshal.StructureToPtr
and Marshal.PtrToStructure
for the marshalling. The use of UnmanagedType.ByValArray
renders pinning impossible.
Summary
I'm pretty sure that I'm only scratching the surface here. This is a quite horrible interface to attempt to call from C#. I simply would not attempt to do so, and I think I'm reasonably proficient at p/invoke. The smart move here is to use the supplied Windows header files from a mixed mode C++/CLI assembly. I urge you to consider that option.
Upvotes: 1