• Welcome to Powerbasic Museum 2020-B.
 

News:

Forum in repository mode. No new members allowed.

Main Menu

COM Tutorial #1: COM Object Memory

Started by Frederick J. Harris, January 20, 2009, 08:38:02 PM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

James C. Fuller

#15
Another little test.
I had more trouble than I should by forgetting to keep the exact same order of the declares!!

If you would rather I didn't clutter up your tutorial with my tests just say so.

James

PbServer

'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}")


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
METHOD iNum2_Get(iNum1 AS LONG)
iNum1 = iNum2
STDOUT "Called iNum2_Get = " + FORMAT$(iNum1)
END METHOD
METHOD iNum2_Set(BYVAL iNum AS LONG)
iNum2 = iNum
STDOUT "Called iNum2_Set = " + FORMAT$(iNum)
END METHOD
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

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



c++ client

#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 HRESULT __stdcall iNum2_Get(int*);
virtual HRESULT __stdcall iNum2_Set(int)=0;
virtual HRESULT __stdcall S1_Get(BSTR*);
virtual HRESULT __stdcall S1_Set(BSTR*);
};




int main(void)
{
  iTest1* piTest01=NULL;
  HRESULT hr;
  IClassFactory*  pCF=NULL;
  HMODULE         hDll=NULL;
 
  int iNum2;
  BSTR str1;
 
//Change Path Here

  hDll=LoadLibrary("D:\\ComTutorial1A\\Server\\PbSource\\PBCTEST01.DLL");
 
  ptrDllGetClassObject=(HRESULT (__stdcall*)(REFCLSID, REFIID, void**))GetProcAddress(hDll,"DllGetClassObject");
  printf("ptrDllGetClassObject = %u\n",ptrDllGetClassObject);
  hr=ptrDllGetClassObject(CLSID_cTest01,IID_IClassFactory,(void**)&pCF);
  if(SUCCEEDED(hr))
  {
    pCF->CreateInstance(NULL,IID_iTest01,(void**)&piTest01);

    pCF->Release();
    hr=piTest01->Fx1(25);
    hr=piTest01->Fx2(50);   
    hr=piTest01->iNum2_Get(&iNum2);
    printf("iNum2 = %u\n",iNum2);   
    hr=piTest01->iNum2_Set(22);
    hr=piTest01->iNum2_Get(&iNum2);
    printf("iNum2 = %u\n",iNum2);
    hr=piTest01->S1_Get(&str1);
    printf("str1 = %s\n",str1);
    SysFreeString(str1);
    str1 = SysAllocStringByteLen("Fuller",6); 
    hr=piTest01->S1_Set(&str1);
    SysFreeString(str1);
    hr=piTest01->S1_Get(&str1);
    printf("str1 = %s\n",str1);
    SysFreeString(str1);
    hr=piTest01->Release();
   
  }


FreeLibrary(hDll);

getchar();
return 0;

}





Frederick J. Harris

I don't mind at all Jim.  I'm glad you've found the topic useful. 

Frederick J. Harris

I see you've used the BSTR type in the C++ code James to access the PowerBASIC String.  I guess that's the way it should be. 

Got me to thinking...

Perhaps its time to deprecate my class CA and come up with a CA Extended that has interfaces with String data types!  After all, its been out nearly a full week already right?

The thing to do would be to create I_X2 and an I_Y2 interfaces that inherit from I_X and I_Y.

Actually, this weekend I've been not working at the computer too much, but rather agonizing over a rather painful observation concerning the outputs of the many programs I've presented here - something of anomalous behaviour I believe.  I'm surprised no one has spotted it. 

Nick Luick

Fred,

If you wouldn't mind I would like to add this link of a good article by Jeff Glatt, where he breaks down creating COM in plain C.  Its just another perspective of your work here explaining basically the same with different words and slightly different approach.   He has broken it down in 7-parts as you can see.


http://www.codeproject.com/search.aspx?q=com+using+C&doctypeid=1%3b2%3b3&authorid=88625

I was reading your thread here last night and it reminded me of similar reading at codeproject.  So I went searching to find this link for all here.

Frederick J. Harris

