Ben
Ben

Reputation: 3440

Delphi and C/C++ DLL Struct vs.Record

I previously asked a question about a delphi and a C/C++ DLL.

I have now another question about a record / struct. The DLL should be able to dynamically CHANGE the VALUE of the pointer vars from the MainAPP.

My delphi MAINAPP has the following record:

type MyRec = record
 MyInteger    : Pointer;
 MyWideString : pwidechar;
 MyString     : pchar;
 MyBool       : Pointer
end;

type
 TMyFunc = function ( p  : pointer ): pointer; stdcall;

procedure test;
var
 MyFunction  : TMyFunc;
 TheRecord   : MyRec;
 AnInteger   : Integer;
 AWideString : WideString;
 AString     : String;
 ABool       : Bool;
begin
 AnInteger                := 1234;
 AWideString              := 'hello';
 AString                  := 'hello2';
 ABool                    := TRUE;
 TheRecord.MyInteger      := @AnInteger;
 TheRecord.MyWideString   := pwidechar(AWideString);
 TheRecord.AString        := pchar(AString);
 TheRecord.ABool          := @ABool;
 [...]
 @MyFunction := GetProcAddress...
 [...]
 MyFunction  (@TheRecord);  // now the DLL should be able to change the values dynamically.
 MessageBoxW (0, pwidechar(AWideString), '', 0); // Show the results how the DLL changed the String to...
end;

C/C++ Code (just example)

typedef struct _TestStruct{
void    *TheInteger;    // Pointer to Integer
wchar_t *TheWideString; // Pointer to WideString
char    *TheAnsiString; // Pointer to AnsiString  
bool    *TheBool        // Pointer to Bool
}TestStruct;

__declspec(dllexport) PVOID __stdcall MyExportedFunc (TestStruct *PTestStruct)
{
MessageBoxW(0 ,PTestStruct->TheWideString, L"Debug" , 0); // We read the value.
    PTestStruct->TheWideString = L"Let me change the value here.";
return 0;
}

For some reasons it crashes etc. What do I do wrong?

Thanks for help.

Upvotes: 0

Views: 4524

Answers (3)

Remy Lebeau
Remy Lebeau

Reputation: 597345

You are mismanaging the string fields. PWideChar and PChar are not the same thing as "pointer to WideString" and "pointer to AnsiString". Delphi has PWideString (WideString*) and PAnsiString (AnsiString*) types for that purpose instead. You should also use Delphi's PInteger (int*) and PBoolean (bool*) types instead of Pointer (void*). Delphi and C++ are both type-safe languages. Stay away from untyped pointers when possible, your code will be better for it.

type
  PMyRec = ^MyRec;
  MyRec = record
    MyInteger    : PInteger;
    MyWideString : PWideString;
    MyAnsiString : PAnsiString;
    MyBool       : PBoolean;
  end;

  TMyFunc = function ( p  : PMyRec ): Integer; stdcall;

procedure test;
var
  MyFunction  : TMyFunc;
  TheRecord   : MyRec;
  AnInteger   : Integer;
  AWideString : WideString;
  AAnsiString : AnsiString;
  ABool       : Bool;
begin
 AnInteger                := 1234;
 AWideString              := 'hello';
 AAnsiString              := 'hello2';
 ABool                    := TRUE;
 TheRecord.MyInteger      := @AnInteger;
 TheRecord.MyWideString   := @AWideString;
 TheRecord.MyAnsiString   := @AAnsiString;
 TheRecord.MyBool         := @ABool;
 [...]
 @MyFunction := GetProcAddress...
 [...]
 MyFunction  (@TheRecord); 
 MessageBoxW (0, PWideChar(AWideString), '', 0);
end;

.

typedef struct _MyRec
{
    int        *MyInteger;    // Pointer to Integer
    WideString *MyWideString; // Pointer to WideString
    AnsiString *MyAnsiString; // Pointer to AnsiString  
    bool       *MyBool;       // Pointer to Bool
} MyRec, *PMyRec;

__declspec(dllexport) int __stdcall MyExportedFunc (PMyRec PRec)
{
    MessageBoxW(NULL, PRec->MyWideString->c_bstr(), L"Debug" , 0);
    *(PRec->MyWideString) = L"Let me change the value here.";
    return 0;
}

With that said, it can be very dangerous to manipulate AnsiString (and UnicodeString) values across a DLL boundary like this, especially if the EXE and DLL are written in different versions of Delphi/C++Builder due to RTL differences, memory manager differences, payload layout differences, etc. WideString is OK to pass around, though, because it's memory and layout are controlled by the OS, not the RTL.

Upvotes: 1

dthorpe
dthorpe

Reputation: 36092

This probably isn't the cause of the crash at the point where the C++ code assigns to the TheWideString pointer, but I do see a problem of expectations...

I notice that you're putting the address of the string data that the Delphi AWideString variable points to into the MyWideString field of the record. You pass the record to the C++ function, which assigns a new pointer value to the TheWideString/MyWideString field of the record. When execution returns to the Delphi code, you output the contents of the AWideString variable.

Your comments indicate that you expect the contents of the AWideString variable to be changed by the C++ function, but that's not what will happen.

The C++ function changes the field in the structure. It does nothing to the memory location that the field previously pointed to. The data that AWideString points to will not be affected by the C++ function.

If the C++ code copied data into the address contained in the field, then it would overwrite the string data that AWideString points to. Since AWideString is a Delphi managed string, and the C++ function would be copying more data to that string memory area than the original string had allocated space for, copying data in the C++ function would write past the end of the Delphi allocated string buffer and probably corrupt the Delphi heap. A crash may occur some time later. So it's a good that you're only assigning the pointer to the field, not copying the data! ;>

To see what the C++ function changed, your Delphi code should output the contents of the MyWideString field of the record after the call to the C++ function.

Upvotes: 2

Serge Z
Serge Z

Reputation: 425

Synchronize fields order in the structures. You can break memory heap using wrong pointers. Also, check alignment both in Delphi and C++.

Upvotes: 1

Related Questions