pythonc++dllmanifestwinsxs

How do I load a C DLL from the SXS in Python?


This is typically done by specifying the DLL dependency in a manifest file that resides with the executable. However, I don't know how to accomplish this in Python. Loading the DLL isn't an issue, but rather finding the appropriate DLL in the SXS to load is the problem.

Is there a standard procedure for specifying where to find the DLL ? For this example let's assume it lives here:

c:\windows\winsxs\amd64_my_handy_lib_<public_key_token>_1.0.0.0_none_<some_ID>

Do I REALLY need to manually search the c:\windows\winsxs directory looking for my DLL by name, then check the parent directory to see if it contains the correct version?

I just don't do Python projects enough to know what the appropriate way to accomplish this.


Solution

  • Here's an example that loads the CRT from the WinSxS directory.

    actctx.manifest:

    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
        <dependency>
            <dependentAssembly>
                <assemblyIdentity
                    type="win32"
                    name="Microsoft.VC90.CRT"
                    version="9.0.21022.8"
                    processorArchitecture="amd64"
                    publicKeyToken="1fc8b3b9a1e18e3b">
                </assemblyIdentity>
            </dependentAssembly>
        </dependency>
    </assembly>
    

    actctx.py:

    from ctypes import *
    from ctypes.wintypes import *
    
    kernel32 = WinDLL("kernel32", use_last_error=True)
    
    ACTCTX_FLAG_PROCESSOR_ARCHITECTURE_VALID = 0x001
    ACTCTX_FLAG_LANGID_VALID = 0x002
    ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID = 0x004
    ACTCTX_FLAG_RESOURCE_NAME_VALID = 0x008
    ACTCTX_FLAG_SET_PROCESS_DEFAULT = 0x010
    ACTCTX_FLAG_APPLICATION_NAME_VALID = 0x020
    ACTCTX_FLAG_HMODULE_VALID = 0x080
    DEACTIVATE_ACTCTX_FLAG_FORCE_EARLY_DEACTIVATION = 1
    
    INVALID_HANDLE_VALUE = HANDLE(-1).value
    ULONG_PTR = WPARAM  # pointer-sized unsigned integer
    
    class ACTCTX(Structure):
        _fields_ = (("cbSize", ULONG),
                    ("dwFlags", DWORD),
                    ("lpSource", LPCWSTR),
                    ("wProcessorArchitecture", USHORT),
                    ("wLangId", LANGID),
                    ("lpAssemblyDirectory", LPCWSTR),
                    ("lpResourceName", LPCWSTR),
                    ("lpApplicationName", LPCWSTR),
                    ("hModule", HMODULE))
    
        def __init__(self, *args, **kwds):
            super(ACTCTX, self).__init__(sizeof(self), *args, **kwds)
    
    CreateActCtxW = kernel32.CreateActCtxW
    CreateActCtxW.restype = HANDLE
    CreateActCtxW.argtypes = (POINTER(ACTCTX),)
    ReleaseActCtx = kernel32.ReleaseActCtx
    ReleaseActCtx.restype = None
    ReleaseActCtx.argtypes = (HANDLE,)
    ActivateActCtx = kernel32.ActivateActCtx
    ActivateActCtx.argtypes = (HANDLE, POINTER(ULONG_PTR))
    DeactivateActCtx = kernel32.DeactivateActCtx
    DeactivateActCtx.argtypes = (DWORD, ULONG_PTR)
    
    if __name__ == "__main__":
        manifest_path = "actctx.manifest" # keep ref
        ctx = ACTCTX(lpSource=manifest_path)
        hActCtx = CreateActCtxW(byref(ctx))
        if hActCtx == INVALID_HANDLE_VALUE:
            raise WinError(get_last_error())
    
        cookie = ULONG_PTR()
        if not ActivateActCtx(hActCtx, byref(cookie)):
            raise WinError()
        msvcr90 = CDLL("msvcr90")
        if not DeactivateActCtx(0, cookie):
            raise WinError(get_last_error())
    
        ReleaseActCtx(hActCtx)
    
        # show DLL path
        hModule = HANDLE(msvcr90._handle)
        path = (c_wchar * 260)()    
        kernel32.GetModuleFileNameW(hModule, path, len(path))
        print(path.value)
    

    output:

    C:\Windows\WinSxS\amd64_microsoft.vc90.crt_1fc8b3b9a1e18e3b_9.0.30729.6161_none_08e61857a83bc251\msvcr90.DLL
    

    This was tested under Python 3.4.2, which is built with VS 2010 and links with msvcr100.dll instead. So at least in this case setting the activation context was really required, else loading msvcr90.dll would fail with ERROR_MOD_NOT_FOUND.