I am having a VB application request a list of users from a C DLL:
VB will ask the DLL how many users there are, and then initialize an array to the appropriate size.
VB will then pass its array by reference to a DLL function, which will fill it with usernames.
我有一个VB应用程序请求来自C DLL的用户列表:VB将询问DLL有多少用户,然后将数组初始化为适当的大小。然后,VB将通过引用DLL函数传递其数组,该函数将使用用户名填充它。
I started writing the C function like this: foo(char **bar);
which would be treated as an array of strings. But then I realized, I'm going to make each item in the array point to a different C-string (the char *username
in the struct userlist
linked list) rather than modify the data already being pointed to. The array of arrays is being passed by value: a copy of a list of addresses, so the addresses point to the original data, but modifying the addresses in that copy won't change the list of addresses of the caller (I think, anyways). So, should I be declaring it foo(char ***bar);
? This would be a pointer to the array of strings, so that if I change the strings that array is pointing to, it will modify the array of strings the caller (VB) is using....right?
我开始写这样的C函数:foo(char ** bar);这将被视为一个字符串数组。但后来我意识到,我将使数组中的每个项指向不同的C字符串(struct userlist链表中的char *用户名),而不是修改已经指向的数据。数组数组按值传递:地址列表的副本,因此地址指向原始数据,但修改该副本中的地址不会更改调用者的地址列表(我认为,无论如何)。那么,我应该宣布它为foo(char *** bar);?这将是一个指向字符串数组的指针,因此,如果我更改数组指向的字符串,它将修改调用者(VB)正在使用的字符串数组....对吗?
This is my usage so far (haven't tested it yet... I'm still just coding the DLL as of yet, there's no VB front-end to call it thus far)
到目前为止这是我的用法(还没有测试过......我还在编码DLL,到目前为止还没有VB前端调用它)
EXPORT void __stdcall update_userlist(char ***ulist){
int i = 0;
userlist *cur_user = userlist_head; //pointer to first item in linked list
for(; i < usercount_; ++i){
*ulist[i] = cur_user->username;
cur_user = cur_user->next;
}
}
3 个解决方案
#1
In general it's not simple to do what you're asking, because VB just doesn't understand C-style ASCIIZ strings and arrays.
一般来说,做你要求的事情并不简单,因为VB只是不理解C风格的ASCIIZ字符串和数组。
If your DLL is not expecting a VB SafeArray of BSTR, you're going to have some difficulty populating it.
如果你的DLL不期望BSTR的VB SafeArray,你将会遇到一些困难。
It would be simple to have VB pass in an array of Long (C int) by reference to the first element, and you could fill that with the pointers to individual strings. The VB side could copy them to VB strings. But in that case, who disposes of the C strings, and when?
通过引用第一个元素让VB传入一个Long(C int)数组会很简单,你可以用指向各个字符串的指针填充它。 VB端可以将它们复制到VB字符串。但在那种情况下,谁处理了C字符串,何时?
If you create the VB array and fill it with pre-sized strings, you'll still have to deal with a SafeArray on the C side, because you can't pass a single VB string array element by reference and expect to find the remaining strings contiguous to it in memory.
如果您创建VB数组并使用预先调整大小的字符串填充它,您仍然需要在C端处理SafeArray,因为您无法通过引用传递单个VB字符串数组元素并期望找到剩余的在内存中与它相邻的字符串。
The best, safest method is to have your DLL create a SafeArray of so-called 'Ansi BSTR', and declare the function in VB as returning an array of strings. Then you don't need two calls, because the array bounds will tell the whole story.
最好,最安全的方法是让你的DLL创建一个所谓的'Ansi BSTR'的SafeArray,并在VB中声明该函数返回一个字符串数组。然后你不需要两个调用,因为数组边界将告诉整个故事。
===== edit =====
=====编辑=====
When VB passes a string array to a Declared function it does some voodoo behind the scenes. It first converts all the strings from Unicode to a bastard form commonly known as 'Ansi BSTR'. To C, these look like and can be treated as ASCIIZ or LPSTR except that you can't create or lengthen them in the normal C way, you can only fill them in. On the C side, the passed array looks like ppSA (SAFEARRAY**). The Ansi BSTR are a series of pointers referenced by the pData member of the SafeArray.
当VB将字符串数组传递给Declared函数时,它会在幕后执行一些伏都教。它首先将所有字符串从Unicode转换为通常称为“Ansi BSTR”的混蛋形式。对于C,这些看起来像可以被视为ASCIIZ或LPSTR,除了你不能以正常的C方式创建或延长它们,你只能填充它们。在C方面,传递的数组看起来像ppSA(SAFEARRAY **)。 Ansi BSTR是由SafeArray的pData成员引用的一系列指针。
You absolutely cannot pass a single string from the array (as char*) and expect to find the rest of the strings contiguous to it in memory. You have to pass the array itself and manipulate it using the SafeArray API (or knowledge of the SA structure).
你绝对不能从数组中传递一个字符串(作为char *),并期望在内存中找到与其相邻的其余字符串。您必须传递数组本身并使用SafeArray API(或SA结构的知识)对其进行操作。
That's why the best option overall is to do all of this directly in the DLL. Create the array using SafeArrayCreate, then create Ansi BSTRs using SysAllocStringByteLen and place those strings (which are BSTR, so a 4-byte pointer) into the array slots. On return, VB does its voodoo and converts the strings to Unicode for you.
这就是为什么总体上最好的选择是直接在DLL中完成所有这些操作。使用SafeArrayCreate创建数组,然后使用SysAllocStringByteLen创建Ansi BSTR并将这些字符串(BSTR,因此是4字节指针)放入数组插槽中。返回时,VB执行其voodoo并将字符串转换为Unicode。
In VB your function would be Declared as returning a String().
在VB中,您的函数将被声明为返回String()。
#2
two asterixes is the way to go.
两个星号是要走的路。
char* // is a pointer to a char
char** // is a pointer to a char pointer
char*** // is a pointer to a pointer to a char pointer - e.g. multi-dimensional array (err...)
I've confused myself :)
我弄糊涂了:)
#3
So let me get this straight. Your function fills in an array of strings from data contained in a linked list ?
所以让我直截了当。您的函数是否填充了链表中包含的数据的字符串数组?
If you know the size of the list beforehand, you can just pass a char **, but if you do not know the size and need to be able to grow the list, you will need a char ***.
如果你事先知道列表的大小,你可以传递一个char **,但是如果你不知道大小并且需要能够增加列表,你将需要一个char ***。
From looking at your code, you seem to already know the length, so you just need to allocate an array of the correct length before you call the function. Here is an example:
从查看代码开始,您似乎已经知道了长度,因此您只需要在调用函数之前分配一个正确长度的数组。这是一个例子:
void update_userlist(char **ulist)
{
int i = 0;
userlist *cur_user = userlist_head;
for(; i < usercount_; ++i)
{
ulist[i] = cur_user->username; // I am assuming that username is a char *
cur_user = cur_user->next;
}
}
// This sets up the array and calls the function.
char **mylist = malloc(sizeof(char*) * usercount_);
update_userlist(mylist);
Update: Here is the difference between the various levels of pointers:
更新:这是各种指针级别之间的区别:
-
void func1(char *data)
This passes a copy of a pointer to a C string. If you change the pointer to point to a different string, the calling function will still point to the original string.void func1(char * data)这会将指针的副本传递给C字符串。如果将指针更改为指向其他字符串,则调用函数仍将指向原始字符串。
-
void func2(char **data)
This passes a copy of a pointer to an array of pointers to C strings. You can replace the pointer to any string in the array and the calling function's array will be changed because it has not made a copy of the array, it only points to the caller's array.void func2(char ** data)这会将指针的副本传递给指向C字符串的指针数组。您可以将指针替换为数组中的任何字符串,并且调用函数的数组将被更改,因为它没有创建数组的副本,它只指向调用者的数组。
-
void func3(char ***data)
This passes a pointer to a pointer to an array of pointers to C strings. With this, you can completely replace the entire array. You would only need this level of indirection if you need to grow the array since C arrays cannot be re-sized.void func3(char *** data)这会传递一个指向指向C字符串指针数组的指针。有了这个,您可以完全替换整个阵列。如果需要增长数组,则只需要这种级别的间接,因为无法重新调整C数组的大小。
#1
In general it's not simple to do what you're asking, because VB just doesn't understand C-style ASCIIZ strings and arrays.
一般来说,做你要求的事情并不简单,因为VB只是不理解C风格的ASCIIZ字符串和数组。
If your DLL is not expecting a VB SafeArray of BSTR, you're going to have some difficulty populating it.
如果你的DLL不期望BSTR的VB SafeArray,你将会遇到一些困难。
It would be simple to have VB pass in an array of Long (C int) by reference to the first element, and you could fill that with the pointers to individual strings. The VB side could copy them to VB strings. But in that case, who disposes of the C strings, and when?
通过引用第一个元素让VB传入一个Long(C int)数组会很简单,你可以用指向各个字符串的指针填充它。 VB端可以将它们复制到VB字符串。但在那种情况下,谁处理了C字符串,何时?
If you create the VB array and fill it with pre-sized strings, you'll still have to deal with a SafeArray on the C side, because you can't pass a single VB string array element by reference and expect to find the remaining strings contiguous to it in memory.
如果您创建VB数组并使用预先调整大小的字符串填充它,您仍然需要在C端处理SafeArray,因为您无法通过引用传递单个VB字符串数组元素并期望找到剩余的在内存中与它相邻的字符串。
The best, safest method is to have your DLL create a SafeArray of so-called 'Ansi BSTR', and declare the function in VB as returning an array of strings. Then you don't need two calls, because the array bounds will tell the whole story.
最好,最安全的方法是让你的DLL创建一个所谓的'Ansi BSTR'的SafeArray,并在VB中声明该函数返回一个字符串数组。然后你不需要两个调用,因为数组边界将告诉整个故事。
===== edit =====
=====编辑=====
When VB passes a string array to a Declared function it does some voodoo behind the scenes. It first converts all the strings from Unicode to a bastard form commonly known as 'Ansi BSTR'. To C, these look like and can be treated as ASCIIZ or LPSTR except that you can't create or lengthen them in the normal C way, you can only fill them in. On the C side, the passed array looks like ppSA (SAFEARRAY**). The Ansi BSTR are a series of pointers referenced by the pData member of the SafeArray.
当VB将字符串数组传递给Declared函数时,它会在幕后执行一些伏都教。它首先将所有字符串从Unicode转换为通常称为“Ansi BSTR”的混蛋形式。对于C,这些看起来像可以被视为ASCIIZ或LPSTR,除了你不能以正常的C方式创建或延长它们,你只能填充它们。在C方面,传递的数组看起来像ppSA(SAFEARRAY **)。 Ansi BSTR是由SafeArray的pData成员引用的一系列指针。
You absolutely cannot pass a single string from the array (as char*) and expect to find the rest of the strings contiguous to it in memory. You have to pass the array itself and manipulate it using the SafeArray API (or knowledge of the SA structure).
你绝对不能从数组中传递一个字符串(作为char *),并期望在内存中找到与其相邻的其余字符串。您必须传递数组本身并使用SafeArray API(或SA结构的知识)对其进行操作。
That's why the best option overall is to do all of this directly in the DLL. Create the array using SafeArrayCreate, then create Ansi BSTRs using SysAllocStringByteLen and place those strings (which are BSTR, so a 4-byte pointer) into the array slots. On return, VB does its voodoo and converts the strings to Unicode for you.
这就是为什么总体上最好的选择是直接在DLL中完成所有这些操作。使用SafeArrayCreate创建数组,然后使用SysAllocStringByteLen创建Ansi BSTR并将这些字符串(BSTR,因此是4字节指针)放入数组插槽中。返回时,VB执行其voodoo并将字符串转换为Unicode。
In VB your function would be Declared as returning a String().
在VB中,您的函数将被声明为返回String()。
#2
two asterixes is the way to go.
两个星号是要走的路。
char* // is a pointer to a char
char** // is a pointer to a char pointer
char*** // is a pointer to a pointer to a char pointer - e.g. multi-dimensional array (err...)
I've confused myself :)
我弄糊涂了:)
#3
So let me get this straight. Your function fills in an array of strings from data contained in a linked list ?
所以让我直截了当。您的函数是否填充了链表中包含的数据的字符串数组?
If you know the size of the list beforehand, you can just pass a char **, but if you do not know the size and need to be able to grow the list, you will need a char ***.
如果你事先知道列表的大小,你可以传递一个char **,但是如果你不知道大小并且需要能够增加列表,你将需要一个char ***。
From looking at your code, you seem to already know the length, so you just need to allocate an array of the correct length before you call the function. Here is an example:
从查看代码开始,您似乎已经知道了长度,因此您只需要在调用函数之前分配一个正确长度的数组。这是一个例子:
void update_userlist(char **ulist)
{
int i = 0;
userlist *cur_user = userlist_head;
for(; i < usercount_; ++i)
{
ulist[i] = cur_user->username; // I am assuming that username is a char *
cur_user = cur_user->next;
}
}
// This sets up the array and calls the function.
char **mylist = malloc(sizeof(char*) * usercount_);
update_userlist(mylist);
Update: Here is the difference between the various levels of pointers:
更新:这是各种指针级别之间的区别:
-
void func1(char *data)
This passes a copy of a pointer to a C string. If you change the pointer to point to a different string, the calling function will still point to the original string.void func1(char * data)这会将指针的副本传递给C字符串。如果将指针更改为指向其他字符串,则调用函数仍将指向原始字符串。
-
void func2(char **data)
This passes a copy of a pointer to an array of pointers to C strings. You can replace the pointer to any string in the array and the calling function's array will be changed because it has not made a copy of the array, it only points to the caller's array.void func2(char ** data)这会将指针的副本传递给指向C字符串的指针数组。您可以将指针替换为数组中的任何字符串,并且调用函数的数组将被更改,因为它没有创建数组的副本,它只指向调用者的数组。
-
void func3(char ***data)
This passes a pointer to a pointer to an array of pointers to C strings. With this, you can completely replace the entire array. You would only need this level of indirection if you need to grow the array since C arrays cannot be re-sized.void func3(char *** data)这会传递一个指向指向C字符串指针数组的指针。有了这个,您可以完全替换整个阵列。如果需要增长数组,则只需要这种级别的间接,因为无法重新调整C数组的大小。