【问题标题】:Delphi and C/C++ DLL Struct vs.RecordDelphi 和 C/C++ DLL 结构与记录
【发布时间】:2012-07-28 18:36:31
【问题描述】:

我之前问过一个关于 delphi 和 C/C++ DLL 的问题。

我现在有另一个关于记录/结构的问题。 DLL 应该能够从 MainAPP 动态更改指针变量的值。

我的delphi MAINAPP有如下记录:

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++ 代码(只是示例)

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;
}

由于某些原因,它会崩溃等。 我做错了什么?

感谢您的帮助。

【问题讨论】:

  • 你在调用之前检查了 MyFunction 引用 nil 吗?
  • Delphi 中的 MyRec 记录声明与 _TestStruct C 结构中的字段顺序不同
  • @BenjaminWeiss:删除了我之前的答案 - 你说 C++ 函数调用成功,然后在调用后某处失败。你能指出失败的代码行吗?
  • 对潜在的回答者保持礼貌 - 包括您在应用崩溃时收到的错误消息。
  • 向您的朋友询问她/他正在收到的错误消息/异常类型

标签: c++ c delphi memory-management dll


【解决方案1】:

这可能不是 C++ 代码分配给 TheWideString 指针时崩溃的原因,但我确实看到了预期问题...

我注意到您将 Delphi AWideString 变量指向的字符串数据的地址放入记录的 MyWideString 字段中。您将记录传递给 C++ 函数,该函数将新的指针值分配给记录的 TheWideString/MyWideString 字段。当执行返回到 Delphi 代码时,您输出 AWideString 变量的内容。

您的 cmets 表明您希望 C++ 函数更改 AWideString 变量的内容,但这不会发生。

C++ 函数更改结构中的字段。它对字段先前指向的内存位置没有任何作用。 AWideString指向的数据不会受到C++函数的影响。

如果 C++ 代码将数据复制到字段中包含的地址,那么它将覆盖AWideString 指向的字符串数据。由于AWideString 是 Delphi 管理的字符串,并且 C++ 函数将比原始字符串分配的空间更多的数据复制到该字符串内存区域,因此在 C++ 函数中复制数据将超过 Delphi 分配的字符串缓冲区的末尾并且可能会破坏 Delphi 堆。一段时间后可能会发生崩溃。所以最好只将指针分配给字段,而不是复制数据! ;>

要查看 C++ 函数发生了什么变化,您的 Delphi 代码应该在调用 C++ 函数后输出记录的MyWideString 字段的内容。

【讨论】:

    【解决方案2】:

    您对字符串字段的管理不善。 PWideCharPChar 不是与“指向 WideString 的指针”和“指向 AnsiString 的指针”相同的东西。 Delphi 有 PWideString (WideString*) 和 PAnsiString (AnsiString*) 类型用于此目的。您还应该使用 Delphi 的 PInteger (int*) 和 PBoolean (bool*) 类型,而不是 Pointer (void*)。 Delphi 和 C++ 都是类型安全的语言。尽可能远离无类型指针,你的代码会更好。

    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;
    }
    

    话虽如此,像这样跨 DLL 边界操作 AnsiString(和 UnicodeString)值可能非常危险,特别是如果 EXE 和 DLL 是用不同版本的 Delphi/C++Builder 编写的,因为到 RTL 差异、内存管理器差异、有效负载布局差异等。WideString 可以传递,因为它的内存和布局由操作系统控制,而不是 RTL。

    【讨论】:

    • 目前无法尝试。但是我为什么要远离无类型指针呢? C/C++ 也有 WideString 吗?
    • 因为当您使用无类型指针时,编译器无法验证您的代码是否安全。您丢弃了编译器为您收集的所有信息。是的,C++Builder 有一个 WideString 类,它与 Delphi 的 WideString 类型二进制兼容,与 AnsiStringUnicodeString 相同。但是如果需要支持其他C++编译器,那么在这种类型的代码中根本不能使用AnsiStringUnicodeString,需要使用不同的设计。
    • WideString 映射到 C++ 中的 BSTR
    【解决方案3】:

    同步结构中的字段顺序。您可以使用错误的指针破坏内存堆。此外,请检查 Delphi 和 C++ 中的对齐方式。

    【讨论】:

    • 结构工作得很好。我可以读取所有值。我就是无法改变它们。
    • 老兄。我已经删除了我以前的帖子——早上我有点傻)当你通过不同的 dll 传递字符串时尝试使用 SysAllocString。像这样:PTestStruct->TheWideString = SysAllocString(L"让我在这里更改值。");
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-02-20
    • 1970-01-01
    • 2012-07-28
    • 1970-01-01
    • 1970-01-01
    • 2013-09-04
    • 1970-01-01
    相关资源
    最近更新 更多