String aus C++ dll an VBA übergeben
-
Falls dieser Thread mal gesucht wird, möchte ich abschließend noch berichten, wie ich das Problem letztlich gelöst habe (früher war es viel einfacher):
Der rückzugebende String muß als Parameter übergeben werden und zuvor in VBA mit Nullen vorgefüllt werden. Zur Vermeidung von Speicherproblemen wird die Länge des vorgefüllten Strings von VBA aus übergeben und in C++ ggf. auf dieses Maß gekürzt. Die Funktion, die den String manipuliert gibt als Ergebnis einen Integer-Wert zurück, der die Länge des Ergebnis-Strings ausgibt. In VBA wiederum wird der manipulierte String durch die Left-Funktion auf diese Länge abgeschnitten, da er sonst rechts weitere Nullen enthält.Im BCB:
extern "C" __declspec(dllexport) __stcall int CProgramm (int Pufferlaenge, char* VBVariable); char* CProgramm (int Pufferlaenge, char* VBVariable) { AnsiString EndErgebnis = „Ergebnis“; int Laenge = EndErgebnis.Length(); //wenn das Ergebnis länger ist als die übergebene Pufferlänge wird das Ergebnis entsprechend gekürzt if (Laenge > Pufferlaenge - 1) { EndErgebnis.Delete (Pufferlaenge, EndErgebnis.Length() - Pufferlaenge); Laenge = EndErgebnis.Length(); } strcpy (VBVariable, EndErgebnis.c_str()); //das Ergebnis in den übergebenen String schreiben return Laenge; //die Länge des Strings als Integer zurückgeben }
In VBA:
Private Declare Function CProgramm Lib "MeineDLL.DLL" (ByVal Pufferlaenge As Integer, ByVal VBAString As String) As Integer Function CFunktion (sUebergabesgtring As String) As String Dim iErgebnislaenge As Integer Dim iPuffer As Integer iPuffer = 1500 'Höchstmögliche Länge des Strings sUebergabestring = String$(iPuffer, vbNullChar) 'den String mit Nullen vorfüllen iErgebnislaenge = CProgramm (iPuffer, sUebergabestring) 'gibt als Resultat die Stringlänge zurück sUebergabestring = Left(sUebergabestring, iErgebnislaenge) 'rechts die nicht benötigten Zeichen abschneiden CProgramm = sUebergabstring End Function
-
char* als Rückgabetyp ist wohl falsch
Früher war gar nichts einfacher, das war schon immer so, schon zu VB3.0 und Turbo C++
Wenn man in Speicher schreibt der einem nicht gehört macht es immer BUMM.
-
Danke, war ein Tippfehler, hab es korrigiert.
Früher war es doch einfacher. Die C++ Funktion hat ein char* zurückgegeben und selbst für die Speicherreservierung gesorgt. Es handelt sich ja nur um einen Pointer auf eine Zeichenkette. Diese Funktion in VBA importiert konnte dort das Ergebnis String liefern. Das hat noch in VB6 und in VBA bis Office 2003 so gut funktioniert.
-
W. Posur schrieb:
Danke, war ein Tippfehler, hab es korrigiert.
Früher war es doch einfacher. Die C++ Funktion hat ein char* zurückgegeben und selbst für die Speicherreservierung gesorgt. Es handelt sich ja nur um einen Pointer auf eine Zeichenkette. Diese Funktion in VBA importiert konnte dort das Ergebnis String liefern. Das hat noch in VB6 und in VBA bis Office 2003 so gut funktioniert.Sicher nicht, wenn das bei Dir so funktioniert hat, dann war das Zufall.
Dann hsat Du keine variable langen Strings in VBA sondern fixlange Strings verwendet.
-
An Zufall glaube ich nicht. Es funktioniert ja immer noch mit Strings variabler Länge in älteren VBA-Versionen sowie in OpenOffice und LibreOffice neuen Datums. Stringlängen über 65000 habe ich allerdings nicht ausprobiert. Warum sollte ein vernünftiges Basic auch nicht in der Lage sein, zu erkennen, daß es von einer externen Funktion einen Zeiger auf eine Null-terminierte Zeichenfolge bekommt und diese in einen Basic-String umzuwandeln ?
Wie auch immer, ich vermute mal, die Änderungen im VBA sind durch die Unicode Funktionalität verursacht.
Mittels weiterer Recherche habe ich jetzt doch noch eine viel elegantere Methode gefunden, damit VB und VBA einen String bekommen. Ich liefere eine BSTR anstatt einen char* zurück, damit kommt auch das neue VBA klar.Im BCB:
extern "C" __declspec(dllexport) BSTR _stdcall CProgramm (); BSTR _stdcall CProgramm () { AnsiString EndErgebnis = „Ergebnis“; //das ist der AnsiString, den meine Funktion ermittelt hat und nun an VBA liefern soll BSTR BErgebnis; // diesen BSTR String wird die Funktion zurückgeben int iLaenge = EndErgebnis.Length(); //die Länge des Ergebnis-AnsiStrings wird ermittelt LPSTR strSrc = (LPSTR)EndErgebnis.c_str(); //Umwandlung Ergebnis in LPSTR LPSTR strDst = (LPSTR) BErgebnis; //dito für Ausgabestring for(int i=0; i<= iLaenge; i++) *strDst++ = *strSrc++; //Umkopieren return BErgebnis; //der Ergebnisstring vom Typ BSTR wird zurückgeliefert }
Und in VBA ist es jetzt wieder ganz einfach:
Private Declare Function CProgramm Lib "MeineDLL.DLL" () As String
-
Du vergleichst Äpfel mit Birnen
BSTR ist etwas ganz anderes als char*
-
Nun ja.
Mein Thema war: Wie schaffe ich es, mit dem BCB eine dll zu schreiben, die einen String zurückgibt, mit dem VBA auch bei neueren Office Versionen etwas anfangen kann.
Ich habe das Problem für mich gelöst.
Ich bedanke mich für die konstruktiven Beiträge.
-
hallo,
habe ein ähnliches problem. teste zunächst mit einer double variable, später soll es mal ein array werden.
also meine Test-Funktion sieht folgendermaßen aus:
extern "C" __declspec(dllexport) double _stdcall square(double & x); double _stdcall square(double & x) { return x*x; }
dann braucht man wohl noch ein def-File:
LIBRARY "square" EXPORTS square
dann muss man das ganze noch verlinken. hab ich nach dieser anleitung gemacht:
http://www.forexfactory.com/showthread.php?p=5250053#post5250053In VBA dann:
Declare Function square _ Lib "E:\C++\dll_datei\Release\square.dll" (ByRef x As Double) As Double
leider läuft es dann in VBA nicht. Die Function taucht zwar auf, aber es erscheint "#WERT!" in der Zelle... Hab ich nen Fehler gemacht?
-
ootobbyoo schrieb:
dann braucht man wohl noch ein def-File:
Beim Builder eigentlich nicht, die Beispiele oben mit def-File waren für VC++.
ootobbyoo schrieb:
Hab ich nen Fehler gemacht?
Ich denke "ByRef x as Double" entspricht einem "double* x", glaube nicht, dass VB C++ Referenzen unterstützt.
-
nn schrieb:
ootobbyoo schrieb:
dann braucht man wohl noch ein def-File:
Beim Builder eigentlich nicht, die Beispiele oben mit def-File waren für VC++.
mh, also ohne def-File läuft es auch nicht. Programmiere mit ecplise und compiliere dann mit dem TDM GCC für 64 bit (http://tdm-gcc.tdragon.net/download).
Leider auch keine Änderung, wenn ich das "ByRef" weg lasse.
Ich habe die Funktion in VBA mal in eine Sub eingebaut, da kommt komischerweise die Fehlermeldung:
"Lautzeitfehler '48':
Datie nicht gefunden: E:\C++\dll_datei\Release\square.dll"Scheint also, als ob er die dll gar nicht erkennt. Da ist sie aber auf jeden Fall...
-
ootobbyoo schrieb:
Programmiere mit ecplise und compiliere dann mit dem TDM GCC für 64 bit (http://tdm-gcc.tdragon.net/download).
...
Scheint also, als ob er die dll gar nicht erkennt. Da ist sie aber auf jeden Fall...
Also die Beispiele hier waren für den C++ Builder und 32-Bit.
Mit 64-Bit Excel habe ich sowas noch nie gemacht, dass ein 32-Bit Excel keine 64-Bit DLL lädt ist klar. In 64-Bit Code sollte doch schon mal __stdcall überflüssig sein ?
-
ALSO,
erstmal ist es korrekt, dass man mit Excel 32 bit keine 64 bit dll aufrufen kann. Danke für den Hinweis. Der GCC kann aber mit dem Befehl "-m32" auch auf 32 bit comilieren.
Dann muss der Name der Function in VBA und der Name der Function in C++ gleich sein!!! Und dann war wohl noch an der Syntax was falsch.
Hab auf jeden Fall eine Lösung gefunden, die dann so aussieht:
function.cpp:
extern "C" __declspec(dllexport) double __stdcall square(double x) { return x*x; }
deffile.def:
LIBRARY "square" EXPORTS square
Compiler & Linker:
g++ -m32
und in VBA dann
Option Explicit Declare Function square Lib "E:\C++\dll_datei\Release\dll_datei.dll" (ByVal d As Double) As Double Sub x() Dim zw As Double zw = square(10) MsgBox zw End Sub
Und jetzt das ganze mit Arrays...