Theo's Forum

IT-Consultant: Frederick J. Harris => Discussion => Topic started by: James C. Fuller on January 26, 2009, 12:43:17 AM

Title: c++ client PowerBASIC Server
Post by: James C. Fuller on January 26, 2009, 12:43:17 AM
Fred,
  I decided to move to discussion as it really has no bearing on your COM Memory exploration.
I'm just experimenting and have a very poor knowledge of c++ so I thought I'd put this code here for you to critique.
I too have been reading (and reading and reading and....)Inside COM. I thought your examples looked a bit familiar. ;D
I can't believe all the c++ code needed to create a server!
I did get the client to compile using Code::Blocks and gcc 3.4.5. I had to add liboleaut32.a and libuuid.a to the Build Options
I read that gcc's COM support is not that hot??
I much prefer RadASM with BCC++ 5.5.
Here's another example of a c++ client PowerBASIC server. Note the use of my PROPERTY Macros.

James

Edit: Had to change a couple UNIT to DWORD so gcc would compile.

c++ Client
Code: [Select]
#include <windows.h>
#include <oleauto.h>     
#include <stdio.h>

static const CLSID CLSID_cDisplayOpenFileGuid = {0x1b0f486f, 0x9cca, 0x47ba,{ 0xa3, 0x38, 0x8f, 0x71, 0x46, 0xb7, 0x3c, 0x26}};
static const IID IID_iDisplayOpenFileGuid = {0xb72cdd37, 0x5349, 0x46b7,{ 0x8a, 0x27, 0x59, 0x2d, 0x23, 0xb3, 0x4f, 0xe3}};

HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);

interface iDisplayOpenFile : IUnknown
{
virtual void __stdcall put_Folder(BSTR);
virtual void __stdcall put_Filter(BSTR);
virtual void __stdcall put_hParent(DWORD);
virtual void __stdcall put_DflExt(BSTR);
virtual void __stdcall put_Start(BSTR);
virtual void __stdcall put_Title(BSTR);
virtual void __stdcall put_Flags(DWORD);
virtual BSTR __stdcall GetName();
};

int main(void)
{
  iDisplayOpenFile* pDof=NULL;
  HRESULT hr;
  IClassFactory*  pCF=NULL;
  HMODULE         hDll=NULL;
  BSTR str1;
// change here for a starting Folder 
  char stemp[]="D:\\Borland\\BCC55\\Include\\Sys";
 
//Change Path Here

  hDll=LoadLibrary("D:\\ComTutorial1A\\Server\\PbSource\\PBCTEST03.DLL");
 
  ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");

  hr=ptrDllGetClassObject(CLSID_cDisplayOpenFileGuid,IID_IClassFactory,(void**)&pCF);
  if(SUCCEEDED(hr))
  {

    pCF->CreateInstance(NULL,IID_iDisplayOpenFileGuid,(void**)&pDof);
    // Release Class Factory
    pCF->Release();
    // Create a BSTR using char stemp
    str1 = SysAllocStringByteLen(stemp,strlen(stemp));
    // Set the starting Folder
    pDof->put_Folder(str1);
    // Free up the string
    SysFreeString(str1);
    //Show OpenFile Dialog and retrieve Selected Files
    str1 = pDof->GetName();
    printf("str1 = %s\n",str1);
    // Release pDof
    hr=pDof->Release();
  }
FreeLibrary(hDll);
 
  getchar();
  return 0;

}   



PowerBASIC Server
Code: [Select]
'SED_PBWIN
#COMPILE DLL "PBCTEST03.DLL"
#DIM ALL
#COM TLIB ON
#INCLUDE ONCE "WIN32API.INC"
MACRO PropGet(PropName,PropType)=PROPERTY GET PropName() AS PropType:PROPERTY=PropName:END PROPERTY
MACRO PropSet(PropName,PropType)=PROPERTY SET PropName(BYVAL param AS PropType):PropName=param:END PROPERTY


$cDisplayOpenFileGuid = GUID$("{1B0F486F-9CCA-47ba-A338-8F7146B73C26}")
$iDisplayOpenFileGuid = GUID$("{B72CDD37-5349-46b7-8A27-592D23B34FE3}")
'GUID's create with GUIDGEN
'// {1B0F486F-9CCA-47ba-A338-8F7146B73C26}
'DEFINE_GUID(<<name>>,
'0x1b0f486f, 0x9cca, 0x47ba, 0xa3, 0x38, 0x8f, 0x71, 0x46, 0xb7, 0x3c, 0x26);
'// {B72CDD37-5349-46b7-8A27-592D23B34FE3}
'DEFINE_GUID(<<name>>,
'0xb72cdd37, 0x5349, 0x46b7, 0x8a, 0x27, 0x59, 0x2d, 0x23, 0xb3, 0x4f, 0xe3);

CLASS cDisplayOpenFile $cDisplayOpenFileGuid AS COM
    INSTANCE sFileName,Folder,Filter,Start,DflExt,Title AS STRING
    INSTANCE hParent,Flags AS LONG

    CLASS METHOD CREATE
        Filter = CHR$("All Files",0,"*.*",0)
        Flags = %OFN_EXPLORER OR %OFN_FILEMUSTEXIST
        Title = "Select File"
        Start = ""
        Folder = CURDIR$
STDOUT "CREATE"
    END METHOD

    INTERFACE iDisplayOpenFile $iDisplayOpenFileGuid : INHERIT IUNKNOWN

PropSet(Folder,STRING)
        PropSet(Filter,STRING)
        PropSet(hParent,DWORD)
        PropSet(DflExt,STRING)
        PropSet(Start,STRING)
        PropSet(Title,STRING)
        PropSet(Flags,DWORD)
        METHOD GetName() AS STRING
            DISPLAY  OPENFILE hParent,,,Title,Folder,Filter,Start,DflExt,Flags TO sFileName
            METHOD = sFileName
        END METHOD
    END INTERFACE
END CLASS


'==============================================================================
'MCM code
'------------------------------------------------------------------------------
 FUNCTION STDOUT (Z AS STRING) AS LONG
 ' returns TRUE (non-zero) on success

   LOCAL hStdOut AS LONG, nCharsWritten AS LONG
   LOCAL w AS STRING


   hStdOut      = GetStdHandle (%STD_OUTPUT_HANDLE)
   IF hSTdOut   = -1&  or hStdOut = 0&  THEN     ' invalid handle value, coded in line to avoid
                                                 ' casting differences in Win32API.INC
                                                 ' %NULL test added for Win/XP
     AllocConsole
     hStdOut  = GetStdHandle (%STD_OUTPUT_HANDLE)
   END IF
   w = Z & $CRLF
   FUNCTION = WriteFile(hStdOut, BYVAL STRPTR(W), LEN(W),  nCharsWritten, BYVAL %NULL)


 END FUNCTION
'==============================================================================
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 26, 2009, 05:17:43 PM
Hi James!

     I'm stuck on the top command in the dll - '#COM TLIB ON'.  My compiler doesn't seem to like that and I don't understand it either even though I've looked through help.  What does that command do and how should I set up my environment to get that to compile?

     Oh, by the way.  Here is the thought or observation that just more or less forced itself upon me on Friday night.  Actually, I had noted it before but tried to forget it do to its unpleasantness.  Take a look at any of the tabular outputs from the many programs I presented that dumps memory as it iterates through the VTables printing function addresses, and take note of the fact that the addresses of QueryInterface(), AddRef() and Release() in each VTable (either I_X or I_Y) don't point to the single implementation of these functions in class CA!  Indeed, when any of these functions get called through the addresses in the respective VTable, we get an output message that the single implementation of that function was called, even though the numbers in the VTable don't match!

     Do you have a plausible explanation for this?  I really believe its a C++ issue rather than a PowerBASIC or COM issue.  Dale Rogerson kind or skirts around the issue in chapter 3 of his book Inside COM when he discusses QueryInterface() and casting with reference to Multiple Inheritance. 

     So what I'm saying is that I think the whole issue concerning memory layout would be 'tied neatly with a red bow' in my mind, in other words would set a good bit better with me, if in the function pointer slots of each VTable for the IUnknown functions the numbers would point to a single implementation of these functions, because, after all, even though many VTables are possible, IUnknown only has one implementation.
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 26, 2009, 06:29:27 PM
Hi Again James!

     I've been a bit remiss in following up on your posts/code so I decided to start at the top.  The 1st one worked fine, but I'm getting crashes on the one where you created the dll with PowerBASIC and set your name 'James' to the s1 variable in the PB Constructor.  Its this one...

