Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations strongm on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

arrays of structures and dlls 2

Status
Not open for further replies.

txjump

Programmer
May 17, 2002
76
US
Hi,


I am a C++ programmer trying to create a dll for a powerbuilder application. im trying to test a dll function that returns an array of c++ structures. (the functions that return standard datatypes work fine.) the structure is duplicated in powerbuilder but i dont know how to declare the external function.

the c++ header declaration is
XC EXPORTS PairData* FAR API getPairData(char*, int &size);

does anyone know if i need to do anything special in the dll? and does anyone know if i can even bring this array into powerbuilder? if so, how? and what does the powerbuilder external function declaration look like? :)

any help/suggestions would be appreciated.

thanks,
stacey

 
Hi,

I found the best way to get C/C++ structure arrays into PB is to use API calls such as RtlMoveMemory(ref buffer, val memptr, val membuffersize) to move data from the C/C++ allocated memory into PB. I am assuming that your C++ code is returning a pointer to the C++ structures. Declare the PB structure (lets call it PBStruct) such that it duplicates the C/C++ structures. Then initialise PBStruct to the sizes you are expecting (max it out if you're nopt sure about the sizes of strings etc.). Then call your .DLL function. It would be useful if your .DLL function returned either the size of the C++ structure array or the number of elements in the array as well as the pointer to the structure array. Either way, you'll need to know the size of the C++ structure array because it is important for RtlMoveMemory.

Then call RtlMoveMemory iterating over the number of C++ structures youe have thus:

Code:
RtlMoveMemory(PBStruct, C++StructurePointer, C++StructureArraySize)

You'll need to increment the C++StructurePointer by the size of your C++ structure elements to step through the array.

Because PBStruct is passed as a reference, RtlMoveMemory gets the target location, and you've already prepared the memory to receive the data from the C++ structure array so, seemingly miraculously, you should end up with your C++ data in your PB structures.

If you are working in Unicode then you need to make allowance for this too since vanilla PB doesn't understand Unicode too good.

Cheers.
 
Could you please post examples of your response. Thanks
 
Here you go. This works on some basic network info about the currently logged in user.

External function declarations:

Code:
subroutine		RtlMoveMemoryWkStaUserInfo(ref wksta_user_info_1 dest, long src, long buffsize) LIBRARY "Kernel32" ALIAS FOR "RtlMoveMemory"

function	long	NetWkstaUserGetInfo(ref String s_Reserved, Long l_Level, ref long user_buffer) LIBRARY "NETAPI32.DLL"

function	long	NetApiBufferFree(long user_buffer) LIBRARY "NETAPI32.DLL"

subroutine		lstrcpyW(ref string dest, long src) Library "Kernel32"  alias for "lstrcpyW"	// unicode copy

function	long	WideCharToMultiByte(unsignedlong CodePage, unsignedlong dwFlags, ref string lpWideCharStr, long cchWideChar, ref string lpMultiByteStr, long cbMultiByte, ref string lpDefaultChar, ref boolean lpUsedDefaultChar) LIBRARY "Kernel32"

/* Object structure. This receives pointers from an API call to NetWkstaUserGetInfo (as defined in MSDN website)*/
Code:
wksta_user_info_1:
long	wkui1_username
long	wkui1_logon_domain
long	wkui1_oth_domains
long	wkui1_logon_server

/* Object function to get basic network info about a user */
Code:
long			lpBuffer
string			ls_null
uint			ret
wksta_user_info_1	user_buffer

SetNull(ls_null)
/* Get the user information buffer */
Code:
ret = NetWkstaUserGetInfo(ls_null, 1, lpBuffer)
/* check return value */
Code:
if ret <> 0 then
	if not IsNull(lpBuffer) then
/* free buffer */
Code:
		ret = NetApiBufferFree(lpbuffer)
	end if
	return -1
end if

RtlMoveMemoryWkStaUserInfo(user_buffer, lpbuffer, 16)	// Move from system allocated buffer to structure, we know it is 16 bytes long because it contains 4 longs (4 * 4bytes)
/* Free the system allocated buffer */
Code:
ret = NetApiBufferFree(lpbuffer)
/* Populate the instance variables */
Code:
is_username = of_GetUserInfoString(user_buffer.wkui1_username)
is_logondomain = of_GetUserInfoString(user_buffer.wkui1_logon_domain)
ls_otherdomains = trim(of_GetUserInfoString(user_buffer.wkui1_oth_domains))

return 1
/* Definition of of_GetUserInfoString(long al_memptr) */
Code:
String		ls_UnicodeBuffer
String		ls_retval

ls_UnicodeBuffer = space(512)

lstrcpyW(ls_UnicodeBuffer, al_memptr)

of_ConvertFromUnicode(ls_UnicodeBuffer, ls_retval)

return trim(ls_retval)
/* of_ConvertFromUnicode(ref String as_UnicodeSource, ref String as_result) */
Code:
boolean	lb_null
long		ll_result, ll_sourcelen
string		ls_buffer, ls_null

SetNull(lb_null)
SetNull(ls_null)

ll_sourcelen = lstrlenW(as_unicodesource)	// the value returned is the length in WIDE characters i.e. actual length is double the reported length.
ll_sourcelen = ll_sourcelen + 2					// add two for the double terminating nulls
/* Find out how big the buffer should be. CP_ACP is a constant defined 0 (zero) */
Code:
ll_result = WideCharToMultiByte( CP_ACP, 0, as_unicodesource, ll_sourcelen, ls_buffer, 0, ls_null, lb_null);	// supplying -1 indicates null terminated as_unicodesource. ll_buflen of 0 asks for len of unicode buffer without converting
if ll_result = 0 then	// operation failed
	return -1
end if
/* Prep buffer */
Code:
ls_buffer = space(ll_result + 1)
/* Convert it */
Code:
ll_result = WideCharToMultiByte( CP_ACP, 0, as_unicodesource, ll_sourcelen, ls_buffer, ll_result, ls_null, lb_null);
if ll_result = 0 then	// operation failed
	return -1
end if

as_result = ls_buffer

return ll_result	// Copied OK, return length of unicode buffer.

Cheers.
 
cool! thanks, ill give it another shot. [ponytails2]
 
Hey betterLateThanNever,

i tried an example similar to yours and i can get it to work. i can also use a pointer to an integer and get that integer value out. i can even pass a pointer to structure and get the pointer from in the structure to a string to work. BUT, i cant get an array of pointers to work. for some reason the RtlMoveMemory doesnt copy the values in my array correctly when called from powerbuilder but it works in C++.

typedef struct numStructTag{
char* aString;
char* bString;
int num;
}numStruct;


//working integer pointer
void* pbIntArrayPtr;
int* myInt = new int;

pbArrayTest(pbIntArrayPtr);
cout << &quot;pbIntArrayPtr: &quot; << pbIntArrayPtr << endl;
RtlMoveMemory(myInt, pbIntArrayPtr, (sizeof(int)) );
cout << &quot;myInt: &quot; << *myInt << endl;
//end working integer pointer


//working code for array of structs!
numStruct* numStrArr[10];
numStruct* numStrValPtr = new numStruct;

int* intValPtr = new int;

char* tempStra = new char[256];
char* tempStrb = new char[256];
void* numStrArrPtr;

fillIntArray(numStrArrPtr);
RtlMoveMemory(numStrArr, numStrArrPtr, (sizeof(int)*(10) ) );
for(int j = 0; j < 10; j++){
void* tempPtr = (int*)numStrArr[j];
RtlMoveMemory(numStrValPtr, tempPtr, (sizeof(int)*3) );
cout << &quot;numStrValPtr = &quot; << numStrValPtr->num << endl;

void* tempPtrStr = (char*)numStrValPtr->aString;
RtlMoveMemory(tempStra, tempPtrStr, (sizeof(char)*(256) ) );
cout << &quot;tempStra = &quot; << tempStra << endl;

tempPtrStr = (char*)numStrValPtr->bString;
RtlMoveMemory(tempStrb, tempPtrStr, (sizeof(char)*(256) ) );
cout << &quot;tempStrb = &quot; << tempStrb << endl << endl << endl;
}
//end of working code



but when i try to do something simple in power builder for an array of pointers to integers it craps out. the following code prints to a file the values in the array after ive done a move. i did this to compare the values to the allocated addresses in the dll. a call to the dll from another c++ app shows the address values match. but in powerbuilder they dont.

any arrptr
long val
long intArray[]
long size

arrptr = 0
size = pbArrayTest(arrptr)
MessageBox(&quot;size is: &quot;, String(size))
int j
for j = 1 to size
intArray[j] = 0
next
RtlMoveMemoryMoveArray(intArray, arrptr, (4 * size) )

int handle
handle = FileOpen(&quot;c:\temp\dllInfo.txt&quot;, LineMode!, Write!)
MessageBox(&quot;handle&quot;, String(handle))

FileWrite(handle, &quot;rcvd arrptr as: &quot; + String(arrptr))

for j = 1 to size
FileWrite(handle, String(intArray[j]))
next

RtlMoveMemoryMoveInt(val, intArray[1], (4) )
MessageBox(&quot;value in firstPtr (should equal file): &quot;, String(val) + &quot;~n value of arrPtr: &quot; + String(arrptr))

FileClose(handle)


FUNCTION long pbIntPtrTest(REF long ptr) Library&quot;pbtestdll.dll&quot;
SUBROUTINE RtlMoveMemoryMoveInt(REF long dest, long source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;
SUBROUTINE RtlMoveMemoryMoveArray(REF long dest[],REF any source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;
SUBROUTINE RtlMoveMemoryMoveStruct(REF struct1 dest, REF long source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;
SUBROUTINE RtlMoveMemoryMoveString( string dest, REF long source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;
FUNCTION long pbArrayTest(REF any ptr) Library&quot;pbtestdll.dll&quot;


have you got any suggestions or ideas as to what the heck im doing wrong? :)

thanks for your help already...ive learned a lot from your sample.

txjump
 
Hi txjump,

Is the code you've posted as it is in your PB app? I ask because your declaration of
Code:
RtlMoveMemoryMoveInt
differs from the rest of your declarations - you are missing a REF keyword from the source argument:

Your declaration (in your example):
Code:
SUBROUTINE RtlMoveMemoryMoveInt(REF long dest, long source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;

should be:
Code:
SUBROUTINE RtlMoveMemoryMoveInt(REF long dest, REF long source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;

Cheers.
 
Hiya betterLateThanNever,

Ive tried it both ways and in my simple example(passing a pointer to an integer)...it only works when its not a ref. thats because the value in the source is the address that i want to start the move from.

Thanks for reviewing my code and for helping. :)

txjujp
[ponytails2]

 
Hi txjump,

I have to admit to being stumped now. We're only moving chunks of memory around so it should not matter whether the data are pointers or values. I'm not a mighty C++ user so don't know if pointers are stored in any different way to values - presumably a pointer is still stored as a 32bit value?

What isn't clear to me is where the mismatch occurs: is it when you do the file write (are the correct values coming out?) or when you do your final move (
Code:
RtlMoveMemoryMoveInt(val, intArray[1], (4) )
)?

The only other thing I can think of, and now I'm straying onto dubious ground, is that PowerBuilder cannot resolve the reference intArray[1] i.e. can't determine the first element's address. If this is the case, then you will have to move the whole array out, then unpick to get the elements you want.

Cheers.
 
Hi BetterLateThanNever,
:)

thanks for your thoughts and reply! im stumped too, i had to give it a few days rest cause i felt like i was banging my head on the wall. hehehehe

The mismatch occurs as soon as an array is introduced. If my DLL passes a pointer to a structure, I can get the information (i think, its been a weeks since i tried that). But, if i pass the first address of the array, then try to index through it after a RtlMoveMemory, i get some strange values copied into the array.

below are the values that get sent to my output file for comparison. &quot;local&quot; is the name of the array in the dll. Down to the line &quot;--------------------&quot; is all dll information. after that is the powerbuilder info.


//when i test the dll w/ a c++ program, the
//c++ values always match this group.
//these are the values in the array represented
//in hex format
hex values of addys in local
2674610
2674640
2674670
26746a0
26746d0
2674700
2674730
2674760
2674790
26747c0

//the powerbuilder array should match this group.
//they are just the values in the array. same
//as above, just represented as decimals
dec values of addys in local
40322576
40322624
40322672
40322720
40322768
40322816
40322864
40322912
40322960
40323008

//i just output these next two sets to
//see if anything ever matched these values.
//they are the addresses of each array
//item...notice they increment by four
//bytes
hex values of array indexes
12f078
12f07c
12f080
12f084
12f088
12f08c
12f090
12f094
12f098
12f09c


dec values of array indexes
1241208
1241212
1241216
1241220
1241224
1241228
1241232
1241236
1241240
1241244

//this is the pointer to the
//array. notice its just a pointer
//to the first value in the array
//(the address of the first item
//in the array)
Returning: 1241208
-------------------------------

//in powerbuilder i get the address
//to the first item in the array
rcvd arrptr as: 1241208

//this is what the array contains
//after the RtlMoveMemory...grrrrrrrrrrrr
//it should match the section above that says
//&quot;dec values of addys in local&quot;
1241208
138496
0
65535
15
0
138496
0
28
19

*********************************************
I hope that makes a little bit of sense. Im gonna go retry getting a single pointer to a structure (which is what each array element holds) to make sure i was able to do that. I'll post again here in a little bit to let you know.

also, your question about c++ addresses. the addresses are indeed 4 byte addresses. i used the sizeof() function to verify...plus i use the sizeof function in my call to RtlMoveMemory in the c++ test code. and my output of the
addresses shows increments of 4 bytes.

i really appreciate the thought youve put into this already. :)

thanks
txjump
 
okay BLTN!

i think im on the road to figuring out my blunder! it has to do w/ the way im allocating memory and the lifetime of the memory.

im gonna research and test a few things and then ill get back to you w/ the results.

:)
txjump
 
Thank you BetterLateThanNever! :)

