Visualizzazione dei risultati da 1 a 9 su 9
  1. #1
    Utente di HTML.it L'avatar di colde
    Registrato dal
    Feb 2001
    Messaggi
    1,802

    Problemi con puntatori

    Ciao Alka,
    sono sempre in ballo con quella famosa DLL che sto sviluppando e ho scoperto che in qualche modo il puntatore utilizzato non si dealloca e alla seconda volta che richiamo la mia funzione, mi ritorna errore EInvalidPointer. Attualmente copio una stringa di tipo string in un puntatore e dalla funzione DLL passo il suo contenuto all'applicativo che ne fa richiesta. Pensavo di modificare la cosa in base ad un tuo suggerimento che mi avevi dato settimane fa, ovvero passare l'indirizzo del puntatore e non il suo contenuto.
    Ho capito bene?

    Non sono ancora in grado di sfruttare bene i puntatori, devo ancora comprenderli a pieno ma vorrei porre alla tua attenzione il codice utilizzato, sia per la DLL che per l'applicazione che la richiama. Magari sai dirmi dove effettivamente è sbagliato o magari mi puoi dare qualche altro suggerimento.

    Funzioni residenti nella DLL (La funzione Polling() richiama la funzione DownloadPolling()):
    codice:
    Function DownloadPolling(IDBOX: Integer): String;
     var
      Response    : String;
      Content     : array of char;
      I           : Integer;
    
    begin
      //Controllo che l'apertura sia stata correttamente eseguita
      if ApdComPort1.Open then
        begin
          //Invio il carattere EOT di chiusura per evitare trasmissioni aperte precedentemente
          ApdComPort1.PutString(chr($04));
          //Invio la stringa di Polling: [SOH][GID][DID][ENQ] che in esadecimale diventa [01][41][?][05]
          ApdComPort1.PutString(chr($01)+chr($41)+IntToStr(IDBOX)+chr($05));
          //Attivo il timer in modo da controllare il buffer per circa un secondo
          NewTimer(ET, 4);
          //Finchè il timer non si azzera controllo se il box mi ha spedito i dati
          I := 0;
          SetLength(Content, 104);
          Response := '';
          repeat
            while ApdComPort1.CharReady do
              begin
                Content[I] := ApdComPort1.GetChar;
                //if I > 5 then
                  //begin
                    //if I in [6..9] then Response := Response+IntToStr(Ord(Content[I]))
                    //else Response := Response+Content[I];
                  //end;
                I := I + 1;
              end;
          until TimerExpired(ET);
          for I := 1 to 104 do
            begin
              Response := Response+Content[I];
            end;
          Content := Nil;
          Result := Response;
        end
      else
        begin
          Result := '';
        end;
    end;
    
    Function Polling(DID: PChar): PChar; export; StdCall;
    var
      ResponseBox : String;
      VerifyXor   : byte;
      I           : Integer;
    begin
      if ApdComPort1 = nil then
        ApdComPort1 := TApdComPort.Create(nil);
      try
        //Configuro la porta seriale e la apro per iniziare le comunicazioni
        ApdComPort1.ComNumber := 1;
        ApdComPort1.Baud := 9600;
        ApdComPort1.Parity := pNone;
        ApdComPort1.DataBits := 8;
        ApdComPort1.StopBits := 1;
        ApdComPort1.InSize := 128;
        ApdComPort1.Open := true;
        FreeMem(Result, 105);
        if ApdComPort1.Open then
        begin
          //for N := 1 to Loop do
            //begin
              repeat
                //repeat
                  ResponseBox := DownloadPolling(StrToInt(DID));
                //until ResponseBox <> '';
                VerifyXor := $00;
                if ResponseBox <> '' then
                  begin
                    for I := 1 to 104 do
                      begin
                        VerifyXor := VerifyXor xor Byte(ResponseBox[I]);
                      end;
                    //Verifico l'esito del VCR, se 0 è corretto altrimenti c'è stato qualche errore
                    if VerifyXor = 0 then
                      begin
                        //I dati sono corretti e invio la stringa di ritorno che il box si aspetta inviando un ACK = Hex(06)
                        ApdComPort1.PutString(Chr($01) + Chr($41) + Chr($31) + Chr($06));
                        //Chiudo la trasmissione con EOT
                        ApdComPort1.PutString(Chr($04));
                      end
                    else
                      begin
                        //I dati sono errati e invio la stringa di ritorno che il box si aspetta inviando un NACK = Hex(15)
                        ApdComPort1.PutString(Chr($01) + Chr($41) + Chr($31) + Chr($15));
                        //Chiudo la trasmissione con EOT
                        ApdComPort1.PutString(Chr($04));
                      end;
                  end
                else
                  begin
                    ResponseBox := 'ERROR';
                  end;
              until VerifyXor = 0;
            //end;
          //Chiudo la porta di comunicazione
          ApdComPort1.Open := false;
          end
        else
          begin
            ResponseBox := 'COM Port Close';
          end;
        //Alloco memoria per contenere il valore di ritorno della funzione da passare al software
        GetMem(Result,105);
        //Copio il valore di ritorno nella variabile che la funzione ritorna
        Result := StrPCopy(Result, ResponseBox);
      finally
        ApdComPort1.Free;
      end;
    end;
    Questo è il codice dell'applicazione
    codice:
    TextReturn := Polling('1');
            //Alloco memoria per contenere il valore di ritorno della funzione da passare al software
            GetMem(Return,Length(TextReturn)+1);
            //Copio il valore di ritorno nella variabile che la funzione ritorna
            Return := StrPCopy(Return, TextReturn);
            Memo1.Lines.Add(Return);
            FreeMem(TextReturn, Length(TextReturn)+1)
    Mi viene un dubbio, se io passo l'indirizzo del puntatore dalla DLL all'applicazione, posso copiare il suo contenuto in una stringa per poi deallocare la memoria del puntatore senza perdere i dati copiati?

    Grazie
    www.beppegrillo.it
    Il blog di Beppe!!

  2. #2
    Moderatore di Programmazione L'avatar di alka
    Registrato dal
    Oct 2001
    residenza
    Reggio Emilia
    Messaggi
    24,301

    Re: Problemi con puntatori

    Originariamente inviato da colde
    Mi viene un dubbio, se io passo l'indirizzo del puntatore dalla DLL all'applicazione, posso copiare il suo contenuto in una stringa per poi deallocare la memoria del puntatore senza perdere i dati copiati?
    La risposta è .

    P.S.: hai usato un tipo String nel valore di ritorno di una funzione all'interno della DLL; tale valore non sarà direttamente interpretabile dai linguaggi che non sono Delphi.
    MARCO BREVEGLIERI
    Software and Web Developer, Teacher and Consultant

    Home | Blog | Delphi Podcast | Twitch | Altro...

  3. #3
    Utente di HTML.it L'avatar di colde
    Registrato dal
    Feb 2001
    Messaggi
    1,802

    Re: Re: Problemi con puntatori

    Originariamente inviato da alka
    P.S.: hai usato un tipo String nel valore di ritorno di una funzione all'interno della DLL; tale valore non sarà direttamente interpretabile dai linguaggi che non sono Delphi.
    Ma quella variabile è solo interna alla DLL e non viene richiamata direttamente da software esterni quindi non dovrebbe creare problemi di interpretazione, sempre che non abbia capito male.
    www.beppegrillo.it
    Il blog di Beppe!!

  4. #4
    Moderatore di Programmazione L'avatar di alka
    Registrato dal
    Oct 2001
    residenza
    Reggio Emilia
    Messaggi
    24,301

    Re: Re: Re: Problemi con puntatori

    Originariamente inviato da colde
    Ma quella variabile è solo interna alla DLL e non viene richiamata direttamente da software esterni quindi non dovrebbe creare problemi di interpretazione, sempre che non abbia capito male.
    Ok, se la funzione DownloadPolling non viene esportata, allora va bene.
    MARCO BREVEGLIERI
    Software and Web Developer, Teacher and Consultant

    Home | Blog | Delphi Podcast | Twitch | Altro...

  5. #5
    Utente di HTML.it L'avatar di colde
    Registrato dal
    Feb 2001
    Messaggi
    1,802
    Mi vengono altri dubbi:

    nella DLL imposto la variabile di ritorno Result di tipo Pointer generico in modo da restituire all'applicazione l'indirizzo di memoria dove il valore è stato memorizzato. Per fare ciò devo creare una variabile aggiuntiva di tipo string nella DLL che verrà usata per salvare la stringa recuperata dalla seriale e a questo punto il puntatore Result dovrà puntare all'indirizzo di quella variabile. Nell'applicazione richiamo la funzione della DLL, assegno l'indirizzo del puntatore Result ad un'altra variabile di tipo pointer, creo una variabile di tipo string a cui assegnerò il contenuto del puntatore (quindi prelevo proprio il suo contenuto e non l'indirizzo), dopodichè posso deallocare sia il buffer usato dal puntatore di ritorno della funzione (quindi Result), sia il buffer usato dalla variabile di tipo pointer dichiarata nell'applicazione (che servirebbe a recuperare l'indirizzo di memoria ritornato dalla funzione). Confermi?
    www.beppegrillo.it
    Il blog di Beppe!!

  6. #6
    Moderatore di Programmazione L'avatar di alka
    Registrato dal
    Oct 2001
    residenza
    Reggio Emilia
    Messaggi
    24,301
    Non sono riuscito a comprendere bene il meccanismo che hai descritto, forse perché al suo stato attuale è molto complesso, ma cerco di fare un riassunto sintetico della questione "stringhe in DLL" con cui forse puoi giungere autonomamente alla risposta.

    Delphi gestisce automaticamente le stringhe, così come avviene con un'altra ristretta cerchia di tipi di dati (Variant, ad esempio).

    Gestire automaticamente significa che la rappresentazione binaria della stringa è "particolare", innanzitutto; in secondo luogo, Delphi opera sulla memoria allocando spazio per le stringhe a seconda dell'uso che se ne fa: ad esempio, se dichiari una stringa e assegni il suo valore ad un'altra, Delphi effettua semplicemente l'assegnazione del puntatore, quindi l'operazione risulta estremamente rapida; non appena tenti di modificare una delle due istanze, Delphi alloca memoria per le modifiche; in sostanza, la memoria viene automaticamente amministrata per allocare lo spazio necessario ad ospitare le stringhe sottoposte a varie operazioni a seconda dell'ambito.

    La memoria viene poi revocata quando le stringhe escono dallo "scope" o non sono più raggiungibili.

    Tutto questo compito è svolto dal memory manager di Delphi, che opera nel contesto di un'applicazione.

    Il problema nasce quando si ha a che fare con DLL, che di fatto rappresentano due istanze di applicazioni (anche se il termine è improprio), cioè due contesti in cui lavorano due memory manager distinti, a meno che non si includa la unit ShareMem, che serve appositamente a delegare il compito di gestione della memoria al "memory manager" condiviso, sfruttando un'apposita DLL esterna ("borlndmm.dll").

    Se la tua DLL deve essere usata da altre applicazioni, non puoi affidarti al memory manager condiviso in quanto altri linguaggi e compilatori non hanno alcuna cognizione di esso, che è valido solo in ambiente Delphi.

    Non puoi nemmeno utilizzare il tipo string in quanto, oltre ad essere particolare e non standard come formato (appositamente per consentire al MM di scrivere in memoria le informazioni necessarie a gestire dinamicamente le risorse stringa), sono sottoposte come si diceva prima ad una gestione automatica della memoria.

    Significa che restituendo un puntatore ad una variabile string, quando la funzione termina, la variabile esce dal suo "scope" e quindi Delphi la dealloca; il programma che invoca la funzione si troverà quindi un puntatore non utilizzabile, anche se si tratta di un'applicazione Delphi, con l'aggiunta di non poter comprendere l'informazione in quanto, se non è Delphi, il puntatore restituisce l'indirizzo ad una struttura di memoria particolare che non può essere direttamente interpretata (a meno che tu non voglia introdurre questa interpretazione nei file sorgenti da distribuire ad altri linguaggi, ma rimarrebbe il problema della gestione della memoria).

    Quindi, in conclusione, laddove sia necessario operare con delle stringhe, devi provvedere a creare dei buffer usando le funzioni che hai già sfruttato e che già conosci, copiando all'interno di essi il contenuto delle stringhe, in quanto i buffer sono semplici array di byte che puoi gestire come desideri, senza che Delphi intervenga distruggendoli automaticamente.

    Riassumendo, non puoi restituire stringhe come valori di ritorno di funzioni esportate dalla DLL.

    Se si tratta di funzioni interne, puoi ovviamente fare uso di stringhe string purché tu provveda a memorizzarle in un buffer prima di ritornare alla routine dell'applicazione che chiama la tua funzione esportata.

    In breve, quando il contesto passa dall'ambito della tua DLL all'ambito dell'applicazione che utilizza la tua libreria, qualsiasi valore di tipo stringa non deve essere restituito come string, né puoi restituire puntatori a variabili di questo tipo, ma devi restituire necessariamente un puntatore ad un buffer allocato (che non viene modificato da Delphi), oppure scrivere all'interno di un buffer che l'applicazione chiamante provvede ad allocare precedentemente passandoti il suo indirizzo, assieme alla lunghezza massima del buffer oltre alla quale non puoi scrivere (troncando la stringa); quest'ultima è in genere la via scelta per le funzioni API, ad esempio, in cui si alloca un buffer di dimensioni sufficienti e si passa alle funzioni una struttura (record) contenente il puntatore al buffer da riempire, la sua dimensione attuale e altre informazioni di contorno che possono essere utili per svolgere il compito specifico affidato alla funzione all'interno della DLL.

    Magari, sfruttando queste indicazioni, verifica come hai steso la tua applicazione e guarda se i casi da te affrontati ricadono nelle condizioni "pericolose" descritte precedentemente.

    Ciao!
    MARCO BREVEGLIERI
    Software and Web Developer, Teacher and Consultant

    Home | Blog | Delphi Podcast | Twitch | Altro...

  7. #7
    Utente di HTML.it L'avatar di colde
    Registrato dal
    Feb 2001
    Messaggi
    1,802
    Ciao Alka,
    grazie al tuo topic sono ritornato al vecchio meccanismo, meno problematico da gestire. Avrei un problema che non capisco da dove salta fuori:

    codice:
    Function Polling(DID: PChar): PChar; export; StdCall;
    var
      ResponseBox : String;
      VerifyXor   : byte;
      I           : Integer;
    begin
      try
        if ApdComPort1 = nil then
        ApdComPort1 := TApdComPort.Create(nil);
        //Configuro la porta seriale e la apro per iniziare le comunicazioni
        ApdComPort1.ComNumber := 1;
        ApdComPort1.Baud := 9600;
        ApdComPort1.Parity := pNone;
        ApdComPort1.DataBits := 8;
        ApdComPort1.StopBits := 1;
        ApdComPort1.InSize := 128;
        ApdComPort1.Open := true;
        if Result <> nil then
          FreeMem(Result);
        if ApdComPort1.Open then
        begin
          //for N := 1 to Loop do
            //begin
              repeat
                repeat
                  ResponseBox := DownloadPolling(StrToInt(DID));
                until ResponseBox <> '';
                VerifyXor := $00;
                if ResponseBox <> '' then
                  begin
                    for I := 1 to 103 do
                      begin
                        VerifyXor := VerifyXor xor Byte(ResponseBox[I]);
                      end;
                    //Verifico l'esito del VCR, se 0 è corretto altrimenti c'è stato qualche errore
                    if VerifyXor = 0 then
                      begin
                        //I dati sono corretti e invio la stringa di ritorno che il box si aspetta inviando un ACK = Hex(06)
                        ApdComPort1.PutString(Chr($01) + Chr($41) + Chr($31) + Chr($06));
                        //Chiudo la trasmissione con EOT
                        ApdComPort1.PutString(Chr($04));
                      end
                    else
                      begin
                        //I dati sono errati e invio la stringa di ritorno che il box si aspetta inviando un NACK = Hex(15)
                        ApdComPort1.PutString(Chr($01) + Chr($41) + Chr($31) + Chr($15));
                        //Chiudo la trasmissione con EOT
                        ApdComPort1.PutString(Chr($04));
                      end;
                  end
                else
                  begin
                    ResponseBox := 'ERROR';
                  end;
              until VerifyXor = 0;
            //end;
          //Chiudo la porta di comunicazione
          //ApdComPort1.Open := false;
          end
        else
          begin
            ResponseBox := 'COM Port Close';
          end;
        //Alloco memoria per contenere il valore di ritorno della funzione da passare al software
        GetMem(Result,104);
        //Copio il valore di ritorno nella variabile che la funzione ritorna
        Result := StrPCopy(Result, ResponseBox);
    
      finally
        ApdComPort1.Free; <---- ERRORE Invalid Pointer Operation
      end;
    end;
    Spiego il problema: alla prima chiamata della funzione non ho nessun tipo di errore o problema, invio i dati tramite seriale e ricevo la risposta correttamente; se richiamo la funzione per due volte consecutive, la seconda chiamata alla funzione mi genera errore di Invalid Pointer Operation sull'istruzione ApdComPort1.Free
    Perchè? Dove sbaglio?
    www.beppegrillo.it
    Il blog di Beppe!!

  8. #8
    Moderatore di Programmazione L'avatar di alka
    Registrato dal
    Oct 2001
    residenza
    Reggio Emilia
    Messaggi
    24,301
    Originariamente inviato da colde
    Spiego il problema: alla prima chiamata della funzione non ho nessun tipo di errore o problema, invio i dati tramite seriale e ricevo la risposta correttamente; se richiamo la funzione per due volte consecutive, la seconda chiamata alla funzione mi genera errore di Invalid Pointer Operation sull'istruzione ApdComPort1.Free
    Perchè? Dove sbaglio?
    All'inizio della funzione, hai inserito una condizione

    codice:
    if ApdComPort1 = nil then ...
    ma il problema è che tale variabile non viene impostata a nil da nessuna parte.

    La chiamata a Free consente di distruggere l'oggetto, ma il riferimento allo stesso non viene impostato a nil; occorre farlo manualmente, oppure usare la procedura FreeAndNil.

    Infine, la creazione dell'oggetto deve essere esterna al ciclo try...finally, in quanto se la creazione fallisce, viene eseguito il blocco del finally che distrugge un oggetto...inesistente.
    MARCO BREVEGLIERI
    Software and Web Developer, Teacher and Consultant

    Home | Blog | Delphi Podcast | Twitch | Altro...

  9. #9
    Utente di HTML.it L'avatar di colde
    Registrato dal
    Feb 2001
    Messaggi
    1,802
    Grazie, ho risolto.
    www.beppegrillo.it
    Il blog di Beppe!!

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved.