Assigning OnDrawItem to TMenuItem dynamically in C++Builder - c++builder

TMenuItem *mi = new TMenuItem(this);
mi->OnDrawItem = &miThemesDrawItem;
results in error:
[bcc64 Error] _TForm1.cpp(280): assigning to 'Vcl::Menus::TMenuDrawItemEvent' (aka 'void ((__closure *))(System::TObject *, Vcl::Graphics::TCanvas *, const System::Types::TRect &, bool) __attribute__((fastcall))') from incompatible type 'void (__closure *)(System::TObject *, Vcl::Graphics::TCanvas *, System::Types::TRect &, bool)'
Function declaration is okay, in fact I can assign it at design-time to menu items that are available at design-time.
void __fastcall miThemesDrawItem(TObject *Sender, TCanvas *ACanvas, TRect &ARect, bool Selected);
I found a workaround. I assigned this handler at design-time on a separator item N1, and then at run-time I just do mi->OnDrawItem = N1->OnDrawItem, and it works fine since OnDrawItem does not get called for a menu separator, but I don't like this!
What am I missing, how to assign this handler?

Function declaration is okay
Actually, it is not. Pay very close attention to what the error message is telling you:
assigning to 'Vcl::Menus::TMenuDrawItemEvent' ... from incompatible type ...'
So, why is miThemesDrawItem() incompatible? Because it is has a different type than what TMenuDrawItemEvent is expecting!
Let's look at TMenuDrawItemEvent first. The error message says its type is:
void ((__closure *))(System::TObject *, Vcl::Graphics::TCanvas *, const System::Types::TRect &, bool) __attribute__((fastcall))
We can ignore the __attribute__ for now, thus the type is:
void (__closure *)(System::TObject *, Vcl::Graphics::TCanvas *, const System::Types::TRect &, bool)
That matches up with the actual declaration of TMenuDrawItemEvent in Vcl.Menus.hpp:
typedef void __fastcall (__closure *TMenuDrawItemEvent)(System::TObject* Sender, Vcl::Graphics::TCanvas* ACanvas, const System::Types::TRect &ARect, bool Selected);
Now, let's look at your miThemesDrawItem(). The error message says its type is:
void (__closure *)(System::TObject *, Vcl::Graphics::TCanvas *, System::Types::TRect &, bool)
That matches up with your actual declaration:
void __fastcall miThemesDrawItem(TObject *Sender, TCanvas *ACanvas, TRect &ARect, bool Selected);
Notice any differences between the declared type of TMenuDrawItemEvent vs the declared type of miThemesDrawItem()? The TRect parameter in miThemesDrawItem() is not declared as const!
You need to declare miThemesDrawItem() to have the exact same signature as how TMenuDrawItemEvent is declared, eg:
void __fastcall miThemesDrawItem(TObject* Sender, TCanvas* ACanvas, const TRect &ARect, bool Selected);
in fact I can assign it at design-time to menu items that are available at design-time
That by itself doesn't guarantee that miThemesDrawItem() is 100% compatible with the TMenuItem::OnDrawItem event. Although the IDE does validate an event handler's type when the user tries to assign it to an event at design-time, the IDE is a little lenient on this matter. The IDE is relying on RTTI when performing the validation, and the RTTI is based on Delphi information, not C++ information. Const-correctness works a little differently in Delphi than in C++. So, sometimes the IDE does allow less-than-compatible handlers through, especially for the sake of backwards compatibility (ie, the TRect parameter of TMenuDrawItemEvent wasn't always const).
After validation, the IDE merely stores the name of the event handler into the DFM, but the actual function is not assigned to the event until run-time (since its memory address is not known at design-time). When the DFM is streamed in at run-time, the function's type will not be validated again when the function is assigned to the event, it is blindly taken as-is. So, even at run-time, there may be subtle issues if the handler is not 100% compatible with the event.
The compiler, on the other hand, requires the type of a function to be 100% compatible with a function pointer that it is being assigned to. There is no room for any differences. So, in this case, a difference in const qualifiers on a parameter is a big no-no. That is why the compiler won't let you assign miThemesDrawItem() to the TMenuItem::OnDrawItem event in your code, until you fix the signature of miThemesDrawItem() to match the type of TMenuDrawItemEvent exactly.

Related

C++ Builder 11.2 assigning C++ method to Delphi event

We have an older project with mixed C++/Delphi code. We are trying to compile it with the latest C++Builder.
This code was OK with RAD Studio 10.1:
NewObject->OnDraw = (TDrawEvent)&(CObj->MyVRMLShapeDraw);
Edit: actual declarations:
TDrawEvent = procedure (Sender: TGLObject; Face: GLenum; Draft: boolean; State: TGLDrawState ) of object;
private
FOnDraw: TDrawEvent;
published
property OnDraw: TDrawEvent read FOnDraw write FOnDraw;
class TManager : public TBaseObject {
public:
void __fastcall MyVRMLShapeDraw(TGLObject *Sender, DWORD Face, bool Draft, char DrawState);
Where NewObject is a class instance from a Delphi class, TDrawEvent is a Delphi event method type (TMethod basically with some parameters), CObj is our C++ object, MyDraw is its C++ method.
TGLDrawState is an enum with a few values.
The latest C++Builder compiler drops this error message:
[bcc64 Error] xy.cpp(1021): cannot compile this unexpected cast lvalue yet
How can we solve this? Is it possible at all to call a C++ method from Delphi code in the usual event handler method style?
EDIT: I solved it with wrapping the call in a global function, but if someone knows the elegant syntax of this, it would be appreciated.
EDIT: My mistake - the newer 11.2 compiler required using unsigned instead of DWORD, and glclasses::TGLDrawState instead of char in the C++ method parameter list. Older compilers accepted less strict type matching.

Why so many pointers?

Searching for references of Win32 LDAP API functions, I found the following JwaWinLDAP.pas unit.
On this unit, to function ldap_search_st is declared:
function ldap_search_st(ld: PLDAP; base: PAnsiChar; scope: ULONG;
filter, attrs: PAnsiChar; attrsonly: ULONG; var timeout: TLDAPTimeVal;
var res: PLDAPMessage): ULONG; cdecl;
The timeout: TLDAPTimeVal parameter is declared as:
PLDAPTimeVal = ^TLDAPTimeVal;
l_timeval = packed record
tv_sec: Longint;
tv_usec: Longint;
end;
LDAP_TIMEVAL = l_timeval;
PLDAP_TIMEVAL = ^LDAP_TIMEVAL;
TLDAPTimeVal = l_timeval;
On the code, if I use something like:
procedure foo;
var
TimeVal: PLDAPTimeVal;
begin
ldap_search_st(foo1, Nil, 0, PAnsiChar('(objectClass=*)'), Nil, 0, TimeVal, foo2);
end;
Compiler gives me error:
[dcc32 Error] Types of actual and formal var parameters must be
identical
because of the timeout parameter. If I change TimeVal type to TLDAPTimeVal it compiles and the application works.
The question is:
when I see declaration of types in Delphi, they are always like:
type
PType1 = ^Type1
Type1 = record...
In the specific example cited, it could be:
l_timeval = packed record
tv_sec: Longint;
tv_usec: Longint;
end;
TLDAPTimeVal = l_timeval;
and it would work the exact same way (I think)... Why so much confusion on this kind of declaration?
type
PType1 = ^Type1
Type1 = record...
Above type declaration declares two types - one is record, and another one is typed pointer for that specific record type. They are usually declared in pairs, because they are related. But if your or any code does not need typed pointer, it does not need to declare pointer type.
Function ldap_search_st uses only declared record type. But some other functions in that unit expect pointer as parameter. That is why declaration has both.
Code in question is LDAP Windows API header translation for Delphi. Windows API uses pointers for passing structures to functions.
API translations are usually complex and sometimes have seemingly superfluous declarations. For completeness, translations usually contain all original declarations (symbols) - those are l_timeval, LDAP_TIMEVAL and PLDAP_TIMEVAL, and while those would be sufficient to use the API, there are two additional declarations whose only purpose is providing Delphi style names for more user friendly experience PLDAPTimeVal and TLDAPTimeVal
If you take a look at original LDAP function declarations they all use pointers for passing structures. For instance:
ULONG ldap_search_st(
_In_ LDAP *ld,
_In_ PCHAR base,
_In_ ULONG scope,
_In_ PCHAR filter,
_In_ PCHAR attrs[],
_In_ ULONG attrsonly,
_In_ struct l_timeval *timeout,
_Out_ LDAPMessage **res
);
and
ULONG ldap_connect(
_In_ LDAP *ld,
_In_ LDAP_TIMEVAL *timeout
);
There is one difference in those two considering timeout parameter.
ldap_search_st expects non-null value in timeout parameter - and Delphi translation of that parameter is var timeout: TLDAPTimeVal to match that intent more clearly - that declaration prevents you from accidentally passing null. While TLDAPTimeVal is not a pointer type, having var keyword makes our timeout parameter to behave like one. Behind the scenes Delphi will pass pointer to the structure and that will perfectly match original function declaration.
On the other hand in ldap_connect timeout can contain null value. In that case default timeout value will be used. The only way to satisfy that requirement is to use pointer type to timeout structure. In another words PLDAPTimeVal and Delphi translation of that function declaration is
function ldap_connect(ld: PLDAP; timeout: PLDAPTimeval): ULONG;
It is the matter of coding standards and conventions and there is so many of them because LDAP declarations were adopted into PSDK and then translated to Delphi. Additionally, since Pascal doesn't allow pointer type declaration (eg ^Integer) within formal parameters unlike plain C (eg int *), corresponding pointer types were added to declaration.
Here, I marked various conventions in the declaration, note the difference in casing and prefixes:
PLDAPTimeVal = ^TLDAPTimeVal; // Delphi pointer (Econos convention)
l_timeval = packed record // canonic structure (LDAP convention)
tv_sec: Longint;
tv_usec: Longint;
end;
LDAP_TIMEVAL = l_timeval; // Windows structure (PSDK convention)
PLDAP_TIMEVAL = ^LDAP_TIMEVAL; // Windows pointer (PSDK convention)
TLDAPTimeVal = l_timeval; // Delphi structure (Econos convention)
Curious thing: the forward declaration (before structure) of Delphi pointer is also mandated by Econos convention. Original PSDK code declares the pointer after the structure.

Pass by reference to const in Delphi

Is there a c++ pass-by-reference-to-const Delpi equivalent in XE2? The c++ code would be:
void passByRef(const MyClass& param);
void passByRef(const MyClass& param);
The const here says that you cannot modify the value to which the reference refers.
Assuming that MyClass is mapped to a Delphi class there is nothing equivalent. In Delphi a class is a reference type. You can pass a const reference like this:
procedure Foo(const param: TMyClass);
This means that the function is not allowed to modify the value of param.
param := ...; // this would result in a compilation error
But the method is allowed to mutate the object to which param refers.
param.DataMember := ...; // compiles
param.SomeProperty := ...; // compiles
param.MutateObject(); // compiles
Delphi is simply missing this aspect of const that is available in C++.
If you instead mapped to a record rather than a class, things would be a little different. Then you'd have:
procedure Foo(const param: TRecord);
Again you could not assign to param, but because this is a value type, you are also prevented from directly modifying any data members of param.
param.DataMember := ...; // does not compile, for TRecord being a record
But the compiler won't stop you calling methods on such a record that mutate the internal state. This is something of an oversight in my view, but hard for the language designers to avoid given the tools at their disposal. Again, the design of C++ is simply richer in this area, with more fine-grained constness specification allowed.
For this reason I would strongly recommend that you consider not writing record types with instance methods that mutate the record's state.

What is the difference between the IntToStr function in C++Builder 6 and C++Builder Seattle?

I can compile successfully the below code snippet in C++Builder 6, but I can't compile it in RAD Studio Seattle:
unsigned long x = 50;
String s = IntToStr(x);
[bcc32 Error] Unit1.cpp(55): E2015 Ambiguity between '_fastcall
System::Sysutils::IntToStr(int) at c:\program files
(x86)\embarcadero\studio\17.0\include\windows\rtl\System.SysUtils.hpp:3182'
and '_fastcall System::Sysutils::IntToStr(__int64) at c:\program files
(x86)\embarcadero\studio\17.0\include\windows\rtl\System.SysUtils.hpp:3183'
I've checked that IntToStr definition.
C++Builder 6:
extern PACKAGE AnsiString __fastcall IntToStr(int Value)/* overload */;
extern PACKAGE AnsiString __fastcall IntToStr(__int64 Value)/* overload */;
C++Builder Seattle:
extern DELPHI_PACKAGE System::UnicodeString __fastcall IntToStr(int Value)/* overload */;
extern DELPHI_PACKAGE System::UnicodeString __fastcall IntToStr(__int64 Value)/* overload */;
extern DELPHI_PACKAGE System::UnicodeString __fastcall UIntToStr(unsigned Value)/* overload */;
extern DELPHI_PACKAGE System::UnicodeString __fastcall UIntToStr(unsigned __int64 Value)/* overload */;
What is the difference between C++Builder 6 and C++ Builder Seattle?
Basically in this scenario there exists an Ambiguity, means there are two overloads of IntToStr , expecting different argument types (int and int64). the ambiguity is to downgrade the provided long type to int or upgrade it to int64.
here the parameter should be cast-ed to most matching type.
Apart from what #Ali Kazmi said, for unsigned you need to use one of the last 2, since your value is unsigned:
UIntToStr()
I think then it will not require you to cast the value and it should compile.
Builder 6 implicitly casted unsigned values to one of the signed types, the new Builders (Since 2010) does not do so by default, you need to explicitly cast it to a signed type or use the unsigned alternatives
You can reduce the complexity of finding the correct converter functions for each variable type (as you have to do in Delphi) by just using one of the overloaded constructors of that mighty UnicodeString class itself, e.g.:
unsigned long x = 50;
String s(x);
Also, you can write String(MyVariableName) anywhere in your code to get a UnicodeString representation of MyVariableName.
In older CBuilder versions, String was a typedef for AnsiString.
Nowadays, it's a typedef for UnicodeString.

how to use Pchar function to use c#

How can I use this function in C#?
function CheckCard (pPortID:LongInt;pReaderID:LongInt;pTimeout:LongInt): PChar;
This function included the dll.
I can try this way:
[DllImport("..\\RFID_107_485.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.ThisCall)]
public static extern char CheckCard(int pccPortID, int pccdReaderID, int pccTimeout);
char pccCheckCard = CheckCard(3, 129, 1000);
Console.WriteLine(pccCheckCard);
but i don't get a true answer...
please help me ? :)
There are many problems here. This is what I can see:
The Delphi code as written uses the Delphi register calling convention. That is only accessible from Delphi code and cannot be called by a p/invoke method. However, it is possible that you have omitted the calling convention from the code and it is in fact stdcall.
Your p/invoke uses CallingConvention.ThisCall which certainly does not match any Delphi function. That calling convention is not supported by Delphi.
You mistranslate PChar, a pointer to null-terminated array of characters as char, a single UTF-16 character.
The Delphi code looks suspicious. The function returns PChar. Well, who is responsible for deallocating the string that is returned. I would not be surprised if the Delphi code was returning a pointer to a string variable that is destroyed when the function returns, a very common error.
You refer to the DLL using a relative path. That is very risky because you cannot easily control whether or not the DLL will be found. Place the DLL in the same directory as the executable, and specify just the DLL's file name.
There is no error checking to be seen.
A variant that might work could look like this:
Delphi
function CheckCard(pPortID: LongInt; pReaderID: LongInt; pTimeout: LongInt): PChar;
stdcall;
C#
[DllImport("RFID_107_485.dll", CallingConvention = CallingConvention.StdCall)]
public static extern IntPtr CheckCard(int pccPortID, int pccdReaderID, int pccTimeout);
....
IntPtr pccCheckCard = CheckCard(3, 129, 1000);
// check pccCheckCard for errors, presumably IntPtr.Zero indicates an error
// assuming ANSI text
string strCheckCard = Marshal.PtrToStringAnsi(pccCheckCard);
// or if the Delphi code returns UTF-16 text
string strCheckCard = Marshal.PtrToStringUni(pccCheckCard);
This leaves unresolved how to deallocate the pointer returned. You'll have to consult your documentation for the function to find that out. The question contains insufficient information.

Resources