c++ Dll Array an VBA



  • So ähnlich vielleicht (wieder ungetestet):

    std::vector<std::wstring> strings; 
    strings.push_back(L"A"); 
    strings.push_back(L"BB"); 
    strings.push_back(L"CCC"); 
    strings.push_back(L"DDDD"); 
    
    // Safearray Bounds vorbereiten 
    
    SAFEARRAYBOUND bound = {}; 
    bound.lLbound = 0;                  // Index des 1. Elements -- muss nicht 0 sein, manche Sprachen verwenden auch gerne 1 
    bound.cElements = strings.size();   // Anzahl Elemente 
    
    // Safearray erzeugen 
    VARIANT result; 
    result.vt = VT_SAFEARRAY| VT_VARIANT; 
    result.parray = SafeArrayCreate(VT_VARIANT, 1, &bound); 
    
    // Strings ins Array kopieren 
    HRESULT hr = S_OK; 
    
    for (size_t i = 0; i < strings.size(); i++) 
    { 
        wchar_t const* str = strings[i].c_str(); 
    
        VARIANT varStr;
        VariantInit(&varStr);
        varStr.bstr = SysAllocString(str);
        if (str && !varStr.bstr)
        {
            hr = E_OUTOFMEMORY;
            break;
        }
        varStr.vt = VT_BSTR;
    
        long index = i; 
        hr = SafeArrayPutElement(result.parray, &index, &varStr); 
        VariantClear(&varStr);
    
        if (FAILED(hr))
            break;
    } 
    
    //...
    

    Wobei ich es wirklich mit Lock/Unlock machen würde, ist im Endeffekt einfacher (und hat weniger Overhead).

    EDIT: Unfug im Code korrigiert.



  • Hallo,

    danke erstmal das Du so schnell antwortest das Hilft sehr. Also wenn ich Access Data wegnehme dann bekomme ich ein Fehlermeldung.

    std::vector<std::wstring> strings;
        strings.push_back(L"A");
        strings.push_back(L"BB");
        strings.push_back(L"CCC");
        strings.push_back(L"DDDD");
    	strings.push_back(L"EEEEE");
    	strings.push_back(L"FFFFFF");
        // Safearray Bounds vorbereiten
    
        SAFEARRAYBOUND bound = {};
        bound.lLbound = 0;                  // Index des 1. Elements -- muss nicht 0 sein, manche Sprachen verwenden auch gerne 1
        bound.cElements = 20;   // Anzahl Elemente
    
        // Safearray erzeugen
    	VARIANT result;
    	result.vt = VT_BSTR| VT_ARRAY;
    	result.parray = SafeArrayCreate(VT_BSTR, 1, &bound);
    
    	void HUGEP* untypedDataPtr = 0;
        HRESULT hr = SafeArrayAccessData(result.parray, &untypedDataPtr);
    
    	if (SUCCEEDED(hr))
        {
    		VARIANT* dataPtr = static_cast<VARIANT*>(untypedDataPtr);
    		// Strings ins Array kopieren
    		BSTR tstBSTR;
            hr = S_OK;
    		long ini = 0;
    
            for (int i =0; i<5;i++)
            {
    
    			// wchar_t const* str = strings[i].c_str();
    			 //tstBSTR = SysAllocString(str);
    			 dataPtr [i].bstrVal = constCharToBSTR("test");
    			 SafeArrayPutElement(result.parray, &ini, &dataPtr);
    			 ini = ini +1;
    			// wcout << SafeArrayGetElement(dataPtr[i],&ini,&tstBSTR);
    		}
    	}
    

    Schreibt der mir die Strings in meinem Array aber immer 4 Positionen später. Die restlichen Felder bekommen schlechtes Ptr



  • Oha ja, hatte den Code nicht durch den Compiler laufen lassen, und übersehen dass das if (SUCCEEDED(hr)) natürlich nicht ohne die Variable hr geht (und auch keinen Sinn macht).

    Hab den Code entsprechend korrigiert.



  • Hallo,

    danke für die Antworten hustbaer. Es funktioniert mit dem Code. Nun die nächste Frage: Wie bekomme ich jetzt ein zwei Dimensionalen Array in meinem VARIANT Result. Ich dachte, ich könnte noch ein zweite for Schleife durchlaufen,
    so das ich in jede Reihen alle Werte ein Mal stehen hätte
    Also:

    std::vector<std::wstring> strings;
    strings.push_back(L"A");
    strings.push_back(L"BB");
    strings.push_back(L"CCC");
    strings.push_back(L"DDDD");
    
    // Safearray Bounds vorbereiten
    
    SAFEARRAYBOUND bound = {};
    bound.lLbound = 0;                  // Index des 1. Elements -- muss nicht 0 sein, manche Sprachen verwenden auch gerne 1
    bound.cElements = strings.size();   // Anzahl Elemente
    
    // Safearray erzeugen
    VARIANT result;
    result.vt = VT_ARRAY| VT_VARIANT;
    result.parray = SafeArrayCreate(VT_VARIANT, 2, &bound);
    
    //BSTR erzeugen
     VARIANT varStr;
     VariantInit(&varStr);
    
    // Strings ins Array kopieren
    HRESULT hr = S_OK;
    
    for (size_t i = 0; i < strings.size(); i++)
    {
    	for (size_t j = 0; j < strings.size(); j++)
    	{
    		wchar_t const* str = strings[[b]j[/b]].c_str();
    
    		varStr.bstrVal = SysAllocString(str);
    		if (str && !varStr.bstrVal)
    		{
    			hr = E_OUTOFMEMORY;
    			break;
    		}
    		varStr.vt = VT_BSTR;
    
    		long index[] = {i,j};
    		hr = SafeArrayPutElement(result.parray, index, &varStr);
    
    		if (FAILED(hr))
    			break;
    	}
    }
    	VariantClear(&varStr);
    

    Aber hier bekomme ich FAILD (hr).
    Was mache ich den hier falsch



  • SAFEARRAYBOUND -> SAFEARRAYBOUND[2]



  • AH Danke habe ich vergessen auch anzupassen. Danke für den Tip. Mache ich gleich.



  • Und das VariantClear(&varStr); gehört auch mit in die innere Schleife, dorthin wo es in meinem Code auch steht.
    Behaupte ich mal.
    Bin mir nicht 100% sicher ob man hier überhaupt VariantClear aufrufen soll, aber ich verstehe die Doku so dass SafeArrayPutElement eine "deep copy" des VARIANT macht, und dann gehört der alte natürlich freigegeben -- weil's sonst leakt.



  • Und die äussere Schleife gehört noch verlassen wenn's ein Problem gegeben hat. Mit beiden Änderungen dann:

    for (size_t i = 0; i < strings.size(); i++) 
    { 
        for (size_t j = 0; j < strings.size(); j++) 
        { 
            wchar_t const* str = strings[j].c_str(); 
    
            varStr.bstrVal = SysAllocString(str); 
            if (str && !varStr.bstrVal) 
            { 
                hr = E_OUTOFMEMORY; 
                break; 
            } 
            varStr.vt = VT_BSTR; 
    
            long index[] = {i,j}; 
            hr = SafeArrayPutElement(result.parray, index, &varStr); 
            VariantClear(&varStr);
    
            if (FAILED(hr)) 
                break; 
        } 
        if (FAILED(hr)) 
            break; 
    }
    

    Den Test ob SafeArrayCreate funktioniert hat hast du auch rausgenommen, der gehört auch wieder rein.
    Und wieso du varStr aus der Schleife rausgezogen hast versteh' ich auch nicht ganz. Immer so lokal wie möglich.



  • Hallo,

    danke das hat sehr gut funktioniert.



  • Hallo,

    eine Frage noch mit Safearray funktioniert es jetzt Perfekt.

    ich erstelle ein DLL, wenn ich diese dann in VBA aufrufe funktioniert es auch.
    Wenn ich aber z.B. Folgende Funktion noch mitrein packe:

    #include "libpq-fe.h"
    ...
    /* Close connection to database */
    void CloseConn(PGconn *conn)
    {
        PQfinish(conn);
      getchar();
        exit(1);
    }
    

    Dann bekomme ich in VBA die Laufzeitfehler 53, dass mein DLL nicht gefunden wird. woran kann das liegen?



  • *Glaskugel-guck*

    Daran dass deine DLL durch das Einbinden der "libpq-fe.h" jetzt eine weitere DLL benötigt, und diese weitere DLL nicht gefunden werden kann.

    Und das wiederrum...

    *Glaskugel-polier*
    *Glaskugel-guck*

    ...liegt daran dass du deine DLL irgendwo liegen hast, wo sie auch registriert ist und alles toll, und die andere DLL auch im selben Verzeichnis. Und das VB Programm in irgend einem anderen Verzeichnis.

    Windows findet nun deine DLL über den in der Registry abgespeicherten Pfad. Sieht dann dass deine DLL noch eine weitere braucht, und versucht die dann auch zu laden. Und dabei guckt Windows eben NICHT in dem Verzeichnis deiner DLL.



  • Hallo,

    gibt es den eine andere Lösung als immer alle Dlls, die für die Datenbankverbindung gebraucht werden in dem gleichen Ordner zu haben?
    Ich war so nah an Ziel?
    Hab ich dann das ganze umsonst gemacht, wenn ich die Dll nicht verschieben kann?



  • borhan schrieb:

    Hallo,

    gibt es den eine andere Lösung als immer alle Dlls, die für die Datenbankverbindung gebraucht werden in dem gleichen Ordner zu haben?

    Naja, wenn es um "statische" DLL-Importe geht, dann muss Windows die DLLs finden können. Wo überall gesucht wird kannst du dir ja ergoogeln.

    Ansonsten kannst du die andere DLL natürlich dynamisch laden. Da kannst du dann den vollständigen Pfad angeben. Dann musst du dir aber auch alle Funktionsadressen "manuell" holen, also mit GetProcAddress. Oder rumtricksen.

    Ich war so nah an Ziel?

    Ist das wirklich eine Frage? 🤡

    Hab ich dann das ganze umsonst gemacht, wenn ich die Dll nicht verschieben kann?

    Weiss nicht. Was willst du denn überhaupt machen?



  • Nee, Das war keine Farge. Ich bin jetzt seit mehr als zwei Wochen dran und ich habe es mit DLL nicht probiert und jetzt kommt sowas.
    Was meinst du mit rum tricksen?



  • Die DLL mit /DELAYLOAD linken, und dann vor dem ersten Zugriff auf eine DLL Funktion manuell LoadLibrary mit dem richtigen Pfad machen.

    Soweit ich weiss lädt Windows immer nur eine DLL pro Filename (exklusive Pfad!). Ein zweites LoadLibrary mit dem selben Filenamen gibt dann einfach die bereits geladenen DLL zurück.

    Hab's noch nicht probiert, aber müsste mMn. funktionieren.

    Tip: an den vollständigen Pfad zu deiner DLL kommst du mit GetModuleFileName(moduleHandleDeinerDLL, ...) .
    Und an das Module-Handle deiner DLL kommst du z.B. durch GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, (LPCTSTR)(AdresseInDeinerDll), &hmodule) .
    Bzw. ich meine das ist auch das selbe wie in DllMain als HINSTANCE mitgegeben wird.

    Alternativ könntest du AddDllDirectory() verwenden. Gibt's halt erst am Win8...



  • Hallo,

    danke für dein Antwort. Also:

    #include <string>
    #include "C:\Program Files (x86)\PostgreSQL\9.2\include\libpq-fe.h"
    #include <iostream>
    #include <windows.h>
    #include <comutil.h> 
    ...
    #pragma comment(linker, "/DelayLoad:libpq.dll")
    ...
    void CloseConn(PGconn *conn)
    {
        PQfinish(conn);
      getchar();
        exit(1);
    }
    

    Wo muss ich jetzt die Dll wieder aufrufen?



  • Ich habe ein schönen Beitrag gefunden auf der Seite http://www.codeguru.com/cpp/cpp/cpp_mfc/tutorials/article.php/c9855/DLL-Tutorial-For-Beginners.htm
    Was mir aber noch unklar ist:
    Also:

    typedef int (*AddFunc)(int,int);
    

    Ich bentöige aber als Parameter nicht int sondern ein Typ, der auch in der DLL defeniert ist. Wie kann man das den lösen?



  • Achje, das wird mir etwas zu mühsam hier.

    Mit anderen Typen geht es genau so. Und damit die bekannt sind, musst du halt das Header-File includen. Evtl. diverse #pragma comment(lib entfernen damit nicht automatisch das .lib File der DLL gelinkt wird.



  • HAllo,

    danke dass du trotzdem antworteset. Ich habe einfach die Path- Variable angepasst. Ansonsten habe ich hierzu keine Fragen mehr!

    Allerdings habe ich eine Frage zum Verständnis. Wenn ich mein Dll- Funktion aus VBA aufrufe dann muss ich noch ein ALIAS Name mit angeben

    #if defined(_MSC_VER)
      //#include <windows.h>
      #define DLL   extern "C"   __declspec(dllexport)
    #else
      #define DLL
    #endif
    //mein Funktion in der DLL
     int add(int a,int b)
    ...
    

    Aufruf in VBA :

    Public Declare Function FUNKTIONSNAME Lib "Pfad" Alias _FUNKTIONSNAME@8
    

    und 8 Weil ich zwei Parameter habe. Wenn ich jetzt drei hätte wären es 12.
    Gibt es vielleicht eine Möglichkeit, den Alias Name wegzulassen?



  • Von VBA hab' ich nicht viel Plan.

    Was sicher geht (aber, falls du es noch nicht hast, vermutlich auch viel Aufwand ist): COM Klassen statt freien Funktionen in der C++ DLL implementieren.

    In VBA kannst du das dann gleich verwenden wie z.B. das "FileSystemObject" oder ähnliche Utility-Klassen.

    ps:
    Du kannst in der C++ DLL extern "C" verwenden wenn du das @8 etc. weghaben willst.

    __declspec(dllexport) void Foo(int a, int b); // _Foo@8
    __declspec(dllexport) extern "C" void Bar(int a, int b); // Bar  (oder _Bar, weiss ich ehrlich gesagt nicht auswendig)
    

    Und es kann sein dass das extern "C" vor das __declspec(dllexport) gehört - diese Dinge merk ich mir nie.


Anmelden zum Antworten