• Welcome to Powerbasic Museum 2020-B.
 

Difference between a PB Type and a C++ struct?

Started by Heinz Grandjean, September 19, 2013, 11:27:37 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Heinz Grandjean

On my way into the "C++ heaven" (oh god) I ran into a problem:
I tried to send a PB Type from a PB-program as an argument to an export-function inside a  C++ DLL. 
So far I got it working, but access to the members inside the C++ Dll was always  postponed.
I could overcome this by doing the following:

Normal practice in PB:

TYPE MyNewType
     Var1  AS LONG
     Var2  AS WORD
     Var3  AS BYTE
END TYPE 


Adding a dummy-member in C++
struct MyNewStruct
{
signed int     dummy; //added !!!!
signed int     Var1;
        short int      Var2;
unsigned _int8 Var3;
};


This solves the problem for the moment, but it is not a correct solution???

Thanks for help,
Heinz Grandjean


Patrice Terrier

What about this

struct MyNewType {
    LONG var1;
    WORD var2;
    BYTE var3;
};
Patrice Terrier
GDImage (advanced graphic addon)
http://www.zapsolution.com

Kev Peel

You need to set the structure packing value. PowerBASIC uses 1-byte packing by default and the C dialects can vary (4 bytes for SDK and MS VC++).

If using Microsoft VC++, use the following code:

#pragma pack (push)
#pragma pack(1)

// Structure defines

#pragma pack (pop)


That will force it to be identical to PB's TYPE/UNIONs

Frederick J. Harris

Because of this, it has become my practice to attempt, when I create my PowerBASIC UDTs, to create them in multiples of 8 or 16 bytes.   To achieve this I'll often put dummy fillers at the end, just as shown above.

Note that a type such as this will work without problems in both C and PowerBASIC ...


struct MyStruct
{
  short int iSp;    // 2 bytes
  short int iDbh;  // 2 bytes
  short int iHt;     // 2 bytes
  short int iCull;  // 2 bytes
};



Type Tree
  iSp As Integer
  iDbh As Integer
  iHt As Integer
  iCull As Integer
End Type


... because both are 8 bytes in size, which is some sort of memory granulation boundary.

I believe there are some obscure compiler command line switches in C/C++ that can control this, but I've personally never fooled with them.

To be perfectly honest, padding structs or types with extra dummy variables can become useful for when application requirements change and extra variables are needed.  Especially when the UDTs are used for templates of file storage into records.

Frederick J. Harris

#4
Also, if you use C's sizeof operator on a struct which, for example, contains a four byte int, a 2 byte word, and a one byte char, the sizeof operator will return 8 bytes - not 7.  I'm going to check that out quick .../ hold on ...

Yep.


#include <cstdio>

struct MyStruct
{
int       iInteger;        // 4
short int iShortInt;   //  2
char      c;                 // 1
};

int main()
{
printf("sizeof(MyStruct) = %d\n",sizeof(MyStruct));
getchar();

return 0;
}

//sizeof(MyStruct) = 8

José Roca

PB did choose the VB path regarding padding. This is inconvenient when working with C++ libraries. Many of the TYPEs in the official PB includes are misaligned. I checked all the ones in my headers with the size given by Visual C and added filler members to keep the correct alignment. The problem is becoming more noticeable since the coming of 64-bit. With 32-bit, the norm was to use 4 bytes alignment, but many of the new APIs use QUADs and 8 bytes alignment.

Heinz Grandjean

Thank you for the replies.

Like Frederick Harris I tested the sizeof in C++.
When I use Kev Peels advice: #pragma pack(1) , I get 7!
Without that I get 8, like Frederick Harris has demonstrated.
But there is no progress to the problem, sorry.
When I want to see Var1 (set in PB to 15, I get a value of  1637928).
This seems to me like a sort of internal header or a pointer or anything in that direction.
When I want to see Var2 then I get Var1 and so on.

The complete PB test routine:

#COMPILE EXE
#DIM ALL

TYPE MyNewType
     Var1  AS LONG
     Var2  AS WORD
     Var3  AS BYTE
END TYPE

DECLARE FUNCTION myTestFunction CDECL LIB "D:\ESTWGJ_Quell\C++\Betrieb_DLL\Debug\Betrieb_Dll.dll" ALIAS "Testfunction" (MyNewType)  AS LONG

FUNCTION PBMAIN () AS LONG
'**************************

LOCAL MyNewT AS MyNewType
MyNewT.Var1 = 15
MyNewT.Var2 = 19
MyNewT.Var3 = 255

?STR$( myTestFunction(MyNewT))

END FUNCTION                       


The Counterpart in C++8; I use MS Visual Studio Express 2010

