2013-04-10 7 views
5

Sono uno scrittore C++ (MSVC), newbie VB che cerca di assistere uno scrittore esperto di VB.net che non ha ancora svolto questa attività in precedenza.passare i puntatori alla funzione esterna C in una DLL da VB

Desideriamo sviluppare entrambe le applicazioni C/C++ e VB per utilizzare una DLL scritta in C++ con funzioni API C esternate. Il programma C++ sta funzionando bene. È VB dove stiamo avendo difficoltà.

La DLL fornisce una funzione di extern C:

RegisterCallback(void* cbFuncPtr, void* dataPtr); 

NOTA 1: Vedere la mia nota di seguito per una modifica di progettazione e le ragioni abbiamo fatto.

NOTA 2: Aggiornamento aggiuntivo aggiunto come risposta di seguito.

in cui la funzione di callback HAVS questo C typedef:

typedef (void)(* CALL_NACK)(void*); 

Il cbFuncPtr dovrebbe essere un puntatore a funzione per qualche funzione VB che andranno chiamato come il CALL_BACK. Il dataPtr è un puntatore a una struttura di dati che ha questa definizione C:

typedef struct 
{ 
    int  retCode; 
    void* a_C_ptr; 
    char  message[500]; 
} cbResponse_t; 

dove a_C_ptr is an internal pointer in the DLL that the VB can cast to long`. Identifica in modo univoco il punto in cui nella DLL è stata effettuata la richiamata e consente alla funzione VB di riconoscere le chiamate da posizioni identiche o diverse.

Siamo in grado di accedere ed eseguire la funzione RegisterCallback() da VB bene. La registrazione mostra che siamo arrivati ​​e che i dati sono passati. Sono i dati reali che sembrano essere il problema.

Nella lettura di un milione di voci nel forum abbiamo appreso che VB non sa quali sono i puntatori e che una struttura VB è più di una semplice memoria organizzata. Siamo abbastanza sicuri che l '"indirizzo" di una struttura VB non è quello che C ritiene sia un indirizzo. Abbiamo visto ripetuti riferimenti al "marshaling" e ai "dati gestiti", ma non abbiamo abbastanza comprensione per sapere cosa ci sta dicendo.

Come dovremmo codificare VB per fornire alla DLL l'indirizzo di esecuzione della sua funzione di callback e come possiamo codificare un costrutto VB che la DLL può compilare proprio come fa per C++?

Potrebbe essere necessaria una funzione DLL in cui l'app chiamante può dire "C" o "VB" e la DLL gestisce i puntatori di sturtura in modo diverso? In tal caso, in che modo inserire un codice in C per riempire la struttura VB?

+1

Questo [articolo del ms su come i dati Marshall una DLL] (http://msdn.microsoft.com/en-us/library/fzhhdwae.aspx) almeno sembra che potrebbe applicarsi .È stato collegato a questo [articolo su come chiamare le DLL C++ da VB] (http://social.msdn.microsoft.com/forums/en-US/Vsexpressvb/thread/4c486d10-fe8b-49da-a5f1-8054b82251ff/). Non sono un programmatore VB o .NET, quindi potrei essere fuori dal marchio. –

+0

@DaveNewman L'ultimo post di quella discussione è già di grande aiuto. Come programmatore C, l'idea che i dati possano semplicemente muoversi e spostarsi e che i puntatori a esso siano attendibili solo per circa il tempo in cui l'istruzione che li ha interrogati, è un'eresia. Credo che dovrò creare la struttura sul lato DLL e dire all'app dove metto i dati. Ciò sconfigge l'idea di lasciare che l'app possegga i dati in modo che sia lì, nonostante qualsiasi cosa possa fare la DLL. –

+1

Per quanto ne so, in vb.net è molto utile preferire la struttura IntPtr per i puntatori. Ma se hai già una definizione di puntatore, non sei sicuro se davvero necessario. I puntatori sono anche disponibili in vb.net, solo un po 'più difficile da trovare – Amegon

risposta

1

Questo è un po 'troppo grande e profondo per essere solo una modifica al messaggio originale ...

Dal link pubblicato da @DaveNewman, ho estratto questo gioiello:


Ecco un po 'del framework compatto, ma è lo stesso nel framework degli adulti :

.NET Compact Framework supporta il marshalling automatico delle strutture e classi che contengono tipi semplici. Tutti i campi sono disposti in ordine sequenziale nello stesso ordine in cui appaiono nella definizione della struttura o della classe .Entrambe le classi e le strutture vengono visualizzate nel codice nativo come puntatori alle strutture C/C++.

Gli oggetti nell'heap gestito possono essere spostati nella memoria in qualsiasi momento dal garbage collector, quindi i loro indirizzi fisici potrebbero cambiare senza preavviso. P/Invoke inserisce automaticamente gli oggetti gestiti passati per riferimento per la durata di ciascuna chiamata di metodo. Questo significa che i puntatori passati a codice non gestito saranno validi per quella chiamata. Tenere presente che non è garantito che l'oggetto non sia spostato su un indirizzo di memoria diverso nelle chiamate successive.

http://msdn.microsoft.com/en-us/library/aa446538.aspx#netcfmarshallingtypes_topic6


Questo è grande ostacolo per una funzione RegisterCallback(fcnPtr, dataPtr). Il puntatore passato al momento della registrazione potrebbe cambiare in qualsiasi momento il RegisterCallback() non è l'istruzione corrente. L'autore della pubblicazione lo ha riassunto in questo modo

Non è necessario fare nulla in quanto le strutture vengono automaticamente bloccate per la durata della chiamata.

che implica, ovviamente, non bloccato all'esterno della chiamata.

Per questo motivo abbiamo deciso di modificare la struttura per incorporare la struttura di risposta, per così dire, nel mondo C/C++ della DLL, non nello spazio di VB. In questo modo rimarrà. La firma della funzione di callback effettiva rimarrà invariata, quindi il programma VB può sapere dove la DLL ha inserito la risposta. Ciò consente inoltre ai responder nella DLL di allocare strutture di risposta separate per esigenze separate.

+0

Un aggiornamento. Sulla base del post di @DaveNewman e dell'ottimo lavoro svolto dal mio collega, stiamo aggiungendo le classi gestite a non gestite suggerite in [Chiamare il codice gestito dal codice non gestito e viceversa] (http://www.codeproject.com/Articles/ 9903/Calling gestiti-codice-da-non gestito-Code-e-vizio # xx3493475xx). –

0

Ancora una volta il mio aggiornamento è troppo grande per un semplice commento!

aggiornamento 18 Apr 2013:

Ebbene, il tentativo di utilizzare il codice da Calling Managed Code from Unmanaged Code citata era un busto. Alla fine abbiamo dovuto aggiungere/clr alla DLL per trasformare la DLL in codice gestito, cosa che lo rendeva inutilizzabile da un'applicazione C.

Stiamo testando l'esempio a Callback Sample che è stato possibile mostrare una DLL che funzionava con VB e C++. Dovresti avere il PinvokeLib.dll Source per farlo funzionare.

Ecco il codice per il tester C++ (C veramente). Compilato come progetto MSVC.


NOTA: Si noti la __cdecl in questa linea:

typedef bool (__cdecl *FPtr)(BOOL_FP_INT fp, int i); 

Era il segreto che dovevo trovare. La DLL e questa app sono compilate con collegamento __cdecl, non __stdcall. Sono predefiniti in VC++ e ho appena usato i valori predefiniti. Ho provato a cambiare tutto in __stdcall ma non ha funzionato. Deve essere __cdecl.


// PinvokeTester.cpp : Defines the entry point for the console application. 
// 


#include <stdio.h> 
#include <cstdio> 
#include <stdlib.h> 
#include <cstdlib> 
#include <string.h> 
#include <cstring> 
#include <sstream> 
#include <iostream> 
#include <algorithm> 
#include <Windows.h> 

#define PINVOKELIB_API __declspec(dllimport) 

HINSTANCE  hLib;       // Windows DLL handle 

bool  CALLBACK VBCallBack(int value); 
bool  AttachLibrary(void); 
void * GetFuncAddress(HINSTANCE hLib, const char* procname); 

int main(int argc, char* argv[]) 
{ 
    if (!AttachLibrary()) 
    { 
     printf("Lib did not attach.\n"); 
     exit(1); 
    } 

    typedef bool (CALLBACK *BOOL_FP_INT)(int i); 

    typedef bool (__cdecl *FPtr)(BOOL_FP_INT fp, int i); 

    FPtr TestCallBack = (FPtr)GetFuncAddress(hLib, "TestCallBack"); 

    TestCallBack((BOOL_FP_INT)VBCallBack, 255); 

    return 0; 
} 

bool CALLBACK VBCallBack(int value) 
{ 
    printf("\nCallback called with param: %d", value); 
    return true; 
} 


bool  AttachLibrary(void) 
{ 
    // Get a var for the IPC-dll library. 
    std::string dllName; 

    /*--- First, link to the IPC-dll library or report failure to do so. ---*/ 
    dllName = ".\\PinvokeLib"; 
    if (NULL == (hLib = LoadLibraryA(dllName.c_str()))) 
    { 
     printf("\nERROR: Library \"%s\" Not Found or Failed to Load. \n\n", dllName.c_str()); 
     printf("\"%s\"\n", GetLastError()); 
     return false; 
    } 

    return true; 
} 

//===================================================================== 
void * GetFuncAddress(HINSTANCE hLib, const char* procname) 
{ 
    void * procAddr = NULL; 

    procAddr = (void *)GetProcAddress(hLib, procname); 

    // If the symbol wasn't found, handle error --------------------- 
    if (NULL == procAddr) 
    { 
     std::cout << "ERROR: Could not get an address for the \"" 
       << procname << "\" function. : " 
       << GetLastError() << std::endl; 
     exit(7); 
     procAddr = (void*)NULL; 
    } 

    return procAddr; 
} 
Problemi correlati