you put me on the right path and gave me some great ideas. it turns out that what i was doing wrong was simplifying the declaration in c++. i was going to have to malloc an array in the final code but thought i would keep it simple for testing by just delcaring a local array of a specific size. i dont know why it didnt dawn on me that the array was going to die after i left the function. i new that i had to dynamically allocate the structures and strings so they would stay in place but the array just didnt occur to me.

i changed the local static array to a global, and it started to work. the array was staying intact but then i had to play w/ the alias' to get the references right.

final code snippet for anyone who has to do something similar. this code has a dynamically allocated array instead of a global declaration.

Code:
//----------------pb code----------------
//pointer to c++ array
long carrptr

//pointer to each structure
long pbStructPtr

//size of array
long size

//pb strings to hold the data moved from
//what the structure points to
String strStringa
String strStringb

//allocate the space...assume 256 max size
//as stated in dll info
strStringa = Space(256)
strStringb = Space(256)

//declare structure
struct1 ustruct1

//clear structures integer value
ustruct1.num = 0;

//call the function in the dll
//carrptr is loaded w/ the address of the 
//array allocated in the dll
size = mallocArrOfStructPtrs(carrptr)

MessageBox(&quot;&quot;, &quot;Size: &quot; + String(size))

//powerbuilder array, and initialize array
//to length returned by dll function
long pbarr[]
long i = 1
for i = 1 to size
	pbarr[i] = 0;
