IntelliSense support for COM interfaces contained in a type library - phpstorm

Is there a way get normal IntelliSense functionality in PhpStorm for types contained in a COM type library?
In FoxPro I get full IntelliSense as soon as a variable is declared as a COM interface (it pulls the info from the registry); in Delphi and Visual Studio I can have the IDE create some import artifacts for the type libraries (import unit, interop assembly) to make IntelliSense work. However, so far I couldn't find a way to make IntelliSense work with type libraries in PhpStorm.
All mentions of COM type libraries that I found referred only to PHP's ability to load constants (com_load_typelib()), but I want method and parameter info. I wouldn't mind generating files for PhpStorm from my type libraries or even hand-crafting some definitions for the COM interfaces that I use most often. However, I really need normal IntelliSense for the COM interfaces to keep from going crazy.
Is there a way to peel this cat?

As suggested by LazyOne, the manual approach can be as simple as writing a stub declaration and dumping the php file somewhere in the project tree or search path (like an apposite stubs directory):
<?php
/** allows observing the server lock/object counts and unloadability state of a COM module that uses the
* Zrbj.COM.ComServerLocking infrastructure
* #property-read int $Revision revision of the observer module implementation
* #property-read string $ServerDLL executable which houses the code for this observer object
*/
interface ISrvDllObserver
{
/** server lock count information (ICounterInfo) for this object's own server module */
public function GetCountsForOwnModule (): ICounterInfo;
/** server lock count information (ICounterInfo) for a loaded COM module that uses Zrbj.COM.ComServerLocking.
* #param string $module_name basename without file extension is sufficient unless there are multiple loaded
* modules with the same basename; use path and/or file extension to disambiguate
*/
public function GetCountsForLoadedModule (string $module_name): ICounterInfo;
}
From that point on IntelliSense works perfectly.
The manual process may give perfect results, but it is exceedingly laborious. I remembered a scriptable object for processing type libraries that shipped with FoxPro and earlier versions of Visual Studio, tlbinf32.dll (+ tlbinf32.chm). The ProgID to start with is TLI.TLIApplication.
The only extant reference to this tool that I could find at microsoft.com is this old article:
Visual Basic: Inspect COM Components Using the TypeLib Information Object Library
The tool still ships with Visual Studio, but it is now called vstlbinf.dll and no longer documented. vstlbinf.dll still refers to the old tlbinf32.chm as its help file but that doesn't get shipped anymore. The old documentation is still useful for understanding the object model, even though it refers to a version that is two decades older. (Note: it may be necessary to register the DLL manually.)
So I took Delphi and set about taming TLI.TLIApplication. It turned out not to be a whole lot easier than working with ITypeLib, ITypeInfo etc. directly, but the advantage is that TLI.TLIApplication can be used from any scripting language, including PHP itself.
Here`s a sample of the information that can be ripped from a COM type library for use in a PHP stub:
<?php
// 'k:\VS2019\Community\Common7\IDE\vstlbinf.dll' (2021-04-21 10:05:35)
// processed 2021-05-02 22:39:10 by Zrbj.COM.PhpStubs.pas rev. 2021-05-02
//
// Library: TLI
// Version: 1.0
// LIBID : {8B217740-717D-11CE-AB5B-D41203C10000}
// Comment: TypeLib Information
// 32 interface(s) and 3 coclass(es)
//
// coclasses:
// * {8B217752-717D-11CE-AB5B-D41203C10000} -> _SearchHelper (ProgID TLI.SearchHelper)
// 'Helper object for GetMembersWithSubString and multiple TypeLibs'
// * {8B217746-717D-11CE-AB5B-D41203C10000} -> _TypeLibInfo (ProgID TLI.TypeLibInfo)
// 'TypeLib information'
// * {8B21775E-717D-11CE-AB5B-D41203C10000} -> _TLIApplication (ProgID TLI.TLIApplication)
// 'TLIApplication object'
/// {8B21774B-717D-11CE-AB5B-D41203C10000} dual nonextensible dispatchable
/** VarType information for parameters and return types
* #property-read TypeInfo $TypeInfo Type information for VT_PTR VarType
* #property-read int $TypeInfoNumber TypeInfo number for 0 VarType (Cheaper than TypeInfo property)
* #property-read variant $TypedVariant Get a variant with this VarType
* #property-read bool $IsExternalType Is TypeInfo external to this library
* #property-read TypeLibInfo $TypeLibInfoExternal External typelib. Same as TypeInfo.Parent.
* #property-read int $PointerLevel Dereferencing level of type
* #property-read int $VarType VarType of Parameter
* #property-read int $ElementPointerLevel Dereferencing level for type of an array element
*/
interface VarTypeInfo
{
/** Get bounds for VT_VECTOR array. LBound in column 1, UBound in column 2. */
public function ArrayBounds (int $Bounds): int;
}
/// {8B217749-717D-11CE-AB5B-D41203C10000} dual nonextensible dispatchable
/** Parameter Information
* #property-read string $Name Name of the object
* #property-read bool $Optional Optional Parameter
* #property-read VarTypeInfo $VarTypeInfo VarTypeInfo object for this parameter
* #property-read bool $Default Default Parameter
* #property-read variant $DefaultValue Default value
* #property-read bool $HasCustomData Check if custom data is available
* #property-read CustomDataCollection $CustomDataCollection Custom data GUIDs and Values
* #property-read int $Flags Parameter Flags
*/
interface ParameterInfo
{
}
...
Conspicuously absent are parameter comments, because there is no such thing in Microsoft IDL or type libraries. But on the whole the result of processing type libraries into PHP stubs seems quite satisfactory, and it certainly makes working with COM objects in PHP a lot easier.

Related

Testing type matching differences in Java 6 and Java 8

I updated an application JDK from 1.6 to 1.8 but I kept the language level 1.6 in IntelliJ.
After updating I got compilation error in some tests in assertThat statements with checking type of an object. The code is like this:
assertThat((Class) myList.get(0).getMyClassType(), is(equalTo(MySubclass.class)));
myList is looking like this:
List<MyClassDefinition> myList = myClassDefinition.getMyClassDefinitions(reader);
Where definition of MyClassDefinition and getMyClassType() are like this:
public class MyClassDefinition {
private Class<? extends MyClass> classType;
public Class<? extends MyClass> getMyClassType() {
return classType;
}
}
and MySubclass is Myclass's subclass:
public class MySubclass extends MyClass {
#Override
public void initialise() {
// some action here!
}
}
The definition of MyClass is
public abstract class MyClass extends AnotherClass<AnotherType>{
//Definitions
}
The import for Assert is and equals library is this:
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
and the hamcrest version is hamcrest-all-1-3
After changing project SDK to jdk 1.8 I am getting this error message:
Error:(136, 9) java: no suitable method found for assertThat(java.lang.Class,org.hamcrest.Matcher<java.lang.Class<MySubClass>>)
method org.hamcrest.MatcherAssert.assertThat(java.lang.String,boolean) is not applicable
(argument mismatch; java.lang.Class cannot be converted to java.lang.String)
method org.hamcrest.MatcherAssert.<T>assertThat(java.lang.String,T,org.hamcrest.Matcher<? super T>) is not applicable
(cannot infer type-variable(s) T
(actual and formal argument lists differ in length))
method org.hamcrest.MatcherAssert.<T>assertThat(T,org.hamcrest.Matcher<? super T>) is not applicable
(cannot infer type-variable(s) T
(argument mismatch; org.hamcrest.Matcher<java.lang.Class<MySubClass>> cannot be converted to org.hamcrest.Matcher<? super java.lang.Class>))
application is build with Ant and we have added hamcrest jar file to class file, so there is no change when we are altering JDK in project.
So my question is why this code is working with JDK 1.6 but not working with 1.8 while I am using same level of language (1.6) for compilation? Is it depend to different libraries some how?
Apparently, you had a problem with the generic type signatures which you worked around by inserting a type cast to the raw type Class, which causes the compiler to treat the entire statement as an unchecked operation using raw types only.
As you can see from the compiler message, org.hamcrest.Matcher<java.lang.Class<MySubClass>> cannot be converted to org.hamcrest.Matcher<? super java.lang.Class>, it now did a generic type check which (correctly) failed, but imho, it shouldn’t do that type check under Java 6 language rules. The problem stems from the fact that JDK8 doesn’t include the original Java 6 compiler but rather let the new compiler attempt to compile using the old rules, which may not be that precise.
But regardless of whether the behavior is correct or not, I wouldn’t expect a fix for it, as emulating the old Java 6 language rules is not of a high priority.
Note that with source level 1.8, the code compiles without problems, even without the raw type cast. The original problem stems from the restrictive signature of CoreMatchers.equalTo(…). The standalone expression equalTo(InstanceOfFoo) returns a Matcher<Foo>, which being passed to assertThat allows to only test instances of Foo (it’s even worse when using isA(Foo.class) which also can only test instances of Foo, which makes the test pointless).
With Java 8, there is target type inference, which allows, e.g.
Matcher<Object> m = equalTo(InstanceOfFoo);, which will infer Object for <T> and passes, as InstanceOfFoo is also assignable to Object. This also works in conjunction with the compound assertThat statement of your question, inferring a suitable type.
This guides us to the general solution that works with all language levels. Just use
assertThat(myList.get(0).getMyClassType(), is(equalTo((Object)MySubclass.class)));
Class<MySubclass> is, of course, assignable to Object which makes the cast a no-op. But then, getting a Matcher<Object>, you can check every object you like, including the result of myList.get(0).getMyClassType(), which is, of course, also assignable to Object. Unlike your original workaround, this doesn’t bear any raw type usage nor unchecked operation.
You could also use an explicit type instead of a cast, CoreMatchers.<Object>equalTo(MySubclass.class), but this eliminates the benefit of import static.
By the way, when matching Class objects, you may use sameInstance instead of equalTo.

php storm customize docblock

Is there a way to customize php storm's way of generating the docblocks ?
By default, I would like to have unknown type vars considered as strings.
This is the default way of generating docblock
(using php storm 4.01)
/**
* #param $name
*/
private function test($name){
}
And I would like this instead if possible :
/**
* #param string $name
*/
private function test($name){
}
No, you cannot provide default type for generated #param.
PhpStorm provides correct type whenever it is possible to detect from function declaration. In your example it is not possible (it can be anything).
And I personally do not see a real reason why it needs to be a string (or any other type) by default. Unknown/missing type tells me that I need to finish editing this PHPDoc block so it reflects the actual/correct parameter type. With default type entered already such check is not possible (what if the type should be some class (e.g. Person) or array instead?)
P.S.
Kind of related (at least to the actual subject):
This is the Feature Request ticket for Editable PHPDoc Template (that can define what #tags and in what order should be used): http://youtrack.jetbrains.com/issue/WI-11111

Should we overload the meaning of configuration settings?

Imagine an instance of some lookup of configuration settings called "configuration", used like this:
if(! string.IsNullOrEmpty(configuration["MySetting"])
{
DoSomethingWithTheValue(configuration["MySetting"]);
}
The meaning of the setting is overloaded. It means both "turn this feature on or off" and "here is a specific value to do something with". These can be decomposed into two settings:
if(configuration["UseMySetting"])
{
DoSomethingWithTheValue(configuration["MySetting"]);
}
The second approach seems to make configuration more complicated, but slightly easier to parse, and it separate out the two sorts of behaviour. The first seems much simpler at first but it's not clear what we choose as the default "turn this off" setting. "" might actually a valid value for MySetting.
Is there a general best practice rule for this?
I find the question to be slightly confusing, because it talks about (1) parsing, and (2) using configuration settings, but the code samples are for only the latter. That confusion means that my answer might be irrelevant to what you intended to ask. Anyway...
I suggest an approach that is illustrated by the following pseudo-code API (comments follow afterwards):
class Configuration
{
void parse(String fileName);
boolean exists(String name);
String lookupString(String name);
String lookupString(String name, String defaultValue);
int lookupInt(String name);
int lookupInt(String name, int defaultValue);
float lookupFloat(String name);
float lookupFloat(String name, float defaultValue);
boolean lookupBoolean(String name);
boolean lookupBoolean(String name, boolean defaultValue);
... // more pairs of lookup<Type>() operations for other types
}
The parse() operation parses a configuration file and stores the parsed data in a convenient format, for example, in a map or hash-table. (If you want, parse() can delegate the parsing to a third-party library, for example, a parser for XML, Java Properties, JSON, .ini files or whatever.)
After parsing is complete, your application can invoke the other operations to retrieve/use the configuration settings.
A lookup<Type>() operation retrieves the value of the specified name and parses it into the specified type (and throws an exception if the parsing fails). There are two overloadings for each lookup<Type>() operation. The version with one parameter throws an exception if the specified variable does not exist. The version with an extra parameter (denoting a default value) returns that default value if the specified variable does not exist.
The exists() operation can be used to test whether a specified name exists in the configuration file.
The above pseudo-code API offers two benefits. First, it provides type-safe access to configuration data (which wasn't a stated requirement in your question, but I think it is important anyway). Second, it enables you to distinguish between "variable is not defined in configuration" and "variable is defined but its value happens to be an empty string".
If you have already committed yourself to a particular configuration syntax, then just implement the above Configuration class as a thin wrapper around a parser for the existing configuration syntax. If you haven't already chosen a configuration syntax and if your project is in C++ or Java, then you might want to look at my Config4* library, which provides a ready-to-use implementation of the above pseudo-code class (with a few extra bells and whistles).

Deserialization problem: Error when deserializing from a different program version

I finally decided myself to post my problem, after a couple of hours spent searching the Internet for solutions and trying some.
[Problem Context]
I am developing an application which will be deployed in two parts:
an XML Importer tool: its role is to Load/Read an xml file in order to fill some datastructures, which are afterwards serialized into a binary file.
the end user application: it will Load the binary file generated by the XML Importer and do some stuff with the recovered data structures.
For now, I only use the XML Importer for both purposes (meaning I first load the xml and save it to a binary file, then I reopen the XML Importer and load my binary file).
[Actual Problem]
This works just fine and I am able to recover all the data I had after XML loading, as long as I do that with the same build of my XML Importer. This is not viable, as I will need at the very least two different builds, one for the XML Importer and one for the end user application. Please note that the two versions of the XML Importer I use for my testing are exactly the same concerning the source code and thus the datastructures, the only difference lies in the build number (to force a different build I just add a space somewhere and build again).
So what I'm trying to do is:
Build a version of my XML Importer
Open the XML Importer, load an XML file and save the resulting datastructures to a binary file
Rebuild the XML Importer
Open the XML Importer newly built, load the previously created binary file and recover my datastructures.
At this time, I get an Exception:
SerializationException: Could not find type 'System.Collections.Generic.List`1[[Grid, 74b7fa2fcc11e47f8bc966e9110610a6, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]'.
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadType (System.IO.BinaryReader reader, TypeTag code)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadTypeMetadata (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObjectInstance (System.IO.BinaryReader reader, Boolean isRuntimeObject, Boolean hasTypeInfo, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info)
System.Runtime.Serialization.Formatters.Binary.ObjectReader.ReadObject (BinaryElement element, System.IO.BinaryReader reader, System.Int64& objectId, System.Object& value, System.Runtime.Serialization.SerializationInfo& info)
For your information (don't know if useful or not), the actual type it is struggling to deserialize is a List, Grid being a custom Class (which is correctly serializable, as I am able to do it when using the same version of XML Importer).
[Potential Solution]
I do believe it comes from somewhere around the Assembly, as I read many posts and articles about this. However, I already have a custom Binder taking care of the differences of Assembly names, looking like this:
public sealed class VersionDeserializationBinder : SerializationBinder
{
public override Type BindToType( string assemblyName, string typeName )
{
if ( !string.IsNullOrEmpty( assemblyName ) && !string.IsNullOrEmpty( typeName ) )
{
Type typeToDeserialize = null;
assemblyName = Assembly.GetExecutingAssembly().FullName;
// The following line of code returns the type.
typeToDeserialize = Type.GetType( String.Format( "{0}, {1}", typeName, assemblyName ) );
return typeToDeserialize;
}
return null;
}
}
which I assign to the BinaryFormatter before deserializing here:
public static SaveData Load (string filePath)
{
SaveData data = null;//new SaveData ();
Stream stream;
stream = File.Open(filePath, FileMode.Open);
BinaryFormatter bformatter = new BinaryFormatter();
bformatter.Binder = new VersionDeserializationBinder();
data = (SaveData)bformatter.Deserialize(stream);
stream.Close();
Debug.Log("Binary version loaded from " + filePath);
return data;
}
Do any of you guys have an idea on how I could fix it? Would be awesome, pretty please :)
Move the working bits to a separate assembly and use the assembly in both "server" and "client". Based on your explanation of the problem, this should get around the "wrong version" problem, if that is the core issue. I would also take any "models" (i.e. bits of state like Grid) to a domain model project, and use that in both places.
I just bumped into your thread while I had the same problem. Especially your code sample with the SerializationBinder helped me a lot. I just had to modify it slightly to tell a difference between my own assemblies and those of Microsoft. Hopefully it still helps you, too:
sealed class VersionDeserializationBinder : SerializationBinder
{
public override Type BindToType(string assemblyName, string typeName)
{
Type typeToDeserialize = null;
string currentAssemblyInfo = Assembly.GetExecutingAssembly().FullName;
//my modification
string currentAssemblyName = currentAssemblyInfo.Split(',')[0];
if (assemblyName.StartsWith(currentAssemblyName))assemblyName = currentAssemblyInfo;
typeToDeserialize = Type.GetType(string.Format("{0}, {1}", typeName, assemblyName));
return typeToDeserialize;
}
}
I believe the problem is that you are telling it to look for List<> in the executing assembly, whereas in fact it is in the System assembly. You should only re-assign the assembly name in your binder if the original assembly is one of yours.
Also, you might have to handle the parameter types for generics specifically in the binder, by parsing out the type name and making sure the parameter types are not specific to the foreign assembly when you return the parameterized generic type.

Auto generate function documentation in Visual Studio

I was wondering if there is a way (hopefully keyboard shortcut) to create auto generate function headers in visual studio.
Example:
Private Function Foo(ByVal param1 As String, ByVal param2 As Integer)
And it would automagically become something like this...
'----------------------------------
'Pre:
'Post:
'Author:
'Date:
'Param1 (String):
'Param2 (Integer):
'Summary:
Private Function Foo(ByVal param1 As String, ByVal param2 As Integer)
Make that "three single comment-markers"
In C# it's ///
which as default spits out:
/// <summary>
///
/// </summary>
/// <returns></returns>
Here's some tips on editing VS templates.
GhostDoc!
Right-click on the function, select "Document this" and
private bool FindTheFoo(int numberOfFoos)
becomes
/// <summary>
/// Finds the foo.
/// </summary>
/// <param name="numberOfFoos">The number of foos.</param>
/// <returns></returns>
private bool FindTheFoo(int numberOfFoos)
(yes, it is all autogenerated).
It has support for C#, VB.NET and C/C++. It is per default mapped to Ctrl+Shift+D.
Remember: you should add information beyond the method signature to the documentation. Don't just stop with the autogenerated documentation. The value of a tool like this is that it automatically generates the documentation that can be extracted from the method signature, so any information you add should be new information.
That being said, I personally prefer when methods are totally selfdocumenting, but sometimes you will have coding-standards that mandate outside documentation, and then a tool like this will save you a lot of braindead typing.
///
is the shortcut for getting the Method Description comment block.
But make sure you have written the function name and signature before adding it.
First write the Function name and signature.
Then above the function name just type ///
and you will get it automatically
Visual Assist has a nice solution too, and is highly customizable.
After tweaking it to generate doxygen-style comments, these two clicks would produce -
/**
* Method: FindTheFoo
* FullName: FindTheFoo
* Access: private
* Qualifier:
* #param int numberOfFoos
* #return bool
*/
private bool FindTheFoo(int numberOfFoos)
{
}
(Under default settings, its a bit different.)
Edit:
The way to customize the 'document method' text is under VassistX->Visual Assist Options->Suggestions, select 'Edit VA Snippets', Language: C++, Type: Refactoring, then go to 'Document Method' and customize. The above example is generated by:
To Insert The Snippet: with cursor in method name/signature, alt+shift+q > "document method"
Normally, Visual Studio creates it automatically if you add three single comment-markers above the thing you like to comment (method, class).
In C# this would be ///.
If Visual Studio doesn't do this, you can enable it in
Options->Text Editor->C#->Advanced
and check
Generate XML documentation comments for ///
In visual basic, if you create your function/sub first, then on the line above it, you type ' three times, it will auto-generate the relevant xml for documentation. This also shows up when you mouseover in intellisense, and when you are making use of the function.
You can use code snippets to insert any lines you want.
Also, if you type three single quotation marks (''') on the line above the function header, it will insert the XML header template that you can then fill out.
These XML comments can be interpreted by documentation software, and they are included in the build output as an assembly.xml file. If you keep that XML file with the DLL and reference that DLL in another project, those comments become available in intellisense.
I'm working on an open-source project called Todoc which analyzes words to produce proper documentation output automatically when saving a file. It respects existing comments and is really fast and fluid.
http://todoc.codeplex.com/