我已经搞定了。我在“本机”Obj-C 代码中缺少CFSocketNativeHandle
类似的东西,我猜它会将 CFNetwork->CFSocket 转换为本机文件描述符指针。
这是我的完整实现(C# 扩展):
public static class CFSocketExtensions
{
[DllImport(Constants.CoreFoundationLibrary)]
extern static nint CFSocketSendData(IntPtr handle, IntPtr address, IntPtr data, double timeout);
[DllImport("libc", SetLastError = true)]
extern static int setsockopt(CFSocketNativeHandle s, int level, int optname, IntPtr optval, int optlen);
[DllImport(Constants.CoreFoundationLibrary)]
extern static CFSocketNativeHandle CFSocketGetNative(IntPtr handle);
public static void SendData(this CFSocket socket, CFSocketAddress address, byte[] data, double timeout)
{
using (var buffer = new CFDataBuffer(data))
{
var error = (CFSocketError)(long)CFSocketSendData(socket.Handle, address.Handle, buffer.Handle, timeout);
if (error != CFSocketError.Success)
throw new CFSocketException(error);
}
}
public static bool EnableBroadcast(this CFSocket socket, bool enable = true)
{
int size = Marshal.SizeOf<int>();
IntPtr pBool = Marshal.AllocHGlobal(size);
Marshal.WriteInt32(pBool, 0, enable ? 1 : 0); // last parameter 0 (FALSE), 1 (TRUE)
var res = setsockopt(CFSocketGetNative(socket.Handle), (int)SocketOptionLevel.Socket, (int)SocketOptionName.Broadcast, pBool, size);
Marshal.FreeHGlobal(pBool);
return res > -1;
}
}
public class CFSocketAddress : CFDataBuffer
{
public CFSocketAddress(IPEndPoint endpoint)
: base(CreateData(endpoint))
{
}
internal static IPEndPoint EndPointFromAddressPtr(IntPtr address)
{
using (var buffer = new CFDataBuffer(address))
{
if (buffer[1] == 30)
{ // AF_INET6
int port = (buffer[2] << 8) + buffer[3];
var bytes = new byte[16];
Buffer.BlockCopy(buffer.Data, 8, bytes, 0, 16);
return new IPEndPoint(new IPAddress(bytes), port);
}
else if (buffer[1] == 2)
{ // AF_INET
int port = (buffer[2] << 8) + buffer[3];
var bytes = new byte[4];
Buffer.BlockCopy(buffer.Data, 4, bytes, 0, 4);
return new IPEndPoint(new IPAddress(bytes), port);
}
else
{
throw new ArgumentException();
}
}
}
static byte[] CreateData(IPEndPoint endpoint)
{
if (endpoint == null)
throw new ArgumentNullException("endpoint");
if (endpoint.AddressFamily == AddressFamily.InterNetwork)
{
var buffer = new byte[16];
buffer[0] = 16;
buffer[1] = 2; // AF_INET
buffer[2] = (byte)(endpoint.Port >> 8);
buffer[3] = (byte)(endpoint.Port & 0xff);
Buffer.BlockCopy(endpoint.Address.GetAddressBytes(), 0, buffer, 4, 4);
return buffer;
}
else if (endpoint.AddressFamily == AddressFamily.InterNetworkV6)
{
var buffer = new byte[28];
buffer[0] = 32;
buffer[1] = 30; // AF_INET6
buffer[2] = (byte)(endpoint.Port >> 8);
buffer[3] = (byte)(endpoint.Port & 0xff);
Buffer.BlockCopy(endpoint.Address.GetAddressBytes(), 0, buffer, 8, 16);
return buffer;
}
else
{
throw new ArgumentException();
}
}
}
public static class CFObject
{
[DllImport(Constants.CoreFoundationLibrary)]
internal extern static void CFRelease(IntPtr obj);
[DllImport(Constants.CoreFoundationLibrary)]
internal extern static IntPtr CFRetain(IntPtr obj);
}
public class CFData : INativeObject, IDisposable
{
internal IntPtr handle;
public CFData(IntPtr handle)
: this(handle, false)
{
}
public CFData(IntPtr handle, bool owns)
{
if (!owns)
CFObject.CFRetain(handle);
this.handle = handle;
}
~CFData()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public IntPtr Handle
{
get { return handle; }
}
[DllImport(Constants.CoreFoundationLibrary, EntryPoint = "CFDataGetTypeID")]
public extern static /* CFTypeID */ nint GetTypeID();
protected virtual void Dispose(bool disposing)
{
if (handle != IntPtr.Zero)
{
CFObject.CFRelease(handle);
handle = IntPtr.Zero;
}
}
public nint Length
{
get { return CFDataGetLength(handle); }
}
[DllImport(Constants.CoreFoundationLibrary)]
extern static /* CFIndex */ nint CFDataGetLength(/* CFDataRef */ IntPtr theData);
public byte[] GetBuffer()
{
var buffer = new byte[Length];
var ptr = CFDataGetBytePtr(handle);
Marshal.Copy(ptr, buffer, 0, buffer.Length);
return buffer;
}
[DllImport(Constants.CoreFoundationLibrary)]
extern static /* UInt8* */ IntPtr CFDataGetBytePtr(/* CFDataRef */ IntPtr theData);
/*
* Exposes a read-only pointer to the underlying storage.
*/
public IntPtr Bytes
{
get { return CFDataGetBytePtr(handle); }
}
[DllImport(Constants.CoreFoundationLibrary)]
extern static /* CFDataRef */ IntPtr CFDataCreate(/* CFAllocatorRef */ IntPtr allocator, /* UInt8* */ IntPtr bytes, /* CFIndex */ nint length);
public static CFData FromData(IntPtr buffer, nint length)
{
return new CFData(CFDataCreate(IntPtr.Zero, buffer, length), true);
}
[DllImport(Constants.CoreFoundationLibrary)]
extern static /* CFDataRef */ IntPtr CFDataCreateCopy(/* CFAllocatorRef */ IntPtr allocator, /* CFDataRef */ IntPtr theData);
public CFData Copy()
{
return new CFData(CFDataCreateCopy(IntPtr.Zero, Handle), true);
}
}
public class CFDataBuffer : IDisposable
{
byte[] buffer;
CFData data;
public CFDataBuffer(byte[] buffer)
{
this.buffer = buffer;
/*
* Copy the buffer to allow the native side to take ownership.
*/
//fixed (byte* ptr = buffer)
// data = CFData.FromData((IntPtr)ptr, buffer.Length);
GCHandle pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned);
data = CFData.FromData(pinnedBuffer.AddrOfPinnedObject(), buffer.Length);
pinnedBuffer.Free();
}
public CFDataBuffer(IntPtr ptr)
{
data = new CFData(ptr, false);
buffer = data.GetBuffer();
}
~CFDataBuffer()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public IntPtr Handle
{
get { return data.Handle; }
}
public byte[] Data
{
get { return buffer; }
}
public byte this[int idx]
{
get { return buffer[idx]; }
}
protected virtual void Dispose(bool disposing)
{
if (data != null)
{
data.Dispose();
data = null;
}
}
}
此实现的唯一缺点是 CFSocketAddress、CFDataBuffer 等,它们在源代码中被定义为内部类。