#COMPILE DLL "PBCTEST01.DLL"   

     When I run the C++ program I get as far as

     hr=piTest01->S1_Get(&str1);

     ...and then a crash.  Here is the last output I get...

Code: [Select]
ptrDllGetClassObject = 9983009
Constructor Called #1
Called Fx1 iNum = 25
Called Fx2 iNum = 50
Called iNum2_Get = 17
iNum2 = 17
Called iNum2_Set = 22
Called iNum2_Get = 22
iNum2 = 22

     So I expect there may be something wrong there.  Is that running to a successful completion for you?

     Also, I noted in the PB Dll code that you were passing the String Method parameters Byref...

Code: [Select]
METHOD S1_Get(Param AS STRING)
      Param = S1
END METHOD
METHOD S1_Set(Param AS STRING)
      S1 = Param
      STDOUT "Called S1_Set -> "+ S1
END METHOD

     I thought I read somewhere in the PB Docs that string parameters to methods should be Byval, or am I wrong there or doesn't it matter?  I don't know that's why I'm asking?  I remember ruminating on the issue for a bit when the compiler 1st came out, and ended up thinking Byval would be the way to do it because the instance of a class needs to have its own copy of the whole string, not a reference to one somewhere else? 

     I really need to learn more about the BSTR type.  I used it some on the work I was doing a month or two ago with Typelibs, but that was my 1st use of it.       
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 26, 2009, 07:21:45 PM
Fred,
  I confess I have not examined your memory tests too close. That goes a little deeper than I care to venture.
I'm having a hard enough time getting a superficial grasp on COM to the point so it works  :)


Ok, to make sure we are on the same page as regard to my examples. The ones in the other thread were experiments and I may have changed them since.

Re: #COM TLIB ON
  It should compile with 9.00? It's not needed.

I changed to BYVAL on all Property Sets and will continue to use it from now on.

I used code::Blocks with gcc(g++) 3.4.5 from the mingw setup to compile. I noticed the above OpenFile code was only
a 6k exe with gcc where the bc++ was 53k ??? There may be debug code in the bcc one. I'm not all that familiar with RadASM.


I think the void should probably be HRESULT now that I'm using properties in the server??

I am not really using BSTR as a true BSTR just as a receptacle that will compile and work with PB strings.
True BSTR are unicode.


The thing I like is using PB with my PropGet/Set's and having the code accessable rather easily from c++.
I did try to get it to work with Peles c but very little luck in that department.

James


c++ client

Code: [Select]
// Compiled with g++ 3.4.5
#include <windows.h>
#include <oleauto.h>
#include <stdio.h>
static const CLSID CLSID_cTest01   = {0xeedfbd9e, 0x8cd7, 0x43ae,{0x8b, 0x58, 0x66, 0x76, 0xee, 0x2b, 0x1e, 0x72}};
static const IID   IID_iTest01    = {0x7e58490e, 0x8445, 0x4b61,{0x9c, 0xed, 0x61, 0x18, 0x46, 0xf8, 0x35, 0xd7}};



HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);

interface iTest1 : IUnknown
{
 virtual HRESULT __stdcall Fx1(int)=0;
 virtual HRESULT __stdcall Fx2(int)=0;
 virtual long __stdcall get_iNum2();
 virtual void __stdcall put_iNum2(int);
 virtual BSTR __stdcall get_S1();
 virtual void __stdcall put_S1(BSTR);
};




int main(void)
{
  iTest1* piTest01=NULL;
  HRESULT hr;
  IClassFactory*  pCF=NULL;
  HMODULE         hDll=NULL;

  int iNum2,iNum3=9;
  BSTR str1;
  char stemp[]="Fuller";

//Change Path Here

  hDll=LoadLibrary("D:\\ComTutorial1A\\Server\\PbSource\\PBCTEST01.DLL");

  ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");

  hr=ptrDllGetClassObject(CLSID_cTest01,IID_IClassFactory,(void**)&pCF);
  if(SUCCEEDED(hr))
  {
    pCF->CreateInstance(NULL,IID_iTest01,(void**)&piTest01);
    // Release Class Factory
    pCF->Release();

    hr=piTest01->Fx1(25);
    hr=piTest01->Fx2(50);
    iNum2 = piTest01->get_iNum2();
    printf("iNum2 = %u\n",iNum2);
    piTest01->put_iNum2(iNum3);
    iNum2 = piTest01->get_iNum2();
    printf("iNum2 = %u\n",iNum2);
    str1 = piTest01->get_S1();
    printf("str1 = %s\n",str1);
    str1 = SysAllocStringByteLen(stemp,strlen(stemp));
    piTest01->put_S1(str1);
    SysFreeString(str1);
    str1 = piTest01->get_S1();
    printf("str1 = %s\n",str1);

// Added 01-27-09
// Need to free all string memory
    SysFreeString(str1);
    hr=piTest01->Release();

  }


 FreeLibrary(hDll);

 getchar();
 return 0;

}




PbServer
Code: [Select]
'SED_PBWIN
#COMPILE DLL "PBCTEST01.DLL"
#DIM ALL
#INCLUDE ONCE "WIN32API.INC"
$CLSID_cTest01 =GUID$("{EEDFBD9E-8CD7-43ae-8B58-6676EE2B1E72}")
$IID_iTest1 = GUID$("{7E58490E-8445-4b61-9CED-611846F835D7}")
MACRO PropGet(PropName,PropType)=PROPERTY GET PropName() AS PropType:PROPERTY=PropName:END PROPERTY
MACRO PropSet(PropName,PropType)=PROPERTY SET PropName(BYVAL param AS PropType):PropName=param:END PROPERTY


CLASS cTest01 $CLSID_cTest01 AS COM
INSTANCE S1 AS STRING, iNum2 AS LONG
CLASS METHOD CREATE
STDOUT "Constructor Called #1"
iNum2 = 17
S1 = "James"
END METHOD
CLASS METHOD DESTROY
STDOUT "Destructor Called"
END METHOD
INTERFACE iTest1 $IID_iTest1 : INHERIT IUNKNOWN
METHOD Fx1(BYVAL iNum AS LONG)
STDOUT "Called Fx1 iNum = " + FORMAT$(iNum)
END METHOD

METHOD Fx2(BYVAL iNum AS LONG)
STDOUT "Called Fx2 iNum = " + FORMAT$(iNum)
END METHOD

PropGet(iNum2,LONG)
PropSet(iNum2,LONG)


PropGet(S1,STRING)
PropSet(S1,STRING)


END INTERFACE
END CLASS



