将字符串返回到MacOSX中的VBA

将字符串返回到MacOSX中的VBA,c,string,macos,vba,C,String,Macos,Vba,我目前正在MacOSX中编写一个C动态库,然后使用VBA在Excel(2011)中使用这些函数,例如: Declare Function TestF Lib "path_to_lib:mylib.dylib" Alias "test" (ByRef res As String) As String 这对于返回整数等的函数很好,但我不知道如何将字符串传递回VBA。我的C函数如下所示: char* test(char *res) { res = "test"; return res; }

我目前正在MacOSX中编写一个C动态库,然后使用VBA在Excel(2011)中使用这些函数,例如:

Declare Function TestF Lib "path_to_lib:mylib.dylib" Alias "test" (ByRef res As String) As String
这对于返回整数等的函数很好,但我不知道如何将字符串传递回VBA。我的C函数如下所示:

char* test(char *res)
{
  res = "test";
  return res;
}
但是在VBA中调用函数TestF作为

Dim res As String
res = TestF(res)
出类拔萃。。。 如果我使用MathLink(Mathematica)库提供的函数,例如

#include "mathlink.h"
char* test(MLINK link, char *res)
{
  MLGetString(link, &res);
  return res;
}
此函数成功地为“res”分配了一个字符串,我可以在VBA中使用该字符串。有关MLGetString的信息可在此处找到:

显然,这个函数能够创建一个字符串并将其传递给
res
,然后我可以在VBA中使用该字符串。你知道这个函数是如何实现的吗,或者我如何在不使用OLE等的情况下将字符串传递给VBA

使用
BSTR
对象表示:它们由指向以空结尾的宽字符串(UTF-16)的指针组成,该字符串前面有一个4字节长的前缀。例如,在一台小型endian机器上,4个字符的字符串“test”在内存中是什么样子的:

+-------------+-------+-------+-------+-------+-------+
| 08 00 00 00 | 74 00 | 65 00 | 73 00 | 74 00 | 00 00 |
+-------------+-------+-------+-------+-------+-------+
 Length:        't'     'e'     's'     't'     '\0'
 8 bytes
 (not including null)
最重要的是,
BSTR
指针本身指向字符串的开头(本例中的第一个
74
字节),而不是长度前缀

对于读取
BSTR
s,您只需将它们视为指向宽字符串的常规指针,在大多数情况下您都会没事。但是,如果你需要编写/创建它们,就有点麻烦了

创建
BSTR
s时,通常使用(及其相关项)分配它们,并使用取消分配它们。您需要弄清楚OS X上的Excel如何公开这些函数(可能是通过OLE动态库),因为它们不是标准C。如果您试图返回一个未通过
SysAllocString
分配的字符串,那么Excel将尝试使用
SysFreeString
释放它,这可能会损坏堆并使程序崩溃。所以不要那样做


还请注意,默认情况下,
wchar\u t
在OS X上的大小为4字节,而不是2字节,因此在处理
BSTR
s时不能使用
wchar\u t
数据类型-您需要使用显式的16位数据类型(例如
uint16\u t
),或者使用
-fshort wchar
编译器选项进行编译,强制
wchar\u t
为2字节,但要注意可能导致的二进制不兼容问题。

我最初的帖子是尝试使用malloc()模拟SysAllocStringByteLen(),但当Excel尝试释放返回的内存时,这将失败。使用Excel分配内存修复了该问题,代码也更少,例如:

在测试c中:

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define LPCSTR const char *
#define LPSTR char *
#define __declspec(dllexport)
#define WINAPI

char *saved_string = NULL;
int32_t saved_len = -1;

#define _CLEANUP if(saved_string) free(saved_string)

__attribute__((destructor))
static void finalizer(void) {
  _CLEANUP;
}

int32_t __declspec(dllexport) WINAPI get_saved_string(LPSTR pszString, int cSize) {
  int32_t old_saved_len = saved_len;
  if(saved_len > 0 && cSize >= saved_len)
    strncpy(pszString, saved_string, saved_len);
  if(saved_string) {
    free(saved_string);
    saved_string = NULL;
    saved_len = -1;
  }
  return old_saved_len;
}

int32_t __declspec(dllexport) WINAPI myfunc(LPCSTR *pszString) {
  int len = (pszString && *pszString ? strlen(*pszString) : 0);
  saved_string = malloc(len + 5);
  saved_len = len + 5;
  sprintf(saved_string, "%s%.*s", "abc:", len, *pszString);
  return saved_len;
}
然后,在新的VBA模块中,使用以下命令并运行“test”,它将在字符串“hi there”前加上“abc:”,并将结果输出到调试窗口:

Public Declare Function myfunc Lib "<colon-separated-path>:test.dylib" (s As String) As Long
Public Declare Function get_saved_string Lib "<colon-separated-path>:test.dylib" (ByVal s As String, ByVal csize As Long) As Long

Option Explicit

Public Function getDLLString(string_size As Long) As String
    Dim s As String
    If string_size > 0 Then
        s = Space$(string_size + 1)
        get_saved_string s, string_size + 1
    End If
    getDLLString = s
End Function

Public Sub test()
Debug.Print getDLLString(myfunc("hi there"))
End Sub
Public声明函数myfunc Lib“:test.dylib”(s作为字符串)的长度
公共声明函数get_saved_string Lib“:test.dylib”(ByVal s作为字符串,ByVal csize作为Long)作为Long
选项显式
公共函数getDLLString(字符串大小与字符串长度相同)
像线一样变暗
如果字符串大小>0,则
s=空间$(字符串大小+1)
获取保存的字符串,字符串大小+1
如果结束
getDLLString=s
端函数
公共子测试()
Debug.Print getDLLString(myfunc(“hi-there”))
端接头

事实上,mac os x(比如office 2011 excel)下VBA中BSTR的内存布局与Adam Rosenfield回答中描述的windows BSTR不同。它使用以null结尾的
char*
字符串的内存布局

例如,见我问题中的备注2:


现在,您的问题是:在c++/dylib端为一个
char*
分配内存,在windows下,该内存将在VBA端释放,而mac OS X则不可能,因为释放任务无法传递到另一个进程,因为它可以在windows下…

非常有用,谢谢!由于我在C中使用的另一个库(Mathematica中的MathLink)显然能够在不使用OLE库的情况下将字符串传递给VBA,我希望我也可以在函数中这样做?例如,当通过C函数将字符串参数传递给Excel时,该函数工作正常。这实际上是错误的。mac os x(比如说office 2011 excel)下VBA中的
BSTR
的内存布局不是您描述的windows
BSTR
。它使用了以null结尾的
char*
字符串的内存布局。请参见我问题中的备注2:我在中添加了上面的C版本(没有C++)。基本上,我刚删除了C++字符串的用法,这在编译GCC时有好处,不用修改名字。我保持上述答案,因为有些人可能喜欢C++,而且从上面的修改到普通C是很容易的。
Public Declare Function myfunc Lib "<colon-separated-path>:test.dylib" (s As String) As Long
Public Declare Function get_saved_string Lib "<colon-separated-path>:test.dylib" (ByVal s As String, ByVal csize As Long) As Long

Option Explicit

Public Function getDLLString(string_size As Long) As String
    Dim s As String
    If string_size > 0 Then
        s = Space$(string_size + 1)
        get_saved_string s, string_size + 1
    End If
    getDLLString = s
End Function

Public Sub test()
Debug.Print getDLLString(myfunc("hi there"))
End Sub