I've attached a patch that adds support for generic types to RDNZL; the approach used might not be ideal, I'm not sure.
The problem with generics in RDNZL was mainly an issue in GetType making the "BaseType´n[ParameterType1, ...,ParameterTypen]"-syntax fail to construct the correct type if the base type and parameter types lived in different assemblies. Here is an example:
RDNZL-USER 25 > (invoke "System.Type" "GetType" "System.Action`1[System.Int32]") #<RDNZL::CONTAINER System.Type #xBD5B28>
RDNZL-USER 26 > (invoke "System.Type" "GetType" "System.Action`2[System.Int32,System.Int32]") Warning: Returning NULL object from .NET call NIL
Even if it had worked, some syntax would have been nice to let this interact with USE-NAMESPACE.
Now, if all the type-names involved in a generic type-name like above were fully assembly-qualified, GetType would find the correct type. For the above type that would be "System.Action`2[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]], System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
To make it easier to refer to this type, and to make it interact better with USE-NAMESPACE, I propose allowing a list (tree) to denote generic types. Since the length of the list will encode the number of parameters, this also allows one to drop the `n-part of the typename, and denote the type above like this (with the System namespace used:)
'("Action" "Int32" "Int32")
The patch implements this syntax in a particular way that might be debatable on several points. It modifies RESOLVE-TYPE-NAME to accept a list of this format, returning a new list of each type resolved (and hashed). That is, a tree of type names is mapped to a tree of fully qualified type names, with the first element in each list (and sublist) treated specially. Further, it modifies MAKE-TYPE-FROM-NAME so that this too accepts a tree of types, and if so, creates a closed generic type from the base type and parameters. To do this it first retrieves the base type, then calls MakeGenericType on it.
All functions that can take a string as a type-name is further modified to accept also a tree, passing this on to RESOLVE-TYPE-NAME. This allows one to do stuff like
(new '("Func" "Int32" "String") (lambda (x) (format nil "~@r" x)))
and so forth. This all works:
RDNZL-USER 31 > (setf f (new '("Func" "Int32" "String") (lambda (x) (format nil "~@r" x)))) #<RDNZL::CONTAINER System.Func`2[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089],[System.String, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]] #x3BDE2C8>
RDNZL-USER 32 > (invoke f "Invoke" 2008) "MMVIII"
The debatable points:
1. It might have sufficed to just have made RESOLVE-TYPE-NAME create the fully qualified typename directly. This would have either implied doing some string manipulation (like a function I posted earlier) or by creating the type using MakeGenericType and returning the AssemblyQualifiedName. The first alternative didn't feel quite right and the second would be pretty much what I've implemented except that the type would be thrown away after its name was resolved. I thought perhaps it was a bit neater to modify both RESOLVE and MAKE-TYPE-FROM-NAME, keeping the basic function of each basically like the current implementation.
2. Having every function accept trees makes it often neccessary to tests for CONTAINERP, STRINGP and finally CONSP which perhaps isn't that nice.
3. INVOKE accepts a syntax (INVOKE (assembly . type-name) method) where the static method named is called on the type, resolved from type-name using only the assembly given. I extended this to also accept generic types, but how to resolve the parameter-types isn't then completely obvious (the implementation just uses the normal type-lookup for parameter-types.) Also, this function already used CONSP for the type-specifier (compatibly, though).
4. Perhaps using lists like this is to 'bland' syntactically. It might do to have an operator like (generic "Func" "Int32" "String") to return a closed generic type, and make sure all callers accepted the resulting type-object. This would leave RESOLVE-, MAKE-TYPE-FROM and the rest as they were, doing all the extra work in a single spot. But this would pretty much be a type-name too, and one extra word longer than absolutely necessary: (new (generic "Func" "Int32" "String") (lambda (x) ...))
If the patch looks acceptable however, I'll of course update the documentation too.
This contains no support for generic methods. The reason is that these are even more problematic to handle using the given interface. In particular, GetMethod will not retrieve the method (or anything) if a generic method is overloaded; so one has to implement a search manually before MakeGenericMethod can be called (http://blogs.msdn.com/yirutang/archive/2005/09/14/466280.aspx). But I think this is still useful mostly for toying around with the newer C#-features, so proper support can probably wait.
Regards, Iver
On Thu, 14 Feb 2008 01:21:15 +0100, "Iver Odin Kvello" iverodin@gmail.com wrote:
If the patch looks acceptable however, I'll of course update the documentation too.
Thanks. I think that looks reasonable and I've integrated this into 0.12.0 already. I think I also updated all the relevant documentation entries, but you might want to check if I missed something.
FWIW, the Excel example doesn't work for me anymore. This might be due to the changes in the C++ code (and the upgrade to VS 2005), or due to the generic types patch, or due to something being different on my new laptop from how it used to be before, or something entirely different. I don't have enough time to look into this right now, but I'd be happy if someone else did.
Thanks, Edi.
FWIW, the Excel example doesn't work for me anymore. This might be due to the changes in the C++ code (and the upgrade to VS 2005), or due to the generic types patch, or due to something being different on my new laptop from how it used to be before, or something entirely different. I don't have enough time to look into this right now, but I'd be happy if someone else did.
I'm seeing the same thing, no solution yet.
I tried using 0.12.0 with the old (but patched) rdnzl.dll, with this excel.lisp worked, but not with the newest rdznl.dll. I was first guessing that this had something to do with the Interop stuff, as with http://blogs.msdn.com/ptorr/archive/2004/02/05/67872.aspx, but the problem doesn't actually occur until the code has retrieved a Workbooks-object; this object has no methods (not even GetType.) The object created just using 'new' on the ApplicationClass does have methods and can be made visible, quitted and so forth.
It *should* have nothing to do with the generic types, because all types named are simple non-generic types; and therefore the code should work exactly as before; and indeed as noted 0.12.0 does work with the older .dll.
When using both 0.12.0 and the new dll this happens: RDNZL-USER(7): (range-contents :file-name (namestring (translate-logical-pathname #P"rdnzl:examples;example.xls"))) Error: .NET error (System.Exception): Instance method not found: Microsoft.Office.Interop.Excel.Workbooks::Open(System.String,System.Reflection.Missing, (etc etc etc)
Other instance methods not found are GetType and so forth; basically Workbooks is null or something. But with the older dll, (("Last name" "First name" "Superhero") ("Kent" "Clark" "Superman") ("Wayne" "Bruce" "Batman") ("Parker" "Peter" "Spiderman")) is returned.
This older .dll is just the previous version, patched with the Unbox_Any thing, and compiled with VS2008 express.
Regards, Iver
On Thu, 14 Feb 2008 21:47:19 +0100, "Iver Odin Kvello" iverodin@gmail.com wrote:
I'm seeing the same thing, no solution yet.
I tried using 0.12.0 with the old (but patched) rdnzl.dll, with this excel.lisp worked, but not with the newest rdznl.dll. I was first guessing that this had something to do with the Interop stuff, as with http://blogs.msdn.com/ptorr/archive/2004/02/05/67872.aspx, but the problem doesn't actually occur until the code has retrieved a Workbooks-object; this object has no methods (not even GetType.) The object created just using 'new' on the ApplicationClass does have methods and can be made visible, quitted and so forth.
It *should* have nothing to do with the generic types, because all types named are simple non-generic types; and therefore the code should work exactly as before; and indeed as noted 0.12.0 does work with the older .dll.
When using both 0.12.0 and the new dll this happens: RDNZL-USER(7): (range-contents :file-name (namestring (translate-logical-pathname #P"rdnzl:examples;example.xls"))) Error: .NET error (System.Exception): Instance method not found: Microsoft.Office.Interop.Excel.Workbooks::Open(System.String,System.Reflection.Missing, (etc etc etc)
Other instance methods not found are GetType and so forth; basically Workbooks is null or something. But with the older dll, (("Last name" "First name" "Superhero") ("Kent" "Clark" "Superman") ("Wayne" "Bruce" "Batman") ("Parker" "Peter" "Spiderman")) is returned.
This older .dll is just the previous version, patched with the Unbox_Any thing, and compiled with VS2008 express.
Which version of the C++ source code did you use for this? The "official" releases before 0.7.0 were all built with VS 2003 and AFAIR didn't even build with newer version of VS.
Have you tried to compile the DLL from the 0.7.0 source code using VS 2008? I'm asking because I'm beginning to suspect that (my installation of) VS 2005 is the culprit. I've tried with Michael Goffioul's VS 2005 version of 0.5.0 and with Matthew D Swank's VS 2005 version of 0.6.0 and both failed as well.
Strange...
Which version of the C++ source code did you use for this? The "official" releases before 0.7.0 were all built with VS 2003 and AFAIR didn't even build with newer version of VS.
Version 0.6.0, it did build, though I had to change one argument - runtime library I think, to /MD, and getting this warning:
1>cl : Command line warning D9035 : option 'clr:oldsyntax' has been deprecated and will be removed in a future release
I don't know the implications of changing the parameters I did, but the resulting dll worked fine (ACL, LW) so I figured things were OK.
Have you tried to compile the DLL from the 0.7.0 source code using VS 2008? I'm asking because I'm beginning to suspect that (my installation of) VS 2005 is the culprit. I've tried with Michael Goffioul's VS 2005 version of 0.5.0 and with Matthew D Swank's VS 2005 version of 0.6.0 and both failed as well.
Yes; it compiles with no warnings or modifications, but the same error occurs with Workbooks not having the Open (or any other) methods.
Iver
On Fri, 15 Feb 2008 10:07:57 +0100, "Iver Odin Kvello" iverodin@gmail.com wrote:
Version 0.6.0, it did build, though I had to change one argument - runtime library I think, to /MD, and getting this warning:
1>cl : Command line warning D9035 : option 'clr:oldsyntax' has been deprecated and will be removed in a future release
Aha! Where did the "clr:oldsyntax" come from? Can't find it in the tarball.
I think the main difference between 0.6.0 and 0.7.0 is that 0.7.0 doesn't use the "old syntax" anymore. It is based on Michael Goffioul's port which can still be found here:
http://weitz.de/files/RDNZL-cpp-0.5-vc8.tar.bz2
You'll see quite a lot of changes in there - ^ instead of *, uses of cli::pin_ptr, gcnew, safe_cast, etc. One of those must be the reason for the problems we see, but I don't know which one... And I don't understand why these problems only occur with the Excel example. (Maybe the Office library itself is an "old" system?)
Anyway, I think the changes to make the C++ compatible with the "new" syntax are necessary - see the deprecation note you got above. Still, it'd be nice to have the Excel example working again. Any C++ experts out there?
Good news follows -
Aha! Where did the "clr:oldsyntax" come from? Can't find it in the tarball.
I'm guessing VS put it there itself, probably. It did something or other to the project when opened.
Anyway, I think the changes to make the C++ compatible with the "new" syntax are necessary - see the deprecation note you got above. Still, it'd be nice to have the Excel example working again. Any C++ experts out there?
I have gotten some very kind help from a local C++ expert, and the bug seems to be found and fixed with the attached patch. The fix is due to Per Arild Fiskum at selvaag.no and is of course one misplaced character (twice) in InvokeMember, specifically using != instead of == when testing a methodinfo-pointer against nullptr.
It only affected interface-methods, and therefore in particular COM objects of course.
This is 0.7.0 with the patch: RDNZL-USER(7): (range-contents :file-name +initial-filename+) (("Last name" "First name" "Superhero") ("Kent" "Clark" "Superman") ("Wayne" "Bruce" "Batman") ("Parker" "Peter" "Spiderman"))
Iver
On Tue, 19 Feb 2008 13:08:24 +0100, "Iver Odin Kvello" iverodin@gmail.com wrote:
I have gotten some very kind help from a local C++ expert, and the bug seems to be found and fixed with the attached patch.
That's wonderful! Thanks a lot. I'll prepare a new release tonight.
The fix is due to Per Arild Fiskum at selvaag.no
He'll be immortalized in the ChangeLog... :)
and is of course one misplaced character (twice) in InvokeMember, specifically using != instead of == when testing a methodinfo-pointer against nullptr.
Ah, that must have happened when porting to Visual Studio 2005. The code in 0.6.0 simply tested for !mi instead of mi != nullptr. So, the attempt to write cleaner and more idiomatic code ended up in two errors... :)
Thanks again, Edi.
For the record, both tarballs have been updated. I could confirm that the Excel example works again on my machine.