next

//call RtlMoveMemory to move the c++ array into
//powerbuilder space
RtlMoveMemoryMoveArray(pbarr, carrptr, (size*4) )

//debug code
int handle
handle = FileOpen(&quot;c:\temp\dllInfo.txt&quot;, LineMode!, Write!)
MessageBox(&quot;handle&quot;, String(handle))

FileWrite(handle, &quot;rcvd arrptr as: &quot; + String(carrptr))

//int j
//for j = 1 to size
//	FileWrite(handle, String(pbarr[j]))
//next


//for each pointer in the array, you must move the 
//pointed to structure into pb space
i = 1
for i = 1 to size
	//move the address into pb space
	RtlMoveMemoryMoveInt(pbStructPtr, pbarr[i], 4)
	FileWrite(handle, String(pbarr[i]))
	
	//move the structure into pb space
	RtlMoveMemoryStruct1(ustruct1, pbarr[i], 12);

	//move both strings into pb space
	RtlMoveMemoryMoveString(strStringa, ustruct1.aString, 256);
	RtlMoveMemoryMoveString(strStringb, ustruct1.bString, 256);
	
	//debug code
	FileWrite(handle, &quot;     &quot; + String(ustruct1.aString))	
	FileWrite(handle, &quot;     &quot; + String(ustruct1.bString))	

	MessageBox(&quot;ustruct1.astring&quot;, strStringa)
	MessageBox(&quot;ustruct1.bstring&quot;, strStringb)
	MessageBox(&quot;ustruct1.Number&quot;, String(ustruct1.num))

