Frage zu UnicodeString



  • Ich habe eine Delphi Komponente

    type
    TPort = string;
    FPort: TPort ;
    procedure SetPort(const Value: TPort);
    property Port: TPort read FPort write SetPort;
    

    Jetzt möchte ich in CBuilder Port setzen mit ComPort->Port = "COM1";
    Dann erhalte ich plötzlich:
    c:\programme\codegear\rad studio\6.0\Bin\CodeGear.Cpp.Targets(2089,3): error : Error: Nicht auflösbares externes '__fastcall Cport::TCustomComPort::SetPortA(const System::UnicodeString)' referenziert von ..\UNIT1.OBJ

    Verstehe ehrlich gesagt nicht meinen Fehler. Kann wer weiterhelfen?



  • Du musst die dazugehörige LIB-datei zu deinem Projekt hinzufügen, denn der Compiler findet keine Referenz zu einer DLL.

    Kann die aber nicht sagen, welche LIB-Datei zu brauchst...



  • Das ist ein klassischer Konflikt mit den Makros aus den Windows-API - es gibt nämlich eine Win32-Funktion namens SetPort, und für diese existieren, wie für fast alle WinAPI-Funktionen, eine ANSI- und eine Wide-Variante sowie ein Makro-Mapping:

    // winspool.h, 2886ff
    BOOL
    WINAPI
    SetPortA(
    __in_opt    LPSTR     pName,
    __in        LPSTR     pPortName,
                DWORD       dwLevel,
    __in        LPBYTE      pPortInfo
    );
    BOOL
    WINAPI
    SetPortW(
    __in_opt    LPWSTR     pName,
    __in        LPWSTR     pPortName,
                DWORD       dwLevel,
    __in        LPBYTE      pPortInfo
    );
    #ifdef UNICODE
    #define SetPort  SetPortW
    #else
    #define SetPort  SetPortA
    #endif // !UNICODE
    

    Dieses Makro wirkt sich natürlich auch auf die Methodendeklaration in der Headerdatei aus und macht aus SetPort SetPortA oder SetPortW. In Delphi jedoch gibt es weder Makros noch entsprechende Konflikte, und entsprechend heißt die Methode dort auch erwartungsgemäß SetPort. Wegen des Makros heißt der Symbol-Import deiner C++-Objektdatei aber *SetPortA* oder *SetPortW*, deshalb der Linkerfehler.

    Einen schnellen Workaround könntest du mit #undef hinbekommen; das führt allerdings zu Problemen, weil es immer am richtigen Ort stehen muß und außerdem gewöhnliche Aufrufe von SetPort beeinträchtigt. Es gibt aber auch einen sauberen Workaround, der folgendermaßen aussieht:

    • Zunächst benötigst du den dekorierten Namen der Methode. An diesen kommst du am einfachsten mit dem bei C++Builder mitgelieferten Kommandozeilentool TDUMP. Dazu öffne eine Kommandozeile in dem Verzeichnis, wo die vom Delphi-Compiler für deine Komponente erstellte .obj-, .lib- oder .bpi-Datei liegt und tippe ein:
    // Für Objektdateien:
    TDUMP -m -le TheUnit.obj > TheUnit.dump
    
      // Für .bpi-Dateien
    TDUMP -m -li ThePackage.bpi > ThePackage.dump
    
      // Für .lib-Dateien
    TDUMP -m TheLibrary.lib > TheLibrary.dump
    

    In der .dump-Datei hast du nun eine (bei Objekt- und .bpi-Dateien mehr, bei .lib-Dateien weniger übersichtliche) Liste aller exportierten Funktionen im dekorierten Format ( @<namespace>@<class>@<method>$q<params> ). Wenn du nach "TPort@SetPort" suchst, dürftest du die fragliche Funktion schnell finden.

    • Damit der Linker diese Funktion findet, deklarierst du mit #pragma alias entsprechende Alternativnamen dafür:
    #ifdef UNICODE
     #pragma alias "@<namespace>@<class>@<method>W$q<params>"="@<namespace>@<class>@<method>$q<params>"
    #else
     #pragma alias "@<namespace>@<class>@<method>A$q<params>"="@<namespace>@<class>@<method>$q<params>"
    #endif
    
    • Am besten aufgehoben ist diese Direktive natürlich in der Headerdatei der Komponente. Da die Headerdatei vom Delphi-Compiler automatisch generiert wird, ist es nicht optimal, sie manuell zu bearbeiten - beim nächsten Kompilieren der Komponente sind die Änderungen dann wieder verloren. Daher füge die Deklarationen am besten ins zugehörige Delphi-Unit ein:
    type
      TPort = string;
      FPort: TPort ;
      procedure SetPort(const Value: TPort);
      ...
    end;
    
    {$HPPEMIT '#ifdef UNICODE'}
    {$HPPEMIT ' #pragma alias'}
    {$HPPEMIT ' "@<namespace>@<class>@<method>W$q<params>"="@<namespace>@<class>@<method>$q<params>"'}
    {$HPPEMIT '#else'}
    {$HPPEMIT ' #pragma alias'}
    {$HPPEMIT ' "@<namespace>@<class>@<method>A$q<params>"="@<namespace>@<class>@<method>$q<params>"'}
    {$HPPEMIT '#endif'}
    
    • Wenn du das Problem solchermaßen gelöst hast, solltest du den Komponentenhersteller auf den Konflikt und den Workaround aufmerksam machen, so daß er die HPPEMIT-Direktiven in seine Komponente übernimmt.


  • Wau! Danke für diese Aufklärung, das war mal sehr Umfangreich und hilfreich! 👍 Super.


Anmelden zum Antworten