• Welcome to Jose's Read Only Forum 2023.
 

c++ client PowerBASIC Server

Started by James C. Fuller, January 26, 2009, 12:43:17 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

James C. Fuller

Fred,
This is code I know compiles with gcc (add oleaut32 and uuid for linking).

#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.

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



James C. Fuller

Fred,
Never mind. I wrote a quick PBCC function to parse the PB GUID$ and create the c,c++ one.

James

Frederick J. Harris

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...


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


A somewhat more COM compliant version would be...


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...


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...


#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?
   


Frederick J. Harris

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


' ========================================================================================
' 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
' ========================================================================================

James C. Fuller

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

James C. Fuller

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

Frederick J. Harris

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.

José Roca

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$.

Frederick J. Harris

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.?

José Roca

#24
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.

Frederick J. Harris

Ah!, I get it now.  Hadn't thought of it that way. 

Frederick J. Harris

#26
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?

José Roca

 
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

James C. Fuller

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



Frederick J. Harris

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.