'==============================================================================
'MCM code
'------------------------------------------------------------------------------
 FUNCTION STDOUT (Z AS STRING) AS LONG
 ' returns TRUE (non-zero) on success

   LOCAL hStdOut AS LONG, nCharsWritten AS LONG
   LOCAL w AS STRING


   hStdOut      = GetStdHandle (%STD_OUTPUT_HANDLE)
   IF hSTdOut   = -1&  or hStdOut = 0&  THEN     ' invalid handle value, coded in line to avoid
                                                 ' casting differences in Win32API.INC
                                                 ' %NULL test added for Win/XP
     AllocConsole
     hStdOut  = GetStdHandle (%STD_OUTPUT_HANDLE)
   END IF
   w = Z & $CRLF
   FUNCTION = WriteFile(hStdOut, BYVAL STRPTR(W), LEN(W),  nCharsWritten, BYVAL %NULL)


 END FUNCTION
'==============================================================================
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 26, 2009, 08:07:57 PM
Good job James!  I got it to work and will study your code.  I'm just really looking close at the whole BSTR, OLECHAR thing now.  Certainly strings are important as it would be hard for me to write a program without them!

The thing with the memory is one of those things I'm going to have to put aside until I gather more knowledge on C++ because I suspect the issue goes right to the heart of what the C++ compiler does to make multiple inheritance work.  I've a book on it by Stanley Lippmann named 'The C++ Object Model' but its unfortunately over my head as of yet.  I skimmed through one of the chapters yesterday that seems to be discussing the issue I'm raising, and its unbelievably complex.  It seems there are more tables in memory containing other information beyond what I found, and I'm rather sure within those other tables the final resolution is made that causes the single implementation of the IUnknown functions to be called.

I watch the sizes of executables very close too, and was astounded to see how small the CodeBlocks generated executables were.  That is the 1st instance where I actually found a C or C++ compiler to generate exes smaller than PowerBASIC's.  However, just a few weeks ago I downloaded and installed Microsoft's VC++ 9 (Visual Studio 2008) and its executables are about like CodeBlocks (or perhaps I should say the other way around).  So its my guess there is system code not being statically linked into the newer exes.  I'd be interested if anyone knew the details on this.

You had mentioned that you were astounded by the quantity of C++ code necessary to create a COM server.  If you are basing your comment on the fairly voluminous code bulk of my little test class CA, I can assure you it gets considerably worse than that as that example - while a valid COM server - lacked the code to deal with creating a type lib.  In C++ one does that by 1sr creating a *.idl file and compiling that with midl (Microsoft Interface Definition Language Compiler).  That generates several more files that have to be compiled and linked into the dll.  And if you want to see some nasty looking C code you'll find it in those files as they contain  a lot of proxy-stubb marshalling code to allow the COM server to function transparently across a network.

The reason I know as much as I do about that end of it is I spent a lot of time in Guy and Henry Eddon's book "Inside Distributed COM' (which I found three or four years ago in a used bookstore for about $2.00 - CD and all ), and its a pretty good book.  They start out in that book with IDL, as opposed to Dale Rogerson's book which doesn't introduce it to near the end.

Right at the moment I'm trying to work through "Developer's Workshop to COM and ATL 3.0" by Andrew W. Troelson.  Its pretty much consuming my time.
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 26, 2009, 08:33:56 PM
Hi Jim!

     With the removal of that #COM metastatement I compiled PBCTest03.bas to the dll, made the C++ project, and ran it OK!  You're making fast progress.  I'm getting to like that business of using GetProcAddress on the dll instead of registering it.  Saves cluttering up the registry -  cuz I know I'll never find all the junk I'd end up putting in there with my tests.

     One thing about that CodeBlocks.  There is a neat Linux version of it I installed on all my Laptops (I set up all my laptops as dual boots with Linux/Windows).  Unfortunately, I hadn't gotten beyond Hello, World! type stuff in testing out CodeBlocks on Linux, but the IDE looks and works exactly the same as on Windows.  There is even a template in there to do GTK+ programs but I hadn't gotten to that yet - COM is eating me alive.  I had been working with a lot of Linux programming for some time, but always doing command line compiling.  CodeBlocks was the first IDE I tried.
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 27, 2009, 02:36:32 PM
I was missing a  SysFreeString in the c++ client code.
Hay your the c++ expert here and are supposed to catch these things ;D

James
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 28, 2009, 06:31:29 PM
Hello James!

     I've been 'dark' for the past 48 hours or so as a result of a slight medical emergency.  That's why I havn't been responding.  I had a bad 'boil' or facial absess that I had to get taken care of.  It seems better now so hopefully I can get some work done!

Fred
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 29, 2009, 12:11:22 PM
Fred,
  Sorry to hear about your malady. I went through a few myself and know the distraction the pain can cause.
Mine were never facial though and one of the worst was at the other end. Had trouble sitting for quite awhile.

I know you are investigating the memory aspects of COM components where my focus is on developing different programming
 language code to access MY created PowerBASIC COM servers. This way I can supply the access code in the native code.
For starters I am attacking c and c++ (of which my knowledge is limited) but there is a wealth of info out there and here.
All code will use dynamic loading and instantiation.

Is there another way to code this:
Code: [Select]

HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);

ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");
hr=ptrDllGetClassObject(CLSID_cTest01,IID_IClassFactory,(void**)&pCF);


If this is the way it's normally done then ok, but I have a hard time following exactly what it means??

I don't want to use MFC,STL,ATL..... or any of the other ABCDEFG's

possibly with a typedef ???

My c++ is VERY weak. I am learning, but now basically just throwing things together I find in my surfing.

James

Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 29, 2009, 12:55:24 PM
I did change this so it would compile under pelles and it also works with bcc and g++.

HRESULT (__stdcall* ptrDllGetClassObject) (const CLSID&, const IID&, void**);
To
HRESULT (__stdcall* ptrDllGetClassObject) (REFCLSID,REFIID, void**);

James

Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 29, 2009, 05:56:57 PM
Yes James, there is another way to do it with typedefs.  I had thought of posting it several days back and even explaining in brutal detail how it works, but I know your knowledge of macro expansion & substitution is very advanced, and as for others this is afterall a PowerBASIC oriented forum, so I refrained from going into the details.

I have a little demo that describes typedefs pretty good.  Give me a few minutes to dig it up.
Title: Re: c++ client PowerBASIC Server
Post by: Edwin Knoppert on January 29, 2009, 07:18:08 PM
>True BSTR are unicode.
Can be both
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 29, 2009, 07:25:31 PM
Jim,

     Here is a little demonstration that should explain the use of typedef in helping with C's ugly function pointer syntax.  Believe me, it confused me for years until I finally 'got it'.  The funny thing about typedefs is that nowhere can one find a good explanation of how they work with regard to function pointer syntax.  So I believe the little explanation I will give below is something that most folks don't really understand.

     I understand you have CodeBlocks.  This demo should work with whatever you have - I expect Pelles C too because I don't believe there is anything intrinsic to C++ in it.

     Start by creating a dll project in whatever environment/compiler you are using.  We'll make a simple server dll that just uses the printf function to write to the console window some string passed in to the dll.  We'll call the single function Prt like this...

Code: [Select]
//dllServer.cpp
#include <windows.h>
#include <stdio.h>

extern "C" __declspec(dllexport) void Prt(char* msg)
{
 printf("%s\n",msg);
}


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
 switch (fdwReason)
 {
  case DLL_PROCESS_ATTACH:
    // attach to process
    // return FALSE to fail DLL load
    break;
  case DLL_PROCESS_DETACH:
    // detach from process
    break;
  case DLL_THREAD_ATTACH:
    // attach to thread
    break;
  case DLL_THREAD_DETACH:
    // detach from thread
    break;
 }

 return TRUE; // succesful
}

Compile that into a dll.  Note that all it does is contain one function named "Prt" that returns nothing (void), and takes a char pointer as a single parameter.  The calling convention in C or C++ is __cdecl unless changed to something else such as __stdcall.

Now, lets create a client program named perhaps dllHost.cpp where we'll load the dll with GetProcAddress and call the function both with and without using typedefs to clean it up.

