• Welcome to Theos PowerBasic Museum 2017.

News:

Attachments are only available to registered users.
Please register using your full, real name.

Main Menu

LoadTypeLibEx() Question

Started by Frederick J. Harris, April 05, 2010, 01:39:54 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

Frederick J. Harris

I'm working on an Exe COM server in PowerBASIC which is a translation of one I've got working in C++.  Anyway, I've compiled the midl created typelib into my exe with PBTyp.exe (yes, it worked and shows up fine in OleView!!), and now I need to call LoadTypeLibEx() to register it in the Registry.  This is what I've got so far using Jose's includes...


Function ExeRegisterServer(hInstance As Long) As Long
  Local strAsciPath,strWideCharPath As String
  Local szPath As Asciiz*256   
  Local pTypeLib As ITypeLib
  Local hr As Long
 
  Print "  Entering ExeRegisterServer()"
  If GetModuleFileName(hInstance, szPath, 256) Then
     Print "    szPath = " szPath
     strAsciPath=szPath
     strWideCharPath=UCode$(strAsciPath)
     hr=LoadTypeLibEx(Strptr(strWideCharPath), %REGKIND_REGISTER, pTypeLib)
     If SUCCEEDED(hr) Then
        Print "    LoadTypeLib() Succeeded!"
        Call pTypeLib.Release()
     Else
        Print "    LoadTypeLib() Failed!"
     End If   
  End If
  Print "  Leaving ExeRegisterServer()"
 
  Function=hr
End Function


It compiles OK but I havn't run it yet.  I've learned to proceed with caution in these matters, because last year around this time when I was preparing one of my COM posts, I took out a whole Windows XP registry with one of my runs on bad registry code.  Amazingly, Windows XP will manage to start with a completely destroyed registry, but you would be amazed at how little you can do with it (that was an OS reinstall day!

     Anyway, as far as my code goes, does that look OK so far?

     Amazing work on that OAIdl.inc file Jose!

José Roca

#1
I will change


strWideCharPath=UCode$(strAsciPath)


to


strWideCharPath=UCode$(strAsciPath & $NUL)


and


Call pTypeLib.Release()


to


pTypeLib = NOTHING


It should work. My browser loads the typelibs using:


bstrFile = UCODE$(strPath & $NUL)
hr = LoadTypeLibEx(STRPTR(bstrFile), %REGKIND_NONE, pITypeLib)


However, this will only register the typelib, not the EXE server. And the typelib alone is of little use (in fact, PB doesn't need it).

Quote
Amazing work on that OAIdl.inc file Jose!

Without it, I couldn't have written the TypeLib Browser for the latest PB compilers.

Frederick J. Harris

Thanks for the input on that Jose.  I hadn't thought about concatenating a $Nul on the end.  Actually, that whole issue confuses me a bit.  I need to sink some time into getting a better understanding of just when nulls are added to BSTRs and when not both in C/C++ and PowerBASIC.  It occurs to me that difficulties could arise when BSTR pointers are used in various Api functions expecting the null terminator and it isn't there. 

Yes, I know I need to do the standard COM registration.  I'll likely work on that today.  The actual C++ code I'm translating is this where you can see I'm calling my RegisterServer() function in the return of ExeRegisterServer().  I just don't have that working yet in PB although it should be an easy modification of the registry code in my #2 tutorial...


HRESULT ExeRegisterServer(HINSTANCE hInstance)
{
OLECHAR wcExePath[256];
char szExePath[256];
ITypeLib* pTypeLib;
HRESULT hr;

if(GetModuleFileName(hInstance, szExePath, sizeof(szExePath)/sizeof(char)))
{
    mbstowcs(wcExePath, szExePath, 256);
    hr=LoadTypeLibEx(wcExePath, REGKIND_REGISTER, &pTypeLib);
    if(FAILED(hr))
       return hr;
    pTypeLib->Release();
   
}
   
return RegisterServer(szExePath, &CLSID_CF, &LIBID_CFLibrary, g_szFriendlyName, g_szVerIndProgID, g_szProgID);
}


bool CmdLnProcessing(HINSTANCE hInstance, LPSTR lpCmdLine, DWORD& regID )
{
CoInitialize(NULL);                   //don't need to call this in PB
if(strstr(lpCmdLine, "/r"))
{
    ExeRegisterServer(hInstance);
    return true;
}
if(strstr(lpCmdLine, "/u"))
{
    ExeUnregisterServer(hInstance);
    return true;
}
if(strstr(lpCmdLine, "/Embedding") || strstr(lpCmdLine, "-Embedding"))
    CoRegisterClassObject(CLSID_CF, (IClassFactory*)&CF_Factory, CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &regID);

return false;
}


int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
char szClassName[]="CF";
DWORD regID=0;
WNDCLASSEX wc;
MSG messages;
 
if(CmdLnProcessing(hInstance, lpCmdLine, regID))
    return 0;
AttachEventHandlers();
wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnWndProc;
wc.cbSize=sizeof (WNDCLASSEX);               wc.style=CS_DBLCLKS;
wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);     wc.hInstance=hInstance;
wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;    wc.cbWndExtra=4;
wc.lpszMenuName=NULL;                        wc.cbClsExtra=0;
RegisterClassEx(&wc);
hMainWnd=CreateWindowEx(0,szClassName,szClassName,WS_OVERLAPPEDWINDOW,100,100,545,340,HWND_DESKTOP,0,hInstance,lpCmdLine);
while(GetMessage(&messages,NULL,0,0))
{
    TranslateMessage(&messages);
    DispatchMessage(&messages);
}
Terminate(lpCmdLine,regID);

return messages.wParam;
}



