The thread at How to get version info shows the code to get FileVersion, I need to get other values, includuing some I myself have added to VersionInfo table.
How can I get them using C++Builder 10.2 (Tokyo) ?
I used to get them in C++Builder 6.0 using the VerQueryValue method but it is raising too many exception on types.
I do not know how to change the code to C++Builder 10.2.
Bellow is the actual code I am using:
class.h
struct TransArray
{
WORD LanguageID, CharacterSet;
};
DWORD VerInfo, VerSize;
HANDLE MemHandle;
LPVOID MemPtr, BufferPtr;
UINT BufferLength;
TransArray *Array;
char QueryBlock[255];
String FFileVersion ;
class.cpp
// this one of the methods which have errors
String __fastcall TAppVersion::GetFileVersion(void)
{
String Result ;
BufferPtr = NULL ;
// Get the product version.
wsprintf(QueryBlock, "\\StringFileInfo\\%04x%04x\\FileVersion",
Array[0].LanguageID, Array[0].CharacterSet);
VerQueryValue(MemPtr, QueryBlock, &BufferPtr, &BufferLength);
if(BufferPtr) Result = (char *)BufferPtr;
return(Result);
}
//---------------------------------------------------
__fastcall TAppVersion::TAppVersion()
{
FFileName = Application->ExeName ;
VerSize = GetFileVersionInfoSize(FFileName.c_str(), &VerInfo);
if (VerSize > 0) {
MemHandle = GlobalAlloc(GMEM_MOVEABLE, VerSize);
MemPtr = GlobalLock(MemHandle);
GetFileVersionInfo(FFileName.c_str(), VerInfo, VerSize, MemPtr);
VerQueryValue(MemPtr, "\\VarFileInfo\\Translation", &BufferPtr,
&BufferLength);
Array = (TransArray *)BufferPtr;
FFileVersion = GetFileVersion();
}
}
//-----------------------------------------------
Your code is mixing ANSI and UNICODE logic incorrectly. VerQueryValue() is a preprocessor macro that maps to either VerQueryValueW() or VerQueryValueA() depending on whether UNICODE is defined or not, respectively. Your code is assuming VerQueryValueA() is being used, but that is not always the case. In modern versions of C++Builder, VerQueryValueW() would be used instead, by default.
Try something more like this instead:
struct TransArray
{
WORD LanguageID, CharacterSet;
};
DWORD VerInfo, VerSize;
LPVOID MemPtr, BufferPtr;
UINT BufferLength;
TransArray *Array;
String FFileName, FFileVersion;
...
#include <tchar.h>
String __fastcall TAppVersion::GetFileVersion(void)
{
String Result;
if (MemPtr && Array)
{
// Get the product version.
TCHAR QueryBlock[40];
_stprintf(QueryBlock, _T("\\StringFileInfo\\%04x%04x\\FileVersion"), Array[0].LanguageID, Array[0].CharacterSet);
if (VerQueryValue(MemPtr, QueryBlock, &BufferPtr, &BufferLength)) {
Result = String(static_cast<TCHAR*>(BufferPtr), BufferLength).Trim();
}
}
return Result;
}
//---------------------------------------------------
__fastcall TAppVersion::TAppVersion()
{
MemPtr = NULL;
Array = NULL;
FFileName = Application->ExeName;
DWORD Unused;
VerSize = GetFileVersionInfoSize(FFileName.c_str(), &Unused);
if (VerSize == 0) return;
MemPtr = new BYTE[VerSize];
if (GetFileVersionInfo(FFileName.c_str(), Unused, VerSize, MemPtr)) {
if (VerQueryValue(MemPtr, TEXT("\\VarFileInfo\\Translation"), &BufferPtr, &BufferLength) {
Array = (TransArray *) BufferPtr;
FFileVersion = GetFileVersion();
}
}
}
//-----------------------------------------------
__fastcall TAppVersion::~TAppVersion()
{
delete[] static_cast<BYTE*>(MemPtr);
}
//-----------------------------------------------
Though really, you shouldn't rely on TCHAR at all in modern code:
struct TransArray
{
WORD LanguageID, CharacterSet;
};
DWORD VerInfo, VerSize;
LPVOID MemPtr, BufferPtr;
UINT BufferLength;
TransArray *Array;
UnicodeString FFileName, FFileVersion;
...
UnicodeString __fastcall TAppVersion::GetFileVersion(void)
{
UnicodeString Result;
if (MemPtr && Array)
{
// Get the product version.
WCHAR QueryBlock[40];
swprintf(QueryBlock, L"\\StringFileInfo\\%04x%04x\\FileVersion", Array[0].LanguageID, Array[0].CharacterSet);
if (VerQueryValueW(MemPtr, QueryBlock, &BufferPtr, &BufferLength)) {
Result = UnicodeString(static_cast<WCHAR*>(BufferPtr), BufferLength).Trim();
}
}
return Result;
}
//---------------------------------------------------
__fastcall TAppVersion::TAppVersion()
{
MemPtr = NULL;
Array = NULL;
FFileName = Application->ExeName;
DWORD Unused;
VerSize = GetFileVersionInfoSizeW(FFileName.c_str(), &Unused);
if (VerSize == 0) return;
MemPtr = new BYTE[VerSize];
if (GetFileVersionInfoW(FFileName.c_str(), Unused, VerSize, MemPtr)) {
if (VerQueryValueW(MemPtr, L"\\VarFileInfo\\Translation", &BufferPtr, &BufferLength) {
Array = (TransArray *) BufferPtr;
FFileVersion = GetFileVersion();
}
}
}
//-----------------------------------------------
__fastcall TAppVersion::~TAppVersion()
{
delete[] static_cast<BYTE*>(MemPtr);
}
//-----------------------------------------------
A while ago I did some playing with this. It is not complete and I would call it beta 1. But here it goes.
The header:
class TAppVerInfo
{
public:
__fastcall TAppVerInfo(const wchar_t* pModPath);
__fastcall virtual ~TAppVerInfo(void);
__property System::String LanguagesCodePage = {read = GetCodePage};
__property System::String Comments[System::String LanId] = {read = GetComments};
__property System::String InternalName[System::String LanId] = {read = GetInternalName};
__property System::String ProductName[System::String LanId] = {read = GetProductName};
__property System::String CompanyName[System::String LanId] = {read = GetCompanyName};
__property System::String LegalCopyright[System::String LanId] = {read = GetLegalCopyright};
__property System::String ProductVersion[System::String LanId] = {read = GetProductVersion};
__property System::String FileDescription[System::String LanId] = {read = GetFileDescription};
__property System::String LegalTrademarks[System::String LanId] = {read = GetLegalTrademarks};
__property System::String PrivateBuild[System::String LanId] = {read = GetPrivateBuild};
__property System::String FileVersion[System::String LanId] = {read = GetFileVersion};
__property System::String OriginalFilename[System::String LanId] = {read = GetOriginalFilename};
__property System::String SpecialBuild[System::String LanId] = {read = GetSpecialBuild};
protected:
void *VerInfo;
System::String __fastcall GetCodePage(void);
System::String __fastcall GetComments(System::String LanId);
System::String __fastcall GetInternalName(System::String LanId);
System::String __fastcall GetProductName(System::String LanId);
System::String __fastcall GetCompanyName(System::String LanId);
System::String __fastcall GetLegalCopyright(System::String LanId);
System::String __fastcall GetProductVersion(System::String LanId);
System::String __fastcall GetFileDescription(System::String LanId);
System::String __fastcall GetLegalTrademarks(System::String LanId);
System::String __fastcall GetPrivateBuild(System::String LanId);
System::String __fastcall GetFileVersion(System::String LanId);
System::String __fastcall GetOriginalFilename(System::String LanId);
System::String __fastcall GetSpecialBuild(System::String LanId);
System::String __fastcall GetValue(const System::String& LanId, const wchar_t* pName);
};
and the CPP:
__fastcall TAppVerInfo::TAppVerInfo(const wchar_t* pModPath)
{
DWORD dwUnused;
DWORD VerInfoSize = GetFileVersionInfoSize(pModPath, &dwUnused);
VerInfo = malloc(VerInfoSize);
if(VerInfo)
{
if(0 == GetFileVersionInfo(pModPath, 0, VerInfoSize, VerInfo))
{
free(VerInfo);
VerInfo = NULL;
throw Exception(SysErrorMessage(GetLastError()));
}
} //040904E4
}
//---------------------------------------------------------------------------
__fastcall TAppVerInfo::~TAppVerInfo(void)
{
if(VerInfo)
free(VerInfo);
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetCodePage(void)
{
System::String retVal;
if(VerInfo)
{
struct LANGANDCODEPAGE
{
WORD wLanguage;
WORD wCodePage;
} *lpTranslate;
UINT cbTranslate;
TAutoStringList tStr(new TStringList);
UINT i;
VerQueryValue(VerInfo,L"\\VarFileInfo\\Translation", (LPVOID*)&lpTranslate, &cbTranslate);
for(i = 0; i < (cbTranslate/sizeof(LANGANDCODEPAGE)); i++)
tStr->Add(System::String().sprintf(L"%04x%04x", lpTranslate[i].wLanguage, lpTranslate[i].wCodePage));
retVal = tStr->CommaText;
}
return retVal;
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetComments(System::String LanId)
{
return GetValue(LanId, L"Comments");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetInternalName(System::String LanId)
{
return GetValue(LanId, L"InternalName");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetProductName(System::String LanId)
{
return GetValue(LanId, L"ProductName");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetCompanyName(System::String LanId)
{
return GetValue(LanId, L"CompanyName");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetLegalCopyright(System::String LanId)
{
return GetValue(LanId, L"LegalCopyright");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetProductVersion(System::String LanId)
{
return GetValue(LanId, L"ProductVersion");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetFileDescription(System::String LanId)
{
return GetValue(LanId, L"FileDescription");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetLegalTrademarks(System::String LanId)
{
return GetValue(LanId, L"LegalTrademarks");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetPrivateBuild(System::String LanId)
{
return GetValue(LanId, L"PrivateBuild");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetFileVersion(System::String LanId)
{
return GetValue(LanId, L"FileVersion");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetOriginalFilename(System::String LanId)
{
return GetValue(LanId, L"OriginalFilename");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetSpecialBuild(System::String LanId)
{
return GetValue(LanId, L"SpecialBuild");
}
//---------------------------------------------------------------------------
System::String __fastcall TAppVerInfo::GetValue(const System::String& LanId, const wchar_t* pName)
{
System::String retVal;
if(VerInfo)
{
System::String aPath(System::String().sprintf(L"\\StringFileInfo\\%s\\%s", LanId.c_str(), pName));
wchar_t *pBuf;
UINT uLen;
if(VerQueryValue(VerInfo, aPath.c_str(), (void**)&pBuf, &uLen))
retVal = System::String(pBuf);
}
return retVal;
}
Related
I have semi-successfully translated reinit.pas to C++ to use it in my project. The part where int __fastcall LoadNewResourceModule(LCID locale); is called works fine, in fact I can even call it prior to Application->Initialize() and it will load the proper language at startup. However, the part that calls void __fastcall ReinitializeForms(void); does not work, and gives a runtime error:
Resource TControl not found
Here is the dirty version of .cpp, and .h, I've yet to clean it up and comment it properly, at this point the thing just has to work fully. Please help me sort this out.
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <SysInit.hpp>
#include <Vcl.Forms.hpp>
#pragma hdrstop
#include "reinit.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
class TAsInheritedReader : public TReader
{
public:
void __fastcall ReadPrefix(TFilerFlags &_flags, int &_aChildPos);
inline __fastcall TAsInheritedReader(TStream* Stream, int BufSize) : TReader(Stream, BufSize) {}
};
//---------------------------------------------------------------------------
void __fastcall TAsInheritedReader::ReadPrefix(TFilerFlags &_flags, int &_aChildPos)
{
TReader::ReadPrefix(_flags, _aChildPos);
_flags = _flags << ffInherited;
}
//---------------------------------------------------------------------------
int __fastcall SetResourceHInstance(int _newInstance)
{
PLibModule CurModule = LibModuleList;
while(CurModule != NULL) {
if (reinterpret_cast<void*>(CurModule->Instance) == HInstance) {
if (CurModule->ResInstance != CurModule->Instance) {
FreeLibrary(reinterpret_cast<HMODULE>(CurModule->ResInstance));
CurModule->ResInstance = _newInstance;
return _newInstance;
}
CurModule = CurModule->Next;
}
}
return 0;
}
//---------------------------------------------------------------------------
int __fastcall LoadNewResourceModule(LCID locale)
{
wchar_t FileName[260];
PChar P;
wchar_t LocaleName[4];
int NewInst = 0;
GetModuleFileName(HInstance, FileName, sizeof(FileName));
GetLocaleInfo(locale, LOCALE_SABBREVLANGNAME, LocaleName, sizeof(LocaleName));
P = PChar(&FileName) + lstrlen(FileName);
while((*P != L'.') && (P != reinterpret_cast<PChar>(&FileName))) {
--P;
}
if (P != reinterpret_cast<PChar>(&FileName)) {
++P;
if (LocaleName[0] != L'\0') {
NewInst = reinterpret_cast<int>(LoadLibraryEx(FileName, 0, LOAD_LIBRARY_AS_DATAFILE));
if (NewInst == 0) {
LocaleName[2] = L'\0';
lstrcpy(P, LocaleName);
NewInst = reinterpret_cast<int>(LoadLibraryEx(FileName, 0, LOAD_LIBRARY_AS_DATAFILE));
}
}
}
if (NewInst != 0) {
return SetResourceHInstance(NewInst);
}
return 0;
}
//---------------------------------------------------------------------------
bool __fastcall InternalReloadComponentRes(const String ResName, THandle HInst, TComponent* Instance)
{
//TResourceStream* ResStream = new TResourceStream;
//TAsInheritedReader* AsInheritedReader = new TAsInheritedReader;
if (HInst == 0) {
HInst = reinterpret_cast<THandle>(HInstance);
}
THandle HRsrc = reinterpret_cast<THandle>(FindResource((HMODULE)HInst, (LPCWSTR)ResName.w_str(), (LPCWSTR)RT_RCDATA));
if(HRsrc != 0) {
return false;
}
/* THIS IS THE OFFENDING LINE OF CODE THAT THROWS EXCEPTION
I checked HInst, it is not 0...
ResName = "TControl"
and it throws exception here for some reason
saying resource tcontrol not found
*/
TResourceStream* ResStream = new TResourceStream(HInst, ResName, RT_RCDATA);
try {
TAsInheritedReader* AsInheritedReader = new TAsInheritedReader(ResStream, 4096);
try {
Instance = AsInheritedReader->ReadRootComponent(Instance);
} __finally {
delete AsInheritedReader;
}
}
__finally {
delete ResStream;
}
return true;
}
//---------------------------------------------------------------------------
bool __fastcall InitComponent(TClass ClassType)
{
if ((ClassType->ClassName() == "TComponent") || (ClassType->ClassName() == "RootAncestor")) {
return false;
}
InitComponent(ClassType->ClassParent());
return InternalReloadComponentRes(ClassType->ClassName(), FindResourceHInstance(FindClassHInstance(ClassType)), (TComponent*)&ClassType);
}
//---------------------------------------------------------------------------
bool __fastcall ReloadInheritedComponent(TComponent* Instance)
{
return InitComponent(Instance->ClassType());
}
//---------------------------------------------------------------------------
void __fastcall ReinitializeForms(void)
{
for(int i=0; i<Screen->FormCount-1; i++) {
ReloadInheritedComponent(Screen->Forms[i]);
}
}
#ifndef _reinit_h
#define _reinit_h
#include <windows.h>
extern int __fastcall LoadNewResourceModule(LCID locale);
extern void __fastcall ReinitializeForms(void);
#endif
You don't really need to translate the code at all. You can use Delphi .pas units as-is in C++Builder projects. Simply add the .pas file to your project and compile it, the IDE will generate a .hpp file that you can then #include in your C++ code.
In any case, your translation is not correct in many places.
For instance, the original code wasn't written with Unicode in mind, but you are using Unicode strings in your code. Expressions like sizeof(FileName) and sizeof(LocaleName) are the wrong buffer sizes to pass to the Win32 APIs being used, which can potentially allow buffer overflows. The code was clearly expecting BYTE-sized narrow characters, not WORD-sized wide characters.
It also looks like the original code was not written with 64-bit in mind, either. It is using 32-bit integers in places where 64-bit integers would be needed (for resource handles, etc).
So, the original code needs some updating to support modern systems properly.
But also, your translation of InitComponent() is wrong. It is using strings where the original code is using metaclass references instead, and it is passing the wrong value in the last parameter of InternalReloadComponentRes(), which you have not even declared correctly.
And also, your loop in ReinitializeForms() is skipping the last TForm in the Screen->Forms[] array.
Now, that all being said, try something more like this:
ReInit.h
#ifndef REINIT_H
#define REINIT_H
void __fastcall ReinitializeForms();
NativeUInt __fastcall LoadNewResourceModule(unsigned long Locale);
#endif
ReInit.cpp
#include "ReInit.h"
#include <Windows.hpp>
#include <SysInit.hpp>
#include <SysUtils.hpp>
#include <Classes.hpp>
#include <Forms.hpp>
#include <memory>
class TAsInheritedReader : public TReader
{
public:
void __fastcall ReadPrefix(TFilerFlags &Flags, int &AChildPos) override;
};
void __fastcall TAsInheritedReader::ReadPrefix(TFilerFlags &Flags, int &AChildPos)
{
TReader::ReadPrefix(Flags, AChildPos);
Flags << ffInherited;
}
NativeUInt __fastcall SetResourceHInstance(NativeUInt NewInstance)
{
PLibModule CurModule = LibModuleList;
while (CurModule)
{
if (CurModule->Instance == HInstance)
{
if (CurModule->ResInstance != CurModule->Instance)
::FreeLibrary(reinterpret_cast<HMODULE>(CurModule->ResInstance));
CurModule->ResInstance = NewInstance;
return NewInstance;
}
CurModule = CurModule->Next;
}
return 0;
}
NativeUInt __fastcall LoadNewResourceModule(unsigned long Locale)
{
WCHAR FileName[MAX_PATH+1] = {};
::GetModuleFileNameW(reinterpret_cast<HMODULE>(HInstance), FileName, MAX_PATH+1);
WCHAR LocaleName[5] = {};
::GetLocaleInfoW(Locale, LOCALE_SABBREVLANGNAME, LocaleName, 5);
LPWSTR P = &FileName[lstrlenW(FileName)];
while ((*P != L'.') && (P != FileName)) --P;
HMODULE NewInst = nullptr;
if (P != FileName)
{
++P;
if (LocaleName[0] != L'\0')
{
// Then look for a potential language/country translation
lstrcpyW(P, LocaleName);
NewInst = LoadLibraryEx(FileName, 0, LOAD_LIBRARY_AS_DATAFILE);
if (!NewInst)
{
// Finally look for a language only translation
LocaleName[2] = L'\0';
lstrcpyW(P, LocaleName);
NewInst = LoadLibraryEx(FileName, 0, LOAD_LIBRARY_AS_DATAFILE);
}
}
}
if (NewInst)
return SetResourceHInstance(reinterpret_cast<NativeUInt>(NewInst));
return 0;
}
bool __fastcall InternalReloadComponentRes(const UnicodeString &ResName, NativeUInt HInst, TComponent* &Instance)
{
// avoid possible EResNotFound exception
if (HInst == 0) HInst = HInstance;
HRSRC HRsrc = FindResourceW(reinterpret_cast<HMODULE>(HInst), ResName.c_str(), MAKEINTRESOURCEW(10)/*RT_RCDATA*/);
if (!HRsrc) return false;
auto ResStream = std::make_unique<TResourceStream>(HInst, ResName, MAKEINTRESOURCEW(10)/*RT_RCDATA*/);
auto AsInheritedReader = std::make_unique<TAsInheritedReader>(ResStream.get(), 4096);
Instance = AsInheritedReader->ReadRootComponent(Instance);
return true;
}
bool __fastcall ReloadInheritedComponent(TComponent *Instance, TClass RootAncestor)
{
const auto InitComponent = [&Instance,RootAncestor](TClass ClassType) -> bool
{
auto InitComponent_impl = [&Instance,RootAncestor](TClass ClassType, auto& InitComponent_ref) -> bool
{
if ((ClassType == __classid(TComponent)) || (ClassType == RootAncestor)) return false;
bool Result = InitComponent_ref(ClassType->ClassParent(), InitComponent_ref);
return InternalReloadComponentRes(ClassType->ClassName(), FindResourceHInstance(FindClassHInstance(ClassType)), Instance) || Result;
}
return InitComponent_impl(ClassType, InitComponent_impl);
}
return InitComponent(Instance->ClassType());
}
void __fastcall ReinitializeForms()
{
int Count = Screen->FormCount;
for(int i = 0; i < Count; ++i)
{
ReloadInheritedComponent(Screen->Forms[I], __classid(TForm));
}
}
There is a library for parsing.
I added ALXmlDoc.pas to the project, C++Builder created ALXmlDoc.hpp
In ALXmlDoc.pas lines 177,178:
property Nodes [const Name: AnsiString]: TALXMLNode read GetNodeByName; default;
property Nodes [const Index: integer]: TALXMLNode read GetNodeByIndex; default;
In ALXmlDoc.hpp:
__property TALXMLNode * Nodes [const System :: AnsiString Name] = {read = GetNodeByName / *, default */};
__property TALXMLNode * Nodes [const int Index] = {read = GetNodeByIndex};
I get an error about duplication - how to fix it?
In .pas line 705
property OnParseText: TAlXMLParseTextEvent read FonParseText Write FonParseText; // [added from TXMLDocument]
In ALXmlDoc.hpp:
__property _di_TAlXMLParseTextEvent OnParseText = {read = FonParseText, write = FonParseText};
__interface TAlXMLParseTextEvent: public System :: IInterface
{
virtual void __fastcall Invoke (System :: TObject * Sender, const System :: AnsiString Path, const System :: AnsiString Str) = 0;
};
private:
_di_TAlXMLParseTextEvent FonParseText;
protected:
void __fastcall DoParseText (const System :: AnsiString Path, const System :: AnsiString Str);
In my Unit1.h:
void __fastcall OnParseText (System :: TObject * Sender, const System :: AnsiString Path, const System :: AnsiString Str);
In my Unit1.cpp:
void __fastcall TForm1 :: OnParseText (System :: TObject * Sender, const System :: AnsiString Path, const System :: AnsiString Str)
{
ShowMessage(Str);
}
// ------------------------------------------------ ---------------------------
void __fastcall TForm1 :: Button1Click (TObject * Sender)
{
TALXMLDocument * aXMLDocument = new TALXMLDocument ("root");
aXMLDocument-> OnParseText = OnParseText;
}
I get an error:
[bcc32 Error] Unit1.cpp (30): E2235 Member function must be called or its address taken
How to declare an event?
In C++, array properties can't be overloaded solely on their index type. So you will have to rename one of the offending properties, there is no other option. And then I would suggest you file a report with the library author asking to make the library be more friendly to C++ users.
In the Delphi code, TAlXMLParseTextEvent is a reference to an anonymous method:
TAlXMLParseTextEvent = reference to procedure (Sender: TObject; const Path, Str: AnsiString);
Which is why it gets translated to an __interface on the C++ side (because anonymous methods really are implemented behind the scenes using interfaces). Delphi-style anonymous methods require extra handling in C++, as is documented on Embarcadero's DocWiki:
How to Handle Delphi Anonymous Methods in C++
Under the cover, Delphi implements anonymous methods types (also known as method references) via an interface that implements an Invoke(...) method.
So a method that takes a method reference parameter in Delphi is exposed to C++ as a method that takes an interface.
As such, your C++ code would need to do something more like this instead:
struct TParseTextMethod
{
void operator()(TObject *Sender, const AnsiString Path, const AnsiString Str)
{
ShowMessage(Str);
}
};
//------------------------------------------------ ---------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TALXMLDocument *aXMLDocument = new TALXMLDocument("root");
// the TMethodRef functor is defined in the documentation,
// you can copy/paste it as-is into your code...
typedef TMethodRef<TAlXMLParseTextEvent,
TParseTextMethod,
void,
TObject *Sender,
const AnsiString,
const AnsiString> MyMethRef;
aXMLDocument->OnParseText = _di_TAlXMLParseTextEvent(new MyMethRef(TParseTextMethod()));
}
Or, you can streamline the functor usage a bit more by not using the TMethodRef wrapper (see Inheritance and Interfaces and Implementing Interfaces):
class TParseTextMethod : public TCppInterfacedObject<TAlXMLParseTextEvent>
{
public:
TParseTextMethod() {}
INTFOBJECT_IMPL_IUNKNOWN(TInterfacedObject);
void __fastcall Invoke(TObject *Sender, const AnsiString Path, const AnsiString Str)
{
ShowMessage(Str);
}
};
//------------------------------------------------ ---------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TALXMLDocument *aXMLDocument = new TALXMLDocument("root");
aXMLDocument->OnParseText = _di_TAlXMLParseTextEvent(new TParseTextMethod());
}
Or, if you want to keep using your OnParseText() method as-is, then you will have to wrap it inside a functor, eg:
void __fastcall TForm1::OnParseText(TObject *Sender, const AnsiString Path, const AnsiString Str)
{
ShowMessage(Str);
}
//------------------------------------------------ ---------------------------
typedef void __fastcall (__closure *TAlXMLParseTextMethod)(TObject *Sender, const AnsiString Path, const AnsiString Str);
struct TParseTextMethod
{
TAlXMLParseTextMethod Method;
TParseTextMethod(TAlXMLParseTextMethod aMethod) : Method(aMethod) {}
void operator()(TObject *Sender, const AnsiString Path, const AnsiString Str)
{
Method(Sender, Path, Str);
}
};
//------------------------------------------------ ---------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TALXMLDocument *aXMLDocument = new TALXMLDocument("root");
typedef TMethodRef<TAlXMLParseTextEvent,
TParseTextMethod,
void,
TObject *Sender,
const AnsiString,
const AnsiString> MyMethRef;
aXMLDocument->OnParseText = _di_TAlXMLParseTextEvent(new MyMethRef(TParseTextMethod(&OnParseText)));
}
Or:
void __fastcall TForm1::OnParseText(TObject *Sender, const AnsiString Path, const AnsiString Str)
{
ShowMessage(Str);
}
//------------------------------------------------ ---------------------------
typedef void __fastcall (__closure *TAlXMLParseTextMethod)(TObject *Sender, const AnsiString Path, const AnsiString Str);
class TParseTextMethod : public TCppInterfacedObject<TAlXMLParseTextEvent>
{
public:
TAlXMLParseTextMethod Method;
TParseTextMethod(TAlXMLParseTextMethod aMethod) : Method(aMethod) {}
INTFOBJECT_IMPL_IUNKNOWN(TInterfacedObject);
void __fastcall Invoke(TObject *Sender, const AnsiString Path, const AnsiString Str)
{
Method(Sender, Path, Str);
}
};
//------------------------------------------------ ---------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TALXMLDocument *aXMLDocument = new TALXMLDocument("root");
aXMLDocument->OnParseText = _di_TAlXMLParseTextEvent(new TParseTextMethod(&OnParseText));
}
Alternatively, if you are using one of the Clang-based compilers then you can use a C++ style lambda instead of a functor:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TALXMLDocument *aXMLDocument = new TALXMLDocument("root");
aXMLDocument->OnParseText = [](TObject*, const AnsiString, const AnsiString Str) {
ShowMessage(Str);
};
}
Or:
void __fastcall TForm1::OnParseText(TObject *Sender, const AnsiString Path, const AnsiString Str)
{
ShowMessage(Str);
}
//------------------------------------------------ ---------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
TALXMLDocument *aXMLDocument = new TALXMLDocument("root");
aXMLDocument->OnParseText = [this](TObject *Sender, const AnsiString Path, const AnsiString Str) {
OnParseText(Sender, Path, Str);
};
}
The accepted answer in this question shows how to check for and use an interface on a VCL-derived object in Delphi.
How to use Interface with VCL Classes - Part 2
procedure Test;
var
O1: TSomeThing;
Intf: ISomething;
begin
O1 := TSomeThing.Create(nil);
if Supports(O1, ISomething, Intf) then
begin
Intf.DoSomething;
end;
Basically, I want to do the same in C++Builder but haven't worked out how to use "Supports" from C++.
Attempting to use <dynamic_cast> fails at compile time when using VCL-derived classes...
TSomeThing * O1;
ISomething *i = dynamic_cast<ISomething*>(O1); // Error: Can't cast
The suggested Inheritance and Interfaces article mentions TObject::GetInterface() but when I try that I get an error "Pure Virtual Function called".
_di_IFoo mc;
if (this->GetInterface(mc)) ...
Update: First, the objects I'm adding interfaces to are existing VCL controls so NOT derived from TInterfacedObject.
Second - there's no COM involved - I hope! The use of Interfaces is purely to allow me to use the concept of interfaces for multiple inheritance with VCL components which C++Builder (at least, in 2010) does not support.
So my interface looks like this (NOTE that there's no __addref/__release etc...):
__interface INTERFACE_UUID("{C527B88F-3F8E-1134-80e0-01A04F57B270}") IMyInterface : public IInterface
{
public:
virtual UTF8String getHello() = 0;
};
And my objects looks like this
class TMyPanel: public TPanel, IMyInterface
{
...
public:
UTF8String getHello() { return "Hello from a TMyPanel";}
...
};
class TMyLabel: public TLabel, IMyInterface
{
...
public:
UTF8String getHello() { return "Hello from a TMyLabel";}
...
};
That's straightforward, and as described in the Embarcadero docs for Interfaces.
But, how to tell if a particular TObject supports IMyInterface???
The following template function does this for me, based on TObject.GetInterfaceEntry() from system.pas:
template<typename T>
T* getInterface(TObject *obj)
{
T *intf = NULL;
PInterfaceEntry interfaceEntry = obj->GetInterfaceEntry(__uuidof(T) );
if (interfaceEntry && interfaceEntry->IOffset != 0)
{
intf = (T*)(((char *)obj) + interfaceEntry->IOffset);
}
return intf;
}
And we use it like this:
IMyInterface *myIf = getInterface<IMyInterface>(aRandomTObject);
if (myIf)
{
UTF8String s = myIf->getHello();
}
Please let me know if there's a better way than this, as the VTable/Pointer spelunking makes my teeth itch...
You do it the same way in C++ as in Delphi - via the Sysutils::Supports() function.
This works for me when I try it:
//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ExtCtrls.hpp>
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published: // IDE-managed Components
void __fastcall FormClick(TObject *Sender);
private: // User declarations
TPanel *p;
TLabel *l;
void Test();
public: // User declarations
__fastcall TForm1(TComponent* Owner);
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm7 *Form1;
//---------------------------------------------------------------------------
__interface INTERFACE_UUID("{C527B88F-3F8E-1134-80e0-01A04F57B270}") IMyInterface : public IInterface
{
public:
virtual UTF8String getHello() = 0;
};
#if !defined(INTFOBJECT_IMPL_IUNKNOWN)
#define INTFOBJECT_IMPL_IUNKNOWN(BASE) \
ULONG __stdcall AddRef() { return BASE::_AddRef();} \
ULONG __stdcall Release(){ return BASE::_Release();} \
HRESULT __stdcall QueryInterface(REFIID iid, void** p){ return BASE::QueryInterface(iid, p);}
#endif
class TMyPanel : public TPanel, public IMyInterface
{
INTFOBJECT_IMPL_IUNKNOWN(TPanel)
public:
__fastcall TMyPanel(TComponent *Owner) : TPanel(Owner) {}
UTF8String getHello() { return "Hello from a TMyPanel"; }
};
class TMyLabel : public TLabel, public IMyInterface
{
INTFOBJECT_IMPL_IUNKNOWN(TLabel)
public:
__fastcall TMyLabel(TComponent *Owner) : TLabel(Owner) {}
UTF8String getHello() { return "Hello from a TMyLabel"; }
};
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
p = new TMyPanel(this);
p->Parent = this;
l = new TMyLabel(this);
l->Parent = p;
l->Caption = L"Test";
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormClick(TObject *Sender)
{
Test();
}
//---------------------------------------------------------------------------
void TForm1::Test()
{
DelphiInterface<IMyInterface> Intf;
if (Supports(p, __uuidof(IMyInterface), (void*)&Intf))
{
UTF8String s = Intf->getHello();
ShowMessage(s);
Intf.Release();
}
if (Supports(l, __uuidof(IMyInterface), (void*)&Intf))
{
UTF8String s = Intf->getHello();
ShowMessage(s);
Intf.Release();
}
}
//---------------------------------------------------------------------------
My Environment:
C++ Builder XE4
I am trying to use TList for storing struct data. Following is my sample code.
struct data_t {
int key;
String name;
};
TList *list;
void __fastcall TForm1::addButtonClick(TObject *Sender)
{
// add records
list = new TList();
data_t *dataArray = new data_t[2];
dataArray[0].key = 31415;
dataArray[0].name = L"pi";
list->Add(&dataArray[0]);
dataArray[1].key = 27182;
dataArray[1].name = L"napier";
list->Add(&dataArray[1]);
ShowMessage(L"added");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::showButtonClick(TObject *Sender)
{
// get records
data_t *src;
for(int idx=0; idx < list->Count; idx++) {
src = (data_t*)list->Items[idx];
String msg;
msg = L"key:" + IntToStr(src->key) + " name:" + src->name;
ShowMessage(msg);
}
// free
for(int idx=0; idx < list->Count; idx++) {
src = (data_t*)list->Items[idx];
delete src; // will not return
}
delete list;
list = NULL;
}
//--------------------------------------------------------------------------
The problem with above code is that the code will not return in delete src.
Without delete src, the code is executed to the end, but I think I should delete data_t assignments with delete src.
I would like to know the workaround for this, or some mistakes I made.
The pointers you have stored in the TList were not allocated individually with new, so DO NOT try to delete them individually. You need to instead delete[] the original array that you allocated with new[]:
struct data_t {
int key;
String name;
};
TList *list = NULL;
data_t *dataArray = NULL;
void __fastcall TForm1::addButtonClick(TObject *Sender)
{
// add records
list = new TList();
dataArray = new data_t[2];
dataArray[0].key = 31415;
dataArray[0].name = L"pi";
list->Add(&dataArray[0]);
dataArray[1].key = 27182;
dataArray[1].name = L"napier";
list->Add(&dataArray[1]);
ShowMessage(L"added");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::showButtonClick(TObject *Sender)
{
// get records
data_t *src;
for(int idx=0; idx < list->Count; idx++) {
src = (data_t*)list->Items[idx];
String msg;
msg = L"key:" + IntToStr(src->key) + " name:" + src->name;
ShowMessage(msg);
}
// free
delete list;
list = NULL;
delete[] dataArray;
dataArray = NULL;
}
//--------------------------------------------------------------------------
If you want to delete the individual items, you have to new them individually:
struct data_t {
int key;
String name;
};
TList *list = NULL;
void __fastcall TForm1::addButtonClick(TObject *Sender)
{
// add records
list = new TList();
data_t *item = new data_t;
item->key = 31415;
item->name = L"pi";
list->Add(item);
item = new data_t;
item->key = 27182;
item->name = L"napier";
list->Add(item);
ShowMessage(L"added");
}
//---------------------------------------------------------------------------
void __fastcall TForm1::showButtonClick(TObject *Sender)
{
// get records
data_t *src;
for(int idx=0; idx < list->Count; idx++) {
src = (data_t*)list->Items[idx];
String msg;
msg = L"key:" + IntToStr(src->key) + " name:" + src->name;
ShowMessage(msg);
}
// free
for(int idx=0; idx < list->Count; idx++) {
src = (data_t*)list->Items[idx];
delete src;
}
delete list;
list = NULL;
}
//--------------------------------------------------------------------------
How do I use a TComInterface<IStream> object and read it into a TMemoryStream? Can I take TComInterface<IStream> and use it as a TStream* somehow to use the TMemoryStream LoadFromStream call?
One way is to Read() the IStream data into a buffer that you then Write() to the TMemoryStream, eg:
TComInterface<IStream> pIStrm;
TMemoryStream *pMStrm;
...
STATSTG stat = {0};
OleCheck(pIStrm->Stat(&stat, STATFLAG_NONAME));
LARGE_INTEGER li;
li.QuadPart = 0;
ULARGE_INTEGER ul;
OleCheck(pIStrm->Seek(li, STREAM_SEEK_CUR, &ul));
unsigned __int64 ulPos = ul.QuadPart;
BYTE buffer[1024];
while (ulPos < stat.cbSize.QuadPart)
{
ULONG ulRead;
OleCheck(pIStrm->Read(buffer, min(stat.cbSize.QuadPart - ulPos, sizeof(buffer)), &ulRead));
pMStrm->WriteBuffer(buffer, ulRead);
ulPos += ulRead;
}
...
Another option is to write a TStream-derived class that accesses the IStream internally (similar to how the RTL's TStreamAdapter class wraps a TStream so it can be passed around as an IStream), eg:
class TIStreamWrapper : public TStream
{
private:
TComInterface<IStream> pIStrm;
protected:
virtual __int64 __fastcall GetSize();
virtual void __fastcall SetSize(const __int64 NewSize);
public:
__fastcall TIStreamWrapper(IStream *Strm);
virtual int __fastcall Read(void *Buffer, int Count);
virtual int __fastcall Write(const void *Buffer, int Count);
virtual __int64 __fastcall Seek(const __int64 Offset, TSeekOrigin Origin);
};
__fastcall TIStreamWrapper::TIStreamWrapper(IStream *Strm)
: pIStrm(Strm, true)
{
}
__int64 __fastcall TIStreamWrapper::GetSize()
{
STATSTG stat = {0};
OleCheck(pIStrm->Stat(&stat, STATFLAG_NONAME));
return stat.cbSize.QuadPart;
}
void __fastcall TIStreamWrapper::SetSize(const __int64 NewSize)
{
ULARGE_INTEGER ul;
ul.QuadPart = NewSize;
OleCheck(pIStrm->SetSize(ul));
}
int __fastcall TIStreamWrapper::Read(void *Buffer, int Count)
{
ULONG ulRead;
OleCheck(pIStrm->Read(Buffer, Count, &ulRead));
return ulRead;
}
int __fastcall TIStreamWrapper::Write(const void *Buffer, int Count)
{
ULONG ulWritten;
OleCheck(pIStrm->Write(Buffer, Count, &ulWritten));
return ulWritten;
}
static const DWORD IStreamSeekOrigin[] = {STREAM_SEEK_SET, STREAM_SEEK_CUR, STREAM_SEEK_END};
__int64 __fastcall TIStreamWrapper::Seek(const __int64 Offset, TSeekOrigin Origin)
{
LARGE_INTEGER li;
li.QuadPart = Offset;
ULARGE_INTEGER ul;
OleCheck(pIStrm->Seek(li, IStreamSeekOrigin[Origin], &ul));
return ul.QuadPart;
}
TComInterface<IStream> pIStrm;
TMemoryStream *pMStrm;
...
TIStreamWrapper *pWrapper = new TIStreamWrapper(pIStrm);
try
{
pMStrm->LoadFromStream(pWrapper);
// or: pMStrm->CopyFrom(pWrapper, 0);
}
__finally
{
delete pWrapper;
}
...