Here is a program without using typedefs.  Note that the __cdecl isn't really necessary (because its standard), but I thought it worthwhile to include it for the reason that the function pointer syntax necessary to call the IUnknown functions needs __stdcall, and with this simple demo the placement of __cdecl would hopefully show how the substitution is done.  Here is a Hello World program that calls the dll we just made...

Code: [Select]
#include <windows.h>                //How to get a grasp of function pointer casting
#include <stdlib.h>                 //in C/C++ with and without typedefs.  It actually
                                    //isn't magic, however, its pretty close!
int main(void)
{
 char szMsg[]="Hello, World!";      //array of characters assigned to szMsg
 void (*pFn)(char*);                //C/C++ function pointer syntax for function
 HINSTANCE hIns;                    //that returns void and takes 1 char pointer param

 hIns=LoadLibrary("dllServer.dll");
 if(hIns)
 {
    pFn=(void (__cdecl*)(char*))GetProcAddress(hIns,"Prt");  //In other words,
    pFn(szMsg);                                              //void (__cdecl*)(char*) within
    FreeLibrary(hIns);                                       //an enclosing pair of braces and
 }                                                           //right before the GetProcAddress()
 system("PAUSE");       //call, tells the C compiler to cast the return from GetProcAddress to
                        //a pointer to a function with no return (void) that takes one
 return 0;              //character pointer parameter, and assign this address to pFn.  Note
}                       //that pFn has to match this mess, i.e., void (__cdecl*)(char*).
*/

/*
Hello, World!
Press any key to continue . . .

Process returned 0 (0x0)   execution time : 2.875 s
Press any key to continue.

*/

Now, here is the same program using typedef for a __cdecl function that returns void and takes a char * parameter...

Code: [Select]
#include <windows.h>                   //Here is an example using the
#include <stdlib.h>                    //same dll but calling the function
typedef void (__cdecl* FNPTR)(char*);  //address with the help of typedef

int main(void)                         //to clean up the messy C function
{                                      //pointer syntax.  Note that all the
 char szMsg[]="Hello, World!";         //C/C++ keywords/symbols such as 'void',
 HINSTANCE hIns;                       //'*', 'char', '__cdecl, etc, are
 FNPTR pFn;                            //substituted wherever the FNPTR is
 
 hIns=LoadLibrary("dllServer.dll");    //found in the code.  That way,
 if(hIns)                              //(FNPTR)GetProcAddress(hIns,"Prt")
 {                                         //becomes... pFn=(void (__cdecl*)(char*))GetProcAddress(hIns,"Prt")
    pFn=(FNPTR)GetProcAddress(hIns,"Prt"); //In still other words, pFn is a pointer to a function that
    pFn(szMsg);                        //returns nothing, uses standard C declension call stack setup, and takes
    FreeLibrary(hIns);                 //one char pointer parameter.  See below.
 }
 system("PAUSE");

 return 0;
}


/*=====Output========================================
Hello, World!
Press any key to continue . . .

Process returned 0 (0x0)   execution time : 3.344 s
Press any key to continue.
*/

/*
Now, here's an explanation of how to rationalize it or think about it.

(FNPTR)
(void (__cdecl* )(char*))

typedef void (__cdecl* FNPTR)(char*);  //typedef statement
void (__cdecl*)(char*)                 //remove everything from typedef statement that isn't C/C++ symbol or keyword
(FNPTR)                                //from right in front of GetProcAddress().  will replace 'FNPTR' with text 'void (__cdecl*)(char*)'
()                                     //remove term 'FNPTR'
(  void (__cdecl*)(char*)   )          //insert text string from typedef inside remaining quotes
(void (__cdecl*)(char*))               //Walah, Shizamm, Ausgezeichnet, Whatever!

(              void                         (__cdecl*)                              (char*)             )

                ^                                ^                                     ^

     Function returns nothing     Standard C Function Pointer Syntax     Function Takes a char* parameter

*/


Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 29, 2009, 07:51:28 PM
Thanks Fred that really helped.

James
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 29, 2009, 07:54:36 PM
And below is a version of the program I posted several days back when you had 1st asked about using LoadLibrary() and
GetProcAddress() on the COM dll.  In it I made a typedef for the ugly function pointer syntax.  With the help of the little sample programs above you should be able to figure it out.  In fact, it would be a good exercise to create typedefs for the QueryInterface(), AddRef() and Release() function calls too.  If you did that you would have a better understanding of all those crazy numbers in the tables and where they came from...

Code: [Select]
//CAClient8.cpp
#include <windows.h> //CAClient
#include <stdio.h>   //IID_IClassFactory={00000001-0000-0000-C000-000000000046};
const CLSID CLSID_CA ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04}}; //Class ID of Class CA, ie., Class A
const IID   IID_I_X  ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05}}; //Interface X
const IID   IID_I_Y  ={0x20000000,0x0000,0x0000,{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06}}; //Interface Y
const IID   IID_Junk ={0x12345678,0x9876,0x5432,{0x12,0x34,0x56,0x78,0x98,0x76,0x54,0x32}}; //Junk Number So QueryInterface() Fails

typedef HRESULT (__stdcall*LPDLLGETCLASSOBJECT)(const CLSID&, const IID&, void**);
        HRESULT (__stdcall* ptrQueryInterface)    (int, const IID&, void**);
        ULONG   (__stdcall* ptrAddRef)            (int);
        ULONG   (__stdcall* ptrRelease)           (int);
        void    (__stdcall* pIFn)                 (int,int);


interface I_X : IUnknown
{
 virtual HRESULT __stdcall Fx1(int)=0;
 virtual HRESULT __stdcall Fx2(int)=0;
};


interface I_Y : IUnknown
{
 virtual HRESULT __stdcall Fy1(int)=0;
 virtual HRESULT __stdcall Fy2(int)=0;
};


