将字符串从VBA传递到C++;动态链接库 我真的很困惑把VBA字符串传递给C++。以下是VBA代码: Private声明子passBSTRVal Lib“foo.dll”(ByVal s作为字符串) 私有声明子passbstref Lib“foo.dll”(ByRef s作为字符串) 私有声明子passByNearoval Lib“foo.dll”(ByVal s作为字符串) 私有声明子passByNearRef库“foo.dll”(ByRef s作为字符串) 私有声明子passByWideVal Lib“foo.dll”(ByVal s作为字符串) 私有声明子passByWideRef库“foo.dll”(ByRef s作为字符串) 副词foobar() Dim s作为字符串,str作为字符串 str=“你好,世界!” s=str 呼叫passByBSTRVal(s) s=str 呼叫PassbyBSTREF(s) s=str 呼叫PassbyNerrowVal(s) s=str 呼叫PassbyNearRef(s) s=str 呼叫passByWideVal(s) s=str 呼叫passByWideRef(s) 端接头 和C++ DLL代码: void __stdcall passByBSTRVal( BSTR s ) { MessageBox(NULL, s, L"Pass BSTR by value", MB_OK | MB_ICONINFORMATION); } void __stdcall passByBSTRRef( BSTR *s ) { MessageBox(NULL, *s, L"Pass BSTR by ref", MB_OK | MB_ICONINFORMATION); } void __stdcall passByNarrowVal( LPCSTR s ) { USES_CONVERSION; MessageBox(NULL, A2W(s), L"Pass by Narrow Val", MB_OK | MB_ICONINFORMATION); } void __stdcall passByNarrowRef( LPCSTR* s ) { USES_CONVERSION; MessageBox(NULL, A2W(*s), L"Pass by Narrow Ref", MB_OK | MB_ICONINFORMATION); } void __stdcall passByWideVal( LPCWSTR s ) { MessageBox(NULL, s, L"Pass by Wide Val", MB_OK | MB_ICONINFORMATION); } void __stdcall passByWideRef( LPCWSTR* s ) { MessageBox(NULL, *s, L"Pass by Wide Ref", MB_OK | MB_ICONINFORMATION); }
我的期望是,对passByBSTRVal和passbybstref的前两个调用都能正常工作。为什么?因为VBA字符串是COM BSTR对象。然而,在通过C++代码的同时,这两个函数的S值是垃圾(一堆汉字)。此外,显示的消息框是(相同的)。我真的很惊讶前两个功能不起作用 我的下一个期望是对PassbyNearVal和PassbyNearRef的第二次调用不起作用,因为BSTR被定义为“typedef ollechar*BSTR”,而ollechar是宽字符类型,而LPCSTR是窄字符类型。然而,与我的预期相反,这两个功能实际上起了作用。当我通过C++代码时,参数S正好是我所期望的。我的期望又错了 最后,我对最后两个函数(pass by wide val和ref)的期望是它们可以工作,因为OLECHAR是一个宽字符字符串,所以LPCWSTR应该能够指向BSTR。但正如案例1(我想这两个案例是相同的)一样,我的预期是错误的。参数s由垃圾字符组成(MessageBox显示相同的垃圾字符)将字符串从VBA传递到C++;动态链接库 我真的很困惑把VBA字符串传递给C++。以下是VBA代码: Private声明子passBSTRVal Lib“foo.dll”(ByVal s作为字符串) 私有声明子passbstref Lib“foo.dll”(ByRef s作为字符串) 私有声明子passByNearoval Lib“foo.dll”(ByVal s作为字符串) 私有声明子passByNearRef库“foo.dll”(ByRef s作为字符串) 私有声明子passByWideVal Lib“foo.dll”(ByVal s作为字符串) 私有声明子passByWideRef库“foo.dll”(ByRef s作为字符串) 副词foobar() Dim s作为字符串,str作为字符串 str=“你好,世界!” s=str 呼叫passByBSTRVal(s) s=str 呼叫PassbyBSTREF(s) s=str 呼叫PassbyNerrowVal(s) s=str 呼叫PassbyNearRef(s) s=str 呼叫passByWideVal(s) s=str 呼叫passByWideRef(s) 端接头 和C++ DLL代码: void __stdcall passByBSTRVal( BSTR s ) { MessageBox(NULL, s, L"Pass BSTR by value", MB_OK | MB_ICONINFORMATION); } void __stdcall passByBSTRRef( BSTR *s ) { MessageBox(NULL, *s, L"Pass BSTR by ref", MB_OK | MB_ICONINFORMATION); } void __stdcall passByNarrowVal( LPCSTR s ) { USES_CONVERSION; MessageBox(NULL, A2W(s), L"Pass by Narrow Val", MB_OK | MB_ICONINFORMATION); } void __stdcall passByNarrowRef( LPCSTR* s ) { USES_CONVERSION; MessageBox(NULL, A2W(*s), L"Pass by Narrow Ref", MB_OK | MB_ICONINFORMATION); } void __stdcall passByWideVal( LPCWSTR s ) { MessageBox(NULL, s, L"Pass by Wide Val", MB_OK | MB_ICONINFORMATION); } void __stdcall passByWideRef( LPCWSTR* s ) { MessageBox(NULL, *s, L"Pass by Wide Ref", MB_OK | MB_ICONINFORMATION); },c++,excel,vba,C++,Excel,Vba,我的期望是,对passByBSTRVal和passbybstref的前两个调用都能正常工作。为什么?因为VBA字符串是COM BSTR对象。然而,在通过C++代码的同时,这两个函数的S值是垃圾(一堆汉字)。此外,显示的消息框是(相同的)。我真的很惊讶前两个功能不起作用 我的下一个期望是对PassbyNearVal和PassbyNearRef的第二次调用不起作用,因为BSTR被定义为“typedef ollechar*BSTR”,而ollechar是宽字符类型,而LPCSTR是窄字符类型。然而,与
为什么我的直觉完全错了?有人能解释一下我不理解的地方吗?这种形式的外部函数调用与早期版本的Visual Basic兼容,并继承了它们的语义。特别是,VB3在16位窗口上运行,只处理ANSI(即MBCS)字符串
Declare
语法具有相同的限制。VBA转换字符串的前提是它正在将字符串从UTF-16转换为ASCII。这允许用VB3编写的代码在VB4、VB5和VB6中不受影响地工作
例如,“AZ”以\u0041\u005A
开头,转换为ANSI,变成\x41\x5A
,重新解释为\u5A41
,即婁".
(使用VB4,Microsoft将WordBasic、Excel Basic和Visual Basic合并为一种语言VBA。)
从VBA调用函数的“新”方法是使用MIDL为需要使用的外部函数创建一个类型库,并将其添加为项目的引用。类型库可以描述函数的确切签名,(例如,BSTR
,LPCSTR
,LPCWSTR
,[out]BSTR*
,等等)特别是,从VBA调用函数时,不需要将函数包装在COM对象中(尽管如果希望从VBScript调用函数,则需要这样做)
- 一组DLL函数被描述为MIDL
:模块
或者,你不必费心为一个函数启动
midl
,你可以使用VarPtr
/StrPtr
/CopyMemory
hack。这相当于PEEK
和POKE
大注:我不是程序员,我只是很喜欢编程,所以请善待我我。我想提高,所以非常欢迎比我更熟练的人(基本上是每个人)的建议和评论
本,如果你正在读这篇文章,我想你让我看到了正在发生的事情。MIDL听起来是正确的做法,我打算学习它,但这似乎是一个很好的学习机会,我从来没有让这些让我错过
我认为现在的情况是,窄字符被编组到宽字符存储中。例如,使用窄字符存储的字符串“hello”如下所示:
|h |e |l |l |o |\0 |
|h |e |l |l |o |\0 |
并以宽字符存储,如下所示:
|h |e |l |l |o |\0 |
|h |e |l |l |o |\0 |
<>但是,当你把字符串从VBA传递到C++时,会发生一些奇怪的事情。你把窄字符编成一个宽字符,比如:
|h e |l l |o \0 | | | |
这就是为什么使用LPCSTR/LPCSTR*有效。是的,BSTR使用了一个wchar_t字符串,但这种编组使它看起来像一个char字符串。使用char*访问时交替指向wchar_t每一半中的第一个和第二个字符(h,然后是e.l,然后是l.o,然后是\0)。尽管char*和wchar_t*的指针算法不同,但它之所以有效,是因为字符的编组方式很有趣。事实上,我们传递了一个指向数据字符串的指针,但如果您想访问BSTR的长度,即数据字符串前4个字节,您可以使用指针算法玩游戏,以达到您想要的目的。假设BSTR作为LPCSTR s传入
char* ptrToChar; // 1 byte
wchar_t* ptrToWChar; // 2 bytes
int* ptrToInt; // 4 bytes
size_t strlen;
ptrToChar = (char *) s;
strlen = ptrToChar[-4];
ptrToWChar = (wchar_t *) s;
strlen = ptrToWChar[-2];
ptrToInt = (int *) s;
strlen = ptrToInt[-1];
当然,如果字符串作为LPCSTR*s传入,那么您当然需要首先通过以下方式访问来解除对s的引用:
ptrToChar = (char *)(*s);
等等
如果想使用LPCWSTR或BSTR来接收VBA字符串,则必须在这个编组中跳舞。例如,为了创建一个将VBA字符串转换成大写的C++ DLL,我做了以下的操作:
BSTR __stdcall pUpper( LPCWSTR* s )
{
// Get String Length (see previous discussion)
int strlen = (*s)[-2];
// Allocate space for the new string (+1 for the NUL character).
char *dest = new char[strlen + 1];
// Accessing the *LPCWSTR s using a (char *) changes what we mean by ptr arithmetic,
// e.g. p[1] hops forward 1 byte. s[1] hops forward 2 bytes.
char *p = (char *)(*s);
// Copy the string data
for( int i = 0; i < strlen; ++i )
dest[i] = toupper(p[i]);
// And we're done!
dest[strlen] = '\0';
// Create a new BSTR using our mallocated string.
BSTR bstr = SysAllocStringByteLen(dest, strlen);
// dest needs to be garbage collected by us. COM will take care of bstr.
delete dest;
return bstr;
}
BSTR\uu stdcall pUpper(LPCWSTR*s)
{
//获取字符串长度(请参阅前面的讨论)
int strlen=(*s)[-2];
//为新字符串分配空间(NUL字符为+1)。
char*dest=新字符[strlen+1];
//使用访问*LPCWSTR
; MidlForModules.def : Declares the module parameters.
LIBRARY
EXPORTS
DllCanUnloadNow PRIVATE
DllGetClassObject PRIVATE
DllRegisterServer PRIVATE
DllUnregisterServer PRIVATE
DllInstall PRIVATE
_MyAbs @656
pUpper
Option Explicit
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Sub Workbook_Open()
'* next line establishes relative position of Dll
Debug.Assert Dir(ThisWorkbook.Path & "\IDLForModules.dll") = "IDLForModules.dll"
'* next line loads the Dll so we can avoid very long Lib "c:\foo\bar\baz\barry.dll"
LoadLibrary ThisWorkbook.Path & "\IDLForModules.dll"
'* next go to Tools References are check "Idl For Modules"
'* "Idl For Modules" Iis set in the IDL with helpstring("Idl For Modules")
End Sub
Option Explicit
Sub TestAbs()
Debug.Print IDLForModulesLib.Math.Abs(-5)
End Sub
Sub TestUpper()
Debug.Print IDLForModulesLib.Strings.Upper("foobar")
End Sub
STDAPI ToUpperLPWSTR(LPCWSTR in, LPWSTR out, int cch)
{
// unicode version
LCMapStringW(LOCALE_USER_DEFAULT, LCMAP_LINGUISTIC_CASING | LCMAP_UPPERCASE, in, lstrlenW(in), out, cch);
return S_OK;
}
STDAPI ToUpperBSTR(BSTR in, BSTR out, int cch)
{
// unicode version
// note the usage SysStringLen here. I can do it because it's a BSTR
// and it's slightly faster than calling lstrlen...
LCMapStringW(LOCALE_USER_DEFAULT, LCMAP_LINGUISTIC_CASING | LCMAP_UPPERCASE, in, SysStringLen(in), out, cch);
return S_OK;
}
STDAPI ToUpperLPSTR(LPCSTR in, LPSTR out, int cch)
{
// ansi version
LCMapStringA(LOCALE_USER_DEFAULT, LCMAP_LINGUISTIC_CASING | LCMAP_UPPERCASE, in, lstrlenA(in), out, cch);
return S_OK;
}
Private Declare PtrSafe Function ToUpperLPWSTR Lib "foo.dll" (ByVal ins As LongPtr, ByVal out As LongPtr, ByVal cch As Long) As Long
Private Declare PtrSafe Function ToUpperBSTR Lib "foo.dll" (ByVal ins As LongPtr, ByVal out As LongPtr, ByVal cch As Long) As Long
Private Declare PtrSafe Function ToUpperLPSTR Lib "foo.dll" (ByVal ins As String, ByVal out As String, ByVal cch As Long) As Long
Sub Button1_Click()
Dim result As String
result = String(256, 0)
// note I use a special character 'é' to make sure it works
// I can't use any unicode character because VBA's IDE has not been updated and does not suppport the
// whole unicode range (internally it does, but you'll have to store the texts elsewhere, and load it as an opaque thing w/o the IDE involved)
ToUpperLPWSTR StrPtr("héllo world"), StrPtr(result), 256
MsgBox result
ToUpperBSTR StrPtr("héllo world"), StrPtr(result), 256
MsgBox result
ToUpperLPSTR "héllo world", result, 256
MsgBox result
End Sub
Declare PtrSafe Function GetWindowsDirectoryW Lib "kernel32" _
(ByVal lpBuffer As LongPtr, ByVal nSize As Long) As Long
Sub TestGetWindowsDirectoryW()
Dim WindowsDir As String
WindowsDir = Space$(256)
GetWindowsDirectoryW StrPtr(WindowsDir), 256
MsgBox WindowsDir
End Sub