Make a DLL in C# for FoxPro

A

Andrus

Willy,
Anyway, if you need to expose your classes to COM clients (here I mean not
a specific client like a VFP application), you'll have to follow the rules
(COM and obviously .NET), that is you need to author your classes to be
*COM friendly*. How you do this is all explained in MSDN (search for COM
interop), AND you need to register your assembly for COM interop when
deploying.

I understand from your replies that only reasonable way to use .NET is to
create C# wrapper assembly containing COM visible type for
unit-of-work which is called from OLE .

Is it possible to use Reg-free with .NET COM visible assembly ?
This would allow to use .NET from COM client without registering.

I'm also wondering why those guis mentioned in previous links try to create
c++ wrapper dlls .
Calvin in MS FoxPro designer, C++ programmer ,MS Employee. Rick is VFP/.NET
MVP.

No idea why they are creating wrapper dlls in C++ instead of using Reg-Free
or REGASM.

Andrus.
 
W

Willy Denoyette [MVP]

Andrus,

See inline

Willy.
Andrus said:
Willy,


I understand from your replies that only reasonable way to use .NET is to
create C# wrapper assembly containing COM visible type for
unit-of-work which is called from OLE .

Is it possible to use Reg-free with .NET COM visible assembly ?
This would allow to use .NET from COM client without registering.
No, Reg-Free COM is an OS feature (XP and higher) which is currently only
available for native code COM.
Don't forget that .NET -> COM interop is actually more common than COM->.NET
interop, so the need to register .NET classes for COM interop is pretty
rare, and reg-free COM is not a silver bullet either, it's more simple to
regasm an assembly really.
I'm also wondering why those guis mentioned in previous links try to
create c++ wrapper dlls .
Calvin in MS FoxPro designer, C++ programmer ,MS Employee. Rick is
VFP/.NET MVP.

I don't know really, such C++ shims are publically available on the net, you
can use this to bootstrap the CLR for .NET interop without the need to
register a .NET class for COM interop. There is nothing basically wrong with
this, but why throwing a third language in the pack when you can achieve the
same using only two.
All that's being done in C++ can be done in VFP (or VB6 or C++), VFP can
call native C code exports (DECLARE DLL, to call the CorBindToRuntimeEx
function) to load the CLR, VFP can import the mscorlib.tlb, and VFP can
handle raw interface pointers and turn them into IDispatch pointers (see
SYS(3096, ..) function). But even that is IMO too complicated, all you need
to do is build a single shim assembly/class in C# (or whatever .NET
language) and implement a couple of COM friendly methods doing exactly
what's been done in ClrCreateInstance and ClrCreateInstanceFrom.
All you need to do is regasm this single assembly (which is exactly what
Rick is trying to prevent), you don't need to explicitly load the CLR, this
is taken care of when you first create an instance of your shim. The
remaining is exactly the same as illustrated by Rick.

No idea why they are creating wrapper dlls in C++ instead of using
Reg-Free or REGASM.

Well, if you look what Rick says...
<snip
..NET Components require a special utility RegAsm to register rather than
just the standard regsvr32 so getting components registered and updated can
be a pain
/>
it looks like he doesn't like using Regasm. However, I don't see why regasm
would be a special utility and regsvr32 not?
Also, he wrote ...
<snip
no more polluting of the registry with gazillions of COM interfaces ..
/>
well, I don't see why registering a single assembly for COM interop would
pollute the registry, the COM catalog (part of the registry) is exactly
meant for this - COM registration

Willy.
 
R

Rick Strahl

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.

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.

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.

The point is relying on ComVisible components only is extremely limiting as
to what you can actually do.

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.

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.


+++ Rick ---


----- Original Message -----
From: "Willy Denoyette [MVP]" <[email protected]>
Newsgroups: microsoft.public.dotnet.languages.csharp
Sent: Wednesday, January 30, 2008 12:13 PM
Subject: Re: Make a DLL in C# for FoxPro
 
W

Willy Denoyette [MVP]

Rick Strahl said:
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, &params, &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.
 
W

Willy Denoyette [MVP]

Andrus said:
Willy,


I understand from your replies that only reasonable way to use .NET is to
create C# wrapper assembly containing COM visible type for
unit-of-work which is called from OLE .

Is it possible to use Reg-free with .NET COM visible assembly ?
This would allow to use .NET from COM client without registering.


Well, I finally found out that it's perfectly possible to use "COM exposed"
..NET classes (COM Visible) without registering the assembly. That is,
reg-free activation of .NET components is perfectly possible, it took some
time to read all topics about "isolated applications and Side By Side
activation" in MSDN.

Basically what you need is an assembly manifest for both, the component (the
..NET assembly) and the native client application.
These manifests need to reside in the same folder as the component and the
client, or they need to be embedded.
Note that this requires XP SP2, WS2003 SP2, Vista or WS2008. Pre-SP2
versions of XP and WS2003, will require the manifest to be embedded as a
resource.
Keep in mind that the .NET classes need to respect the rules of COM, you
should carefully design your classes with COM interop in mind, this is not
just a matter of applying a ComVisible attribute.

Willy.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top