int main(void)
{
 LPDLLGETCLASSOBJECT  ptrDllGetClassObject=NULL;
 IClassFactory*       pCF=NULL;
 HMODULE              hDll=NULL;
 BOOL blnFree         =FALSE;
 IUnknown*            pIUnk=NULL;
 unsigned int*        pVTbl=0;
 unsigned int*        VTbl=0;
 HRESULT              hr;

 hDll=LoadLibrary("C:\\Code\\VStudio\\VC++6\\Projects\\COM\\ComObjects\\CA\\Release\\CA.dll");
 if(hDll)
 {
    //ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");
    ptrDllGetClassObject=(LPDLLGETCLASSOBJECT)GetProcAddress(hDll,"DllGetClassObject");   //<<<<< here's what you want
    printf("ptrDllGetClassObject = %u\n",ptrDllGetClassObject);
    hr=ptrDllGetClassObject(CLSID_CA,IID_IClassFactory,(void**)&pCF);
    if(SUCCEEDED(hr))
    {
       puts("Worked!  Miracles Never Cease!");
       pCF->CreateInstance(NULL,IID_IUnknown,(void**)&pVTbl);
       pCF->Release();
       printf("pVTbl   = %u\n",pVTbl);
       printf("\n");
       printf("&pVTbl[i]\t&VTbl[i]\tVTbl[i]\t\tFunction Call Through Pointer\n");
       printf("=============================================================================\n");
       for(unsigned int i=0;i<=1;i++)
       {
           VTbl=(unsigned int*)pVTbl[i];                                             //Call...
           printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[0],VTbl[0]);  
           ptrQueryInterface=(HRESULT(__stdcall*)(int, const IID&, void**)) VTbl[0];
           ptrQueryInterface((int)&pVTbl[i],IID_Junk,(void**)&pIUnk);                //QueryInterface()
           printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[1],VTbl[1]);                        
           ptrAddRef=(ULONG(__stdcall*)(int)) VTbl[1];
           ptrAddRef((int)&pVTbl[i]);                                                //AddRef()
           printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[2],VTbl[2]);                        
           ptrRelease=(ULONG(__stdcall*)(int)) VTbl[2];
           ptrRelease((int)&pVTbl[i]);                                               //Release()
           printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[3],VTbl[3]);                        
           pIFn=(void(__stdcall*)(int,int)) VTbl[3];
           pIFn((int)&pVTbl[i],i);                                                   //Fx1() / Fy1()    
           printf("%u\t%u\t%u\t",&pVTbl[i],&VTbl[4],VTbl[4]);                        
           pIFn=(void(__stdcall*)(int,int)) VTbl[4];
           pIFn((int)&pVTbl[i],i);                                                   //Fx2() / Fy2()  
           printf("\n");
       }
       ptrRelease=(ULONG(__stdcall*)(int)) VTbl[2];
       ptrRelease((int)&pVTbl[0]);
       getchar();
    }  
    blnFree=FreeLibrary(hDll);
    printf("blnFree=%u\n",blnFree);
 }
 
 return 0;
}
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 30, 2009, 01:52:49 PM
Fred,
This is code I know compiles with gcc (add oleaut32 and uuid for linking).
Code: [Select]
#include <windows.h>
#include <oleauto.h>
#include <stdio.h>
static const CLSID CLSID_cTest01   = {0xeedfbd9e, 0x8cd7, 0x43ae,{0x8b, 0x58, 0x66, 0x76, 0xee, 0x2b, 0x1e, 0x72}};
static const IID   IID_iTest01    = {0x7e58490e, 0x8445, 0x4b61,{0x9c, 0xed, 0x61, 0x18, 0x46, 0xf8, 0x35, 0xd7}};
HRESULT (__stdcall* ptrDllGetClassObject) (REFCLSID,REFIID, void**);
#undef  INTERFACE
#define INTERFACE   iTest
DECLARE_INTERFACE_ (INTERFACE, IUnknown)
{
STDMETHOD  (QueryInterface) (THIS_ REFIID, void **) PURE;
STDMETHOD_ (ULONG, AddRef) (THIS) PURE;
STDMETHOD_ (ULONG, Release) (THIS) PURE;
STDMETHOD  (Fx1) (THIS_ int) PURE;
STDMETHOD  (Fx2) (THIS_ int) PURE;
STDMETHOD_ (LONG,get_iNum2) (THIS) PURE;
STDMETHOD  (put_iNum2) (THIS_ int) PURE;
STDMETHOD_ (BSTR,get_S1) (THIS) PURE;
STDMETHOD  (put_S1) (THIS_ BSTR) PURE;
};
#define Test_QueryInterface(riid,ppvObject) piTest->lpVtbl->QueryInterface->(piTest,riid,ppvObject)
#define Test_AddRef piTest->lpVtbl->AddRef(piTest)
#define Test_Release piTest->lpVtbl->Release(piTest)
#define Test_Fx1(Val) piTest->lpVtbl->Fx1(piTest,Val)
#define Test_Fx2(Val) piTest->lpVtbl->Fx2(piTest,Val)
#define Test_get_iNum2 piTest->lpVtbl->get_iNum2(piTest)
#define Test_put_iNum2(Val) piTest->lpVtbl->put_iNum2(piTest,Val)
#define Test_get_S1 piTest->lpVtbl->get_S1(piTest)
#define Test_put_S1(Val) piTest->lpVtbl->put_S1(piTest,str1)
int main(void)
{
iTest* piTest=NULL;
HRESULT hr;
IClassFactory*  pCF=NULL;
HMODULE         hDll=NULL;
  int iNum2,iNum3=9;
  BSTR str1;
  char stemp[]="Fuller";

// Change Com Dll Location
hDll=LoadLibrary("D:/ComTutorial1A/Server/PbSource/PBCTEST01.DLL");

    if (hDll==NULL)
{
printf("Bad hDll");
getchar();
return 0;
}
// It appears we don't need this
// if (!CoInitialize(0))
// {
ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");

hr=ptrDllGetClassObject(&CLSID_cTest01,&IID_IClassFactory,(void**)&pCF);
if(SUCCEEDED(hr))
{
hr = pCF->lpVtbl->CreateInstance(pCF, 0, &IID_iTest01,(void**) &piTest);
pCF->lpVtbl->Release(pCF);
if(SUCCEEDED(hr))
    {
    Test_Fx1(25);
Test_Fx2(50);

iNum2 = Test_get_iNum2;
printf("iNum2 = %u\n",iNum2);

Test_put_iNum2(iNum3);
iNum2 = Test_get_iNum2;
  printf("iNum2 = %u\n",iNum2);

// Get String S1 contents
str1 = Test_get_S1;
  printf("str1 = %s\n",str1);

        // Free String Memory
SysFreeString(str1);

        // Allocate a new PB Type String
str1 = SysAllocStringByteLen(stemp,strlen(stemp));

//Set The Instance Variable S1
Test_put_S1(str1);

// Free our String Memory
    SysFreeString(str1);
// Get the new string contents
  str1 = Test_get_S1;
  printf("str1 = %s\n",str1);
// Once again free our string memory
SysFreeString(str1);
// release the object
Test_Release;
}
}
// CoUninitialize();
// }
FreeLibrary(hDll);
  getchar();
  return 0;
}


Is there another method to declare/use the guid's.
Code: [Select]
static const CLSID CLSID_cTest01   = {0xeedfbd9e, 0x8cd7, 0x43ae,{0x8b, 0x58, 0x66, 0x76, 0xee, 0x2b, 0x1e, 0x72}};
static const IID   IID_iTest01    = {0x7e58490e, 0x8445, 0x4b61,{0x9c, 0xed, 0x61, 0x18, 0x46, 0xf8, 0x35, 0xd7}};

the results from GUIDGEN need to be edited a bit to conform to this syntax.

I've attached the pb dll in case you want to test anything with above code.

I missed your last post yesterday so I'll study it today.

James


Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 30, 2009, 03:32:38 PM
Fred,
Never mind. I wrote a quick PBCC function to parse the PB GUID$ and create the c,c++ one.

James
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 30, 2009, 06:08:11 PM
Yes, there certainly are piles of macros in C/C++ COM.  Different programmers seem to use or not use macros.  I fall in the camp of not liking them too much.  I usually unravel them to see what they are hiding.  I just don't like to hide things.

The 'interface' word is a macro for struct, i.e.,

#define interface struct

Truth be told, interfaces could be defined equally well with the class, struct, or interface terms.  Without any macros my I_X interface looks like this...

Code: [Select]
interface I_X : IUnknown
{
 virtual HRESULT __stdcall Fx1(int)=0;
 virtual HRESULT __stdcall Fx2(int)=0;
};

A somewhat more COM compliant version would be...

Code: [Select]
interface I_X : IUnknown
{
 STDMETHOD(Fx1)(int n)=0;
 STDMETHOD(Fx2)(int n)=0;
};

and the macro STDMETHOD is defined like so...

#define STDMETHOD(method)   virtual HRESULT STDMETHODCALLTYPE method

and STDMETHODCALLTYPE is defined as __stdcall

The most COM compliant version that in my mind obfusicates things as much as possible is this...

Code: [Select]
DECLARE_INTERFACE_(I_X, IUnknown)
{
 STDMETHOD(Fx1)(int n)  PURE;
 STDMETHOD(Fx2)(int n)  PURE;
};

