Added POSIX TLB reader functions
git-svn-id: https://svn.code.sf.net/p/nsis/code/NSIS/trunk@6914 212acab6-be3b-0410-9dea-997c60f758d6
This commit is contained in:
parent
a51d89712c
commit
5bb70ce76b
2 changed files with 439 additions and 0 deletions
411
Source/BinInterop.cpp
Normal file
411
Source/BinInterop.cpp
Normal file
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* BinInterop.cpp
|
||||
*
|
||||
* This file is a part of NSIS.
|
||||
*
|
||||
* Copyright (C) 2017 Anders Kjersem
|
||||
*
|
||||
* Licensed under the zlib/libpng license (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
*
|
||||
* Licence details can be found in the file COPYING.
|
||||
*
|
||||
* This software is provided 'as-is', without any express or implied
|
||||
* warranty.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "BinInterop.h"
|
||||
#include "ResourceEditor.h"
|
||||
#include "util.h"
|
||||
#include <wchar.h> // _tcstoul
|
||||
#include <stdexcept>
|
||||
|
||||
enum { DEBUGDUMP = 0 }; // Dump debug info on little-endian systems?
|
||||
|
||||
#define MKPTR(cast, base, offset) ( (cast) ( ((char*)(base)) + (offset) ) )
|
||||
const size_t invalid_res_id = ~(size_t)0;
|
||||
|
||||
FILE* MSTLB_fopen(const TCHAR*filepath, size_t*pResId)
|
||||
{
|
||||
size_t resid = invalid_res_id; // MSDN:"By default, the type library is extracted from the first resource of type ITypeLib"
|
||||
FILE*f = FOPEN(filepath, ("rb"));
|
||||
if (!f)
|
||||
{
|
||||
// Retry with the "filename.exe\1234" syntax supported by LoadTypeLib
|
||||
tstring name = get_file_name(filepath), parent = get_dir_name(filepath);
|
||||
const TCHAR *pStart;
|
||||
TCHAR *pEnd;
|
||||
resid = _tcstoul(pStart = name.c_str(), &pEnd, 10);
|
||||
if (pEnd != pStart && !*pEnd && (f = FOPEN(parent.c_str(), ("rb"))))
|
||||
{
|
||||
USHORT mz, valid = false;
|
||||
valid = fread(&mz, 1, sizeof(mz), f) == 2 && (mz == 0x4d5a || mz == 0x5a4d);
|
||||
if (!valid) fclose(f), f = 0; // Only allow the special syntax on executable files
|
||||
}
|
||||
}
|
||||
if (pResId) *pResId = resid;
|
||||
return f;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) || defined(NSIS_GETTLBVERSION_FORCEINTERNAL)
|
||||
|
||||
unsigned long get_file_size32(FILE*f)
|
||||
{
|
||||
unsigned long error = ~(0UL), result = error, cb, restoreseek = false;
|
||||
fpos_t orgpos;
|
||||
if (restoreseek ? 0 == fgetpos(f, &orgpos) : true)
|
||||
if (0 == fseek(f, 0, SEEK_END))
|
||||
if ((cb = ftell(f)) != -1L)
|
||||
if (restoreseek ? 0 == fsetpos(f, &orgpos) : true)
|
||||
result = cb;
|
||||
return result;
|
||||
}
|
||||
|
||||
void* alloc_and_read_file(FILE*f, unsigned long&size)
|
||||
{
|
||||
void *result = 0, *mem = 0;
|
||||
if (!f) return result;
|
||||
size = get_file_size32(f);
|
||||
mem = (~(0UL) != size) ? malloc(size) : 0;
|
||||
if (mem)
|
||||
if (0 == fseek(f, 0, SEEK_SET))
|
||||
if (fread(mem, 1, size, f) == size)
|
||||
result = mem, mem = 0;
|
||||
free(mem);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*void* alloc_and_read_file(const TCHAR*filepath, unsigned long&size)
|
||||
{
|
||||
void *result = 0;
|
||||
FILE*f = FOPEN(filepath, ("rb"));
|
||||
if (f)
|
||||
result = alloc_and_read_file(f, size), fclose(f);
|
||||
return result;
|
||||
}*/
|
||||
|
||||
#if 0
|
||||
// midl /DOLDTLB=1 /oldtlb /tlb SLTG.tlb test.idl && midl /DNEWTLB=1 /newtlb /tlb MSFT.tlb test.idl
|
||||
#ifdef NEWTLB
|
||||
import "unknwn.idl";
|
||||
[
|
||||
object, uuid(42424242-1111-1111-0001-424242424242),
|
||||
//oleautomation, //TYPEFLAG_FOLEAUTOMATION?
|
||||
//dual, //"Specifying dual on an interface implies that the interface is compatible with Automation, and therefore causes both the TYPEFLAG_FDUAL and TYPEFLAG_FOLEAUTOMATION flags to be set"
|
||||
]
|
||||
interface IInTeRfAcE1 : IUnknown { HRESULT I1_MeThOd1(); };
|
||||
|
||||
[ object, uuid(42424242-1111-1111-0002-424242424242) ]
|
||||
interface IInTeRfAcE2 : IUnknown { [idempotent] HRESULT I2_MeThOd1(); };
|
||||
#endif
|
||||
|
||||
[ //msdn.microsoft.com/en-us/library/windows/desktop/aa367069
|
||||
uuid(42424242-1234-1234-1234-424242424242), lcid(0x0809), version(666.1337),
|
||||
helpstring("HeLpStRiNg"), // ICreateTypeInfo::SetDocString?
|
||||
helpfile("HeLpFiLe"), helpcontext(0xBABEFACE),
|
||||
#ifdef NEWTLB
|
||||
helpstringdll("HeLpStRiNgDlL"), helpstringcontext(0xF00DBABE),
|
||||
#endif
|
||||
control, /* LIBFLAG_FCONTROL */ hidden, /* LIBFLAG_FHIDDEN */ restricted, /* LIBFLAG_FRESTRICTED */
|
||||
]
|
||||
library LiBnAmE
|
||||
{
|
||||
#ifdef NEWTLB
|
||||
//importlib("stdole2.tlb");
|
||||
#else
|
||||
importlib("stdole32.tlb");
|
||||
#endif
|
||||
[ uuid(42424242-0000-0000-0000-424242424242), helpstring("CoClAsSHeLpStRiNg") ]
|
||||
coclass CoClAsS
|
||||
{
|
||||
#ifdef NEWTLB
|
||||
[default] interface IInTeRfAcE1; interface IInTeRfAcE2;
|
||||
#else
|
||||
[default] interface IUnknown;
|
||||
#endif
|
||||
};
|
||||
};
|
||||
#endif //~ .IDL
|
||||
|
||||
#pragma pack(1)
|
||||
typedef struct {
|
||||
UINT32 Sig; // 'MSFT'
|
||||
USHORT FmtVerMaj, FmtVerMin; // This is just a guess, always seems to be 2.1?
|
||||
UINT32 Unknown;
|
||||
UINT32 UnkLocale; // idl:library:lcid
|
||||
UINT32 Locale; // idl:library:lcid & ICreateTypeLib::SetLcid? This is the LCID returned in TLIBATTR.
|
||||
UINT32 FlagsAndSK; // 0x03=tagSYSKIND mask. 0x0010=idl:library:helpfile 0x0100=idl:library:helpstringdll
|
||||
USHORT VerMaj, VerMin; // ICreateTypeLib::SetVersion?
|
||||
USHORT LibFlags; // tagLIBFLAGS (TLIBATTR.wLibFlags)
|
||||
USHORT Unknown2;
|
||||
UINT32 Unknown3; // Count of? This changes when the number of interfaces in the .idl changes.
|
||||
UINT32 HelpString;
|
||||
UINT32 HelpStringContext;
|
||||
UINT32 HelpContext;
|
||||
// ...?
|
||||
} MSTLB_MSFT_MINIHEADER;
|
||||
|
||||
typedef struct {
|
||||
UINT32 Sig; // 'SLTG'
|
||||
USHORT Count; // Count of stream descriptors
|
||||
USHORT CompObjFooterSize, CompObjHeaderSize;
|
||||
USHORT First; // Index of the first stream (Streams must be enumerated in the correct order!)
|
||||
char Guid[16]; // DEFINE_OLEGUID(CLSID_?, 0x000204ff, 0, 0)
|
||||
} MSTLB_SLTG_HEADER;
|
||||
typedef struct {
|
||||
BYTE Sig[1+7+1+3+1]; // "\001CompObj\0dir" (Is "dir" part of the signature or is it the offset in one of the stream descriptors?)
|
||||
} MSTLB_SLTG_COMPOBJ;
|
||||
typedef struct {
|
||||
UINT32 Size; // Size of stream
|
||||
USHORT Unknown; // Offset to something in the COMPOBJ header?
|
||||
USHORT Next; // Next stream index
|
||||
} MSTLB_SLTG_SD;
|
||||
typedef struct {
|
||||
USHORT Len; // 0xffff if there is no string.
|
||||
} MSTLB_SLTG_CSHDR; // Counted narrow string without \0 terminator.
|
||||
typedef struct {
|
||||
enum { SIG = 0x51cc, CSCOUNT = 3 };
|
||||
USHORT Sig; // 'CC51'
|
||||
USHORT Unknown[2];
|
||||
MSTLB_SLTG_CSHDR Strings[CSCOUNT]; // (Unknown, idl:library:helpstring and idl:library:helpfile)
|
||||
} MSTLB_SLTG_BLOCK_LIBATTR_HEADER;
|
||||
typedef struct {
|
||||
UINT32 HelpContext; // idl:library:helpcontext
|
||||
USHORT SysKind; // tagSYSKIND (All 16-bits returned in TLIBATTR.syskind)
|
||||
UINT32 Locale;
|
||||
USHORT BadLocale; // If this is non-zero the returned TLIBATTR LCID is 0.
|
||||
USHORT LibFlags; // tagLIBFLAGS (TLIBATTR.wLibFlags)
|
||||
USHORT VerMaj, VerMin;
|
||||
char LibGuid[16]; // idl:library:uuid
|
||||
//...?
|
||||
} MSTLB_SLTG_BLOCK_LIBATTR_FOOTER;
|
||||
#pragma pack()
|
||||
|
||||
static bool MSTLB_GetVersion_MSFT(const void*pData, size_t cbData, DWORD &high, DWORD &low)
|
||||
{
|
||||
if (cbData >= sizeof(MSTLB_MSFT_MINIHEADER))
|
||||
{
|
||||
const MSTLB_MSFT_MINIHEADER &h = *(MSTLB_MSFT_MINIHEADER*) pData;
|
||||
if (FIX_ENDIAN_INT32(h.Sig) == 0x5446534D)
|
||||
{
|
||||
if (DEBUGDUMP)
|
||||
{
|
||||
for (UINT32 i = 0; i <= min(cbData / 4, 20) && DEBUGDUMP > 1; ++i)
|
||||
_tprintf(_T("%2x=%.8x (%.4x:%.4x)\n"), i * 4, ((UINT32*)&h)[i], ((USHORT*)&h)[(i*2)+0], ((USHORT*)&h)[(i*2)+1]);
|
||||
}
|
||||
|
||||
if (FIX_ENDIAN_INT16(h.FmtVerMaj) == 2 && FIX_ENDIAN_INT16(h.FmtVerMin) == 1) // Is this always 2.1?
|
||||
{
|
||||
if (DEBUGDUMP)
|
||||
{
|
||||
UINT32 fask = FIX_ENDIAN_INT32(h.FlagsAndSK), lcid = FIX_ENDIAN_INT32(h.Locale), lf = FIX_ENDIAN_INT16(h.LibFlags);
|
||||
_tprintf(_T("lcid=%#.8x sk=%#.8x lf=%#.4x hc=%#x\n"), lcid, fask & 0x03, lf | 0x08, FIX_ENDIAN_INT32(h.HelpContext)); // 0x08 for LIBFLAG_FHASDISKIMAGE because it is not stored in the .TLB
|
||||
}
|
||||
|
||||
// lcid = FIX_ENDIAN_INT32(h.Locale);
|
||||
// sysk = FIX_ENDIAN_INT32(h.FlagsAndSK) & 0x03;
|
||||
// libf = FIX_ENDIAN_INT16(h.LibFlags) | 0x08; // 0x08 for LIBFLAG_FHASDISKIMAGE
|
||||
high = FIX_ENDIAN_INT16(h.VerMaj), low = FIX_ENDIAN_INT16(h.VerMin);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static USHORT GetLenLEToHE(const MSTLB_SLTG_CSHDR &s)
|
||||
{
|
||||
USHORT len = FIX_ENDIAN_INT16(s.Len);
|
||||
return len != 0xffff ? len : 0;
|
||||
}
|
||||
|
||||
static inline void DebugDumpLE(const MSTLB_SLTG_CSHDR &s, const char *prefix = 0)
|
||||
{
|
||||
if (DEBUGDUMP)
|
||||
{
|
||||
USHORT len = GetLenLEToHE(s);
|
||||
if (len)
|
||||
{
|
||||
if (prefix) _tprintf(_T("%") NPRIns _T(": "), prefix);
|
||||
TCHAR fmt[50];
|
||||
_stprintf(fmt, _T("\"%%.%u") NPRIns _T("\"\n"), len);
|
||||
_tprintf(fmt, MKPTR(char*, &s, sizeof(USHORT)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool MSTLB_IsSerializedOleGuid(const void*pData, UINT32 Bits1 = 0, UINT32 Mask1 = 0)
|
||||
{
|
||||
bool failed = false;
|
||||
failed |= (FIX_ENDIAN_INT32(*MKPTR(UINT32*, pData, 4*0)) & Mask1) != Bits1;
|
||||
failed |= (FIX_ENDIAN_INT32(*MKPTR(UINT32*, pData, 4*1))) != 0x00000000;
|
||||
failed |= (FIX_ENDIAN_INT32(*MKPTR(UINT32*, pData, 4*2))) != 0x000000C0;
|
||||
failed |= (FIX_ENDIAN_INT32(*MKPTR(UINT32*, pData, 4*3))) != 0x46000000;
|
||||
return !failed; // Does it look like a DEFINE_OLEGUID() GUID?
|
||||
}
|
||||
|
||||
static bool MSTLB_GetVersion_SLTG(const void*pData, size_t cbData, DWORD &high, DWORD &low)
|
||||
{
|
||||
if (cbData >= sizeof(MSTLB_SLTG_HEADER))
|
||||
{
|
||||
size_t eofPtr = MKPTR(size_t, pData, cbData);
|
||||
const MSTLB_SLTG_HEADER &h = *(MSTLB_SLTG_HEADER*) pData;
|
||||
if (FIX_ENDIAN_INT32(h.Sig) == 0x047544C53 && MSTLB_IsSerializedOleGuid(h.Guid, 0x00020400, 0xffffff00)) // 0x000204xx for IID_ITypeLib and friends
|
||||
{
|
||||
MSTLB_SLTG_SD *pSD = MKPTR(MSTLB_SLTG_SD*, &h, sizeof(MSTLB_SLTG_HEADER));
|
||||
UINT32 streamCount = FIX_ENDIAN_INT16(h.Count);
|
||||
// compobjCount = streamCount >= 1 ? streamCount - 1 : 0, compobjHdrSize = 13;
|
||||
if (DEBUGDUMP)
|
||||
{
|
||||
for (UINT32 i = 0; i <= min(cbData / 4, 100 / 4) && DEBUGDUMP > 1; ++i)
|
||||
_tprintf(_T("%2x=%.8x (%.4x:%.4x)\n"), i * 4, ((UINT32*)&h)[i], ((USHORT*)&h)[(i*2)+0], ((USHORT*)&h)[(i*2)+1]);
|
||||
_tprintf(_T("Count=%d First=%d\n"), FIX_ENDIAN_INT16(h.Count), FIX_ENDIAN_INT16(h.First));
|
||||
for (UINT32 i = 0, c = streamCount; i < c; ++i)
|
||||
_tprintf(_T("S%2d: %.8x=%-4u %.4x=%-2u %.4x=%-2u\n"), i, pSD[i].Size, pSD[i].Size, pSD[i].Unknown, pSD[i].Unknown, pSD[i].Next, pSD[i].Next);
|
||||
}
|
||||
|
||||
// Check the data in each stream until we find the LIBATTR block
|
||||
void *pFirstStreamData = MKPTR(void*, pSD, (sizeof(MSTLB_SLTG_SD) * streamCount) + FIX_ENDIAN_INT16(h.CompObjHeaderSize) + FIX_ENDIAN_INT16(h.CompObjFooterSize));
|
||||
for (UINT32 tries = 0, i = FIX_ENDIAN_INT16(h.First), c = streamCount, o = 0; tries < c && i < c; ++tries)
|
||||
{
|
||||
MSTLB_SLTG_BLOCK_LIBATTR_HEADER *pBH = MKPTR(MSTLB_SLTG_BLOCK_LIBATTR_HEADER*, pFirstStreamData, o), *pD1 = pBH;
|
||||
if (eofPtr < MKPTR(size_t, pBH, sizeof(USHORT))) break; // The stream must at least have a signature
|
||||
|
||||
if (DEBUGDUMP)
|
||||
{
|
||||
_tprintf(_T("Sig=%#.4x Size=%#.6x @ %#.6x in stream %u\n"), FIX_ENDIAN_INT16(pBH->Sig), FIX_ENDIAN_INT32(pSD[i].Size), (unsigned int) ((size_t) pBH - (size_t) pData), i);
|
||||
}
|
||||
|
||||
if (FIX_ENDIAN_INT16(pBH->Sig) == pD1->SIG && eofPtr > MKPTR(size_t, pD1, sizeof(*pD1)))
|
||||
{
|
||||
unsigned long o2 = sizeof(USHORT) * 3; // Skip past the initial members
|
||||
for (UINT32 strIdx = 0; strIdx < pD1->CSCOUNT; ++strIdx)
|
||||
{
|
||||
MSTLB_SLTG_CSHDR *pS = MKPTR(MSTLB_SLTG_CSHDR*, pD1, o2);
|
||||
if (DEBUGDUMP) DebugDumpLE(*pS);
|
||||
o2 += sizeof(MSTLB_SLTG_CSHDR) + GetLenLEToHE(*pS); // Skip past the embedded counted string
|
||||
}
|
||||
|
||||
MSTLB_SLTG_BLOCK_LIBATTR_FOOTER *pD2 = MKPTR(MSTLB_SLTG_BLOCK_LIBATTR_FOOTER*, pD1, o2);
|
||||
if (eofPtr < MKPTR(size_t, pD2, sizeof(*pD2))) break;
|
||||
|
||||
if (DEBUGDUMP)
|
||||
{
|
||||
UINT32 sk = FIX_ENDIAN_INT16(pD2->SysKind), lcid = FIX_ENDIAN_INT32(pD2->Locale), lf = FIX_ENDIAN_INT16(pD2->LibFlags);
|
||||
_tprintf(_T("lcid=%#.8x sk=%#.8x lf=%#.4x hc=%#x\n"), lcid, sk, lf, FIX_ENDIAN_INT32(pD2->HelpContext));
|
||||
}
|
||||
|
||||
// lcid = FIX_ENDIAN_INT32(pD2->Locale);
|
||||
// sysk = FIX_ENDIAN_INT16(pD2->SysKind);
|
||||
// libf = FIX_ENDIAN_INT16(pD2->LibFlags);
|
||||
high = FIX_ENDIAN_INT16(pD2->VerMaj), low = FIX_ENDIAN_INT16(pD2->VerMin);
|
||||
return true;
|
||||
}
|
||||
o += FIX_ENDIAN_INT32(pSD[i].Size), i = FIX_ENDIAN_INT16(pSD[i].Next);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool MSTLB_GetVersion(const void*pData, size_t cbData, DWORD &high, DWORD &low)
|
||||
{
|
||||
bool rv = MSTLB_GetVersion_MSFT(pData, cbData, high, low);
|
||||
if (!rv) rv = MSTLB_GetVersion_SLTG(pData, cbData, high, low);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static BYTE* GetResource(CResourceEditor&RE, const TCHAR*RT, int RN, int RL, size_t&cbData)
|
||||
{
|
||||
BYTE *pResData = RE.GetResource(RT, RN, RL);
|
||||
if (pResData) cbData = RE.GetResourceSize(RT, RN, RL);
|
||||
return pResData;
|
||||
}
|
||||
|
||||
static bool GetTLBVersionUsingRE(const void*pPEFile, size_t cbData, size_t resid, DWORD &high, DWORD &low)
|
||||
{
|
||||
bool result = false;
|
||||
try
|
||||
{
|
||||
const TCHAR* rt = _T("TYPELIB");
|
||||
int rn = (int) resid, rl = CResourceEditor::ANYLANGID;
|
||||
CResourceEditor re((BYTE*) pPEFile, (int) cbData);
|
||||
BYTE *pResData = resid == invalid_res_id ? re.GetFirstResource(rt, cbData) : GetResource(re, rt, rn, rl, cbData);
|
||||
if (pResData)
|
||||
{
|
||||
result = MSTLB_GetVersion(pResData, cbData, high, low);
|
||||
re.FreeResource(pResData);
|
||||
}
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool GetTLBVersionInterop(const TCHAR *filepath, DWORD &high, DWORD &low)
|
||||
{
|
||||
unsigned long size;
|
||||
size_t resid;
|
||||
FILE *f = MSTLB_fopen(filepath, &resid);
|
||||
bool result = false, resonly = invalid_res_id != resid;
|
||||
void *filedata = alloc_and_read_file(f, size);
|
||||
if (f) fclose(f);
|
||||
if (filedata)
|
||||
{
|
||||
if (!result && !resonly) result = MSTLB_GetVersion(filedata, size, high, low); // A raw TLB file?
|
||||
if (!result) result = GetTLBVersionUsingRE(filedata, size, resid, high, low); // A resource in a PE file?
|
||||
// TODO: if (!result) result = GetTLBVersion16(filedata, size, resid, high, low); // A resouce in a 16-bit executable?
|
||||
free(filedata);
|
||||
}
|
||||
// Not supported: if (!result) result = GetTLBVersionFromMoniker(filepath, high, low);
|
||||
return result;
|
||||
}
|
||||
#else //! !_WIN32
|
||||
static bool GetTLBVersionUsingAPI(const TCHAR *filepath, DWORD &high, DWORD &low)
|
||||
{
|
||||
bool found = false;
|
||||
HRESULT hr;
|
||||
ITypeLib *pTL;
|
||||
TCHAR fullpath[1024], *p;
|
||||
if (!GetFullPathName(filepath, COUNTOF(fullpath), fullpath, &p)) return false;
|
||||
|
||||
#ifdef _UNICODE
|
||||
hr = LoadTypeLib(fullpath, &pTL);
|
||||
#else
|
||||
WCHAR *olepath = (WCHAR*) WinWStrDupFromTChar(fullpath);
|
||||
if (!olepath) return false;
|
||||
hr = LoadTypeLib(olepath, &pTL);
|
||||
free(olepath);
|
||||
#endif //~ _UNICODE
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
TLIBATTR *tlatt;
|
||||
hr = pTL->GetLibAttr(&tlatt);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
if (DEBUGDUMP)
|
||||
{
|
||||
_tprintf(_T("lcid=%#.8x sk=%#.8x lf=%#.4x\n"), tlatt->lcid, tlatt->syskind, tlatt->wLibFlags);
|
||||
}
|
||||
|
||||
high = tlatt->wMajorVerNum, low = tlatt->wMinorVerNum;
|
||||
found = true;
|
||||
}
|
||||
pTL->Release();
|
||||
}
|
||||
return found;
|
||||
}
|
||||
#endif //~ !_WIN32
|
||||
|
||||
bool GetTLBVersion(const TCHAR *filepath, DWORD &high, DWORD &low)
|
||||
{
|
||||
bool found = false;
|
||||
#if defined(_WIN32) && !defined(NSIS_GETTLBVERSION_FORCEINTERNAL)
|
||||
found = GetTLBVersionUsingAPI(filepath, high, low);
|
||||
#else //! _WIN32
|
||||
found = GetTLBVersionInterop(filepath, high, low);
|
||||
#endif //~ _WIN32
|
||||
return found;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue