mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Initial stab at replacing use of MemoryModule
This commit is contained in:
parent
923048a387
commit
77a0558cf2
@ -1,488 +0,0 @@
|
|||||||
/*
|
|
||||||
* Memory DLL loading code
|
|
||||||
* Version 0.0.3
|
|
||||||
*
|
|
||||||
* Copyright (c) 2004-2012 by Joachim Bauch / mail@joachim-bauch.de
|
|
||||||
* http://www.joachim-bauch.de
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public License Version
|
|
||||||
* 2.0 (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
* http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
||||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing rights and limitations under the
|
|
||||||
* License.
|
|
||||||
*
|
|
||||||
* The Original Code is MemoryModule.c
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is Joachim Bauch.
|
|
||||||
*
|
|
||||||
* Portions created by Joachim Bauch are Copyright (C) 2004-2012
|
|
||||||
* Joachim Bauch. All Rights Reserved.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __GNUC__
|
|
||||||
// disable warnings about pointer <-> DWORD conversions
|
|
||||||
#pragma warning( disable : 4311 4312 )
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef _WIN64
|
|
||||||
#define POINTER_TYPE ULONGLONG
|
|
||||||
#else
|
|
||||||
#define POINTER_TYPE DWORD
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
#include <winnt.h>
|
|
||||||
#ifdef DEBUG_OUTPUT
|
|
||||||
#include <stdio.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifndef IMAGE_SIZEOF_BASE_RELOCATION
|
|
||||||
// Vista SDKs no longer define IMAGE_SIZEOF_BASE_RELOCATION!?
|
|
||||||
#define IMAGE_SIZEOF_BASE_RELOCATION (sizeof(IMAGE_BASE_RELOCATION))
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include "MemoryModule.h"
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
PIMAGE_NT_HEADERS headers;
|
|
||||||
unsigned char *codeBase;
|
|
||||||
HMODULE *modules;
|
|
||||||
int numModules;
|
|
||||||
int initialized;
|
|
||||||
} MEMORYMODULE, *PMEMORYMODULE;
|
|
||||||
|
|
||||||
typedef BOOL (WINAPI *DllEntryProc)(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved);
|
|
||||||
|
|
||||||
#define GET_HEADER_DICTIONARY(module, idx) &(module)->headers->OptionalHeader.DataDirectory[idx]
|
|
||||||
|
|
||||||
#ifdef DEBUG_OUTPUT
|
|
||||||
static void
|
|
||||||
OutputLastError(const char *msg)
|
|
||||||
{
|
|
||||||
LPVOID tmp;
|
|
||||||
char *tmpmsg;
|
|
||||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
||||||
NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&tmp, 0, NULL);
|
|
||||||
tmpmsg = (char *)LocalAlloc(LPTR, strlen(msg) + strlen(tmp) + 3);
|
|
||||||
sprintf(tmpmsg, "%s: %s", msg, tmp);
|
|
||||||
OutputDebugString(tmpmsg);
|
|
||||||
LocalFree(tmpmsg);
|
|
||||||
LocalFree(tmp);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static void
|
|
||||||
CopySections(const unsigned char *data, PIMAGE_NT_HEADERS old_headers, PMEMORYMODULE module)
|
|
||||||
{
|
|
||||||
int i, size;
|
|
||||||
unsigned char *codeBase = module->codeBase;
|
|
||||||
unsigned char *dest;
|
|
||||||
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers);
|
|
||||||
for (i=0; i<module->headers->FileHeader.NumberOfSections; i++, section++) {
|
|
||||||
if (section->SizeOfRawData == 0) {
|
|
||||||
// section doesn't contain data in the dll itself, but may define
|
|
||||||
// uninitialized data
|
|
||||||
size = old_headers->OptionalHeader.SectionAlignment;
|
|
||||||
if (size > 0) {
|
|
||||||
dest = (unsigned char *)VirtualAlloc(codeBase + section->VirtualAddress,
|
|
||||||
size,
|
|
||||||
MEM_COMMIT,
|
|
||||||
PAGE_READWRITE);
|
|
||||||
|
|
||||||
section->Misc.PhysicalAddress = (DWORD)dest;
|
|
||||||
memset(dest, 0, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// section is empty
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// commit memory block and copy data from dll
|
|
||||||
dest = (unsigned char *)VirtualAlloc(codeBase + section->VirtualAddress,
|
|
||||||
section->SizeOfRawData,
|
|
||||||
MEM_COMMIT,
|
|
||||||
PAGE_READWRITE);
|
|
||||||
memcpy(dest, data + section->PointerToRawData, section->SizeOfRawData);
|
|
||||||
section->Misc.PhysicalAddress = (DWORD)dest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Protection flags for memory pages (Executable, Readable, Writeable)
|
|
||||||
static int ProtectionFlags[2][2][2] = {
|
|
||||||
{
|
|
||||||
// not executable
|
|
||||||
{PAGE_NOACCESS, PAGE_WRITECOPY},
|
|
||||||
{PAGE_READONLY, PAGE_READWRITE},
|
|
||||||
}, {
|
|
||||||
// executable
|
|
||||||
{PAGE_EXECUTE, PAGE_EXECUTE_WRITECOPY},
|
|
||||||
{PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
static void
|
|
||||||
FinalizeSections(PMEMORYMODULE module)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(module->headers);
|
|
||||||
#ifdef _WIN64
|
|
||||||
POINTER_TYPE imageOffset = (module->headers->OptionalHeader.ImageBase & 0xffffffff00000000);
|
|
||||||
#else
|
|
||||||
#define imageOffset 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// loop through all sections and change access flags
|
|
||||||
for (i=0; i<module->headers->FileHeader.NumberOfSections; i++, section++) {
|
|
||||||
DWORD protect, oldProtect, size;
|
|
||||||
int executable = (section->Characteristics & IMAGE_SCN_MEM_EXECUTE) != 0;
|
|
||||||
int readable = (section->Characteristics & IMAGE_SCN_MEM_READ) != 0;
|
|
||||||
int writeable = (section->Characteristics & IMAGE_SCN_MEM_WRITE) != 0;
|
|
||||||
|
|
||||||
if (section->Characteristics & IMAGE_SCN_MEM_DISCARDABLE) {
|
|
||||||
// section is not needed any more and can safely be freed
|
|
||||||
VirtualFree((LPVOID)((POINTER_TYPE)section->Misc.PhysicalAddress | imageOffset), section->SizeOfRawData, MEM_DECOMMIT);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine protection flags based on characteristics
|
|
||||||
protect = ProtectionFlags[executable][readable][writeable];
|
|
||||||
if (section->Characteristics & IMAGE_SCN_MEM_NOT_CACHED) {
|
|
||||||
protect |= PAGE_NOCACHE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine size of region
|
|
||||||
size = section->SizeOfRawData;
|
|
||||||
if (size == 0) {
|
|
||||||
if (section->Characteristics & IMAGE_SCN_CNT_INITIALIZED_DATA) {
|
|
||||||
size = module->headers->OptionalHeader.SizeOfInitializedData;
|
|
||||||
} else if (section->Characteristics & IMAGE_SCN_CNT_UNINITIALIZED_DATA) {
|
|
||||||
size = module->headers->OptionalHeader.SizeOfUninitializedData;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size > 0) {
|
|
||||||
// change memory access flags
|
|
||||||
if (VirtualProtect((LPVOID)((POINTER_TYPE)section->Misc.PhysicalAddress | imageOffset), size, protect, &oldProtect) == 0)
|
|
||||||
#ifdef DEBUG_OUTPUT
|
|
||||||
OutputLastError("Error protecting memory page")
|
|
||||||
#endif
|
|
||||||
;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#ifndef _WIN64
|
|
||||||
#undef imageOffset
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
PerformBaseRelocation(PMEMORYMODULE module, SIZE_T delta)
|
|
||||||
{
|
|
||||||
DWORD i;
|
|
||||||
unsigned char *codeBase = module->codeBase;
|
|
||||||
|
|
||||||
PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_BASERELOC);
|
|
||||||
if (directory->Size > 0) {
|
|
||||||
PIMAGE_BASE_RELOCATION relocation = (PIMAGE_BASE_RELOCATION) (codeBase + directory->VirtualAddress);
|
|
||||||
for (; relocation->VirtualAddress > 0; ) {
|
|
||||||
unsigned char *dest = codeBase + relocation->VirtualAddress;
|
|
||||||
unsigned short *relInfo = (unsigned short *)((unsigned char *)relocation + IMAGE_SIZEOF_BASE_RELOCATION);
|
|
||||||
for (i=0; i<((relocation->SizeOfBlock-IMAGE_SIZEOF_BASE_RELOCATION) / 2); i++, relInfo++) {
|
|
||||||
DWORD *patchAddrHL;
|
|
||||||
#ifdef _WIN64
|
|
||||||
ULONGLONG *patchAddr64;
|
|
||||||
#endif
|
|
||||||
int type, offset;
|
|
||||||
|
|
||||||
// the upper 4 bits define the type of relocation
|
|
||||||
type = *relInfo >> 12;
|
|
||||||
// the lower 12 bits define the offset
|
|
||||||
offset = *relInfo & 0xfff;
|
|
||||||
|
|
||||||
switch (type)
|
|
||||||
{
|
|
||||||
case IMAGE_REL_BASED_ABSOLUTE:
|
|
||||||
// skip relocation
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IMAGE_REL_BASED_HIGHLOW:
|
|
||||||
// change complete 32 bit address
|
|
||||||
patchAddrHL = (DWORD *) (dest + offset);
|
|
||||||
*patchAddrHL += (DWORD)delta;
|
|
||||||
break;
|
|
||||||
|
|
||||||
#ifdef _WIN64
|
|
||||||
case IMAGE_REL_BASED_DIR64:
|
|
||||||
patchAddr64 = (ULONGLONG *) (dest + offset);
|
|
||||||
*patchAddr64 += delta;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
default:
|
|
||||||
//printf("Unknown relocation: %d\n", type);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance to next relocation block
|
|
||||||
relocation = (PIMAGE_BASE_RELOCATION) (((char *) relocation) + relocation->SizeOfBlock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
BuildImportTable(PMEMORYMODULE module)
|
|
||||||
{
|
|
||||||
int result=1;
|
|
||||||
unsigned char *codeBase = module->codeBase;
|
|
||||||
|
|
||||||
PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY(module, IMAGE_DIRECTORY_ENTRY_IMPORT);
|
|
||||||
if (directory->Size > 0) {
|
|
||||||
PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR) (codeBase + directory->VirtualAddress);
|
|
||||||
for (; !IsBadReadPtr(importDesc, sizeof(IMAGE_IMPORT_DESCRIPTOR)) && importDesc->Name; importDesc++) {
|
|
||||||
POINTER_TYPE *thunkRef;
|
|
||||||
FARPROC *funcRef;
|
|
||||||
HMODULE handle = LoadLibrary((LPCSTR) (codeBase + importDesc->Name));
|
|
||||||
if (handle == NULL) {
|
|
||||||
#if DEBUG_OUTPUT
|
|
||||||
OutputLastError("Can't load library");
|
|
||||||
#endif
|
|
||||||
result = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
module->modules = (HMODULE *)realloc(module->modules, (module->numModules+1)*(sizeof(HMODULE)));
|
|
||||||
if (module->modules == NULL) {
|
|
||||||
result = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
module->modules[module->numModules++] = handle;
|
|
||||||
if (importDesc->OriginalFirstThunk) {
|
|
||||||
thunkRef = (POINTER_TYPE *) (codeBase + importDesc->OriginalFirstThunk);
|
|
||||||
funcRef = (FARPROC *) (codeBase + importDesc->FirstThunk);
|
|
||||||
} else {
|
|
||||||
// no hint table
|
|
||||||
thunkRef = (POINTER_TYPE *) (codeBase + importDesc->FirstThunk);
|
|
||||||
funcRef = (FARPROC *) (codeBase + importDesc->FirstThunk);
|
|
||||||
}
|
|
||||||
for (; *thunkRef; thunkRef++, funcRef++) {
|
|
||||||
if (IMAGE_SNAP_BY_ORDINAL(*thunkRef)) {
|
|
||||||
*funcRef = (FARPROC)GetProcAddress(handle, (LPCSTR)IMAGE_ORDINAL(*thunkRef));
|
|
||||||
} else {
|
|
||||||
PIMAGE_IMPORT_BY_NAME thunkData = (PIMAGE_IMPORT_BY_NAME) (codeBase + (*thunkRef));
|
|
||||||
*funcRef = (FARPROC)GetProcAddress(handle, (LPCSTR)&thunkData->Name);
|
|
||||||
}
|
|
||||||
if (*funcRef == 0) {
|
|
||||||
result = 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
HMEMORYMODULE MemoryLoadLibrary(const void *data)
|
|
||||||
{
|
|
||||||
PMEMORYMODULE result;
|
|
||||||
PIMAGE_DOS_HEADER dos_header;
|
|
||||||
PIMAGE_NT_HEADERS old_header;
|
|
||||||
unsigned char *code, *headers;
|
|
||||||
SIZE_T locationDelta;
|
|
||||||
DllEntryProc DllEntry;
|
|
||||||
BOOL successfull;
|
|
||||||
|
|
||||||
dos_header = (PIMAGE_DOS_HEADER)data;
|
|
||||||
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) {
|
|
||||||
#if DEBUG_OUTPUT
|
|
||||||
OutputDebugString("Not a valid executable file.\n");
|
|
||||||
#endif
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
old_header = (PIMAGE_NT_HEADERS)&((const unsigned char *)(data))[dos_header->e_lfanew];
|
|
||||||
if (old_header->Signature != IMAGE_NT_SIGNATURE) {
|
|
||||||
#if DEBUG_OUTPUT
|
|
||||||
OutputDebugString("No PE header found.\n");
|
|
||||||
#endif
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reserve memory for image of library
|
|
||||||
code = (unsigned char *)VirtualAlloc((LPVOID)(old_header->OptionalHeader.ImageBase),
|
|
||||||
old_header->OptionalHeader.SizeOfImage,
|
|
||||||
MEM_RESERVE,
|
|
||||||
PAGE_READWRITE);
|
|
||||||
|
|
||||||
if (code == NULL) {
|
|
||||||
// try to allocate memory at arbitrary position
|
|
||||||
code = (unsigned char *)VirtualAlloc(NULL,
|
|
||||||
old_header->OptionalHeader.SizeOfImage,
|
|
||||||
MEM_RESERVE,
|
|
||||||
PAGE_READWRITE);
|
|
||||||
if (code == NULL) {
|
|
||||||
#if DEBUG_OUTPUT
|
|
||||||
OutputLastError("Can't reserve memory");
|
|
||||||
#endif
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result = (PMEMORYMODULE)HeapAlloc(GetProcessHeap(), 0, sizeof(MEMORYMODULE));
|
|
||||||
result->codeBase = code;
|
|
||||||
result->numModules = 0;
|
|
||||||
result->modules = NULL;
|
|
||||||
result->initialized = 0;
|
|
||||||
|
|
||||||
// XXX: is it correct to commit the complete memory region at once?
|
|
||||||
// calling DllEntry raises an exception if we don't...
|
|
||||||
VirtualAlloc(code,
|
|
||||||
old_header->OptionalHeader.SizeOfImage,
|
|
||||||
MEM_COMMIT,
|
|
||||||
PAGE_READWRITE);
|
|
||||||
|
|
||||||
// commit memory for headers
|
|
||||||
headers = (unsigned char *)VirtualAlloc(code,
|
|
||||||
old_header->OptionalHeader.SizeOfHeaders,
|
|
||||||
MEM_COMMIT,
|
|
||||||
PAGE_READWRITE);
|
|
||||||
|
|
||||||
// copy PE header to code
|
|
||||||
memcpy(headers, dos_header, dos_header->e_lfanew + old_header->OptionalHeader.SizeOfHeaders);
|
|
||||||
result->headers = (PIMAGE_NT_HEADERS)&((const unsigned char *)(headers))[dos_header->e_lfanew];
|
|
||||||
|
|
||||||
// update position
|
|
||||||
result->headers->OptionalHeader.ImageBase = (POINTER_TYPE)code;
|
|
||||||
|
|
||||||
// copy sections from DLL file block to new memory location
|
|
||||||
CopySections(data, old_header, result);
|
|
||||||
|
|
||||||
// adjust base address of imported data
|
|
||||||
locationDelta = (SIZE_T)(code - old_header->OptionalHeader.ImageBase);
|
|
||||||
if (locationDelta != 0) {
|
|
||||||
PerformBaseRelocation(result, locationDelta);
|
|
||||||
}
|
|
||||||
|
|
||||||
// load required dlls and adjust function table of imports
|
|
||||||
if (!BuildImportTable(result)) {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark memory pages depending on section headers and release
|
|
||||||
// sections that are marked as "discardable"
|
|
||||||
FinalizeSections(result);
|
|
||||||
|
|
||||||
// get entry point of loaded library
|
|
||||||
if (result->headers->OptionalHeader.AddressOfEntryPoint != 0) {
|
|
||||||
DllEntry = (DllEntryProc) (code + result->headers->OptionalHeader.AddressOfEntryPoint);
|
|
||||||
if (DllEntry == 0) {
|
|
||||||
#if DEBUG_OUTPUT
|
|
||||||
OutputDebugString("Library has no entry point.\n");
|
|
||||||
#endif
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
// notify library about attaching to process
|
|
||||||
successfull = (*DllEntry)((HINSTANCE)code, DLL_PROCESS_ATTACH, 0);
|
|
||||||
if (!successfull) {
|
|
||||||
#if DEBUG_OUTPUT
|
|
||||||
OutputDebugString("Can't attach library.\n");
|
|
||||||
#endif
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
result->initialized = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (HMEMORYMODULE)result;
|
|
||||||
|
|
||||||
error:
|
|
||||||
// cleanup
|
|
||||||
MemoryFreeLibrary(result);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
FARPROC MemoryGetProcAddress(HMEMORYMODULE module, const char *name)
|
|
||||||
{
|
|
||||||
unsigned char *codeBase = ((PMEMORYMODULE)module)->codeBase;
|
|
||||||
int idx=-1;
|
|
||||||
DWORD i, *nameRef;
|
|
||||||
WORD *ordinal;
|
|
||||||
PIMAGE_EXPORT_DIRECTORY exports;
|
|
||||||
PIMAGE_DATA_DIRECTORY directory = GET_HEADER_DICTIONARY((PMEMORYMODULE)module, IMAGE_DIRECTORY_ENTRY_EXPORT);
|
|
||||||
if (directory->Size == 0) {
|
|
||||||
// no export table found
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
exports = (PIMAGE_EXPORT_DIRECTORY) (codeBase + directory->VirtualAddress);
|
|
||||||
if (exports->NumberOfNames == 0 || exports->NumberOfFunctions == 0) {
|
|
||||||
// DLL doesn't export anything
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// search function name in list of exported names
|
|
||||||
nameRef = (DWORD *) (codeBase + exports->AddressOfNames);
|
|
||||||
ordinal = (WORD *) (codeBase + exports->AddressOfNameOrdinals);
|
|
||||||
for (i=0; i<exports->NumberOfNames; i++, nameRef++, ordinal++) {
|
|
||||||
if (_stricmp(name, (const char *) (codeBase + (*nameRef))) == 0) {
|
|
||||||
idx = *ordinal;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx == -1) {
|
|
||||||
// exported symbol not found
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((DWORD)idx > exports->NumberOfFunctions) {
|
|
||||||
// name <-> ordinal number don't match
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddressOfFunctions contains the RVAs to the "real" functions
|
|
||||||
return (FARPROC) (codeBase + (*(DWORD *) (codeBase + exports->AddressOfFunctions + (idx*4))));
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryFreeLibrary(HMEMORYMODULE mod)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
PMEMORYMODULE module = (PMEMORYMODULE)mod;
|
|
||||||
|
|
||||||
if (module != NULL) {
|
|
||||||
if (module->initialized != 0) {
|
|
||||||
// notify library about detaching from process
|
|
||||||
DllEntryProc DllEntry = (DllEntryProc) (module->codeBase + module->headers->OptionalHeader.AddressOfEntryPoint);
|
|
||||||
(*DllEntry)((HINSTANCE)module->codeBase, DLL_PROCESS_DETACH, 0);
|
|
||||||
module->initialized = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module->modules != NULL) {
|
|
||||||
// free previously opened libraries
|
|
||||||
for (i=0; i<module->numModules; i++) {
|
|
||||||
if (module->modules[i] != INVALID_HANDLE_VALUE) {
|
|
||||||
FreeLibrary(module->modules[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
free(module->modules);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (module->codeBase != NULL) {
|
|
||||||
// release memory of library
|
|
||||||
VirtualFree(module->codeBase, 0, MEM_RELEASE);
|
|
||||||
}
|
|
||||||
|
|
||||||
HeapFree(GetProcessHeap(), 0, module);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Memory DLL loading code
|
|
||||||
* Version 0.0.3
|
|
||||||
*
|
|
||||||
* Copyright (c) 2004-2012 by Joachim Bauch / mail@joachim-bauch.de
|
|
||||||
* http://www.joachim-bauch.de
|
|
||||||
*
|
|
||||||
* The contents of this file are subject to the Mozilla Public License Version
|
|
||||||
* 2.0 (the "License"); you may not use this file except in compliance with
|
|
||||||
* the License. You may obtain a copy of the License at
|
|
||||||
* http://www.mozilla.org/MPL/
|
|
||||||
*
|
|
||||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
||||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
||||||
* for the specific language governing rights and limitations under the
|
|
||||||
* License.
|
|
||||||
*
|
|
||||||
* The Original Code is MemoryModule.h
|
|
||||||
*
|
|
||||||
* The Initial Developer of the Original Code is Joachim Bauch.
|
|
||||||
*
|
|
||||||
* Portions created by Joachim Bauch are Copyright (C) 2004-2012
|
|
||||||
* Joachim Bauch. All Rights Reserved.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef __MEMORY_MODULE_HEADER
|
|
||||||
#define __MEMORY_MODULE_HEADER
|
|
||||||
|
|
||||||
#include <Windows.h>
|
|
||||||
|
|
||||||
typedef void *HMEMORYMODULE;
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
|
|
||||||
HMEMORYMODULE MemoryLoadLibrary(const void *);
|
|
||||||
|
|
||||||
FARPROC MemoryGetProcAddress(HMEMORYMODULE, const char *);
|
|
||||||
|
|
||||||
void MemoryFreeLibrary(HMEMORYMODULE);
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // __MEMORY_MODULE_HEADER
|
|
@ -6,8 +6,7 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||||
__docformat__ = 'restructuredtext en'
|
__docformat__ = 'restructuredtext en'
|
||||||
|
|
||||||
import sys, os, shutil, glob, py_compile, subprocess, re, zipfile, time, textwrap
|
import sys, os, shutil, glob, py_compile, subprocess, re, zipfile, time, textwrap, errno
|
||||||
from itertools import chain
|
|
||||||
|
|
||||||
from setup import (Command, modules, functions, basenames, __version__,
|
from setup import (Command, modules, functions, basenames, __version__,
|
||||||
__appname__)
|
__appname__)
|
||||||
@ -16,8 +15,7 @@ from setup.build_environment import (
|
|||||||
from setup.installer.windows.wix import WixMixIn
|
from setup.installer.windows.wix import WixMixIn
|
||||||
|
|
||||||
OPENSSL_DIR = os.environ.get('OPENSSL_DIR', os.path.join(SW, 'private', 'openssl'))
|
OPENSSL_DIR = os.environ.get('OPENSSL_DIR', os.path.join(SW, 'private', 'openssl'))
|
||||||
SW = r'C:\cygwin64\home\kovid\sw'
|
IMAGEMAGICK = os.path.join(SW, 'build', 'ImageMagick-*\\VisualMagick\\bin')
|
||||||
CRT = r'C:\Microsoft.VC90.CRT'
|
|
||||||
LZMA = os.path.join(SW, *('private/easylzma/build/easylzma-0.0.8'.split('/')))
|
LZMA = os.path.join(SW, *('private/easylzma/build/easylzma-0.0.8'.split('/')))
|
||||||
QT_DIR = subprocess.check_output([QMAKE, '-query', 'QT_INSTALL_PREFIX']).decode('utf-8').strip()
|
QT_DIR = subprocess.check_output([QMAKE, '-query', 'QT_INSTALL_PREFIX']).decode('utf-8').strip()
|
||||||
|
|
||||||
@ -50,51 +48,6 @@ def walk(dir):
|
|||||||
for f in record[-1]:
|
for f in record[-1]:
|
||||||
yield os.path.join(record[0], f)
|
yield os.path.join(record[0], f)
|
||||||
|
|
||||||
# Remove CRT dep from manifests {{{
|
|
||||||
def get_manifest_from_dll(dll):
|
|
||||||
import win32api, pywintypes
|
|
||||||
LOAD_LIBRARY_AS_DATAFILE = 2
|
|
||||||
d = win32api.LoadLibraryEx(os.path.abspath(dll), 0, LOAD_LIBRARY_AS_DATAFILE)
|
|
||||||
try:
|
|
||||||
resources = win32api.EnumResourceNames(d, 24)
|
|
||||||
except pywintypes.error as err:
|
|
||||||
if err.winerror == 1812:
|
|
||||||
return None, None # no resource section (probably a .pyd file)
|
|
||||||
raise
|
|
||||||
if resources:
|
|
||||||
return resources[0], win32api.LoadResource(d, 24, resources[0])
|
|
||||||
return None, None
|
|
||||||
|
|
||||||
def update_manifest(dll, rnum, manifest):
|
|
||||||
import win32api
|
|
||||||
h = win32api.BeginUpdateResource(dll, 0)
|
|
||||||
win32api.UpdateResource(h, 24, rnum, manifest)
|
|
||||||
win32api.EndUpdateResource(h, 0)
|
|
||||||
|
|
||||||
_crt_pat = re.compile(r'Microsoft\.VC\d+\.CRT')
|
|
||||||
|
|
||||||
def remove_CRT_from_manifest(dll, log=print):
|
|
||||||
from lxml import etree
|
|
||||||
rnum, manifest = get_manifest_from_dll(dll)
|
|
||||||
if manifest is None:
|
|
||||||
return
|
|
||||||
root = etree.fromstring(manifest)
|
|
||||||
found = False
|
|
||||||
for ai in root.xpath('//*[local-name()="assemblyIdentity" and @name]'):
|
|
||||||
name = ai.get('name')
|
|
||||||
if _crt_pat.match(name):
|
|
||||||
p = ai.getparent()
|
|
||||||
pp = p.getparent()
|
|
||||||
pp.remove(p)
|
|
||||||
if len(pp) == 0:
|
|
||||||
pp.getparent().remove(pp)
|
|
||||||
found = True
|
|
||||||
if found:
|
|
||||||
manifest = etree.tostring(root, pretty_print=True)
|
|
||||||
update_manifest(dll, rnum, manifest)
|
|
||||||
log('\t', os.path.basename(dll))
|
|
||||||
# }}}
|
|
||||||
|
|
||||||
class Win32Freeze(Command, WixMixIn):
|
class Win32Freeze(Command, WixMixIn):
|
||||||
|
|
||||||
description = 'Freeze windows calibre installation'
|
description = 'Freeze windows calibre installation'
|
||||||
@ -114,7 +67,7 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
action='store_true', help='Dont strip the generated binaries (no-op on windows)')
|
action='store_true', help='Dont strip the generated binaries (no-op on windows)')
|
||||||
|
|
||||||
def run(self, opts):
|
def run(self, opts):
|
||||||
self.SW = SW
|
self.python_base = os.path.join(SW, 'private', 'python')
|
||||||
self.portable_uncompressed_size = 0
|
self.portable_uncompressed_size = 0
|
||||||
self.opts = opts
|
self.opts = opts
|
||||||
self.src_root = self.d(self.SRC)
|
self.src_root = self.d(self.SRC)
|
||||||
@ -124,36 +77,21 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
self.lib_dir = self.j(self.base, 'Lib')
|
self.lib_dir = self.j(self.base, 'Lib')
|
||||||
self.pylib = self.j(self.base, 'pylib.zip')
|
self.pylib = self.j(self.base, 'pylib.zip')
|
||||||
self.dll_dir = self.j(self.base, 'DLLs')
|
self.dll_dir = self.j(self.base, 'DLLs')
|
||||||
self.plugins_dir = os.path.join(self.base, 'plugins2')
|
|
||||||
self.portable_base = self.j(self.d(self.base), 'Calibre Portable')
|
self.portable_base = self.j(self.d(self.base), 'Calibre Portable')
|
||||||
self.obj_dir = self.j(self.src_root, 'build', 'launcher')
|
self.obj_dir = self.j(self.src_root, 'build', 'launcher')
|
||||||
|
|
||||||
self.initbase()
|
self.initbase()
|
||||||
self.build_launchers()
|
self.build_launchers()
|
||||||
self.build_utils()
|
self.build_utils()
|
||||||
self.add_plugins()
|
|
||||||
self.freeze()
|
self.freeze()
|
||||||
self.embed_manifests()
|
self.embed_manifests()
|
||||||
self.install_site_py()
|
self.install_site_py()
|
||||||
self.archive_lib_dir()
|
self.archive_lib_dir()
|
||||||
self.remove_CRT_from_manifests()
|
# self.create_installer()
|
||||||
self.create_installer()
|
|
||||||
if not is64bit:
|
if not is64bit:
|
||||||
self.build_portable()
|
self.build_portable()
|
||||||
self.build_portable_installer()
|
self.build_portable_installer()
|
||||||
self.sign_installers()
|
# self.sign_installers()
|
||||||
|
|
||||||
def remove_CRT_from_manifests(self):
|
|
||||||
'''
|
|
||||||
The dependency on the CRT is removed from the manifests of all DLLs.
|
|
||||||
This allows the CRT loaded by the .exe files to be used instead.
|
|
||||||
'''
|
|
||||||
self.info('Removing CRT dependency from manifests of:')
|
|
||||||
for dll in chain(walk(self.dll_dir), walk(self.plugins_dir)):
|
|
||||||
bn = self.b(dll)
|
|
||||||
if bn.lower().rpartition('.')[-1] not in {'dll', 'pyd'}:
|
|
||||||
continue
|
|
||||||
remove_CRT_from_manifest(dll, self.info)
|
|
||||||
|
|
||||||
def initbase(self):
|
def initbase(self):
|
||||||
if self.e(self.base):
|
if self.e(self.base):
|
||||||
@ -162,38 +100,16 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
|
|
||||||
def add_plugins(self):
|
def add_plugins(self):
|
||||||
self.info('Adding plugins...')
|
self.info('Adding plugins...')
|
||||||
tgt = self.plugins_dir
|
tgt = self.dll_dir
|
||||||
if os.path.exists(tgt):
|
|
||||||
shutil.rmtree(tgt)
|
|
||||||
os.mkdir(tgt)
|
|
||||||
base = self.j(self.SRC, 'calibre', 'plugins')
|
base = self.j(self.SRC, 'calibre', 'plugins')
|
||||||
for f in glob.glob(self.j(base, '*.pyd')):
|
for f in glob.glob(self.j(base, '*.pyd')):
|
||||||
# We dont want the manifests as the manifest in the exe will be
|
|
||||||
# used instead
|
|
||||||
shutil.copy2(f, tgt)
|
shutil.copy2(f, tgt)
|
||||||
|
|
||||||
def fix_pyd_bootstraps_in(self, folder):
|
|
||||||
for dirpath, dirnames, filenames in os.walk(folder):
|
|
||||||
for f in filenames:
|
|
||||||
name, ext = os.path.splitext(f)
|
|
||||||
bpy = self.j(dirpath, name + '.py')
|
|
||||||
if ext == '.pyd' and os.path.exists(bpy):
|
|
||||||
with open(bpy, 'rb') as f:
|
|
||||||
raw = f.read().strip()
|
|
||||||
if (not raw.startswith('def __bootstrap__') or not
|
|
||||||
raw.endswith('__bootstrap__()')):
|
|
||||||
raise Exception('The file %r has non'
|
|
||||||
' bootstrap code'%self.j(dirpath, f))
|
|
||||||
for ext in ('.py', '.pyc', '.pyo'):
|
|
||||||
x = self.j(dirpath, name+ext)
|
|
||||||
if os.path.exists(x):
|
|
||||||
os.remove(x)
|
|
||||||
|
|
||||||
def freeze(self):
|
def freeze(self):
|
||||||
shutil.copy2(self.j(self.src_root, 'LICENSE'), self.base)
|
shutil.copy2(self.j(self.src_root, 'LICENSE'), self.base)
|
||||||
|
|
||||||
self.info('Adding CRT')
|
# self.info('Adding CRT')
|
||||||
shutil.copytree(CRT, self.j(self.base, os.path.basename(CRT)))
|
# shutil.copytree(CRT, self.j(self.base, os.path.basename(CRT)))
|
||||||
|
|
||||||
self.info('Adding resources...')
|
self.info('Adding resources...')
|
||||||
tgt = self.j(self.base, 'resources')
|
tgt = self.j(self.base, 'resources')
|
||||||
@ -201,8 +117,8 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
shutil.rmtree(tgt)
|
shutil.rmtree(tgt)
|
||||||
shutil.copytree(self.j(self.src_root, 'resources'), tgt)
|
shutil.copytree(self.j(self.src_root, 'resources'), tgt)
|
||||||
|
|
||||||
self.info('Adding Qt and python...')
|
self.info('Adding Qt...')
|
||||||
shutil.copytree(r'C:\Python%s\DLLs'%self.py_ver, self.dll_dir,
|
shutil.copytree(os.path.join(self.python_base, 'DLLs') , self.dll_dir,
|
||||||
ignore=shutil.ignore_patterns('msvc*.dll', 'Microsoft.*'))
|
ignore=shutil.ignore_patterns('msvc*.dll', 'Microsoft.*'))
|
||||||
for x in glob.glob(self.j(OPENSSL_DIR, 'bin', '*.dll')):
|
for x in glob.glob(self.j(OPENSSL_DIR, 'bin', '*.dll')):
|
||||||
shutil.copy2(x, self.dll_dir)
|
shutil.copy2(x, self.dll_dir)
|
||||||
@ -211,30 +127,27 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
|
|
||||||
for x in QT_DLLS:
|
for x in QT_DLLS:
|
||||||
shutil.copy2(os.path.join(QT_DIR, 'bin', x), self.dll_dir)
|
shutil.copy2(os.path.join(QT_DIR, 'bin', x), self.dll_dir)
|
||||||
shutil.copy2(r'C:\windows\system32\python%s.dll'%self.py_ver,
|
shutil.copy2(os.path.join(self.python_base, 'python%s.dll'%self.py_ver), self.dll_dir)
|
||||||
self.dll_dir)
|
for dirpath, dirnames, filenames in os.walk(r'%s\Lib'%self.python_base):
|
||||||
for dirpath, dirnames, filenames in os.walk(r'C:\Python%s\Lib'%self.py_ver):
|
|
||||||
if os.path.basename(dirpath) == 'pythonwin':
|
if os.path.basename(dirpath) == 'pythonwin':
|
||||||
continue
|
continue
|
||||||
for f in filenames:
|
for f in filenames:
|
||||||
if f.lower().endswith('.dll'):
|
if f.lower().endswith('.dll'):
|
||||||
f = self.j(dirpath, f)
|
f = self.j(dirpath, f)
|
||||||
shutil.copy2(f, self.dll_dir)
|
shutil.copy2(f, self.dll_dir)
|
||||||
shutil.copy2(
|
self.add_plugins()
|
||||||
r'C:\Python%(v)s\Lib\site-packages\pywin32_system32\pywintypes%(v)s.dll'
|
|
||||||
% dict(v=self.py_ver), self.dll_dir)
|
|
||||||
|
|
||||||
def ignore_lib(root, items):
|
def ignore_lib(root, items):
|
||||||
ans = []
|
ans = []
|
||||||
for x in items:
|
for x in items:
|
||||||
ext = os.path.splitext(x)[1]
|
ext = os.path.splitext(x)[1]
|
||||||
if (not ext and (x in ('demos', 'tests'))) or \
|
if (not ext and (x in ('demos', 'tests', 'test'))) or \
|
||||||
(ext in ('.dll', '.chm', '.htm', '.txt')):
|
(ext in ('.dll', '.chm', '.htm', '.txt')):
|
||||||
ans.append(x)
|
ans.append(x)
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
shutil.copytree(r'C:\Python%s\Lib'%self.py_ver, self.lib_dir,
|
self.info('Adding python...')
|
||||||
ignore=ignore_lib)
|
shutil.copytree(r'%s\Lib'%self.python_base, self.lib_dir, ignore=ignore_lib)
|
||||||
|
|
||||||
# Fix win32com
|
# Fix win32com
|
||||||
sp_dir = self.j(self.lib_dir, 'site-packages')
|
sp_dir = self.j(self.lib_dir, 'site-packages')
|
||||||
@ -242,13 +155,6 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
shutil.copytree(self.j(comext, 'shell'), self.j(sp_dir, 'win32com', 'shell'))
|
shutil.copytree(self.j(comext, 'shell'), self.j(sp_dir, 'win32com', 'shell'))
|
||||||
shutil.rmtree(comext)
|
shutil.rmtree(comext)
|
||||||
|
|
||||||
# Fix PyCrypto and Pillow, removing the bootstrap .py modules that load
|
|
||||||
# the .pyd modules, since they do not work when in a zip file
|
|
||||||
for folder in os.listdir(sp_dir):
|
|
||||||
folder = self.j(sp_dir, folder)
|
|
||||||
if os.path.isdir(folder):
|
|
||||||
self.fix_pyd_bootstraps_in(folder)
|
|
||||||
|
|
||||||
for pat in (r'PyQt5\uic\port_v3', ):
|
for pat in (r'PyQt5\uic\port_v3', ):
|
||||||
x = glob.glob(self.j(self.lib_dir, 'site-packages', pat))[0]
|
x = glob.glob(self.j(self.lib_dir, 'site-packages', pat))[0]
|
||||||
shutil.rmtree(x)
|
shutil.rmtree(x)
|
||||||
@ -259,11 +165,12 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
self.info('Adding calibre sources...')
|
self.info('Adding calibre sources...')
|
||||||
for x in glob.glob(self.j(self.SRC, '*')):
|
for x in glob.glob(self.j(self.SRC, '*')):
|
||||||
if os.path.isdir(x):
|
if os.path.isdir(x):
|
||||||
shutil.copytree(x, self.j(sp_dir, self.b(x)))
|
if os.path.exists(os.path.join(x, '__init__.py')):
|
||||||
|
shutil.copytree(x, self.j(sp_dir, self.b(x)))
|
||||||
else:
|
else:
|
||||||
shutil.copy(x, self.j(sp_dir, self.b(x)))
|
shutil.copy(x, self.j(sp_dir, self.b(x)))
|
||||||
|
|
||||||
for x in (r'calibre\manual', r'calibre\trac', 'pythonwin'):
|
for x in (r'calibre\manual', r'calibre\plugins', 'pythonwin'):
|
||||||
deld = self.j(sp_dir, x)
|
deld = self.j(sp_dir, x)
|
||||||
if os.path.exists(deld):
|
if os.path.exists(deld):
|
||||||
shutil.rmtree(deld)
|
shutil.rmtree(deld)
|
||||||
@ -273,9 +180,13 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
if not f.endswith('.py'):
|
if not f.endswith('.py'):
|
||||||
os.remove(self.j(x[0], f))
|
os.remove(self.j(x[0], f))
|
||||||
|
|
||||||
|
self.extract_pyd_modules(sp_dir)
|
||||||
|
|
||||||
self.info('Byte-compiling all python modules...')
|
self.info('Byte-compiling all python modules...')
|
||||||
for x in ('test', 'lib2to3', 'distutils'):
|
for x in ('test', 'lib2to3', 'distutils'):
|
||||||
shutil.rmtree(self.j(self.lib_dir, x))
|
x = self.j(self.lib_dir, x)
|
||||||
|
if os.path.exists(x):
|
||||||
|
shutil.rmtree(x)
|
||||||
for x in os.walk(self.lib_dir):
|
for x in os.walk(self.lib_dir):
|
||||||
root = x[0]
|
root = x[0]
|
||||||
for f in x[-1]:
|
for f in x[-1]:
|
||||||
@ -315,14 +226,8 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
|
|
||||||
self.info('\tAdding misc binary deps')
|
self.info('\tAdding misc binary deps')
|
||||||
bindir = os.path.join(SW, 'bin')
|
bindir = os.path.join(SW, 'bin')
|
||||||
for x in ('pdftohtml', 'pdfinfo', 'pdftoppm', 'jpegtran-calibre', 'cjpeg-calibre'):
|
for x in ('pdftohtml', 'pdfinfo', 'pdftoppm', 'jpegtran-calibre', 'cjpeg-calibre', 'optipng-calibre'):
|
||||||
shutil.copy2(os.path.join(bindir, x+'.exe'), self.base)
|
shutil.copy2(os.path.join(bindir, x+'.exe'), self.base)
|
||||||
for x in ('', '.manifest'):
|
|
||||||
fname = 'optipng.exe' + x
|
|
||||||
src = os.path.join(bindir, fname)
|
|
||||||
shutil.copy2(src, self.base)
|
|
||||||
src = os.path.join(self.base, fname)
|
|
||||||
os.rename(src, src.replace('.exe', '-calibre.exe'))
|
|
||||||
for pat in ('*.dll',):
|
for pat in ('*.dll',):
|
||||||
for f in glob.glob(os.path.join(bindir, pat)):
|
for f in glob.glob(os.path.join(bindir, pat)):
|
||||||
ok = True
|
ok = True
|
||||||
@ -355,6 +260,57 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
'-outputresource:%s;%d'%(dll,res)])
|
'-outputresource:%s;%d'%(dll,res)])
|
||||||
os.remove(manifest)
|
os.remove(manifest)
|
||||||
|
|
||||||
|
def extract_pyd_modules(self, site_packages_dir):
|
||||||
|
self.info('\nExtracting .pyd modules from site-packages...')
|
||||||
|
|
||||||
|
def extract_pyd(path, root):
|
||||||
|
fullname = os.path.relpath(path, root).replace(os.sep, '/').replace('/', '.')
|
||||||
|
dest = os.path.join(self.dll_dir, fullname)
|
||||||
|
if os.path.exists(dest):
|
||||||
|
raise ValueError('Cannot extract %s into DLLs as it already exists' % fullname)
|
||||||
|
os.rename(path, dest)
|
||||||
|
bpy = dest[:-1]
|
||||||
|
if os.path.exists(bpy):
|
||||||
|
with open(bpy, 'rb') as f:
|
||||||
|
raw = f.read().strip()
|
||||||
|
if (not raw.startswith('def __bootstrap__') or not raw.endswith('__bootstrap__()')):
|
||||||
|
raise ValueError('The file %r has non bootstrap code'%bpy)
|
||||||
|
for ext in ('', 'c', 'o'):
|
||||||
|
try:
|
||||||
|
os.remove(bpy + ext)
|
||||||
|
except EnvironmentError as err:
|
||||||
|
if err.errno != errno.ENOENT:
|
||||||
|
raise
|
||||||
|
|
||||||
|
def find_pyds(base):
|
||||||
|
for dirpath, dirnames, filenames in os.walk(base):
|
||||||
|
for fname in filenames:
|
||||||
|
if fname.lower().endswith('.pyd'):
|
||||||
|
yield os.path.join(dirpath, fname)
|
||||||
|
|
||||||
|
def process_root(root, base=None):
|
||||||
|
for path in find_pyds(root):
|
||||||
|
extract_pyd(path, base or root)
|
||||||
|
|
||||||
|
def absp(x):
|
||||||
|
return os.path.normcase(os.path.abspath(os.path.join(site_packages_dir, x)))
|
||||||
|
|
||||||
|
roots = set()
|
||||||
|
for pth in glob.glob(os.path.join(site_packages_dir, '*.pth')):
|
||||||
|
for line in open(pth, 'rb').readlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line and not line.startswith('#') and os.path.exists(os.path.join(site_packages_dir, line)):
|
||||||
|
roots.add(absp(line))
|
||||||
|
|
||||||
|
for x in os.listdir(site_packages_dir):
|
||||||
|
x = absp(x)
|
||||||
|
if x in roots:
|
||||||
|
process_root(x)
|
||||||
|
elif os.path.isdir(x):
|
||||||
|
process_root(x, site_packages_dir)
|
||||||
|
elif x.lower().endswith('.pyd'):
|
||||||
|
extract_pyd(x, site_packages_dir)
|
||||||
|
|
||||||
def compress(self):
|
def compress(self):
|
||||||
self.info('Compressing app dir using 7-zip')
|
self.info('Compressing app dir using 7-zip')
|
||||||
subprocess.check_call([r'C:\Program Files\7-Zip\7z.exe', 'a', '-r',
|
subprocess.check_call([r'C:\Program Files\7-Zip\7z.exe', 'a', '-r',
|
||||||
@ -604,11 +560,11 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
dflags = (['/Zi'] if debug else [])
|
dflags = (['/Zi'] if debug else [])
|
||||||
dlflags = (['/DEBUG'] if debug else ['/INCREMENTAL:NO'])
|
dlflags = (['/DEBUG'] if debug else ['/INCREMENTAL:NO'])
|
||||||
base = self.j(self.src_root, 'setup', 'installer', 'windows')
|
base = self.j(self.src_root, 'setup', 'installer', 'windows')
|
||||||
sources = [self.j(base, x) for x in ['util.c', 'MemoryModule.c']]
|
sources = [self.j(base, x) for x in ['util.c',]]
|
||||||
headers = [self.j(base, x) for x in ['util.h', 'MemoryModule.h']]
|
headers = [self.j(base, x) for x in ['util.h',]]
|
||||||
objects = [self.j(self.obj_dir, self.b(x)+'.obj') for x in sources]
|
objects = [self.j(self.obj_dir, self.b(x)+'.obj') for x in sources]
|
||||||
cflags = '/c /EHsc /MD /W3 /Ox /nologo /D_UNICODE'.split()
|
cflags = '/c /EHsc /MD /W3 /Ox /nologo /D_UNICODE'.split()
|
||||||
cflags += ['/DPYDLL="python%s.dll"'%self.py_ver, '/IC:/Python%s/include'%self.py_ver]
|
cflags += ['/DPYDLL="python%s.dll"'%self.py_ver, '/I%s/include'%self.python_base]
|
||||||
for src, obj in zip(sources, objects):
|
for src, obj in zip(sources, objects):
|
||||||
if not self.newer(obj, headers+[src]):
|
if not self.newer(obj, headers+[src]):
|
||||||
continue
|
continue
|
||||||
@ -621,7 +577,7 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
cmd = [msvc.linker, '/DLL', '/VERSION:'+ver, '/OUT:'+dll,
|
cmd = [msvc.linker, '/DLL', '/VERSION:'+ver, '/OUT:'+dll,
|
||||||
'/nologo', '/MACHINE:'+machine] + dlflags + objects + \
|
'/nologo', '/MACHINE:'+machine] + dlflags + objects + \
|
||||||
[self.embed_resources(dll),
|
[self.embed_resources(dll),
|
||||||
'/LIBPATH:C:/Python%s/libs'%self.py_ver,
|
'/LIBPATH:%s/libs'%self.python_base,
|
||||||
'python%s.lib'%self.py_ver,
|
'python%s.lib'%self.py_ver,
|
||||||
'/delayload:python%s.dll'%self.py_ver]
|
'/delayload:python%s.dll'%self.py_ver]
|
||||||
self.info('Linking calibre-launcher.dll')
|
self.info('Linking calibre-launcher.dll')
|
||||||
@ -651,7 +607,7 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
self.info('Linking', bname)
|
self.info('Linking', bname)
|
||||||
cmd = [msvc.linker] + ['/MACHINE:'+machine,
|
cmd = [msvc.linker] + ['/MACHINE:'+machine,
|
||||||
'/LIBPATH:'+self.obj_dir, '/SUBSYSTEM:'+subsys,
|
'/LIBPATH:'+self.obj_dir, '/SUBSYSTEM:'+subsys,
|
||||||
'/LIBPATH:C:/Python%s/libs'%self.py_ver, '/RELEASE',
|
'/LIBPATH:%s/libs'%self.python_base, '/RELEASE',
|
||||||
'/OUT:'+exe] + dlflags + [self.embed_resources(exe),
|
'/OUT:'+exe] + dlflags + [self.embed_resources(exe),
|
||||||
dest, lib]
|
dest, lib]
|
||||||
self.run_builder(cmd)
|
self.run_builder(cmd)
|
||||||
@ -661,27 +617,6 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
self.zf_timestamp = time.localtime(time.time())[:6]
|
self.zf_timestamp = time.localtime(time.time())[:6]
|
||||||
self.zf_names = set()
|
self.zf_names = set()
|
||||||
with zipfile.ZipFile(self.pylib, 'w', zipfile.ZIP_STORED) as zf:
|
with zipfile.ZipFile(self.pylib, 'w', zipfile.ZIP_STORED) as zf:
|
||||||
# Add the .pyds from python and calibre to the zip file
|
|
||||||
for x in (self.plugins_dir, self.dll_dir):
|
|
||||||
for pyd in os.listdir(x):
|
|
||||||
if pyd.endswith('.pyd') and pyd not in {
|
|
||||||
# sqlite_custom has to be a file for
|
|
||||||
# sqlite_load_extension to work
|
|
||||||
'sqlite_custom.pyd',
|
|
||||||
# calibre_style has to be loaded by Qt therefore it
|
|
||||||
# must be a file
|
|
||||||
'calibre_style.pyd',
|
|
||||||
# Because of https://github.com/fancycode/MemoryModule/issues/4
|
|
||||||
# any extensions that use C++ exceptions must be loaded
|
|
||||||
# from files
|
|
||||||
'unrar.pyd', 'wpd.pyd', 'podofo.pyd', 'imageops.pyd',
|
|
||||||
'progress_indicator.pyd', 'hunspell.pyd',
|
|
||||||
# dupypy crashes when loaded from the zip file
|
|
||||||
'dukpy.pyd',
|
|
||||||
}:
|
|
||||||
self.add_to_zipfile(zf, pyd, x)
|
|
||||||
os.remove(self.j(x, pyd))
|
|
||||||
|
|
||||||
# Add everything in Lib except site-packages to the zip file
|
# Add everything in Lib except site-packages to the zip file
|
||||||
for x in os.listdir(self.lib_dir):
|
for x in os.listdir(self.lib_dir):
|
||||||
if x == 'site-packages':
|
if x == 'site-packages':
|
||||||
@ -690,12 +625,7 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
|
|
||||||
sp = self.j(self.lib_dir, 'site-packages')
|
sp = self.j(self.lib_dir, 'site-packages')
|
||||||
# Special handling for PIL and pywin32
|
# Special handling for PIL and pywin32
|
||||||
handled = set(['pywin32.pth', 'win32'])
|
handled = {'pywin32.pth', 'win32'}
|
||||||
pil_dir = glob.glob(self.j(sp, 'Pillow*', 'PIL'))[-1]
|
|
||||||
if is64bit:
|
|
||||||
# PIL can raise exceptions, which cause crashes on 64bit
|
|
||||||
shutil.copytree(pil_dir, self.j(self.dll_dir, 'PIL'))
|
|
||||||
handled.add(self.b(self.d(pil_dir)))
|
|
||||||
base = self.j(sp, 'win32', 'lib')
|
base = self.j(sp, 'win32', 'lib')
|
||||||
for x in os.listdir(base):
|
for x in os.listdir(base):
|
||||||
if os.path.splitext(x)[1] not in ('.exe',):
|
if os.path.splitext(x)[1] not in ('.exe',):
|
||||||
@ -766,7 +696,7 @@ class Win32Freeze(Command, WixMixIn):
|
|||||||
if ext in ('.dll',):
|
if ext in ('.dll',):
|
||||||
raise ValueError('Cannot add %r to zipfile'%abspath)
|
raise ValueError('Cannot add %r to zipfile'%abspath)
|
||||||
zinfo.external_attr = 0o600 << 16
|
zinfo.external_attr = 0o600 << 16
|
||||||
if ext in ('.py', '.pyc', '.pyo', '.pyd'):
|
if ext in ('.py', '.pyc', '.pyo'):
|
||||||
with open(abspath, 'rb') as f:
|
with open(abspath, 'rb') as f:
|
||||||
zf.writestr(zinfo, f.read())
|
zf.writestr(zinfo, f.read())
|
||||||
|
|
||||||
|
@ -7,67 +7,39 @@ __docformat__ = 'restructuredtext en'
|
|||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import zipimport
|
import imp
|
||||||
import _memimporter
|
|
||||||
|
|
||||||
DEBUG_ZIPIMPORT = False
|
class PydImporter(object):
|
||||||
|
|
||||||
class ZipExtensionImporter(zipimport.zipimporter):
|
__slots__ = ('items', 'description')
|
||||||
'''
|
|
||||||
Taken, with thanks, from the py2exe source code
|
|
||||||
'''
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self):
|
||||||
zipimport.zipimporter.__init__(self, *args, **kwargs)
|
self.items = None
|
||||||
# We know there are no dlls in the zip file, so dont set findproc
|
self.description = ('.pyd', 'rb', imp.C_EXTENSION)
|
||||||
# (performance optimization)
|
|
||||||
#_memimporter.set_find_proc(self.locate_dll_image)
|
|
||||||
|
|
||||||
def find_module(self, fullname, path=None):
|
def find_module(self, fullname, path=None):
|
||||||
result = zipimport.zipimporter.find_module(self, fullname, path)
|
if self.items is None:
|
||||||
if result:
|
dlls_dir = os.path.join(sys.app_dir, 'DLLs')
|
||||||
return result
|
items = self.items = {}
|
||||||
fullname = fullname.replace(".", "\\")
|
for x in os.listdir(dlls_dir):
|
||||||
if (fullname + '.pyd') in self._files:
|
lx = x.lower()
|
||||||
return self
|
if lx.endswith(b'.pyd'):
|
||||||
return None
|
items[lx[:-4]] = os.path.abspath(os.path.join(dlls_dir, x))
|
||||||
|
return self if fullname.lower() in self.items else None
|
||||||
def locate_dll_image(self, name):
|
|
||||||
# A callback function for_memimporter.import_module. Tries to
|
|
||||||
# locate additional dlls. Returns the image as Python string,
|
|
||||||
# or None if not found.
|
|
||||||
if name in self._files:
|
|
||||||
return self.get_data(name)
|
|
||||||
return None
|
|
||||||
|
|
||||||
def load_module(self, fullname):
|
def load_module(self, fullname):
|
||||||
if sys.modules.has_key(fullname):
|
m = sys.modules.get(fullname)
|
||||||
mod = sys.modules[fullname]
|
if m is not None:
|
||||||
if DEBUG_ZIPIMPORT:
|
return m
|
||||||
sys.stderr.write("import %s # previously loaded from zipfile %s\n" % (fullname, self.archive))
|
|
||||||
return mod
|
|
||||||
try:
|
try:
|
||||||
return zipimport.zipimporter.load_module(self, fullname)
|
path = self.items[fullname.lower()]
|
||||||
except zipimport.ZipImportError:
|
except KeyError:
|
||||||
pass
|
raise ImportError('The native code module %s seems to have disappeared from self.items' % fullname)
|
||||||
initname = "init" + fullname.split(".")[-1] # name of initfunction
|
package, name = fullname.rpartition(b'.')[::2]
|
||||||
filename = fullname.replace(".", "\\")
|
m = imp.load_module(fullname, None, path, self.description) # This inserts the module into sys.modules itself
|
||||||
path = filename + '.pyd'
|
m.__loader__ = self
|
||||||
if path in self._files:
|
m.__package__ = package or None
|
||||||
if DEBUG_ZIPIMPORT:
|
return m
|
||||||
sys.stderr.write("# found %s in zipfile %s\n" % (path, self.archive))
|
|
||||||
code = self.get_data(path)
|
|
||||||
mod = _memimporter.import_module(code, initname, fullname, path)
|
|
||||||
mod.__file__ = "%s\\%s" % (self.archive, path)
|
|
||||||
mod.__loader__ = self
|
|
||||||
if DEBUG_ZIPIMPORT:
|
|
||||||
sys.stderr.write("import %s # loaded from zipfile %s\n" % (fullname, mod.__file__))
|
|
||||||
return mod
|
|
||||||
raise zipimport.ZipImportError, "can't find module %s" % fullname
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "<%s object %r>" % (self.__class__.__name__, self.archive)
|
|
||||||
|
|
||||||
|
|
||||||
def abs__file__():
|
def abs__file__():
|
||||||
"""Set all module __file__ attribute to an absolute path"""
|
"""Set all module __file__ attribute to an absolute path"""
|
||||||
@ -92,16 +64,12 @@ def aliasmbcs():
|
|||||||
|
|
||||||
def add_calibre_vars():
|
def add_calibre_vars():
|
||||||
sys.resources_location = os.path.join(sys.app_dir, 'resources')
|
sys.resources_location = os.path.join(sys.app_dir, 'resources')
|
||||||
sys.extensions_location = os.path.join(sys.app_dir, 'plugins2')
|
sys.extensions_location = os.path.join(sys.app_dir, 'DLLs')
|
||||||
|
|
||||||
dv = os.environ.get('CALIBRE_DEVELOP_FROM', None)
|
dv = os.environ.get('CALIBRE_DEVELOP_FROM', None)
|
||||||
if dv and os.path.exists(dv):
|
if dv and os.path.exists(dv):
|
||||||
sys.path.insert(0, os.path.abspath(dv))
|
sys.path.insert(0, os.path.abspath(dv))
|
||||||
|
|
||||||
def makepath(*paths):
|
|
||||||
dir = os.path.abspath(os.path.join(*paths))
|
|
||||||
return dir, os.path.normcase(dir)
|
|
||||||
|
|
||||||
def run_entry_point():
|
def run_entry_point():
|
||||||
bname, mod, func = sys.calibre_basename, sys.calibre_module, sys.calibre_function
|
bname, mod, func = sys.calibre_basename, sys.calibre_module, sys.calibre_function
|
||||||
sys.argv[0] = bname+'.exe'
|
sys.argv[0] = bname+'.exe'
|
||||||
@ -113,7 +81,7 @@ def main():
|
|||||||
sys.setdefaultencoding('utf-8')
|
sys.setdefaultencoding('utf-8')
|
||||||
aliasmbcs()
|
aliasmbcs()
|
||||||
|
|
||||||
sys.path_hooks.insert(0, ZipExtensionImporter)
|
sys.meta_path.insert(0, PydImporter())
|
||||||
sys.path_importer_cache.clear()
|
sys.path_importer_cache.clear()
|
||||||
|
|
||||||
import linecache
|
import linecache
|
||||||
@ -130,5 +98,3 @@ def main():
|
|||||||
sys.path.append(os.path.join(sys.app_dir, 'DLLs'))
|
sys.path.append(os.path.join(sys.app_dir, 'DLLs'))
|
||||||
|
|
||||||
return run_entry_point()
|
return run_entry_point()
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2009 Kovid Goyal
|
* Copyright 2009 Kovid Goyal
|
||||||
* The memimporter code is taken from the py2exe project
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
@ -18,102 +17,6 @@ char is_gui_app() { return GUI_APP; }
|
|||||||
|
|
||||||
int calibre_show_python_error(const wchar_t *preamble, int code);
|
int calibre_show_python_error(const wchar_t *preamble, int code);
|
||||||
|
|
||||||
// memimporter {{{
|
|
||||||
|
|
||||||
#include "MemoryModule.h"
|
|
||||||
|
|
||||||
static char **DLL_Py_PackageContext = NULL;
|
|
||||||
static PyObject **DLL_ImportError = NULL;
|
|
||||||
static char module_doc[] =
|
|
||||||
"Importer which can load extension modules from memory";
|
|
||||||
|
|
||||||
|
|
||||||
static void *memdup(void *ptr, Py_ssize_t size)
|
|
||||||
{
|
|
||||||
void *p = malloc(size);
|
|
||||||
if (p == NULL)
|
|
||||||
return NULL;
|
|
||||||
memcpy(p, ptr, size);
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Be sure to detect errors in FindLibrary - undetected errors lead to
|
|
||||||
very strange behaviour.
|
|
||||||
*/
|
|
||||||
static void* FindLibrary(char *name, PyObject *callback)
|
|
||||||
{
|
|
||||||
PyObject *result;
|
|
||||||
char *p;
|
|
||||||
Py_ssize_t size;
|
|
||||||
|
|
||||||
if (callback == NULL)
|
|
||||||
return NULL;
|
|
||||||
result = PyObject_CallFunction(callback, "s", name);
|
|
||||||
if (result == NULL) {
|
|
||||||
PyErr_Clear();
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (-1 == PyString_AsStringAndSize(result, &p, &size)) {
|
|
||||||
PyErr_Clear();
|
|
||||||
Py_DECREF(result);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
p = memdup(p, size);
|
|
||||||
Py_DECREF(result);
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject *
|
|
||||||
import_module(PyObject *self, PyObject *args)
|
|
||||||
{
|
|
||||||
char *data;
|
|
||||||
int size;
|
|
||||||
char *initfuncname;
|
|
||||||
char *modname;
|
|
||||||
char *pathname;
|
|
||||||
HMEMORYMODULE hmem;
|
|
||||||
FARPROC do_init;
|
|
||||||
|
|
||||||
char *oldcontext;
|
|
||||||
|
|
||||||
/* code, initfuncname, fqmodulename, path */
|
|
||||||
if (!PyArg_ParseTuple(args, "s#sss:import_module",
|
|
||||||
&data, &size,
|
|
||||||
&initfuncname, &modname, &pathname))
|
|
||||||
return NULL;
|
|
||||||
hmem = MemoryLoadLibrary(data);
|
|
||||||
if (!hmem) {
|
|
||||||
PyErr_Format(*DLL_ImportError,
|
|
||||||
"MemoryLoadLibrary() failed loading %s", pathname);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
do_init = MemoryGetProcAddress(hmem, initfuncname);
|
|
||||||
if (!do_init) {
|
|
||||||
MemoryFreeLibrary(hmem);
|
|
||||||
PyErr_Format(*DLL_ImportError,
|
|
||||||
"Could not find function %s in memory loaded pyd", initfuncname);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
oldcontext = *DLL_Py_PackageContext;
|
|
||||||
*DLL_Py_PackageContext = modname;
|
|
||||||
do_init();
|
|
||||||
*DLL_Py_PackageContext = oldcontext;
|
|
||||||
if (PyErr_Occurred())
|
|
||||||
return NULL;
|
|
||||||
/* Retrieve from sys.modules */
|
|
||||||
return PyImport_ImportModule(modname);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyMethodDef methods[] = {
|
|
||||||
{ "import_module", import_module, METH_VARARGS,
|
|
||||||
"import_module(code, initfunc, dllname[, finder]) -> module" },
|
|
||||||
{ NULL, NULL }, /* Sentinel */
|
|
||||||
};
|
|
||||||
|
|
||||||
// }}}
|
|
||||||
|
|
||||||
static int _show_error(const wchar_t *preamble, const wchar_t *msg, const int code) {
|
static int _show_error(const wchar_t *preamble, const wchar_t *msg, const int code) {
|
||||||
wchar_t *buf;
|
wchar_t *buf;
|
||||||
char *cbuf;
|
char *cbuf;
|
||||||
@ -324,11 +227,6 @@ void initialize_interpreter(const char *basename, const char *module, const char
|
|||||||
if (!flag) ExitProcess(_show_error(L"Failed to get debug flag", L"", 1));
|
if (!flag) ExitProcess(_show_error(L"Failed to get debug flag", L"", 1));
|
||||||
//*flag = 1;
|
//*flag = 1;
|
||||||
|
|
||||||
DLL_Py_PackageContext = (char**)GetProcAddress(dll, "_Py_PackageContext");
|
|
||||||
if (!DLL_Py_PackageContext) ExitProcess(_show_error(L"Failed to load _Py_PackageContext from dll", L"", 1));
|
|
||||||
DLL_ImportError = (PyObject**)GetProcAddress(dll, "PyExc_ImportError");
|
|
||||||
if (!DLL_ImportError) ExitProcess(_show_error(L"Failed to load PyExc_ImportError from dll", L"", 1));
|
|
||||||
|
|
||||||
Py_SetProgramName(program_name);
|
Py_SetProgramName(program_name);
|
||||||
Py_SetPythonHome(python_home);
|
Py_SetPythonHome(python_home);
|
||||||
|
|
||||||
@ -359,7 +257,6 @@ void initialize_interpreter(const char *basename, const char *module, const char
|
|||||||
}
|
}
|
||||||
PySys_SetObject("argv", argv);
|
PySys_SetObject("argv", argv);
|
||||||
|
|
||||||
Py_InitModule3("_memimporter", methods, module_doc);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -502,7 +399,6 @@ wchar_t* get_temp_filename(const wchar_t *prefix) {
|
|||||||
|
|
||||||
void redirect_out_stream(FILE *stream) {
|
void redirect_out_stream(FILE *stream) {
|
||||||
FILE *f = NULL;
|
FILE *f = NULL;
|
||||||
wchar_t *temp_file;
|
|
||||||
errno_t err;
|
errno_t err;
|
||||||
|
|
||||||
err = freopen_s(&f, "NUL", "wt", stream);
|
err = freopen_s(&f, "NUL", "wt", stream);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user