...where PURE is defined as '0'.

     I hadn't really given raw C much thought as a method of doing COM work because I see the absolutely overwhelming advantage of C++.  And that is coming from someone who is inclined in many ways to liking raw C.  Again, that gets back to the fact that when Microsoft's most talented developers were designing early COM 2 back around 1992-1993, they looked at the memory footprint being generated by most C++ compilers involving the virtual function table pointer - virtual function table setup, and saw that they could achieve their design goals by exactly emulating that setup.  When you try to do it in C as opposed to C++ you immediately find yourself in the predicament of looking at truely horrible syntax involving multiple levels of indirection with multiple occurances of the '->' in a line of code.  Up against that, the next step is to start creating macros to try to simplify away the ugliness.  Its for these reasons that I tend to avoid both C style syntax and macros in my work; preferring rather that my code only contain variables & functions I've created and bona fide C++ keywords. 

     Of course, to use C++ one must be comfortable with what it is doing behind the scenes.  This is certainly truer for some more than others.  For myself, I absolutely must know how something works, or I simply won't use it.  That's my justification for putting as much effort as I have into understanding the various pointers involved such as the pointer returned by PowerBASIC's ObjPtr() function, and the various VTable addresses.  To give you an idea of how bad pointer notations can become (and I think you are there yourself in looking at your code) take a quick look at this I just dug up.  Its was one of my very early attempts to unravel the memory setup of the I_X and I_Y interfaces we've been playing with...

Code: [Select]
#include <stdio.h>
#include <objbase.h>
typedef void (*FN)(void);

interface IX : IUnknown
{
 virtual void __stdcall Fx1()=0;
 virtual void __stdcall Fx2()=0;
};


interface IY : IUnknown
{
 virtual void __stdcall Fy1()=0;
 virtual void __stdcall Fy2()=0;
};


class CA : public IX, public IY
{
 public:
 virtual HRESULT __stdcall QueryInterface(const IID& iid, void** ppv)
 {
  puts("\nCalled QueryInterface()");
  return S_OK;
 }
 
 virtual ULONG   __stdcall AddRef()
 {
  puts("Called AddRef()");
  return 0;
 }
 
 virtual ULONG   __stdcall Release()
 {
  puts("Called Release()");
  return 0;
 }
 
 virtual void    __stdcall Fx1(){printf("Called Fx1()\n");}
 virtual void    __stdcall Fx2(){printf("Called Fx2()\n");}
 virtual void    __stdcall Fy1(){printf("Called Fy1()\n");}
 virtual void    __stdcall Fy2(){printf("Called Fy2()\n");}
};


int main(void)
{
 CA* pCA=0;
 FN  pFn=0;
 
 pCA=new CA;
 printf("sizeof(CA)            = %u\n",sizeof(CA));
 printf("pCA                   = %u\n",pCA);
 printf("*(int*)(&pCA)         = %u\n",*(int*)(&pCA));
 printf("*(int*)*(int*)(&pCA)  = %u\n",*(int*)*(int*)(&pCA));
 pFn=(FN)*((int*)*(int*)*(int*)(&pCA+0)+0);   //QueryInterface() @offset 0 in 1st vtable
 pFn();
 pFn=(FN)*((int*)*(int*)*(int*)(&pCA+0)+1);   //AddRef()         @offset 1 in 1st vtable
 pFn();
 pFn=(FN)*((int*)*(int*)*(int*)(&pCA+0)+2);   //Release()        @offset 2 in 1st vtable
 pFn();
 pFn=(FN)*((int*)*(int*)*(int*)(&pCA+0)+3);   //Fx1()            @offset 3 in 1st vtable
 pFn();
 pFn=(FN)*((int*)*(int*)*(int*)(&pCA+0)+4);   //Fx2()            @offset 4 in 1st vtable
 pFn();
 //here's where i'm stuck! this crashes hard! //QueryInterface() @offset 0 in 2nd vtable?????
 //pFn=(FN)*((int*)*(int*)*(int*)(&pCA+1)+0); 
 //pFn();                               
 delete pCA;
 getchar();
 
 return 0;
}

/*
sizeof(CA)            = 8
pCA                   = 4144816
*(int*)(&pCA)         = 4144816
*(int*)*(int*)(&pCA)  = 4224084

Called QueryInterface()
Called AddRef()
Called Release()
Called Fx1()
Called Fx2()
*/

     Don't know if I'm answering your questions, but I'm trying.  The past two days or so I've been looking real close at your original program to use PB to create the dll and C++ to call the functions.  I find that interesting, particularly regarding the BSTR usage.  Last night I tried to replace your 'SysAllocStringByteLen(stemp,strlen(stemp));' with various alternatives but to know avail.

     I need to learn more about BSTRs.  At this point all I can figure out that is happening there is that the ansi data is being 'crammed' into a type really designed for UNICODE wide characters.  And when PowerBASIC receives it in the dll it knows its ansi data and not UNICODE data, even though its being sent in a BSTR type. 

     Here is an excerpt from the Visual Studio 6 MSDN CD I have by Bruce McKinney of Microsoft entitled "Strings The OLE Way"

Quote
BSTR SysAllocStringByteLen(LPSTR sz, unsigned len);

Given a null-terminated ANSI string, allocates a new BSTR of the given length and copies up to that length of bytes from the string to the BSTR. The result is a BSTR with two ANSI characters crammed into each wide character. There is very little you could do with such a string, and therefore not much reason to use this function. It's there for string conversion operations such as Visual Basic's StrConv function. What you really want is a function that creates a BSTR from an ANSI string, but this isn't it (we'll write one later). The function works like SysAllocStringLen if you pass a null pointer or a length greater than the length of the input string.

     As you can see, he doesn't think much of this function.  But I expect you know a great deal more about this than I as I havn't found anyway to replace your use of this function in that program.  If I send a real wide character string in that BSTR parameter, all that gets printed out is the first letter of your last name, i.e., 'F', which is a sure indication PowerBASIC is anticipating ansi characters in that unicode string.  Crazy, if you ask me.  But, while BSTRs are supposedly unicode, the OLECHAR type is very close to the same thing, and that can be unicode or ansi, depending.  Any thoughts anyone?
   

Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 30, 2009, 06:18:28 PM
Just took another look at your code.  I see what you are doing now!  Have you looked at the PowerBASIC code of Jose's for his container control - OLECON.inc?  Its pretty awesome.  Anyway, Jose does that all the time, that is, creates a type/interface and puts function pointers to the various functions in the type.  He then uses CodePtr() to set the Dword or Dword Ptr members to the addresses of his functions.  Here is a quick excerpt from Jose's OLECON.inc

Code: [Select]
' ========================================================================================
' IUnknown interface
' ========================================================================================
TYPE OC_IUNKNOWN
   ' === IUnknown methods ================================================================
   pQueryInterface         AS DWORD          ' // QueryInterface method
   pAddRef                 AS DWORD          ' // AddRef method
   pRelease                AS DWORD          ' // Release method
   ' === Custom data =====================================================================
   pVtblAddr               AS DWORD          ' // Address of the virtual table
   cRef                    AS DWORD          ' // Reference count
   pData                   AS OC_DATA PTR    ' // Pointer to the OC_DATA structure
   ' =====================================================================================
END TYPE
' ========================================================================================

' ========================================================================================
' IDispatch interface
' ========================================================================================
TYPE OC_IDISPATCH
   ' === IUnknown methods ================================================================
   pQueryInterface         AS DWORD          ' // QueryInterface method
   pAddRef                 AS DWORD          ' // AddRef method
   pRelease                AS DWORD          ' // Release method
   ' === IDispatch methods ===============================================================
   pGetTypeInfoCount       AS DWORD          ' // GetTypeInfoCount method
   pGetTypeInfo            AS DWORD          ' // GetTypeInfo method
   pGetIDsOfNames          AS DWORD          ' // GetIDsOfNames method
   pInvoke                 AS DWORD          ' // Invoke method
   ' === Custom data =====================================================================
   pVtblAddr               AS DWORD          ' // Address of the virtual table
   cRef                    AS DWORD          ' // Reference count
   pData                   AS OC_DATA PTR    ' // Pointer to the OC_DATA structure
   ' =====================================================================================