Basically, the way I'm setting it up is to call my CmdLnProcessing() function as the first statement in WinMain() as seen above.  That procedure checks the command line to see if the program was started with any command line parameters.  If there are none then the program will just run as a normal exe.  If "/r" is passed in, then my registration code will run to set up the CLSID, ProgID, Interface, and TypeLib keys and sub keys.  If "/u" is passed in everything is undone.  After either of these scenerios the program simply exits WinMain() early.  If "/Embedding" or "-Embedding" is passed in, then the program was loaded/executed by SCM, and my CmdLnProcessing() function must call CoRegisterClassObject() to let Windows know the address of my Class Factory function for the class implemented in the Exe.

I'm having some problems with that.  I was going to look at it further today to see if I can come up with a more definitive conclusion, but since I'm writting this now perhaps I should mention it.

I checked the PowerBASIC includes and it doesn't look like they translated CoRegisterClassObject().  No matter, because I'm using your includes for this for obvious reasons and you did translate CoRegisterClass Object().  This is your translation (at least in the not quite up to date versions of your includes I'm using.  Today I also better get your latest)...


DECLARE FUNCTION CoRegisterClassObject LIB "OLE32.DLL" ALIAS "CoRegisterClassObject" _
( _
  BYREF GUID, _                        ' __in REFCLSID rclsid
  BYVAL IUnknown, _                    ' __in IUnknown * pUnk
  BYVAL DWORD, _                       ' __in DWORD dwClsContext
  BYVAL DWORD, _                       ' __in DWORD flags
  BYREF DWORD _                        ' __out LPDWORD  lpdwRegister
)AS LONG                              ' HRESULT


The 2nd parameter seems to be the problem, i.e., the BYVAL IUnknown.  Its typed in the MSDN docs as an IUnknown* also, and that may work out for C, but I can't seem to get around it in PowerBASIC no matter what I try.  What needs to get passed obviously is the address of a class factory.  Here is my present PB implementation of blnCmdLnProcessing() showing my CoRegisterClassObject() call near the bottom.  My class factory variable is CCClassFactory (CC is the name of the class, i.e., 'Class C'.  You can see the C++ code commented out right above.  To get that to compile I tentatively changed the 2nd parameter in the DECLARE above to Byval Dword, although I suppose a Byref Dword would be just as well and would allow the removal of the Varptr().  Anyway, I havn't run it yet, because I'm still working on the registry
code.  Was wandering if you had any thoughts on this Jose?  At this point anyway my intent is to run it this way when I get the registry code done to see what happens (and on a test computer – not any of my good ones!).


Function blnCmdLnProcessing(Byval hInstance As Long, Byval lpCmdLine As Asciiz Ptr, Byref regID As Dword) As Long
  Print "Entering blnCmdLineProcessing()"
  Print "  " @lpCmdLine
  If InStr(@lpCmdLine,"/r") Then
     Print "  Calling ExeRegisterServer()"
     Call ExeRegisterServer(hInstance)
     Print "Leaving blnCmdLineProcessing()"
     Print
     Function=%TRUE
     Exit Function
  End If
  If InStr(@lpCmdLine,"/u") Then
     Print "  Calling ExeUnregisterServer()"
     Call ExeUnregisterServer(hInstance)
     Print "Leaving blnCmdLineProcessing()"
     Print
     Function=%TRUE
     Exit Function
  End If
  If InStr(@lpCmdLine,"/Embedding") Or InStr(@lpCmdLine,"-Embedding") Then
     Print "  Was Loaded By COM!"
     '    BEGIN C++ code
     '    CoRegisterClassObject(CLSID_CF,  (IClassFactory*)&CF_Factory, CLSCTX_LOCAL_SERVER,  REGCLS_MULTIPLEUSE,  &regID);
     '    END C++ code
     Call CoRegisterClassObject($CLSID_CC, Varptr(CCClassFactory),      %CLSCTX_LOCAL_SERVER, %REGCLS_MULTIPLEUSE, regID)
  End If
  Print "Leaving blnCmdLineProcessing()"
  Print
 
  Function=%FALSE
End Function



José Roca

Quote
Thanks for the input on that Jose.  I hadn't thought about concatenating a $Nul on the end.  Actually, that whole issue confuses me a bit.  I need to sink some time into getting a better understanding of just when nulls are added to BSTRs and when not both in C/C++ and PowerBASIC.  It occurs to me that difficulties could arise when BSTR pointers are used in various Api functions expecting the null terminator and it isn't there. 

This is because PB doesn't have native support for Unicode ASCIIZ strings. If the parameter in the C++ declare is BSTR, then you don't need to add any nuls; otherwise, you should add them.

Quote
To get that to compile I tentatively changed the 2nd parameter in the DECLARE above to Byval Dword, although I suppose a Byref Dword would be just as well and would allow the removal of the Varptr().

It depends of what CCClassFactory contains. As COM uses double indirection, if it contains the address of the virtual table, then you have to pass the address of the variable, but if it contains the address of the address of the virtual table (that is what an IUnknown pointer is), then you have to pass that address by value.

Even if you need to use BYVAL VARPTR, I would not change the parameter in the declare to BYREF: it may confuse you later or the users of your code.


Frederick J. Harris

Quote
It depends of what CCClassFactory contains. As COM uses double indirection, if it contains the address of the virtual table, then you have to pass the address of the variable, but if it contains the address of the address of the virtual table (that is what an IUnknown pointer is), then you have to pass that address by value.

Ahhhh!  I need to think on this some more!

I'm beginning to wonder what a UNICODE enhancement of the PowerBASIC language might look like.  Since we already have the Asciiz variable type, a symetric enhancement to my way of thinking would be something like...

Local szString As Unicode

Dynamic Strings whose underlying implementation is apparently the C/C++ BSTR can already hold one byte or two byte characters, and in any case a change to the dynamic string class to hold only one or the other (Asci/Unicode) would break existing code.

Well, I've got the registry code written.  Its now time to try it on my 'expendable' laptop!!!