mmap:无法在不知道其大小的情况下附加到现有区域(Windows)

时间:2015-07-18 20:50:21

标签: python windows mmap

我正在尝试附加到另一个应用程序创建的现有共享内存区域,而不是用Python编写(这是它的插件模块如何相互通信)。在Windows上,它使用命名的内核对象而不是文件系统中的文件; Python的mmap模块通过tagname参数支持此功能。问题是我无法事先知道共享区域的大小是什么 - 这是另一个应用程序的配置参数,它是根据预期的数据量进行调整的。对于基于文件的共享区域,为大小传递零使用文件的现有大小,但这显然不适用于标记区域。这是我正在尝试的简化版本:

import mmap, random

TAGNAME = 'SHM_1001'

# This is a simulation of what the other application does.
# The size isn't actually random, I simply don't know in advance what it is.
m1 = mmap.mmap(-1, random.randint(1e3, 1e6), TAGNAME)

# This is what I'm trying to do in my application, to attach to the same region.
m2 = mmap.mmap(-1, 0, TAGNAME)
# WindowsError: [Error 87] The parameter is incorrect

如果我指定一个小的非零大小,那么我可以成功地附加到该区域 - 但是当然我只能在该区域的开头访问那么多字节。如果我指定的大小大于该区域的实际大小(可能等于它可能具有的最大大小),则会出现访问错误。 Python 2.7和3.4都存在这个问题。

为大小传递零的方法肯定适用于系统调用级别 - 这正是该应用程序的每个现有C / C ++插件的工作原理 - 因此问题显然在Python的mmap()调用包装器中。关于如何让它发挥作用的任何想法?

2 个答案:

答案 0 :(得分:1)

CreateFileMapping中的参数验证在调用系统服务NtCreateSection之前发生错误,如果调用它将找到现有部分。当hFileINVALID_HANDLE_VALUE( - 1)时使用0大小无效,因为CreateFileMapping假设(在这种情况下错误地)该段需要从页面文件中分配。我假设C插件正在调用OpenFileMapping(即NtOpenSection)。

您可以使用ctypes,PyWin32或C扩展模块。致电OpenFileMappingW后,请致电MapViewOfFile,然后致电VirtualQuery以获取映射的区域大小,向上舍入到页边界。

以下是使用ctypes的示例。

from ctypes import *
from ctypes.wintypes import *

kernel32 = WinDLL('kernel32', use_last_error=True)

FILE_MAP_COPY       = 0x0001
FILE_MAP_WRITE      = 0x0002
FILE_MAP_READ       = 0x0004
FILE_MAP_ALL_ACCESS = 0x001f
FILE_MAP_EXECUTE    = 0x0020

PVOID = LPVOID
SIZE_T = c_size_t

class MEMORY_BASIC_INFORMATION(Structure):
    _fields_ = (('BaseAddress',       PVOID),
                ('AllocationBase',    PVOID),
                ('AllocationProtect', DWORD),
                ('RegionSize',        SIZE_T),
                ('State',             DWORD),
                ('Protect',           DWORD),
                ('Type',              DWORD))

PMEMORY_BASIC_INFORMATION = POINTER(MEMORY_BASIC_INFORMATION)

def errcheck_bool(result, func, args):
    if not result:
        raise WinError(get_last_error())
    return args

kernel32.VirtualQuery.errcheck = errcheck_bool
kernel32.VirtualQuery.restype = SIZE_T
kernel32.VirtualQuery.argtypes = (
    LPCVOID,                   # _In_opt_ lpAddress
    PMEMORY_BASIC_INFORMATION, # _Out_    lpBuffer
    SIZE_T)                    # _In_     dwLength

kernel32.OpenFileMappingW.errcheck = errcheck_bool
kernel32.OpenFileMappingW.restype = HANDLE
kernel32.OpenFileMappingW.argtypes = (
    DWORD,   # _In_ dwDesiredAccess
    BOOL,    # _In_ bInheritHandle
    LPCWSTR) # _In_ lpName

kernel32.MapViewOfFile.errcheck = errcheck_bool
kernel32.MapViewOfFile.restype = LPVOID
kernel32.MapViewOfFile.argtypes = (
    HANDLE, # _In_ hFileMappingObject
    DWORD,  # _In_ dwDesiredAccess
    DWORD,  # _In_ dwFileOffsetHigh
    DWORD,  # _In_ dwFileOffsetLow
    SIZE_T) # _In_ dwNumberOfBytesToMap

kernel32.CloseHandle.errcheck = errcheck_bool
kernel32.CloseHandle.argtypes = (HANDLE,)

if __name__ == '__main__':
    import mmap

    NPAGES = 9
    PAGE_SIZE = 4096

    TAGNAME = 'SHM_1001'
    mm1 = mmap.mmap(-1, PAGE_SIZE * NPAGES, TAGNAME)

    hMap = kernel32.OpenFileMappingW(FILE_MAP_ALL_ACCESS, False, TAGNAME)
    pBuf = kernel32.MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0)
    kernel32.CloseHandle(hMap)

    mbi = MEMORY_BASIC_INFORMATION()
    kernel32.VirtualQuery(pBuf, byref(mbi), PAGE_SIZE)

    assert divmod(mbi.RegionSize, PAGE_SIZE) == (NPAGES, 0)
    mm2 = (c_char * mbi.RegionSize).from_address(pBuf)

    # write using the mmap object
    mm1.seek(100)
    mm1.write(b'Windows')

    # read using the ctypes array
    assert mm2[100:107] == b'Windows'

答案 1 :(得分:0)

它应该像这样工作:

  

如果length大于文件的当前大小,则文件为   扩展为包含长度字节。如果长度为0,则为最大长度   映射的大小是文件的当前大小,除非文件是   空Windows引发异常(您无法创建空映射   在Windows上。)

但目前看来,这是一个已知的错误:http://www.gossamer-threads.com/lists/python/bugs/571941?search_string=1733986;#571941