c++ Dll Array an VBA



  • So in der Art sollte es gehen (ungetestet):

    HRESULT Klasse::Funktion(VARIANT* out_result)
    {
    	// Erstmal sicherstehhen dass alle Output-Parameter nen Wert haben der a) gültig ist und b) nicht freigegeben werden muss
    
    	if (out_result)
    		VariantInit(out_result);
    
    	if (!out_result) // Kein Output Parameter übergeben -> Fehler
    		return E_POINTER;
    
    	// Die Strings die wir als Safearray zurückgeben wollen
    
    	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
    
    	V_ARRAY(out_result) = SafeArrayCreate(VT_BSTR, 1, &bound); // 1D String Array
    	if (!V_ARRAY(out_result))
    	{
    		// Es wurde noch nichts angefordert -> NICHT VariantClear aufrufen sondern einfach VariantInit
    		VariantInit(out_result);
    		return E_OUTOFMEMORY;
    	}
    
    	out_result->vt = VT_SAFEARRAY | VT_BSTR;
    
    	// Zeiger auf die Elemente holen
    
    	void HUGEP* untypedDataPtr = 0;
    	HRESULT hr = SafeArrayAccessData(V_ARRAY(out_result), &untypedDataPtr);
    	if (SUCCEEDED(hr))
    	{
    		BSTR* dataPtr = static_cast<BSTR*>(untypedDataPtr);
    
    		// Strings ins Array kopieren
    
    		hr = S_OK;
    		for (size_t i = 0; i < strings.size(); i++)
    		{
    			ATLASSERT(dataPtr[i] == 0); // Das von SafeArrayAccessData() erzeugte Array sollte aus lauter NULL Strings bestehen...
    
    			wchar_t const* str = strings[i].c_str();
    			// Bei std::wstring ist immer garantiert dass str != 0.
    			// In Fällen wo str 0 sein könnte müssen wir diesen Fall aber behandeln, da SysAllocString(0) wieder 0 zurückgibt, 
    			// was in dem Fall KEIN Fehler ist.
    			if (str != 0)
    			{
    				dataPtr[i] = SysAllocString(str);
    				if (dataPtr[i] == 0)
    				{
    					hr = E_OUTOFMEMORY;
    					break;
    				}
    			}
    		}
    
    		// Element-Zeiger wieder "freigeben"
    		SafeArrayUnaccessData(V_ARRAY(out_result));
    	}
    
    	// Fehler behandeln die bei SafeArrayAccessData() bzw. beim Kopieren der Strings passiert sind
    
    	if (FAILED(hr))
    	{
    		// Wenn wir Fehler zurückmelden müssen alle output Parameter nen gültigen Wert haben, der aber nicht freigegeben werden muss
    		// -> VariantClear
    		// (VariantClear kann SafeArrays freigeben, und kümmert sich ggf. auch um die BSTRs darin -- wir müssen hier also nix "zu Fuss" vorher aufräumen.)
    		VariantClear(out_result);
    		return hr;
    	}
    
    	// Alles OK => Erfolg zurückmelden
    
    	return S_OK;
    }
    

    Falls du den eigentlichen COM Teil schon hast, sollte das alles sein was noch fehlt. Wenn der Code nicht funktioniert schreib nochmal (aber bitte mit genauer Fehlerbeschreibung).
    (Und wenn er funktioniert darfst du natürlich auch gerne nochmal schreiben 🤡 )



  • ps:
    Es wäre vermutlich besser ein SAFEARRAY aus VARIANTs zu machen, wo dann jeder VARIANT vom Typ VT_BSTR ist.

    Siehe Kommentar von Martin Richter hier:
    http://www.c-plusplus.net/forum/p2331792#2331792

    BSTR* dataPtr muss dann zu VARIANT* dataPtr werden, entsprechend auch der Cast angepasst und natürlich die Stelle wo der String reingeschrieben wird entsprechend angepasst.

    Solltest du selbst hinbekommen.

    ps2: Und natürlich VT_SAFEARRAY | VT_VARIANT statt VT_SAFEARRAY | VT_BSTR .



  • Hallo,

    danke für die Antworten. Ich probiere die gerade aus. Eine adere Frage mein dll macht ein Datenbankabfrage. Gibt es keine einfachere Möglichkeit die Daten an VBA zu senden als VARIANT?



  • Ah Ok dann muss ich wohl mich mit VARIANT und safearrays auseinander setzen. Danke erstmal



  • Ne, viel einfacher wird es nicht.
    Du kannst höchstens noch alles in einen String packen. Dann musst du es auf der VB Seite aber wieder auseinanderbasteln. Das ist auf der VB Seite dann umständlich. Und vermutlich auch recht langsam.



  • Wie müsste ich out_result deklarieren, wenn es nicht eine Parameter meiner Funktion wäre?

    VARIANT out_result;
    VARIANT* out_result1 = new VARIANT [?];
    


  • VARIANT result;
    

    ?



  • hustbaer schrieb:

    ATLASSERT(dataPtr[i] == 0);

    Ich kann die ATL Klassen nicht einbinden. Gibt es hierfür eine andere Möglichkeit?



  • dataPtr[i] = SysAllocString(str);
    

    zeigt Fehler. Außerdem ist unser ZielArray ja out_result.
    Wo schreiben wir die Werte den in unserem ZielArray? Oder habe ich das falsch verstanden



  • 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_BSTR;
    	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 (size_t i = 0; i < strings.size(); i++)
            {
    
    			 wchar_t const* str = strings[i].c_str();
    			 tstBSTR = SysAllocString(str);
    			 SafeArrayPutElement(result.parray, &ini, &tstBSTR);
    			 ini++;
    
    		}
    	}
    

    Was ist an diesem Code falsch? Habe ich noch ein Denkfehler?



  • Wenn du SafeArrayPutElement() sollte das SafeArrayAccessData() weg.

    Und natürlich

    ich schrieb:

    ps2: Und natürlich VT_SAFEARRAY | VT_VARIANT statt VT_SAFEARRAY | VT_BSTR.

    Und dann ist ein BSTR trotzdem noch kein VARIANT.



  • 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.


Anmelden zum Antworten