Definition of struct:

#pragma pack (push)
#pragma pack(1)

struct MyNewStruct
{
    signed int     Var1;
    short int      Var2;
    unsigned _int8 Var3;
};
#pragma pack (pop)


declare:

extern "C"
{
      __declspec(dllexport) long Testfunction(MyNewStruct);
}


cpp:

//Betrieb_DLL_Main.cpp

#include <stdio.h>
#include <windows.h>
#include "D:\ESTWGJ_Quell\C++\Header\Dat_Datenheader.h"
#include "Betrieb_Header.h"

long Testfunction (MyNewStruct newT)
{
return newT.Var1;
}



By the way, the C++ compiler doesn't allow "word or byte".

Thanks again
Heinz Grandjean

Kev Peel

It looks correct to me the correct size is 7 (4+2+1).

IMO the full code was not provided so I am thinking the struct is not being initialized to zero and you are seeing a random value. Try memset() after declaring the struct..

Charles Pegge

How does C++ handle arrays of structs?

For instance the 24 bit RGB pixels in a BMP.

Frederick J. Harris

Quote
How does C++ handle arrays of structs?

Just off the top of my head without testing a specific instance, my guess Charles would be that an odd sized struct, e.g., the seven byte one or your three byte example, would get bumped up to the nearest 4 byte boundary.  And the offset of each instance of the struct in memory would be sizeof(struct) bytes from the initial allocation address.  So I'm guessing if you had a three byte struct, i.e., three chars, each struct instance would still occupy four bytes.  Should be easy to test, but that's where I'd put my money. 

Heinz Grandjean

Well, I maybe found it.
You have to use pointers in the C++ DLL.


__declspec(dllexport) long Testfunction(MyNewStruct*);

long Testfunction (MyNewStruct* newT)
{
MyNewStruct newT1 = *newT;
return newT1.Var2;
}


I don't know this to be the ultimate solution, but now it does the job!
I even can remove the #pragma pack...
Thank you for your participation.
Heinz Grandjean

Frederick J. Harris

#11
Well, if this is your PowerBASIC declare ...


DECLARE FUNCTION myTestFunction CDECL LIB "D:\ESTWGJ_Quell\C++\Betrieb_DLL\Debug\Betrieb_Dll.dll" ALIAS "Testfunction" (MyNewType)  AS LONG


... then you are passing the address of an instance of MyNewType. 

Heinz Grandjean

It also works with:

DECLARE FUNCTION myTestFunction CDECL LIB "D:\ESTWGJ_Quell\C++\Betrieb_DLL\Debug\Betrieb_Dll.dll" ALIAS "Testfunction" (BYVAL MyNewType PTR)  AS LONG               

No difference as far as I can oversee it.
Heinz Grandjean

Frederick J. Harris

Here's my attempt to do it ...

In C++


#include <cstdio>

struct MyNewType
{
int var1;
short var2;
unsigned char var3;
};

extern "C" int __declspec(dllexport) MyTestFunction(MyNewType* mnt)
{
printf("mnt->var1=%d\n",mnt->var1);
printf("mnt->var2=%d\n",mnt->var2);
printf("mnt->var3=%d\n",mnt->var3);

return 1;
}


And PB CC Client ...


#Compile Exe
#Dim All

Type MyNewType
  Var1  AS LONG
  Var2  AS WORD
  Var3  AS BYTE
End Type

DECLARE FUNCTION MyTestFunction CDECL LIB "Heinz1.dll" ALIAS "MyTestFunction" (MyNewType)  AS LONG

Function PBMain() As Long
  Local MyType As MyNewType

  MyType.var1=15
  MyType.var2=19
  MyType.var3=255
  Call MyTestFunction(MyType)
  Waitkey$

  PBMain=0
End Function

' Output
' ===========
' mnt->var1=15
' mnt->var2=19
' mnt->var3=255
                   

Frederick J. Harris

#14
So what is going on here in your last example Heinz is that within TestFunction the passed in pointer to a MyNewStruct is being used to initialize a new 2nd local instance of a MyNewStruct with the one passed in through the parameter...


__declspec(dllexport) long Testfunction(MyNewStruct*);

long Testfunction (MyNewStruct* newT)
{
MyNewStruct newT1 = *newT;  // initialize newT1 with what is stored at newT, i.e., *newT
return newT1.Var2;
}


And being that newT1 is a local instance rather than a pointer to a local instance, one can use the dot operator to return the MyNewStruct::Var2 member.

However, by using the '->' pointer notation as seen in my example above, you can directly reference the MyNewStruct::Var2 member, i.e.,

return newT->Var2;