Hi Nick!

     Yes, Jeff Glatt's articles are wonderful.  In this tutorial #1 I used C++, but in many ways one can't really understand the layout of COM objects unless one looks at it from the C perspective.  In my tutorial #2 I used C I believe, and that translates more clearly to PowerBASIC.  The situation becomes especially clearer when one looks at objects with multiple VTables/interfaces from the C perspective.  I found one lack of Jeff's treatement of this to be that his 1st example created an object with just one VTable.  When you have multiple VTables it becomes necessary to understand the mechanisms involved in switching between them.  I have to admit I never looked at any of Jeff's tutorials beyond his 1st one.

James C. Fuller

Fred,
  I used oleview to load a PowerBASIC created tlb.
I then saved  the idl file. I then tried to create the c/c++ header files using MIDL.
I needed to edit the idl file to remove the .dll
All I got was another tlb file no ".h" file??
Can this be done?

James

Frederick J. Harris

Hello James!

     If you use the midl compiler on something like test.idl you should get these files...

test.c
test.h
test.tlb
test_i.c
test_p.c
dlldata.c

dlldata.c and test_p.c is proxy/stub code for creating a marshaller dll.  But you should get a test.h file with a lot of gunk in it.  I'm not quite sure what you mean by this...

Quote
I needed to edit the idl file to remove the .dll

James C. Fuller

Fred,
  Do I need some command line parameters? All midl is creating is another tlb file.
The file I loaded into oleview was PBCTES04.TLB

Here is the IDL file OleView created. I named it TLIB04.IDL


// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: <could not determine filename>

[
  uuid(7273541F-287F-4E32-92D4-4E25F501389E),
  version(1.0),
  helpstring("COM Library")
]
library PBCTEST04
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("stdole32.tlb");

    // Forward declare all types defined in this typelib
    interface IDISPLAYOPENFILE;

    [
      odl,
      uuid(B72CDD37-5349-46B7-8A27-592D23B34FE3),
      helpstring("IDISPLAYOPENFILE is a custom interface for Direct VTable access."),
      nonextensible
    ]
    interface IDISPLAYOPENFILE : IUnknown {
        [propput]
        void _stdcall FOLDER([in] BSTR rhs);
        [propput]
        void _stdcall FILTER([in] BSTR rhs);
        [propput]
        void _stdcall HPARENT([in] unsigned long rhs);
        [propput]
        void _stdcall DFLEXT([in] BSTR rhs);
        [propput]
        void _stdcall START([in] BSTR rhs);
        [propput]
        void _stdcall TITLE([in] BSTR rhs);
        [propput]
        void _stdcall FLAGS([in] unsigned long rhs);
        BSTR _stdcall GETNAME();
    };

    [
      uuid(1B0F486F-9CCA-47BA-A338-8F7146B73C26)
    ]
    coclass CDISPLAYOPENFILE {
        [default] interface IDISPLAYOPENFILE;
    };
};

 


This is copied from the terminal:



D:\ComTutorial1A\Server\PbSource>midl TLIB04.IDL
Microsoft (R) 32b/64b MIDL Compiler Version 7.00.0555
Copyright (c) Microsoft Corporation. All rights reserved.
Processing .\TLIB04.IDL
TLIB04.IDL
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\oaidl.idl

oaidl.idl
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\objidl.id
l
objidl.idl
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\unknwn.id
l
unknwn.idl
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\wtypes.id
l
wtypes.idl
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\basetsd.h

basetsd.h
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\guiddef.h

guiddef.h
Processing C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include\oaidl.acf

oaidl.acf

D:\ComTutorial1A\Server\PbSource>



No files anywhere I could find.

James

Frederick J. Harris

I believe you need to have interfaces declared outside of a IDL library statement if you want to have *.h and other
source code generated by MIDL.

Also, this from p148 of "Developer's Workshop To COM And ATL 3.0" by Andrew W. Troelsen...

Quote
When designing COM interfaces, we should always return HRESULTs as the physical return value of the method...

When I took the IDISPLAYOPENFILE interface out of the library structure, midl wouldn't compile it and complained
about the void return values you had.  I imagine you are working on a COM wrapper for the Open File Dialog, which
of course returns a file name string, so I'd return a BSTR pointer as an [out, retval] as seen in what I have below.
This idl file correctly compiled for me, and the output is below...