next

//close the debug file
FileClose(handle)

//--------------pb external function decs--------
FUNCTION long mallocArrOfStructPtrs(REF long str) LIBRARY &quot;pbtestdll.dll&quot;

SUBROUTINE RtlMoveMemoryMoveArray(REF long dest[], long source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;
SUBROUTINE RtlMoveMemoryMoveInt(REF long dest, long source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;
SUBROUTINE RtlMoveMemoryStruct1( REF struct1 dest, long source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;
SUBROUTINE RtlMoveMemoryMoveString(REF string dest,  long source, long al ) library 'kernel32.dll' ALIAS for &quot;RtlMoveMemory&quot;


//--------------pb structure----------------
global type struct1 from structure
	long		astring
	long		bString
	long		num
end type



//------------------c++ code in the dll------------
int API mallocArrOfStructPtrs(void* &ptr ){
	int javaSize = 10;
	//must declare array as global or malloc a new one inside the 
	//function.  otherwise, when the function returns, the array dies
	struct1** malArray = (struct1**)malloc(sizeof(struct1*)*javaSize);

	//may want to allocate enough memory according to 
	//what java returns + 1 for term null addy so size isnt needed to 
	//index through array
	char charTemp[256];
	for(int i = 0; i < javaSize; i++){
		sprintf(charTemp, &quot;struct test string indexed at %d&quot;, i);
		malArray[i] = new struct1();
		malArray[i]->aString = new char[256];
		malArray[i]->bString = new char[256];
		malArray[i]->aString = strcpy(malArray[i]->aString, charTemp);
		malArray[i]->bString = strcpy(malArray[i]->bString, &quot;struct test string NUM: 2&quot;);
		malArray[i]->num = i+60;
	}
	//malArray[i] = NULL;

	ptr = (void*)(malArray);

	//the rest of this is debug code
	char temp[256];
	ofstream out;
	out.open(&quot;c:\\temp\\dllInfo.txt&quot;);
	out << &quot;hex values of addys in malArray&quot; << endl;
	for(int p = 0; p < javaSize; p++){
		sprintf(temp, &quot;%x&quot;, malArray[p]);
		out << temp << endl;
	}

	out << endl << endl << &quot;dec values of addys in malArray&quot; << endl;
	for(int p = 0; p < javaSize; p++){
		sprintf(temp, &quot;%d&quot;, malArray[p]);
		out << temp << endl;

		sprintf(temp, &quot;     %d&quot;, malArray[p]->aString);
		out << temp << endl;

		sprintf(temp, &quot;     %d&quot;, malArray[p]->bString);
		out << temp << endl;
	}

	out << endl << endl << &quot;hex values of array indexes&quot; << endl;
	for(int p = 0; p < javaSize; p++){
		sprintf(temp, &quot;%x&quot;, &malArray[p]);
		out << temp << endl;
	}

	out << endl << endl << &quot;dec values of array indexes&quot; << endl << endl;
	for(int p = 0; p < javaSize; p++){
		sprintf(temp, &quot;%d&quot;, &malArray[p]);
		out << temp << endl;
	}

	sprintf(temp, &quot;Returning: %d&quot;, ptr);
	messageBox(temp);
	out << temp << endl << &quot;-------------------------------&quot; << endl << endl << endl;

	out.close();



	return javaSize;
}

//-----------------c++ structure--------------------
#pragma pack( push, 1 )
typedef struct struct1tag{
	char* aString;
	char* bString;
	int num;
}struct1;
#pragma pack(pop)
**************************************************
the one thing you need to make a note of is that the memory allocated in the dll has not been released. in the dll you will need to create a release function that takes the pointer back and deallocates the array and structures. after you have done all your moves, call that release function.

typically your dll should NOT allocate memory. you should allocate what you need and pass it to the dll to fill. this is a special case because the dll will serve as a bridge between languages. we are pulling undetermind amounts of data from a database.

anyways...once again, thank you very much BetterLateThanNever. :)

txjump
[ponytails2]
 
Hi,

Please help...

I tried doing the same thing and managed to get the data moved from the c++ array of structures to a datastore in PowerBuilder.

My problem is that every once in a while I get &quot;Instruction at x referenced memory at y. The memory could not be read.&quot; I have no idea what's causing this.

Please help whenever you're free.

Thanks!!
 
i would love to help but im not a powerbuilder programer so i dont know about datastores. i only wrote the above code so i could test my dll.

where did you allocate your array? in the dll or in powerbuilder? if you allocated in the dll make sure you used malloc/free or new/delete for the array.

beyond the code above, i know nothing about powerbuilder. :-( sorry ...

txjump
[ponytails2]
 
Make sure you are moving exactly the right amount of memory around. It is essential that you do not attempt to move too much (more than is there) otherwise you will get the errors you are experiencing. What type of data are you moving around - Unicode strings or Ansi strings? Pointers to values? Are you freeing up system assigned buffers (i.e. NetAPI buffers) - if any? There are a number of API calls which will get you the length of strings - see the example code above.

Cheers.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top