Files
UnrealEngine/Engine/Source/Programs/UnrealBuildAccelerator/ObjTool/Private/UbaImportLibWriter.cpp
2025-05-18 13:04:45 +08:00

656 lines
19 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "UbaImportLibWriter.h"
#include "UbaFileAccessor.h"
#include "UbaObjectFile.h"
#if PLATFORM_WINDOWS
#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#include <algorithm>
#endif
namespace uba
{
#if PLATFORM_WINDOWS
class ImportLib
{
#pragma pack(push, 1)
struct DATA_DESCRIPTOR
{
u16 machine;
u16 a;
u32 date;
u32 sizeOfData;
u32 b;
u32 flags;
};
struct SECTION_DESCRIPTOR_LONG
{
char name[16];
u32 sizeOfData;
u32 offset;
u32 aOffset;
u32 a;
u32 aCount;
u32 b;
};
struct SECTION_DESCRIPTOR_SHORT
{
union { char name[8]; struct { u32 unknown; u32 offset; } a; };
u32 b;
u32 c;
u16 type;
};
#pragma pack(pop)
u8 m_unknownData0[12] = { 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00 };
u8 m_unknownData1[41] = { 0x27, 0x00, 0x13, 0x10, 0x07, 0x00, 0x00, 0x00, 0xD0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x26, 0x00, 0x6A, 0x81, 0x12, 0x4D, 0x69, 0x63, 0x72, 0x6F, 0x73, 0x6F, 0x66, 0x74, 0x20, 0x28, 0x52, 0x29, 0x20, 0x4C, 0x49, 0x4E, 0x4B };
u8 m_unknownData2[20] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
u8 m_unknownData3[30] = { 0xC, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x03, 0x00 };
u32 AfterCompId = 0x0101816A;
u32 PredefinedSymbolsCount = 3;
struct SymbolInfo
{
std::string name;
u32 offsetIndex;
union { u32 hint; u32 ordinal; u32 value; };
u32 type;
u32 tempOrder;
u8 extra;
u8 isData;
};
MemoryBlock m_memory;
u32 m_memoryOffset;
Vector<SymbolInfo*> m_symbols;
u32 m_extraSymbolCount = 0;
u32 m_dataCount;
u16 m_machine = IMAGE_FILE_MACHINE_AMD64;
u64 m_date;
std::string m_moduleName;
std::string m_headerName;
bool m_noHeaderName;
template<typename Func>
void TraverseSortedSymbols(const Func& func)
{
auto sortedVec(m_symbols);
std::sort(sortedVec.begin(), sortedVec.end(), [](SymbolInfo* a, SymbolInfo* b) { return a->name < b->name; });
for (auto& s : sortedVec)
func(*s);
}
void AddSymbol(const char* name, bool isData, u32 value)
{
u32 offsetIndex = u32(m_symbols.size()) - m_extraSymbolCount;
SymbolInfo& s0 = *m_symbols.emplace_back(new SymbolInfo());
s0.isData = isData;
s0.tempOrder = u32(m_symbols.size()-1) - m_extraSymbolCount - 3;
s0.offsetIndex = offsetIndex;
if (s0.isData)
{
s0.name = std::string("__imp_") + name;
s0.value = 0;
s0.type = 5;
return;
}
s0.name = name;
s0.hint = value;
s0.type = 4;
auto& s1 = *m_symbols.emplace_back(new SymbolInfo());
s1.name = "__imp_" + s0.name;
s1.offsetIndex = offsetIndex;
s1.value = 0;
s1.type = 0;
s1.extra = 1;
++m_extraSymbolCount;
}
void InitSymbolList(const char* name)
{
char buf[200];
sprintf_s(buf, sizeof(buf), "__IMPORT_DESCRIPTOR_%s", name);
auto& s0 = *m_symbols.emplace_back(new SymbolInfo());
s0.name = buf;
s0.offsetIndex = 0;
s0.value = 0;
s0.type = 0;
auto& s1 = *m_symbols.emplace_back(new SymbolInfo());
s1.name = "__NULL_IMPORT_DESCRIPTOR";
s1.offsetIndex = 1;
s1.value = 0;
s1.type = 0;
sprintf_s(buf, sizeof(buf), "\x7F%s_NULL_THUNK_DATA", name);
auto& s2 = *m_symbols.emplace_back(new SymbolInfo());
s2.name = buf;
s2.offsetIndex = 2;
s2.value = 0;
s2.type = 0;
}
void Write(const void* data, u64 bytes)
{
u32 newOffset = m_memoryOffset + u32(bytes);
if (m_memory.writtenSize < newOffset)
m_memory.AllocateNoLock(newOffset - m_memory.writtenSize, 1, TC("ImportLibWriter"));
memcpy(m_memory.memory + m_memoryOffset, data, bytes);
m_memoryOffset = newOffset;
}
u32 ByteSwap(u32 Value)
{
return _byteswap_ulong(Value);
}
void WriteU8(u8 value) { Write(&value, sizeof(value)); }
void WriteU16(u16 value) { Write(&value, sizeof(value)); }
void WriteU32(u32 value) { Write(&value, sizeof(value)); }
void SkipWrite(u32 distance) { m_memoryOffset += distance; }
u32 GetWritePosition() { return m_memoryOffset; }
void SetWritePosition(u32 offset) { m_memoryOffset = offset; }
void WriteFileHeader(const char* str, bool prefixWithSlash, u32 a, u32 size)
{
IMAGE_ARCHIVE_MEMBER_HEADER FileHeader;
memset(&FileHeader, ' ', IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR);
u32 stringLength = u32(strlen(str));
if (prefixWithSlash)
{
FileHeader.Name[0] = '/';
memcpy(FileHeader.Name + 1, str, stringLength);
}
else
{
memcpy(FileHeader.Name, str, stringLength);
FileHeader.Name[stringLength] = '/';
}
#pragma warning(push)
#pragma warning(disable : 6386)
sprintf_s((char*)FileHeader.Date, 13, "%-12I64d", s64(m_date));
FileHeader.UserID[0] = ' '; // Remove 0 termination from Date
sprintf_s((char*)FileHeader.Mode, 9, "%-8ho", a);
sprintf_s((char*)FileHeader.Size, 11, "%-10d", int(size));
#pragma warning(pop)
FileHeader.EndHeader[0] = 0x60;
FileHeader.EndHeader[1] = 0xA;
Write(&FileHeader, IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR);
}
void WriteSectionDescriptionLong(const char* sectionName, u32 sizeOfData, u32 offset, u32 aOffset, u32 a, u32 aCount, u32 b)
{
SECTION_DESCRIPTOR_LONG desc;
memset(&desc, 0, sizeof(desc));
strcpy_s(desc.name, sizeof(desc.name), sectionName);
desc.sizeOfData = sizeOfData;
desc.offset = offset;
desc.aOffset = aOffset;
desc.a = a;
desc.aCount = aCount;
desc.b = b;
Write(&desc, sizeof(desc));
}
void WriteSectionDescriptionShort(const char* sectionName, u32 b, u32 c, u16 type)
{
SECTION_DESCRIPTOR_SHORT desc;
memset(&desc, 0, sizeof(desc));
memcpy(desc.name, sectionName, strlen(sectionName));
desc.b = b;
desc.c = c;
desc.type = type;
Write(&desc, sizeof(desc));
}
void WriteSectionDescriptionShort(u32 offset, u32 b, u32 c, u16 type)
{
SECTION_DESCRIPTOR_SHORT desc;
memset(&desc, 0, sizeof(desc));
desc.a.offset = offset;
desc.b = b;
desc.c = c;
desc.type = type;
Write(&desc, sizeof(desc));
}
void WriteDataDescriptor(u16 a, u32 sizeOfData, u32 b)
{
DATA_DESCRIPTOR desc;
desc.machine = m_machine;
desc.a = a;
desc.date = u32(m_date);
desc.sizeOfData = sizeOfData;
desc.b = b;
desc.flags = 0;
Write(&desc, sizeof(desc));
}
void WriteImportLibrary()
{
Write(IMAGE_ARCHIVE_START, IMAGE_ARCHIVE_START_SIZE);
Vector<u32> symbolOffsets;
symbolOffsets.resize(2 + m_symbols.size() - m_extraSymbolCount);
{
// File 1
u32 offset = GetWritePosition();
symbolOffsets[0] = offset;
SkipWrite(IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR);
WriteU32(ByteSwap(u32(m_symbols.size())));
SkipWrite(u32(m_symbols.size()) * sizeof(u32));
for (auto symbol : m_symbols)
Write(symbol->name.data(), symbol->name.size() + 1);
u32 offset2 = GetWritePosition();
SetWritePosition(offset);
WriteFileHeader("", false, 0, offset2 - (offset + IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR));
SetWritePosition(offset2);
if (offset2 & 1)
Write("\n", 1);
// File 2
offset = GetWritePosition();
symbolOffsets[1] = offset;
SkipWrite(IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR);
u32 symbolCount = u32(m_symbols.size()) - m_extraSymbolCount;
WriteU32(symbolCount);
SkipWrite(symbolCount * sizeof(u32));
symbolCount = u32(m_symbols.size());
WriteU32(symbolCount);
SkipWrite(symbolCount * sizeof(u16));
TraverseSortedSymbols([&](SymbolInfo& symbol) { Write(symbol.name.data(), symbol.name.size() + 1); });
offset2 = GetWritePosition();
SetWritePosition(offset);
WriteFileHeader("", false, 0, offset2 - (offset + IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR));
SetWritePosition(offset2);
if (offset2 & 1)
Write("\n", 1);
u32 len = u32(m_moduleName.size()) + 1;
if (len > 0x10)
{
WriteFileHeader("/", false, 0, len);
Write(m_moduleName.data(), len);
if (len & 1)
Write("\n", 1);
}
}
{
u32 offset = GetWritePosition();
symbolOffsets[2] = offset;
SkipWrite(IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR);
u8 moduleNameLen = u8(m_moduleName.size());
u32 stringPad = (moduleNameLen + 1) & 1;
u32 sizeOfData = (sizeof(DATA_DESCRIPTOR) + (sizeof(SECTION_DESCRIPTOR_LONG) * 3)) +
(sizeof(m_unknownData0) + sizeof(moduleNameLen) + moduleNameLen +
sizeof(m_unknownData1)) + (sizeof(m_unknownData2) + sizeof(m_unknownData3)) +
(moduleNameLen + 1) + stringPad;
WriteDataDescriptor(3, sizeOfData, 8);
// header0
sizeOfData = sizeof(m_unknownData0) + sizeof(moduleNameLen) + moduleNameLen + sizeof(m_unknownData1);
u32 offset3 = sizeof(DATA_DESCRIPTOR) + (sizeof(SECTION_DESCRIPTOR_LONG) * 3);
WriteSectionDescriptionLong(".debug$S", sizeOfData, offset3, 0, 0, 0, 0x42100040);
// header1
offset3 += sizeOfData;
sizeOfData = sizeof(m_unknownData2);
u32 AOffset = offset3 + sizeOfData;
WriteSectionDescriptionLong(".idata$2", sizeOfData, offset3, AOffset, 0, 3, 0xC0300040);
// header2
offset3 += sizeOfData + sizeof(m_unknownData3);
sizeOfData = moduleNameLen + 1 + stringPad;
WriteSectionDescriptionLong(".idata$6", sizeOfData, offset3, AOffset, 0, 0, 0xC0200040);
// data0
Write(m_unknownData0, sizeof(m_unknownData0));
Write(&moduleNameLen, sizeof(moduleNameLen));
Write(m_moduleName.data(), moduleNameLen);
Write(m_unknownData1, sizeof(m_unknownData1));
// data1
Write(m_unknownData2, sizeof(m_unknownData2));
// data2
Write(m_unknownData3, sizeof(m_unknownData3));
//
Write(m_moduleName.data(), moduleNameLen + 1);
if (stringPad)
WriteU8(0);
WriteSectionDescriptionShort("@comp.id", AfterCompId, 0xFFFF, 3);
offset3 = sizeof(sizeOfData);
WriteSectionDescriptionShort(offset3, 0, 2, 2);
WriteSectionDescriptionShort(".idata$2", 0xC0000040, 2, 0x68);
WriteSectionDescriptionShort(".idata$6", 0, 3, 3);
WriteSectionDescriptionShort(".idata$4", 0xC0000040, 0, 0x68);
WriteSectionDescriptionShort(".idata$5", 0xC0000040, 0, 0x68);
offset3 += u32(m_symbols[0]->name.size()) + 1;
WriteSectionDescriptionShort(offset3, 0, 0, 2);
offset3 += u32(m_symbols[1]->name.size()) + 1;
WriteSectionDescriptionShort(offset3, 0, 0, 2);
offset3 += u32(m_symbols[2]->name.size()) + 1;
WriteU32(offset3);
for (u32 i = 0; i < PredefinedSymbolsCount; ++i)
Write(m_symbols[i]->name.data(), u32(m_symbols[i]->name.size()) + 1);
u32 offset2 = GetWritePosition();
SetWritePosition(offset);
WriteFileHeader(m_headerName.data(), m_noHeaderName, 0, offset2 - (offset + IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR));
SetWritePosition(offset2);
if (offset2 & 1)
Write("\n", 1);
}
{
u32 offset = GetWritePosition();
symbolOffsets[3] = offset;
SkipWrite(IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR);
// DATA DESCRIPTOR
u8 moduleNameLen = u8(m_moduleName.size());
u32 sizeOfData = (sizeof(DATA_DESCRIPTOR) + (sizeof(SECTION_DESCRIPTOR_LONG) * 2)) +
(sizeof(m_unknownData0) + sizeof(moduleNameLen) + moduleNameLen + sizeof(m_unknownData1)) +
(sizeof(m_unknownData2));
WriteDataDescriptor(2, sizeOfData, 2);
// DATA HEADER 1
sizeOfData = sizeof(m_unknownData0) + sizeof(moduleNameLen) + moduleNameLen + sizeof(m_unknownData1);
u32 offset3 = sizeof(DATA_DESCRIPTOR) + (sizeof(SECTION_DESCRIPTOR_LONG) * 2);
WriteSectionDescriptionLong(".debug$S", sizeOfData, offset3, 0, 0, 0, 0x42100040);
// DATA HEADER 2
offset3 += sizeOfData;
sizeOfData = sizeof(m_unknownData2);
WriteSectionDescriptionLong(".idata$3", sizeOfData, offset3, 0, 0, 0, 0xC0300040);
// DATA 1
Write(m_unknownData0, sizeof(m_unknownData0));
Write(&moduleNameLen, sizeof(moduleNameLen));
Write(m_moduleName.data(), moduleNameLen);
Write(m_unknownData1, sizeof(m_unknownData1));
// DATA 2
Write(m_unknownData2, sizeof(m_unknownData2));
WriteSectionDescriptionShort("@comp.id", AfterCompId, 0xFFFF, 3);
offset3 = sizeof(sizeOfData);
WriteSectionDescriptionShort(offset3, 0, 2, 2);
SymbolInfo& nullImportSymbol = *m_symbols[1];
WriteU32(offset3 + u32(nullImportSymbol.name.size() + 1));
Write(nullImportSymbol.name.data(), nullImportSymbol.name.size() + 1);
u32 offset2 = GetWritePosition();
SetWritePosition(offset);
WriteFileHeader(m_headerName.data(), m_noHeaderName, 0, offset2 - (offset + IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR));
SetWritePosition(offset2);
if (offset2 & 1)
Write("\n", 1);
}
{
u32 offset = GetWritePosition();
symbolOffsets[4] = offset;
SkipWrite(IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR);
u8 moduleNameLen = u8(m_moduleName.size());
u32 pad[2] = { 0 };
u32 sizeOfData = (sizeof(DATA_DESCRIPTOR) + (sizeof(SECTION_DESCRIPTOR_LONG) * 3)) +
(sizeof(m_unknownData0) + sizeof(moduleNameLen) + moduleNameLen + sizeof(m_unknownData1)) +
sizeof(pad) +
sizeof(pad);
WriteDataDescriptor(3, sizeOfData, 2);
// header 0
sizeOfData = sizeof(m_unknownData0) + sizeof(moduleNameLen) + moduleNameLen + sizeof(m_unknownData1);
u32 offset3 = sizeof(DATA_DESCRIPTOR) + (sizeof(SECTION_DESCRIPTOR_LONG) * 3);
WriteSectionDescriptionLong(".debug$S", sizeOfData, offset3, 0, 0, 0, 0x42100040);
// header 1
offset3 += sizeOfData;
sizeOfData = sizeof(pad);
WriteSectionDescriptionLong(".idata$5", sizeOfData, offset3, 0, 0, 0, 0xC0400040);
// header 2
offset3 += sizeOfData;
sizeOfData = sizeof(pad);
WriteSectionDescriptionLong(".idata$4", sizeOfData, offset3, 0, 0, 0, 0xC0400040);
// data 0
Write(m_unknownData0, sizeof(m_unknownData0));
Write(&moduleNameLen, sizeof(moduleNameLen));
Write(m_moduleName.data(), moduleNameLen);
Write(m_unknownData1, sizeof(m_unknownData1));
// data 1 & 2
Write(pad, sizeof(pad));
Write(pad, sizeof(pad));
WriteSectionDescriptionShort("@comp.id", AfterCompId, 0xFFFF, 3);
offset3 = sizeof(sizeOfData);
WriteSectionDescriptionShort(offset3, 0, 2, 2);
SymbolInfo& thunkDataSymbol = *m_symbols[2];
WriteU32(offset3 + u32(thunkDataSymbol.name.size() + 1));
Write(thunkDataSymbol.name.data(), thunkDataSymbol.name.size() + 1);
u32 offset2 = GetWritePosition();
SetWritePosition(offset);
WriteFileHeader(m_headerName.data(), m_noHeaderName, 0, offset2 - (offset + IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR));
SetWritePosition(offset2);
if (offset2 & 1)
Write("\n", 1);
}
{
Vector<SymbolInfo*> sortedFunctions;
for (u32 i = PredefinedSymbolsCount; i < m_symbols.size(); ++i)
if (!m_symbols[i]->extra)
sortedFunctions.push_back(m_symbols[i]);
std::sort(sortedFunctions.begin(), sortedFunctions.end(), [](SymbolInfo* a, SymbolInfo* b)
{
const char* aName = a->name.data();
if (a->isData)
aName += 6;
const char* bName = b->name.data();
if (b->isData)
bName += 6;
return strcmp(aName, bName) < 0;
});
for (u32 i = 0, e = u32(sortedFunctions.size()); i != e; ++i)
{
auto& symbol = *sortedFunctions[i];
symbolOffsets[2 + PredefinedSymbolsCount + symbol.tempOrder] = GetWritePosition();
const char* symbolName = symbol.name.data();
u32 symbolNameLen = u32(symbol.name.size());
if (symbol.isData)
{
symbolName += 6;
symbolNameLen -= 6;
}
u32 sizeOfData = u32(m_moduleName.size()) + symbolNameLen + 2;
WriteFileHeader(m_headerName.data(), m_noHeaderName, 0, sizeOfData + sizeof(IMPORT_OBJECT_HEADER));
IMPORT_OBJECT_HEADER header;
memset(&header, 0, sizeof(IMPORT_OBJECT_HEADER));
header.Sig1 = 0;
header.Sig2 = 0xFFFF;
header.Version = 0;
header.Machine = m_machine;
header.TimeDateStamp = u32(m_date);
header.SizeOfData = sizeOfData;
header.Ordinal = u16(i);
header.Type = symbol.type & 3;
header.NameType = WORD(symbol.type >> 2);
header.Reserved = 0;
Write(&header, sizeof(header));
Write(symbolName, symbolNameLen + 1);
Write(m_moduleName.data(), m_moduleName.size() + 1);
if (GetWritePosition() & 1)
Write("\n", 1);
}
u32 Offset = GetWritePosition();
// Offsets
SetWritePosition(symbolOffsets[0] + IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR + sizeof(u32));
for (auto& symbol : m_symbols)
WriteU32(ByteSwap(symbolOffsets[2 + symbol->offsetIndex]));
// Offset table
SetWritePosition(symbolOffsets[1] + IMAGE_SIZEOF_ARCHIVE_MEMBER_HDR + sizeof(u32));
for (u32 i = 0; i < (m_symbols.size() - m_extraSymbolCount); ++i)
Write((symbolOffsets.data() + (2 + i)), sizeof(u32));
// Offset indices
SkipWrite(sizeof(u32));
TraverseSortedSymbols([&](SymbolInfo& symbol) { WriteU16(u16(symbol.offsetIndex + 1)); });
SetWritePosition(Offset);
}
}
public:
ImportLib() : m_memory(512*1024*1024) // 512mb should be more than enough reserved memory for the biggest import libs
{
}
~ImportLib()
{
for (auto ptr : m_symbols)
delete ptr;
}
bool WriteFile(Logger& logger, const Vector<ObjectFile*>& objFiles, const char* libName, const tchar* libFile)
{
char temp[256];
const char* libExt = strrchr(libName, '.');
if (libExt)
{
u32 len = u32(libExt - libName);
memcpy(temp, libName, len);
temp[len] = 0;
libName = temp;
}
else
libExt = ".dll";
InitSymbolList(libName);
UnorderedSet<StringKey> handled;
for (auto objFile : objFiles)
{
if (!objFile)
continue;
Map<u32, const UnorderedExports::value_type*> sortedSymbols;
for (auto& kv : objFile->GetExports())
if (handled.insert(kv.first).second)
sortedSymbols.try_emplace(kv.second.index, &kv);
for (auto& outerKv : sortedSymbols)
AddSymbol(outerKv.second->second.symbol.c_str(), outerKv.second->second.isData, 0);
}
m_moduleName = std::string(libName) + libExt;
if ((m_moduleName.size() + 1) > 0x10)
{
m_headerName = "0";
m_noHeaderName = TRUE;
}
else
{
m_headerName = m_moduleName;
m_noHeaderName = FALSE;
}
m_date = ~0ull;//_time64(NULL);
m_unknownData0[4] = u8(m_moduleName.size()) + 7; // Have no idea why this matches and what it does.
// BlankProgram-Projects.lib - 0x20
// BlankProgram-Core.lib - 0x1C
// BlankProgram-Json.lib - 0x1C
// BlankProgram-BuildSettings.lib - 0x25
m_memoryOffset = 0;
WriteImportLibrary();
FileAccessor fa(logger, libFile);
if (!fa.CreateMemoryWrite(false, DefaultAttributes(), m_memory.writtenSize))
return false;
memcpy(fa.GetData(), m_memory.memory, m_memory.writtenSize);
if (!fa.Close())
return false;
// Create exp file
StringBuffer<> expFile;
expFile.Append(libFile);
if (const tchar* dot = expFile.Last('.'))
expFile.Resize(dot - expFile.data).Append(TCV(".exp"));
FileAccessor faExp(logger, expFile.data);
if (!faExp.CreateWrite())
return false;
return faExp.Close();
}
};
#endif // PLATFORM_WINDOWS
bool ImportLibWriter::Write(Logger& logger, const Vector<ObjectFile*>& objFiles, const char* libName, const tchar* libFile)
{
#if PLATFORM_WINDOWS
ImportLib lib;
return lib.WriteFile(logger, objFiles, libName, libFile);
#else
return false;
#endif
}
}