import "oaidl.idl";

[object, uuid(B72CDD37-5349-46B7-8A27-592D23B34FE3), helpstring("IDISPLAYOPENFILE is a custom interface for Direct VTable access."), nonextensible]
interface IDISPLAYOPENFILE : IUnknown
{
HRESULT _stdcall FOLDER  ([in]          BSTR           rhs     );
HRESULT _stdcall FILTER  ([in]          BSTR           rhs     );
HRESULT _stdcall HPARENT ([in]          unsigned long  rhs     );
HRESULT _stdcall DFLEXT  ([in]          BSTR           rhs     );
HRESULT _stdcall START   ([in]          BSTR           rhs     );
HRESULT _stdcall TITLE   ([in]          BSTR           rhs     );
HRESULT _stdcall FLAGS   ([in]          unsigned long  rhs     );
HRESULT _stdcall GETNAME ([out, retval] BSTR*          strName );
};


[uuid(7273541F-287F-4E32-92D4-4E25F501389E), version(1.0), helpstring("COM Library")]
library PBCTEST04
{
importlib("stdole32.tlb");
// Forward declare all types defined in this typelib
interface IDISPLAYOPENFILE;                   
 
[uuid(1B0F486F-9CCA-47BA-A338-8F7146B73C26)]
coclass CDISPLAYOPENFILE
{
  [default] interface IDISPLAYOPENFILE;
};
};







C:\Code\VStudio\VC++6\Projects\PBCTES04>midl pbctes04.idl
Microsoft (R) MIDL Compiler Version 5.01.0164
Copyright (c) Microsoft Corp 1991-1997. All rights reserved.
Processing .\pbctes04.idl
pbctes04.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\oaidl.idl
oaidl.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\objidl.idl
objidl.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\unknwn.idl
unknwn.idl
Processing C:\Program Files\Microsoft Visual Studio\VC98\include\wtypes.idl
wtypes.idl

C:\Code\VStudio\VC++6\Projects\PBCTES04>dir
Volume in drive C is Main
Volume Serial Number is 881C-5BB1

Directory of C:\Code\VStudio\VC++6\Projects\PBCTES04

05/17/2011  11:29a      <DIR>          .
05/17/2011  11:29a      <DIR>          ..
05/17/2011  11:29a                 811 dlldata.c
05/17/2011  10:35a                  74 PBCTES04.bat
05/17/2011  11:29a               9,571 pbctes04.h
05/17/2011  11:29a                 962 PBCTES04.idl
05/17/2011  11:29a               2,192 pbctes04.tlb
05/17/2011  11:29a               1,128 pbctes04_i.c
05/17/2011  11:29a              37,104 pbctes04_p.c
               8 File(s)         52,799 bytes
               2 Dir(s)  48,632,020,992 bytes free

C:\Code\VStudio\VC++6\Projects\PBCTES04>

James C. Fuller

Fred,
  Does it appear to be a  problem with the PowerBASIC tlb ?
Create a simple PowerBASIC COM server and see what oleview returns.

James

James C. Fuller

I think another problem I'm having is using vs express 2010 and the win7 sdk which is 32/64. I've played around with /win32 for midl but I still get errorseven when I used midl on an IDL file I created with oleview from scrrun.

James

James C. Fuller

Fred,
  Thanks for your input. I think I have a handle on it now. The void was because I was using IUnknown instead id IDispatch.
It also appears oleview does not produce an IDL in a format for midl to generate the source files. I've decided to parse the oleview idl file myself.

James

Frederick J. Harris

Give me a chance to look into it.  Its been awhile since I've played around with OleView.  Isn't there a flag there (setting) about creating ODL or IDL compliant output?  I noticed there was an ODL attribute in your original *.idl. 

Frederick J. Harris

#28
Hi James!

    Yes, I agree with you.  I just spent some time playing with it, and saw that one gets adequate Interface definitions from OleView, but one must extract them from within the library clause to make a midl compilable *.idl file.  Otherwise, midl just reproduces the typelib as it read it (and as you posted yesterday)!

     Irregardless, its ( OleView ) certainly a really useful tool!

Fred

James C. Fuller

Fred,
  Any examples (pb and c++) using IDispatch instead of IUnknown?

James