END TYPE
' ========================================================================================
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 30, 2009, 06:50:32 PM
Fred,
  Re:SysAllocStringByteLen-> That is what PB uses and using BSTR in c/c++ seems to work ok.
See Working with Visual Basic in the help file.

James
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 30, 2009, 07:06:58 PM
This is really an exercise to promote PowerBASIC's server creation abilities.
Most everyone is focused on using PB's new COM stuff to access servers not write them. One of the reasons I'm looking at c is I want to be able to supply native code to show access from any language, and with c as the most popular, I figured I start there.
After I get it working the way I want I'll branch out to others.
This is to be for early binding of IUnknown interfaces only.
For VB all I have is the new .net crap so I doubt I'll even venture there.
c++ is much easier. I am working on c++ code at the moment to access the GUI COM server I wrote and posted on the Pb site. It's going better that I had hoped. I got tired of hand coding all the interface declares so I'm now coding a utility to parse the inc file from the José browser.

James
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 30, 2009, 07:20:35 PM
Yea, I need to play around with this issue so as to get a better (no pun intended) handle on it.  I found this in one of my ATL COM books...

SysAllocStringByteLen

This function is provided to create BSTRs that contain binary data.  You can use this type of BSTR only in situations where it will not be translated from ANSI to Unicode, or vice-versa.

I understand the 1st part of that, but not the last part.  It seems exactly what we are doing, that is, putting an ansi string in a Unicode data type.  Although we (you) didn't convert anything.  'Fuller' was an ansi string to begin with, and ended up b eing treated like an ansi string when it got to PowerBASIC.  The only ringer is its method of transport was a unicode string.  Well, that's binary data I guess, and it works.
Title: Re: c++ client PowerBASIC Server
Post by: José Roca on January 30, 2009, 07:23:43 PM
Quote
As you can see, he doesn't think much of this function.  But I expect you know a great deal more about this than I as I havn't found anyway to replace your use of this function in that program.  If I send a real wide character string in that BSTR parameter, all that gets printed out is the first letter of your last name, i.e., 'F', which is a sure indication PowerBASIC is anticipating ansi characters in that unicode string.  Crazy, if you ask me.  But, while BSTRs are supposedly unicode, the OLECHAR type is very close to the same thing, and that can be unicode or ansi, depending.  Any thoughts anyone?

BSTR stands for Binary String. It can be used for ansi strings, unicode strings and binary data. It is defined in OleAuto.h as typedef OLECHAR FAR * BSTR;

As PB currently has not native Unicode support, it assumes that all strings are ansi. If it is Unicode, you need to convert it using ACODE$/UCODE$.
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 30, 2009, 07:36:56 PM
I wonder how much unicode is being used?  PowerBASIC is used all over the world; it doesn't have unicode support; all one can assume is that folks are satisfied with the English character set for their computer work.  I'm not trying to be an imperialist or anything like that about this.  I can certainly see the need for unicode.  But I'm wondering how much it is being used by the folks for whom it was actually designed for, that is, Spanish, German, Russian, Italian, French, etc.?
Title: Re: c++ client PowerBASIC Server
Post by: José Roca on January 30, 2009, 07:58:03 PM
You don't need Unicode for Spanish, French, German, Italian or other Western European languages using Latin characters. You need it for Chinese, Japanese, Russian, Indian languages, Arabic, Hebrew, etc.

Anyway, Windows NT systems use always Unicode. Therefore, API functions that use ansi strings are somewhat slower because Windows has to translate the ansi strings to Unicode, call the Unicode version of the function and then convert strings back to ansi if needed.
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 30, 2009, 08:48:38 PM
Ah!, I get it now.  Hadn't thought of it that way. 
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 30, 2009, 09:29:12 PM
Several months ago I was thinking of attempting to put GUI elements in a PowerBASIC COM Dll.  But what stopped me was my realization (which I think to be true, but someone please correct me if I'm not) that getting a GUI element to work from within the PB COM Dll would not constitute the creation of an ActiveX Control, even though many of these have visual interfaces.  That being the case, I could see no compelling reason to incorporate GUI elements within PB COM Dlls; it would be simpler rather to just create custom controls containing GUI interface elements.  I suppose it could be justified somewhat on the basis that a COM object would have a typelib containing various interface capabilities; nontheless, it wouldn't be a full ActiveX control, wouldn't be recognized as such by OLE View, and couldn't be sited within ActiveX Control Containers such As VB, Delphi, IE, etc.  Of course, what I mean by it wouldn't be an ActiveX control is that it wouldn't be one unless all the interfaces were implemented relating to siting and communication with its container.  Are there other ways to look at it?
Title: Re: c++ client PowerBASIC Server
Post by: José Roca on January 30, 2009, 10:12:03 PM
 
A visual ActiveX control is more complex than a non visual server or ActiveX. It needs to support a number of additional interfaces to communicate within the container when it is embedded, and also implement persistence and property pages to allow to set property values in the visual designer.

http://msdn.microsoft.com/en-us/library/ms680757(VS.85).aspx
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 30, 2009, 10:28:25 PM
Fred,
  I had a number of reasons to create a gui com server but none had anything to do with ActiveX or VB.
As I get older my memory has become worse than ever. I can never remember function parameters and when I can
I can't remember the exact order.So I wrapped them in a class along with default parameters so I can actually
create a window without setting any properties if I want. If I want to change just the width then it's
MyWindow.width=72. MyWindow.Create. The approach I use is much different than with MFC or the likes.
With PB the base class create method is never called so traditional inheritance is out. I use what I guess is
a form of containment where I have a Control class and it in turn creates the different types of controls.
Another benefit was an easy PBCC GUI. This lead to my investigation of using a PB COM server from other languages.
As I said I have settled on early binding IUnknown.

James


Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 30, 2009, 11:15:15 PM
Oh, I see now James!  Do the Method calls such as a call to Create a window and such show up in the type lib?  I expect they would. 

I imagine you like editors that provide/list methods/properties like Microsoft's editors do.  I guess the CodeBlocks/Dev-C++ editors do that, but none of the PowerBASIC editors are quite as advanced on that front.  Either that or I havn't learned to use them properly!  That is actually the only thing I miss about Microsoft's products - their program editors.  Like you I switch between different languages a lot;  can't remember everything.  I think its for that reason I tend to know only a small subset of any one language, sort of the lowest common denominator.  Take PowerBASIC's various array minipulation features; hate to say it but I've never even tried them.  If I had to choose one PowerBASIC statement that I loved immediately when I first discovered it - it was ParseCount / Parse.  I'm sure there are others I've never even looked at. 
Title: Re: c++ client PowerBASIC Server
Post by: Dominic Mitchell on January 30, 2009, 11:24:59 PM
http://www.phnxthunder.com/forums/phpBB2/viewtopic.php?t=714

The tree control in the screenshots in the above link is visual ActiveX control I wrote using PowerBASIC.
It was written using low-level COM not PB COM.

Something you will need to be aware of when writing a server or even an OLE container that hands out
objects to clients, is what is known as the lock count on an object.  When the reference count on an
object you handed out reaches zero, it does not necessarily mean that you can go ahead and destroy it.

Title: Re: c++ client PowerBASIC Server
Post by: Dominic Mitchell on January 30, 2009, 11:38:53 PM
What James is doing is creating a server that creates windows.  Those windows are not ActiveX controls
by any stretch of the imagination.
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 31, 2009, 12:35:08 AM
Are you speaking of something above & beyond this type of construct Dominic?

Code: [Select]
STDAPI DllCanUnloadNow()
{                                   
 if(g_lObjs||g_lLocks)         
    return S_FALSE;           
 else                               
    return S_OK;                 
}                                   

in other words...

If CountOfObjects>0  Or CountOfServerLocks > 0 Then
   Don't Unload
Else
   No Reason To Stay!  Bye Bye (CoFreeUnusedLibraries() Come And Get Me!)
End If
Title: Re: c++ client PowerBASIC Server
Post by: Dominic Mitchell on January 31, 2009, 02:07:36 AM
That code snippet you showed is related to the IClassFactory::LockServer method and is the same code I use
in the tree control. But I was also thinking about servers(both in and out of process) and OLE containers that
support Linking to objects. This is where the IExternalConnection interface comes in.  See, for example,

http://www.microsoft.com/msj/0197/activex/activex0197.aspx
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 31, 2009, 02:37:31 PM


  Is it possible to use the "c" method access I demonstrated with Visual Basic: Loading an registered interface
directly from a dll.

As I stated I only have the latest vs 2008 installed so I can't test. I do have a copy of vb5cce somewhere?

James
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 31, 2009, 07:18:06 PM
I've never tried it James, but everything I know about computers (which is considerably less than you, I'm sure), tells me it can be done.  After all, structures in memory are structures in memory, and Api function calls are Api function calls, so as long as the right parameters & structures are used - I'd expect it to work.  However, I know little of the underpinnings of the .NET framework, so I don't know just what functions/interfaces would be helpful there.  With the Visual Studio 4, 5 & 6 stuff though I imagine it would work. 

