|
Hello Ting Xu,
I checked out the help files under index IShellLink and noted that the IShellLink interface is implemented in Shell32.dll.
I then looked up the type library of Shell32.dll via OLEView. I noted that there are no IShellLink interface declared inside my machine's version of Shell32.dll.
There is, however, a IShellLinkDual interface as well as a IShellLinkDual2 interface in my shell32.dll. The IShellLinkDual interface looks similar to IShellLink. The "Dual" most likely signifies "dual interface". Hence it may be a newer version of IShellLink (by virtue of it being of dual interface) Please check this out.
What you can do is to use the OleView tool to view the type library of the target machine's Shell32.dll. If IShellLink is not present but the IShellLinkDual and IShellLinkDual2 interfaces are present, then you may want to use the IShellLinkDual or IShellLinkDual2 interface(s) instead :
IShellLinkDual *pShellLinkDual;
HRESULT hr;
hr = ::CoCreateInstance (CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLinkDual, (void**)&pShellLinkDual);
Hope the above helps.
Best Regards,
Bio.
|
|
|
|
|
I have an .exe application that communicates to a device through a serial port. The application is in VC++ and is dialog-based to provide a GUI for interfacing to the device. I also needed a COM interface to allow access to the device to be scripted. I wanted to allow multiple scripts to access the device and allow the GUI dialog to be opened or closed at any time while the scripts are running. The problem, unless I'm way off track, is that I need there to be only one instance of the server so that it can route all traffic through the same serial port.
After I initially created the application containing an automation class, the problem was that running a .vbs script would create one instance of the server and running the .exe created a second instance of the server.
Then I found that if I moved the COleTemplateServer::RegisterAll() call in front of the "if (RunEmbedded() || RunAutomated())" line that running a .vbs script would attach to the already running instance of the server, but not the other way around. If I run a script first and then execute the .exe a second instance of the server is started and it cannot talk to the serial port because the first instance has control.
Next I used the SingleInstanceApp class I found on this website and with it I was able to detect that I already had an instance of the server running when launching the .exe and it sends a registered message to the first instance and then terminates. The first instance receives the message and launches the GUI dialog if it is not already active. So with this change I now am guaranteed to only have one instance of the server running at a time but it left me with one problem.
Once I launch the dialog I cannot close it because it terminates the server and basically pulls the rug out from under any script clients that are using the server. I tried intercepting the WM_QUIT message using ProcessMessageFilter and that does work if I run a script first, launch the .exe, and then close the .exe. But if I start the .exe first and then start running any scripts, the server is terminated when I close the dialog.
I know this was a long message but I've been banging my head against the wall to try to solve this and any help would be greatly appreciated.
Gil Jones
|
|
|
|
|
But if I start the .exe first and then start running any scripts, the server is terminated when I close the dialog.
As I undertsand it's MFC app. If so, as I recall:
you may consider looking into the call to AfxOleSetUserCtrl(FALSE) in the sequence when you first start .exe and later get Automation CreateInstance...
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..."
Me
|
|
|
|
|
Thanks...that looks promising...I'll give it a try tonight.
When reading up on that function I also noticed the description for AfxOleCanExitApp(). That makes it sound like the application really shouldn't be exiting. But I'm wondering if it's because my application's main dialog is a property sheet. It probably doesn't go through CFrameWnd::OnClose when exiting from a dialog interface and not a document interface.
Gil
|
|
|
|
|
Gil,
There maybe very simple shortcut:
you've already mentioned "if (RunEmbedded() || RunAutomated()) ", because you want it to register with OLE even when runningf from command line: just comment that if , so COleTemplateServer::RegisterAll(); or similar will be called.
May work...
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..."
Me
|
|
|
|
|
Maybe my first post wasn't clear enough, but that's basically what I've done. It now looks like this:
COleTemplateServer::RegisterAll();<br />
if (RunEmbedded() || RunAutomated())<br />
{<br />
}<br />
else<br />
{<br />
COleObjectFactory::UpdateRegistryAll();<br />
}
That change is what allowed a .vbs script to attach to the server instance that was started from the command line instead of creating a new one. But the problem is that the application still disappears when I close the dialog which leaves the clients with no server.
Gil
|
|
|
|
|
OK, now that's clearer.
Yes, your script now attaches to running instance of singleton, even the one started from command line.
Anyway: try AfxOleSetUserCtrl(FALSE); and put it at the end of InitInstance or even in your FrameWnd::OnCreate.
Also, because now you have singleton -- you can easily build Debug version and use Debug facilities to see what's going on. In fact I'm often using that trick with COleTemplateServer::RegisterAll(); to debug my outproc servers (which are not singletons).
Regrds
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..."
Me
|
|
|
|
|
Oops...I'm not at home and I realized it actually has a "return TRUE" if embedded to keep the dialog from activating when accessed via automation.
Like this:
COleTemplateServer::RegisterAll();
if (RunEmbedded() || RunAutomated())
{
return TRUE;
}
else
{
COleObjectFactory::UpdateRegistryAll();
}
Gil
|
|
|
|
|
Unfortunately the AfxOleSetUserCtrl(FALSE) didn't seem to fix the issue. I don't think I have a FrameWnd::OnCreate in a dialog based application do I?
Here's instruction for how to reproduce the problem that I posted to the C++ forum:
http://www.codeproject.com/script/comments/forums.asp?forumid=1647&fr=126#xx600114xx
If your willing to take a few minutes and create a new project you should be able to see the problem.
Gil
|
|
|
|
|
Thanks for the help. I posted another response to the C++ thread on this issue. One thing I notice is that when your using the proxy dialog interface and you've made your server a singleton, if you activate the dialog, run a .vbs script to access the application interface, and then close the dialog before the .vbs script finishes, the server does remain active due to the CanExit() code added to the dialog by ClassWizard. The problem is that the server doesn't destroy when all clients have released their objects. That's because the ClassWizard hasn't created a mechanism for the dialogs CanExit() logic to be called each time a CDlgProxy objects destructor runs. I manually added some code to do this and now it works.
Gil
|
|
|
|
|
That's because the ClassWizard hasn't created a mechanism for the dialogs CanExit() logic to be called each time a CDlgProxy objects destructor runs. I manually added some code to do this and now it works.
Gil,
Right. Default MFC Server implementation is designed for an invokation of the server only through either Automation or as standalone. Not both, as you are doing.
Therefore, what you've done, maybe not clean, but appropriate. There are probably other ways to tweak desired behavior, but if your solution works -- that's what needed. Right? Better sometimes is worse then good.
Regards
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..."
Me
|
|
|
|
|
Hi!
I am very new to COM and ATL and try to implement something like this:
I have a simple COM server with the following interface:
[
object, ...
]
interface ITest : IUnknown
{
[id(1), helpstring("Methode GetInterface")] HRESULT GetInterface([in] int n, [out] ITest2** pITest2);
};
The interface method GetInterface should return a new pointer to the interface
ITest2 . This function should not work like QueryInterface , the
interface ITest2 is not in the same coclass as the ITest , it's in
another coclass.
This function should work like the following DirectX (DirectDraw) function CreateSurface :
LPDIRECTDRAW lpDD = NULL;
LPDIRECTDRAWSURFACE lpDDSPrimary = NULL;
...
HRESULT hError = lpDD->CreateSurface(&DDSurfaceDesc, &lpDDSPrimary, NULL);
...
How can I implement the GetInterface function?
The ITest2 interface (and the coclass of the interface) should not be creatable
with the CoCreateInstance , it should be only created by the GetInterface
method!
Daniel
---------------------------
Never change a running system!
|
|
|
|
|
One solution is containment. Another solution is aggregation given that you want the client to gain direct access to the inner COM object.
Kuphryn
|
|
|
|
|
Can you give my a sample application or a link to a tutorial?
Thanks!
Daniel
---------------------------
Never change a running system!
|
|
|
|
|
If I didn't misunderstand you
static HRESULT CComObject<Base>::CreateInstance(CComObject<Base>** pp)
should be the solution to your problem.
|
|
|
|
|
Any sample or link to a tutorial?
Thanks!
Daniel
---------------------------
Never change a running system!
|
|
|
|
|
Hello Daniel,
I created a sample ATL project with two simple coclass's (Test and Test2). One of them, Test2 is specifically noncreatable. This noncreatble feature can be set easily by including the "noncreatable" attribute for the coclass statement in the IDL file :
[
uuid(A6C64958-F337-4981-8F31-F35AEA02DD98),
helpstring("Test2 Class"),
noncreatable
]
coclass Test2
{
[default] interface ITest2;
[default, source] dispinterface _ITest2Events;
};
This attribute is very useful when your client apps are Visual Basic based. In VB, if a coclass is noncreatable, then when you attempt to create a new instance of it via the "New" keyword, e.g. :
Dim Test2Obj As Test2
...
...
...
Set Test2Obj = New Test2
You will get an error message that reads : "Invalid use of New keyword".
However, the "noncreatable" attribute is not sufficient for VC++ clients. In a VC++ client app, if you have the following statement :
ITest2Ptr spTest2Ptr = NULL;
spTest2Ptr.CreateInstance(__uuidof(Test2));
Surprise, surprise, you will still succeed and obtain an ITest2 smart pointer.
In order to ensure that the above CreateInstance() function call will fail, you will need to comment out the Test2 Object Entry in the ATL Object Map of your ATL project :
BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_Test, CTest)
//OBJECT_ENTRY(CLSID_Test2, CTest2) <-- comment out this line.
END_OBJECT_MAP()
This will ensure that when your ATL DLL is asked to create an instance of an object of coclass Test2 (this happens in the global DllGetClassObject() function), a failure code will be returned.
Note that the good thing about the "noncreatable" attribute and the commenting out of the OBJECT_ENTRY statement will not prevent you from internally creating the CTest2 object. This is, of course, required when the GetInterface() method is called. My implementation of GetInterface() is as follows :
STDMETHODIMP CTest::GetInterface(int n, ITest2 **pITest2)
{
// TODO: Add your implementation code here
CComObject<ctest2> *pTest2New = NULL;
CComObject<ctest2>::CreateInstance(&pTest2New);
if (pTest2New)
{
pTest2New -> QueryInterface (IID_ITest2, (void**)pITest2);
}
return S_OK;
}
I will email you the sample source codes plus VB and a VC++ client apps.
Hope the aboe info will help.
Best Regards,
Bio.
|
|
|
|
|
Hello Daniel,
Please email me your email address so that I can send to you some sample codes.
Best Regards,
Bio.
|
|
|
|
|
Thanks! It works fine!
Cheers,
Daniel.
--
FIND A JOB YOU LOVE, AND YOU'LL NEVER HAVE TO WORK A DAY OF YOUR LIFE.
|
|
|
|
|
I add an interface to an OCX,
when I use the OCX, it throw an exception and pop a message box "parameter not optional".
Why? who can tell me reason?
Thanks a lot!
|
|
|
|
|
Are you passing a VT_EMPTY or a VT_NULL?
Well, you shouldn't
|
|
|
|
|
Just my 2 cents:
On insertion your OCX maybe querying container for some ambient property not supported by your container.
"...Ability to type is not enough to become a Programmer. Unless you type in VB. But then again you have to type really fast..."
Me
|
|
|
|
|
I have an out-of-proc COM server. In one of the method of my COM server, I need Thread Id of the client application who is calling the method. Is there any way to get thread id fo client process in COM server code?
Regards.
|
|
|
|
|
One solution is to require the client to pass in its ID as one of the parameters.
Kuphryn
|
|
|
|
|
You are right, but this is not a solution for the problem. I was looking for an API to get thread id of COM client.
|
|
|
|