传统的应用程序间跨进程高速的传输数据有多种方式,作为Win32下的主流,很多程序中都会看到CreateFileMapping函数,这让大家分享一下如何合理而且安全的建立一个内核对象,以及整个环境的背景和作业流程。


        [StructLayout(LayoutKind.Sequential)]
        internal class SECURITY_ATTRIBUTES
        {
            public int nLength;
            public SafeLocalMemHandle lpSecurityDescriptor;
            public bool bInheritHandle;
            public SECURITY_ATTRIBUTES()
            {
                this.nLength = 12;
                this.lpSecurityDescriptor = new SafeLocalMemHandle(IntPtr.Zero, false);
            }
        }


        [SuppressUnmanagedCodeSecurity, HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
        internal sealed class SafeFileMappingHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
            internal SafeFileMappingHandle() : base(true) { }
            [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
            internal SafeFileMappingHandle(IntPtr handle, bool ownsHandle)
                : base(ownsHandle)
            {
                SetHandle(handle);
            }
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
            private static extern bool CloseHandle(IntPtr handle);
            protected override bool ReleaseHandle()
            {
                return CloseHandle(handle);
            }

        }

 

        [SuppressUnmanagedCodeSecurity, HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)]
        internal sealed class SafeLocalMemHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            internal SafeLocalMemHandle() : base(true) { }
            [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
            internal SafeLocalMemHandle(IntPtr existingHandle, bool ownsHandle)
                : base(ownsHandle)
            {
                SetHandle(existingHandle);
            }
            [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            internal static extern bool ConvertStringSecurityDescriptorToSecurityDescriptor(string StringSecurityDescriptor, int StringSDRevision, out SafeLocalMemHandle pSecurityDescriptor, IntPtr SecurityDescriptorSize);
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll")]
            private static extern IntPtr LocalFree(IntPtr hMem);
            protected override bool ReleaseHandle()
            {
                return (LocalFree(handle) == IntPtr.Zero);
            }
        }

        internal sealed class SafeViewOfFileHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
            internal SafeViewOfFileHandle() : base(true) { }
            [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)]
            internal SafeViewOfFileHandle(IntPtr handle, bool ownsHandle) : base(ownsHandle) { SetHandle(handle); }
            protected override bool ReleaseHandle()
            {
                if (UnmapViewOfFile(handle))
                {
                    base.handle = IntPtr.Zero;
                    return true;
                }
                return false;
            }
        }

 


        const int FILE_MAP_COPY = 0x0001;
        const int FILE_MAP_WRITE = 0x0002;
        const int FILE_MAP_READ = 0x0004;
        const int FILE_MAP_ALL_ACCESS = 0x0002 | 0x0004;


        const int PAGE_READONLY = 0x02;
        const int PAGE_READWRITE = 0x04;
        const int PAGE_WRITECOPY = 0x08;
        const int PAGE_EXECUTE = 0x10;
        const int PAGE_EXECUTE_READ = 0x20;
        const int PAGE_EXECUTE_READWRITE = 0x40;

        const int SEC_COMMIT = 0x8000000;
        const int SEC_IMAGE = 0x1000000;
        const int SEC_NOCACHE = 0x10000000;
        const int SEC_RESERVE = 0x4000000;

 

        const int INVALID_HANDLE_VALUE = -1;

 

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern SafeFileMappingHandle OpenFileMapping(int dwDesiredAccess, bool bInheritHandle, string lpName);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern SafeFileMappingHandle CreateFileMapping(IntPtr hFile, SECURITY_ATTRIBUTES lpFileMappingAttributes, int flProtect, int dwMaximumSizeHigh, int dwMaximumSizeLow, string lpName);
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("kernel32.dll", ExactSpelling = true)]
        static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern SafeViewOfFileHandle MapViewOfFile(SafeFileMappingHandle handle, uint dwDesiredAccess, uint dwFileOffsetHigh, uint dwFileOffsetLow, UIntPtr dwNumerOfBytesToMap);


安全的建立非托管平台的handle是整篇我推崇的地方,因为内核对象如果以外的未能释放会产生内存泄漏,危害很严重。.net平台下PInvoke支持多种封送策略,所以尽量的使用安全的handle显得尤为重要。

下面简要讲一下环境,方法流程和顺序参数。
环境:
文件映射的主体是文件,所有的映射会把文件流在当前建立视图的进程区建立一个地址映射,对这个内存地址的作业,就是对文件的作业,由于是在内存中建立了映射,所以每个进程可以独立的读写自己的映射区,最后可以调用方法同步刷新到文件流上(本文内未实现)。  更为详细的介绍请自行google,需要注意的是命名对象的建立会和建立的进程相关。

方法流程和顺序参数:
CreateFileMapping 第一位是一个文件的handle,如果用INVALID_HANDLE_VALUE将会使用默认的内存页文件作为映射。第二位是一个安全设置,为空的话会使用默认的安全控制表。第三位是读写权限和映射设置,支持OR操作。第三位是64位最大高位地址(为0,因为不会有这么大的文件)第四位是64位最大低位地址(未实际对象的物理内存长度)第五位是命名,为空是匿名对象,这里不讨论。


OpenFileMapping 第一位访问权限,第二位指定子进程是否可以共享该handle资源(涉及到handle的复制)。第三位就是命名。


MapViewOfFile第一位是上面两个方法获得的handle实例,第二位是访问权限,第三和第四位同CreateFileMapping意义上是视图相对的位移量。第五位是映射字节数,为0则映射整个建立块区。

从建立(或打开)映射开始,到建立视图,现在就可以在.net环境下使用对内存的读写了。
可以使用Marshal类的Copy方法,前面建立的安全handle上的DangerousGetHandle()方法将返回作业的安全指针。

handle是可以自释放的。




好了到了结束部分了,本来打算用这来实现某些功能,后来发现不太符合我的需求,结果这些代码就要被我废弃了,大致上我看了十个不到的实现特例,其中以C++为主,而且这些作者都习惯性的作了包装类。

请看完这篇以后参考我的建议,不要在这之上再更多的去包装,对于简单的数据传送和共享已经够了,我觉得毫无必要再繁琐下去加一个自己命名的class也不会突现什么万一设计不当反而会招人唾骂......当然如果刻意要做什么,除非你也往里面加了很多原子操作以及其他的同步作业控制,我也不能飞过来反对。就这样吧,希望大家可以都学到知识。

 
最后这些方法和常量完全的经过测试,请放心使用。