As I continue my study of this rather complicated (h***, its more than complicated.  I think one could just about call it 'rocket science'.) material, I hope to end up with my own version of a TypeLib browser where I could use perhaps a radio button or check box to indicate whether I want C++ declares or PowerBASIC declares and interfaces generated.  I had been working on that several months ago, and actually had the code roughed out enough that it was outputting pretty good declares for me.  There were a few minor problems though and I got to tearing it up pretty good.  At some point I'd like to get back to that work though.  I pretty much consider myself a PowerBASIC programmer rather than a C++ programmer, but what keeps me prettily heavily in the C++ language is that in my work I create handheld data recorder programs for our field data collection in the PA Bur of Forestry, and for that I use Microsoft's eMbedded Visual C++ 4.0.  I've never found a suitable alternative to that.  There are alternatives, but by suitable I mean ability to get real low level into the OS to modify things as I need to.

However, my C++ knowledge has improved quite a bit over the past year or two due to the fact that on the last major programming project I did I decided to use C++ instead of the C I'm more familiar with, and what with all this studying of COM I'm doing, I'm getting a better sense of C++.  For desktop Windows programming, which I also do a lot of, I invariably use PowerBASIC, as its a much more productive environment.

If you want any of that typelib code I've got I'd be happy to give it to you.  Its C++ though.

Added five minutes later:

One other thing James.  If you want to try accessing VB ActiveX controls from C why don't you install that VB5 you have.  You had mentioned you had that in other posts.  I expect you would get a good number of common ActiveX controls installed to give you something to work with.  The one control I really missed when I moved to PB from VB was the MSFlexGrid control.  Its an Ocx or ActiveX control.   To a lesser extent I missed the MSCal control, which is another ActiveX control.  I was able to first get these controls working through code Jose Posted & his TypeLib browsers.  In the generated PowerBASIC code from his TypeLib browser it can be clearly seen how he created the objects in memory (the interfaces) and was able to receive events from these controls.  Essentially, he had to do exactly what a C programmer would do to accomplish this.  In C or pre v9 PowerBASIC, you need to either allocate memory for the VTables or use Types containing pointers to the various interface functions, just as you were doing in your last code you posted.  That's kind of why I'm recommending what I am in terms of studying some of Jose's excellent code.  I believe you would need one of his earlier TypeLib browsers however; with PB9 it wasn't necessary to do any of that to receive events.  Also, I'm not sure how up on Events you are.  You've got to study up on that and it is quite complex - especially so when you get into the enumerators.
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 31, 2009, 07:48:02 PM
Question perhaps for Dominic.   That control looks really nice Dominic.   I'm sure it took a lot of hard work.  You had touched on a point that I've wondered about quite a bit, and that is if it is even possible to tie into PowerBASIC's current COM infrastructure to build a full ActiveX control, or whether it is indeed unfortunately necessary to start from scratch and do everything low level as you apparently have done.  I'm still in the stage of just learning this material yet, and am not yet ready to study the ActiveX end of it, but the issue has occurred to me numerous times. 
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on January 31, 2009, 07:49:37 PM
Re:TypeLib code
A couple days ago I thought I might go that route but instead I wrote a little Pb parser for the Pb inc code
created by José's browser. I still need to tweak the c++ code for Pb longs/dwords to HWND,HFONT,HANDLE and a
few others. I had to go through and add ALIAS to all my methods and Properties (actually I wrote a program to do it) first.
I'm plugging away at translating the PB Client code  to c++. I'm using BCC at the moment but the code should
be fine with g++.


I'm not really interested in accessing ActiveX or anything else from PB at this time. Right now I'm focused on
access of PB COM servers from other languages.
I have extreme tunnel vision when it comes to doing most anything,especially programming.
I don't multi-task well at all. One thing at a time.
James

Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on January 31, 2009, 08:50:50 PM
Quote
I have extreme tunnel vision when it comes to doing most anything,especially programming.

That's exactly how I am too.

Weren't you calling DllGetClassObject() in some of your earlier code on that PB Dll COM server you made?  If my memory serves and without checking I thought you were.  Thatt DllGetClassObject() function is, I believe, part of the COM standard, and so any PB Dll COM server should have it.  And that's the beauty of COM.  Once you're 'in', so to speak, you should be able to use QueryInterface() to get whatever you have prior knowledge of through headers or Type Libraries.
Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on February 01, 2009, 03:11:56 PM
Fred,
  Quick c/c++ question:
How would you convert this PB  snippet? The %TCN_LAST TO %TCN_FIRST. I first check that it's Tab Control Message before I break it down further for explicit messages. I'm a bit familiar with switch case but could find no info on from To type. Would if's be more appropriate?
Thanks
James


Code: [Select]
        CASE %WM_NOTIFY
            lpNmHdr = CB.LPARAM
            SELECT CASE @lpNmHdr.code
                CASE %TCN_LAST TO %TCN_FIRST


Title: Re: c++ client PowerBASIC Server
Post by: James C. Fuller on February 01, 2009, 06:53:56 PM
Never mind I got it I think?

if((lpNmHdr->code >= TCN_LAST) & (lpNmHdr->code <= TCN_FIRST))

James
Title: Re: c++ client PowerBASIC Server
Post by: Frederick J. Harris on February 01, 2009, 07:23:14 PM
Hi James!

     Yes, that works.  In cases where there are more than two or three the if construct becomes awkward, and you'll see this form of construct used...

Code: [Select]
switch(charCode)
{
  case 48:
  case 49:
  case 50:
  case 51:
  case 52:
  case 53:
  case 54
  case 55
  case 56
  case 57:
     //run some code;
     break;
  case other logic
    break
}


...which takes advantage of the fact that C/C++ doesn't break out of a switch until it encounters a 'break'.  The above snippet might be if you are testing for keypresses that are numeric digits.  This is but yet another example of the crudity of C/C++ syntax.  In my mind the BASIC Select Case is so much nicer.
Title: Re: c++ client PowerBASIC Server
Post by: James C Morgan on February 18, 2022, 03:21:12 AM
i most basic console one uses

locate 15,4: Print "hi"

is there a c++ or win32api that can perform this
or can one write a routine does the same