Re: Make a DLL in C# for FoxPro
- From: "Willy Denoyette [MVP]" <willy.denoyette@xxxxxxxxxx>
- Date: Fri, 1 Feb 2008 18:49:53 +0100
"Rick Strahl" <rickstrahl@xxxxxxxxxxx> wrote in message news:43360BF0-9F3E-4FCA-B232-B9A86E309AAB@xxxxxxxxxxxxxxxx
why would you ever use this class from COM is beyond me?
this class is a wrapper around (a COM component) CDO which is directly usable from native COM.
Sorry, System.Net.Mail does NOT use CDO. It's a pure .NET component. You're thinking of System.Web.Mail in pre-.NET 2.0.
True, my bad.
However, my point is, why a native client (not specifically VFP) would feel the need to use System.Net.Mail when there are native components/libraries available both via COM interfaces or native API's (CDO, Simple Mapi, Mapi etc...).
You should think twice before introducing the CLR (including a couple of large libraries) in a native process, the increased memory footprint of ~8MB (and more), only to add some functionality which is available natively, can be a real showstopper, especially in Terminal Server environments, I've seen projects failing because of this, worse, I've seen organizations abandoning ..NET because of this!
Interop from managed -> COM is a different story, here we are talking about new developments, and here you have probably accepted the larger memory footprint :-)
The point is that with a wrapper component you can access any .NET component (with a few exceptions) directly including static types, enums, and through indirect access with Reflection even value types and arrays of value types which won't even think about working properly in VFP (try updating an array of objects in VFP - good luck). Using any form of CREATEOBJECT() only will never let you instantiate those types or call static methods/members.
Agreed, I never said otherwise. Don't know that much about VFP, but I guess CREATEOBJECT is used in late bound scenarios, while you can also early bind by importing a type library, or I'm I wrong here?
The point of a wrapper component is that it gives you much more access beyond what is exposed through COM interop. Using pure COM Interop (ie. CreateObject()) requires that whatever you're running CreateObject() on is ComVisible. As already pointed out a lot of useful stuff is *not* ComVisible. And it's not just code in the core .NET framework - it's also code in third party libraries and maybe more importantly in generated interfaces such as Web Service proxies (which in my work at least is the #1 reason to interface FoxPro with .NET in the first place). If you inherit a .NET assembly that you need to call from a third party you can almost be guaranteed that it WON'T BE COMVISIBLE.
As far as the BCL goes, what's been registered is a basic set needed for COM interop, no real functionally is directly exposed to COM automation clients. The reason is simple, the BCL was designed with .NET in mind, not for COM. Also, it makes little sense to expose .NET classes to COM, when there exist a native COM library, you should avoid COM interop whenever possible.
Also, you need to watch for the definition "COM client". Some client types have greater access to classes not directly accessible by some late bound automation clients (think JScript, VBScript), sure the classes must be COM visible and you are bound by the rules of COM, but you are less restricted.
The point is relying on ComVisible components only is extremely limiting as to what you can actually do.
Here you mean the BCL exported classes, do you?
But there are others, f.i the SQL 2005 registered classes are well designed to be "COM Friendly". These classes are perfectly usable from native COM, no surprises here, same remark about Exchange classes (latest versions).
There are two areas you are discussing here and they are quite separate actually.
* Avoiding COM Registration
* Providing .NET type services to Visual FoxPro
The first is handled by the DLL/C++ code I posted which loads the .NET runtime and allows loading of assemblies and types. This is purely to avoid COM registration. If you're not adverse to registering components you can completely skip this step and just use the .NET wrapper component from FoxPro with CreateObject(). Or you can use reg-free registration to the same effect. This is semantics. To me using the DLL code is natural because my tools already rely on a helper Win32 DLL anyway so this is nothing extra that isn't already in use. To me this is the easiest way to deal with reg free interop with .NET but this is not even critical.
The other is the ability to instantiate non-COM visible types and access members that either don't work at all or badly (like arrays). This provides basic proxy services which is along the same lines what COM Interop does behind the scenes but through a different path.
Realistically all the value is the second piece with the first piece.
The blog post I made showed just the basics and you need to figure out where to take this on your own. But I've actually built this out some time ago into a full class that provides a host of features for calling .NET components without any sort of registration:
http://www.west-wind.com/webconnection/docs?page=_24n1cfw3a.htm
But using this tool you can - without any special formatting or requirements - access just about any .NET components:
CLEAR
DO wwDotNetBridge
LOCAL loBridge as wwDotNetBridge
loBridge = CREATEOBJECT("wwDotNetBridge") && Fox wrapper around .NET component
*** Load an assembly
? loBridge.LoadAssembly("System") && 'System' is a special name and not really necessary - loaded by default
? loBridge.cErrorMsg
*** Note full load is syntax by 'fully qualified assembly name':
* loBridge.LoadAssembly("System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089")
*** Create an instance of a .NET class
loMsg = loBridge.CreateInstance("System.Net.Mail.MailMessage")
? loBridge.cErrorMsg
*!* ? loMsg
? loMsg.ToString()
*** Load Static Method
? loBridge.LoadAssembly("System.Windows.Forms")
? loBridge.InvokeStaticMethod("System.Windows.Forms.MessageBox","Show","Hello World","Title is it")
? loBridge.cErrorMsg
*** Get a Static value (System.Environment.CurrentDirectory)
? loBridge.GetStaticProperty("System.Environment","CurrentDirectory")
*** Get an Enum value (basically a static)
? loBridge.GetEnumValue("System.Windows.Forms.MessageBoxDefaultButton","Button3")
*** Load one of my own assemblies
? loBridge.LoadASsembly("C:\projects2005\WebLogBusiness\bin\debug\weblogbusiness.dll")
? loBridge.cErrorMsg
*** Retrieve a static property which is an object
loConfig = loBridge.GetStaticProperty("Westwind.WebLog.App","Configuration")
*** I could also instantiate the config object directly
*loConfig = loBridge.CreateInstance("Westwind.WebLog.App")
? loBridge.cErrorMsg
*** Object now in VFP - just access properties and methods
? loConfig.WebLogTitle
? loConfig.MailServerName
? loConfig.WebLogTile = "Some OtherValue"
*** Set and read a static property on a custom static object
? loBridge.SetStaticProperty("Westwind.WebLog.App","ApplicationOfflineMessage","NEW VALUE")
? loBridge.GetStaticProperty("Westwind.WebLog.App","ApplicationOfflineMessage")
I'd love to see you do a few of these things - like accessing a static property or getting an enum value - even if you have your main component registered. With pure COM interop you simply can't do this. A wrapper is required to act as a proxy for some of these operations.
Agreed, I never said the contrary. Sorry if I gave this impression.
My point is that you don't need to explicitly load the CLR, all you need is a single managed shim (COM registered) acting as a class factory, similar to what you did in your C++ shim. These functions can load the assembly and create an instance of the managed wrapper and return a Dispatch pointer just like you did. But agreed, this requires one assembly to be registered. Another approach which does not require the shim to be "COM registered" is by using ( mscoree ) ClrCreateManagedInstance API , this API loads the CLR if not yet done loads the assembly and creates an instance of a class, but this requires your assembly (your shim) to be installed in the GAC.
Following (C code) snip loads the assembly "clrshim" , creates an instance of "Foo.Bar" and calls the ToString method on the instance of Foo.Bar, there is no need to explicitely load the CLR, this is explicitely done by ClrCreateManagedInstance. (contrary to what the docs says!)
CoInitializeEx(null, COINIT_MULTITHREADED);
hr = ::ClrCreateManagedInstance(L"Foo.Bar, clrshim, Version=1.0.0.0, Culture=neutral, PublicKeyToken=82cc46cce1740b95, ProcessorArchitecture=x86",
hr = pUnkn->QueryInterface(IID_IDispatch, (void**)&pDisp);
_bstr_t name = L"ToString";
DISPID dispid;
hr = pDisp->GetIDsOfNames(IID_NULL, &name.GetBSTR(), 1, GetUserDefaultLCID(),
&dispid);
_variant_t result;
DISPPARAMS params = {NULL, NULL, 0, 0};
hr = pDisp->Invoke(dispid, IID_NULL, GetUserDefaultLCID(), DISPATCH_METHOD, ¶ms, &result, NULL, NULL);
wprintf_s(L"%s = %s\n", static_cast<wchar_t*>(name), static_cast<wchar_t*>(result.bstrVal));
...
As Nicholas pointed out - this approach doesn't do away with COM interop. Everything still happens over COM. The only difference is rather than COM instantiation firing up and causing the .NET Runtime to be hosted in VFP, the small C++ component does it. But .NET works just fine passing back non-COM visible types - the problem with only COM Interop is that you can only *instantiate* what is ComVisible. The wrapper (the .NET portion of it) makes it possible to get at almost any component.
True, but somehow your client is stil limitted to what COM allows on the interface with the "managed" part.
Willy.
.
- References:
- Re: Make a DLL in C# for FoxPro
- From: Rick Strahl
- Re: Make a DLL in C# for FoxPro
- Prev by Date: Re: OpenFileDialog Is Messing With My .Resources File Path [Win C#]
- Next by Date: Re: Use one form instead of multiple forms
- Previous by thread: Re: Make a DLL in C# for FoxPro
- Next by thread: Re: Make a DLL in C# for FoxPro
- Index(es):
Loading