From cc1160505b72eaab5932001ae3879524f03c4bbd Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sat, 21 Dec 2019 20:25:24 -0500 Subject: [PATCH 1/5] more stuff --- com.d | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/com.d b/com.d index 18e5145..c58fabb 100644 --- a/com.d +++ b/com.d @@ -45,15 +45,28 @@ //writeln(obj.opDispatch!("test", int)()); } --- + + I'll show a COM server example later. It is cool to call D objects + from JScript and such. +/ module arsd.com; +// for arrays to/from IDispatch use SAFEARRAY +// see https://stackoverflow.com/questions/295067/passing-an-array-using-com + +// for exceptions +// see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/705fb797-2175-4a90-b5a3-3918024b10b8 +// see: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/0c0bcf55-277e-4120-b5dc-f6115fc8dc38 + /+ - see: program\comtest.d on the laptop. + see: program\cs\comtest.d on the laptop. as administrator: from program\cs c:\Windows\Microsoft.NEt\Framework64\v4.0.30319\regasm.exe /regfile /codebase test.dll + note: use the 64 bit register for 64 bit programs (Framework64) + use 32 for 32 bit program (\Framework\) + sn -k key.snk program\cs\makefile @@ -69,6 +82,16 @@ module arsd.com; and then fully dynamic can be done with opDispatch for teh lulz. +/ +/+ + createComObject returns the wrapped one + wrapping can go dynamic if it is wrapping IDispatch + some other IUnknown gets minimal wrapping (Translate formats) + all wrappers can return lower level stuff on demand. like LL!string maybe is actually an RAII BSTR. + + i also want variant to jsvar and stuff like that. + createRawComObject returns the IUnknown raw one ++/ + import core.sys.windows.windows; import core.sys.windows.com; import core.sys.windows.oaidl; @@ -167,7 +190,7 @@ template Dify(T) { import std.traits; /// -struct ComClient(DVersion, ComVersion) { +struct ComClient(DVersion, ComVersion = IDispatch) { ComVersion innerComObject_; this(ComVersion t) { this.innerComObject_ = t; @@ -209,7 +232,8 @@ struct ComClient(DVersion, ComVersion) { static if(args.length) { VARIANT[args.length] vargs; foreach(idx, arg; args) { - vargs[idx] = toComVariant(arg); + // lol it is put in backwards way to explain MSFT + vargs[$ - 1 - idx] = toComVariant(arg); } disp_params.rgvarg = vargs.ptr; @@ -217,16 +241,47 @@ struct ComClient(DVersion, ComVersion) { } VARIANT result; + EXCEPINFO einfo; + uint argError; - ComCheck(innerComObject_.Invoke( + //ComCheck(innerComObject_.Invoke( + auto hr =innerComObject_.Invoke( dispid, &GUID_NULL, LOCALE_SYSTEM_DEFAULT, // whatever DISPATCH_METHOD, &disp_params, &result, - null, // exception info - null // arg error - ), "Invoke"); + &einfo, // exception info + &argError // arg error + );//, "Invoke"); + + import std.conv; + if(FAILED(hr)) { + if(hr == DISP_E_EXCEPTION) { + auto code = einfo.scode ? einfo.scode : einfo.wCode; + string source; + string description; + if(einfo.bstrSource) { + // this is really a wchar[] but it needs to be freed so.... + source = einfo.bstrSource[0 .. SysStringLen(einfo.bstrSource)].to!string; + SysFreeString(einfo.bstrSource); + } + if(einfo.bstrDescription) { + description = einfo.bstrDescription[0 .. SysStringLen(einfo.bstrDescription)].to!string; + SysFreeString(einfo.bstrDescription); + } + if(einfo.bstrHelpFile) { + // FIXME: we could prolly use this too + SysFreeString(einfo.bstrHelpFile); + // and dwHelpContext + } + + throw new ComException(code, description ~ " (from com source " ~ source ~ ")"); + + } else { + throw new ComException(hr, "Call failed"); + } + } return getFromVariant!(typeof(return))(result); } else { @@ -338,6 +393,9 @@ T getFromVariant(T)(VARIANT arg) { static if(is(T == int)) { if(arg.vt == 3) return arg.intVal; + } else static if(is(T == bool)) { + if(arg.vt == 11) + return arg.boolVal ? true : false; } else static if(is(T == string)) { if(arg.vt == 8) { auto str = arg.bstrVal; @@ -346,6 +404,12 @@ T getFromVariant(T)(VARIANT arg) { } else static if(is(T == IDispatch)) { if(arg.vt == 9) return arg.pdispVal; + } else static if(is(T : IUnknown)) { + // if(arg.vt == 13) + static assert(0); + } else static if(is(T == ComClient!(D, I), D, I)) { + if(arg.vt == 9) + return ComClient!(D, I)(arg.pdispVal); } throw new Exception("Type mismatch, needed "~ T.stringof ~"got " ~ to!string(arg.vt)); assert(0); @@ -432,7 +496,7 @@ mixin template IDispatchImpl() { } catch(Throwable e) { // FIXME: fill in the exception info if(except !is null) { - except.wCode = 1; + except.sCode = 1; import std.utf; except.bstrDescription = SysAllocString(toUTFz!(wchar*)(e.toString())); except.bstrSource = SysAllocString("amazing"w.ptr); From eae5b8477965ac3b8cb9ca322016bef026b96f38 Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sat, 21 Dec 2019 21:45:24 -0500 Subject: [PATCH 2/5] beginning of ole auto array --- com.d | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/com.d b/com.d index c58fabb..c8671e6 100644 --- a/com.d +++ b/com.d @@ -94,6 +94,7 @@ module arsd.com; import core.sys.windows.windows; import core.sys.windows.com; +import core.sys.windows.wtypes; import core.sys.windows.oaidl; import core.stdc.string; @@ -399,6 +400,7 @@ T getFromVariant(T)(VARIANT arg) { } else static if(is(T == string)) { if(arg.vt == 8) { auto str = arg.bstrVal; + scope(exit) SysFreeString(str); return to!string(str[0 .. SysStringLen(str)]); } } else static if(is(T == IDispatch)) { @@ -410,6 +412,45 @@ T getFromVariant(T)(VARIANT arg) { } else static if(is(T == ComClient!(D, I), D, I)) { if(arg.vt == 9) return ComClient!(D, I)(arg.pdispVal); + } else static if(is(T == E[], E)) { + if(arg.vt & 0x2000) { + auto elevt = arg.vt & ~0x2000; + auto a = arg.parray; + scope(exit) SafeArrayDestroy(a); + + auto bounds = a.rgsabound.ptr[0 .. a.cDims]; + + auto hr = SafeArrayLock(a); + if(SUCCEEDED(hr)) { + scope(exit) SafeArrayUnlock(a); + + // BTW this is where things get interesting with the + // mid-level wrapper. it can avoid these copies + + // maybe i should check bounds.lLbound too..... + + static if(is(E == int)) { + if(elevt == 3) { + assert(a.cbElements == E.sizeof); + return (cast(E*)a.pvData)[0 .. bounds[0].cElements].dup; + } + } else static if(is(E == string)) { + if(elevt == 8) { + //assert(a.cbElements == E.sizeof); + //return (cast(E*)a.pvData)[0 .. bounds[0].cElements].dup; + + string[] ret; + foreach(item; (cast(BSTR*) a.pvData)[0 .. bounds[0].cElements]) { + auto str = item; + scope(exit) SysFreeString(str); + ret ~= to!string(str[0 .. SysStringLen(str)]); + } + return ret; + } + } + + } + } } throw new Exception("Type mismatch, needed "~ T.stringof ~"got " ~ to!string(arg.vt)); assert(0); From c3a590d0fbeb57376bdd19653a9ba8cb2b17d76b Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 23 Dec 2019 18:48:30 -0500 Subject: [PATCH 3/5] more stuff! --- jni.d | 176 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 160 insertions(+), 16 deletions(-) diff --git a/jni.d b/jni.d index 3b4e07b..d905686 100644 --- a/jni.d +++ b/jni.d @@ -191,35 +191,114 @@ private string getJavaName(alias a)() { return name; } -/+ /+ Java class file definitions { +/ // see: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6 +import arsd.declarativeloader; + struct cp_info { enum CONSTANT_Class = 7; // sizeof = 2 + struct CONSTANT_Class_info { + @BigEndian: + ushort name_index; + } enum CONSTANT_Fieldref = 9; // sizeof = 4 + struct CONSTANT_Fieldref_info { + @BigEndian: + ushort class_index; + ushort name_and_type_index; + } enum CONSTANT_Methodref = 10; // sizeof = 4 + struct CONSTANT_Methodref_info { + @BigEndian: + ushort class_index; + ushort name_and_type_index; + } enum CONSTANT_InterfaceMethodref = 11; // sizeof = 4 + struct CONSTANT_InterfaceMethodref_info { + @BigEndian: + ushort class_index; + ushort name_and_type_index; + } enum CONSTANT_String = 8; // sizeof = 2 + struct CONSTANT_String_info { + @BigEndian: + ushort string_index; + } enum CONSTANT_Integer = 3; // sizeof = 4 + struct CONSTANT_Integer_info { + @BigEndian: + int bytes; + } enum CONSTANT_Float = 4; // sizeof = 4 + struct CONSTANT_Float_info { + @BigEndian: + float bytes; + } enum CONSTANT_Long = 5; // sizeof = 8, but eats two slots + struct CONSTANT_Long_info { + @BigEndian: + long bytes; + } enum CONSTANT_Double = 6; // sizeof = 8, but eats two slots + struct CONSTANT_Double_info { + @BigEndian: + double bytes; + } enum CONSTANT_NameAndType = 12; // sizeof = 2 + struct CONSTANT_NameAndType_info { + @BigEndian: + ushort name_index; + ushort descriptor_index; + } enum CONSTANT_Utf8 = 1; // sizeof = 2 + length + struct CONSTANT_Utf8_info { + @BigEndian: + ushort length; + @NumElements!length char[] bytes; // actually modified UTF-8 but meh + } enum CONSTANT_MethodHandle = 15; // sizeof = 3 + struct CONSTANT_MethodHandle_info { + @BigEndian: + ubyte reference_kind; + ushort reference_index; + } enum CONSTANT_MethodType = 16; // sizeof = 2; descriptor index + struct CONSTANT_MethodType_info { + @BigEndian: + ushort descriptor_index; + } enum CONSTANT_InvokeDynamic = 18; // sizeof = 4 + struct CONSTANT_InvokeDynamic_info { + @BigEndian: + ushort bootstrap_method_attr_index; + ushort name_and_type_index; + } ubyte tag; - union { - + @Tagged!(tag) + union Info { + @Tag(CONSTANT_Class) CONSTANT_Class_info class_info; + @Tag(CONSTANT_Fieldref) CONSTANT_Fieldref_info fieldref_info; + @Tag(CONSTANT_Methodref) CONSTANT_Methodref_info methodref_info; + @Tag(CONSTANT_InterfaceMethodref) CONSTANT_InterfaceMethodref_info interfaceMethodref_info; + @Tag(CONSTANT_String) CONSTANT_String_info string_info; + @Tag(CONSTANT_Integer) CONSTANT_Integer_info integer_info; + @Tag(CONSTANT_Float) CONSTANT_Float_info float_info; + @Tag(CONSTANT_Long) CONSTANT_Long_info long_info; + @Tag(CONSTANT_Double) CONSTANT_Double_info double_info; + @Tag(CONSTANT_NameAndType) CONSTANT_NameAndType_info nameAndType_info; + @Tag(CONSTANT_Utf8) CONSTANT_Utf8_info utf8_info; + @Tag(CONSTANT_MethodHandle) CONSTANT_MethodHandle_info methodHandle_info; + @Tag(CONSTANT_MethodType) CONSTANT_MethodType_info methodType_info; + @Tag(CONSTANT_InvokeDynamic) CONSTANT_InvokeDynamic_info invokeDynamic_info; } - ubyte[] info; + Info info; } struct field_info { + @BigEndian: enum ACC_PUBLIC = 0x0001; enum ACC_PRIVATE = 0x0002; @@ -235,15 +314,16 @@ struct field_info { ushort name_index; ushort descriptor_index; ushort attributes_count; - attribute_info attributes[attributes_count]; + @NumElements!attributes_count attribute_info[] attributes; } struct method_info { + @BigEndian: ushort access_flags; ushort name_index; ushort descriptor_index; ushort attributes_count; - attribute_info attributes[attributes_count]; + @NumElements!attributes_count attribute_info[] attributes; enum ACC_PUBLIC = 0x0001; enum ACC_PRIVATE = 0x0002; @@ -260,12 +340,14 @@ struct method_info { } struct attribute_info { + @BigEndian: ushort attribute_name_index; uint attribute_length; - ubyte[attribute_length] info; + @NumBytes!attribute_length ubyte[] info; } struct ClassFile { + @BigEndian: enum ACC_PUBLIC = 0x0001; @@ -277,27 +359,58 @@ struct ClassFile { enum ACC_ANNOTATION = 0x2000; enum ACC_ENUM = 0x4000; + const(char)[] className() { + return this.constant(this.constant(this.this_class).info.class_info.name_index).info.utf8_info.bytes; + } - uint magic; + const(char)[] superclassName() { + return this.constant(this.constant(this.super_class).info.class_info.name_index).info.utf8_info.bytes; + } + + Method[] methodsListing() { + Method[] ms; + foreach(met; this.methods) { + Method m; + m.name = this.constant(met.name_index).info.utf8_info.bytes; + m.signature = this.constant(met.descriptor_index).info.utf8_info.bytes; + m.flags = met.access_flags; + ms ~= m; + } + return ms; + } + + static struct Method { + const(char)[] name; + const(char)[] signature; + ushort flags; + } + + + @MustBe(0xcafebabe) uint magic; ushort minor_version; ushort major_version; - ushort constant_pool_count; - cp_info constant_pool[constant_pool_count-1]; + ushort constant_pool_count_; + // the zeroth item of the constant pool is null, but not actually in the file. + ushort constant_pool_count() { return cast(ushort)(constant_pool_count_ - 1); } + auto constant(ushort number) { + if(number == 0) throw new Exception("invalid"); + return constant_pool[number - 1]; + } + @NumElements!constant_pool_count cp_info[] constant_pool; ushort access_flags; ushort this_class; ushort super_class; ushort interfaces_count; - ushort interfaces[interfaces_count]; + @NumElements!interfaces_count ushort[] interfaces; ushort fields_count; - field_info fields[fields_count]; + @NumElements!fields_count field_info[] fields; ushort methods_count; - method_info methods[methods_count]; + @NumElements!methods_count method_info[] methods; ushort attributes_count; - attribute_info attributes[attributes_count]; + @NumElements!attributes_count attribute_info[] attributes; } /+ } end java class file definitions +/ -+/ // semi-FIXME: java.lang.CharSequence is the interface for String. We should support that just as well. // possibly other boxed types too, like Integer. @@ -560,6 +673,10 @@ private enum ImportImplementationString = q{ auto ret = (*env).CallSTATICIntMethod(env, jobj, _jmethodID, DDataToJni(env, args).args); exceptionCheck(env); return ret; + } else static if(is(typeof(return) : IJavaObject)) { + auto ret = (*env).CallSTATICObjectMethod(env, jobj, _jmethodID, DDataToJni(env, args).args); + exceptionCheck(env); + return typeof(return).fromExistingJavaObject(ret); } else static if(is(typeof(return) == long)) { auto ret = (*env).CallSTATICLongMethod(env, jobj, _jmethodID, DDataToJni(env, args).args); exceptionCheck(env); @@ -1085,6 +1202,15 @@ class JavaClass(string javaPackage, CRTP) : IJavaObject { enum Import; /// UDA to indicate you are importing the method from Java. Do NOT put a body on these methods. enum Export; /// UDA to indicate you are exporting the method to Java. Put a D implementation body on these. + static CRTP fromExistingJavaObject(jobject o) { + import core.memory; + auto ptr = GC.malloc(__traits(classInstanceSize, CRTP)); + ptr[0 .. __traits(classInstanceSize, CRTP)] = typeid(CRTP).initializer[]; + auto obj = cast(CRTP) ptr; + obj.internalJavaHandle_ = o; + return obj; + } + /+ /++ D constructors on Java objects don't work right, so this is disabled to ensure @@ -1120,8 +1246,20 @@ class JavaClass(string javaPackage, CRTP) : IJavaObject { import core.stdc.stdio; auto internalJavaClassHandle_ = (*env).FindClass(env, (_javaParameterString[1 .. $-1] ~ "\0").ptr); + + /+ if(!internalJavaClassHandle_) { - fprintf(stderr, ("Cannot find Java class for " ~ CRTP.stringof)); + static if(CRTP.stringof == "ssTest2") { + import std.file; + auto bytes = cast(byte[]) read("Test2.class"); + auto loader = ClassLoader.getSystemClassLoader().getJavaHandle(); + internalJavaClassHandle_ = (*env).DefineClass(env, "wtf/Test2", loader, bytes.ptr, cast(int) bytes.length); + } + } + +/ + + if(!internalJavaClassHandle_) { + fprintf(stderr, ("Cannot find Java class for " ~ CRTP.stringof ~ "\n")); return 1; } @@ -1146,6 +1284,12 @@ class JavaClass(string javaPackage, CRTP) : IJavaObject { __gshared /* immutable */ int function(JNIEnv* env)[] classInitializers_; +/+ +final class ClassLoader : JavaClass!("java.lang", ClassLoader) { + @Import static ClassLoader getSystemClassLoader(); +} ++/ + From f3fdccb62014ed10d3887737eb04ae4bc231f06a Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 23 Dec 2019 18:49:06 -0500 Subject: [PATCH 4/5] new module to help work with files --- declarativeloader.d | 178 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 declarativeloader.d diff --git a/declarativeloader.d b/declarativeloader.d new file mode 100644 index 0000000..a7861a4 --- /dev/null +++ b/declarativeloader.d @@ -0,0 +1,178 @@ +/++ + A declarative file/stream loader/saver. You define structs with a handful of annotations, this read and writes them to/from files. ++/ +module arsd.declarativeloader; + +import std.range; + +// @VariableLength indicates the value is saved in a MIDI like format +// @BigEndian, @LittleEndian +// @NumBytes!Field or @NumElements!Field controls length of embedded arrays +// @Tagged!Field indicates a tagged union. Each struct within should have @Tag(X) which is a value of Field +// @MustBe() causes it to throw if not the given value + +// @NotSaved indicates a struct member that is not actually saved in the file + +enum BigEndian; +enum LittleEndian; +enum VariableLength; +struct NumBytes(alias field) {} +struct NumElements(alias field) {} +struct Tagged(alias field) {} +struct TagStruct(T) { T t; } +auto Tag(T)(T t) { + return TagStruct!T(t); +} +enum NotSaved; +struct MustBeStruct(T) { T t; } +auto MustBe(T)(T t) { + return MustBeStruct!T(t); +} + +static bool fieldSaved(alias a)() { + bool saved; + static if(is(typeof(a.offsetof))) { + saved = true; + static foreach(attr; __traits(getAttributes, a)) + static if(is(attr == NotSaved)) + saved = false; + } + return saved; +} + +static bool bigEndian(alias a)(bool def) { + bool be = def; + static foreach(attr; __traits(getAttributes, a)) { + static if(is(attr == BigEndian)) + be = true; + else static if(is(attr == LittleEndian)) + be = false; + } + return be; +} + +static auto getTag(alias a)() { + static foreach(attr; __traits(getAttributes, a)) { + static if(is(typeof(attr) == TagStruct!T, T)) { + return attr.t; + } + } + assert(0); +} + +union N(ty) { + ty member; + ubyte[ty.sizeof] bytes; +} + +// input range of ubytes... +int loadFrom(T, Range)(ref T t, auto ref Range r, bool assumeBigEndian = false) { + int bytesConsumed; + ubyte next() { + auto bfr = r.front; + r.popFront; + bytesConsumed++; + return bfr; + } + + bool endianness = bigEndian!T(assumeBigEndian); + static foreach(memberName; __traits(allMembers, T)) {{ + static if(is(typeof(__traits(getMember, T, memberName)))) { + alias f = __traits(getMember, T, memberName); + alias ty = typeof(f); + static if(fieldSaved!f) { + endianness = bigEndian!f(endianness); + // FIXME VariableLength + static if(is(ty : ulong) || is(ty : double)) { + N!ty n; + if(endianness) { + foreach(i; 0 .. ty.sizeof) { + version(BigEndian) + n.bytes[i] = next(); + else + n.bytes[$ - 1 - i] = next(); + } + } else { + foreach(i; 0 .. ty.sizeof) { + version(BigEndian) + n.bytes[$ - 1 - i] = next(); + else + n.bytes[i] = next(); + } + } + + // FIXME: MustBe + + __traits(getMember, t, memberName) = n.member; + } else static if(is(ty == struct)) { + bytesConsumed += loadFrom(__traits(getMember, t, memberName), r, endianness); + } else static if(is(ty == union)) { + static foreach(attr; __traits(getAttributes, ty)) + static if(is(attr == Tagged!Field, alias Field)) + enum tagField = __traits(identifier, Field); + static assert(is(typeof(tagField)), "Unions need a Tagged UDA on the union type (not the member) indicating the field that identifies the union"); + + auto tag = __traits(getMember, t, tagField); + // find the child of the union matching the tag... + static foreach(um; __traits(allMembers, ty)) { + if(tag == getTag!(__traits(getMember, ty, um))) { + bytesConsumed += loadFrom(__traits(getMember, __traits(getMember, t, memberName), um), r, endianness); + } + } + } else static if(is(ty == E[], E)) { + static foreach(attr; __traits(getAttributes, f)) { + static if(is(attr == NumBytes!Field, alias Field)) + ulong numBytesRemaining = __traits(getMember, t, __traits(identifier, Field)); + else static if(is(attr == NumElements!Field, alias Field)) { + ulong numElementsRemaining = __traits(getMember, t, __traits(identifier, Field)); + } + } + + static if(is(typeof(numBytesRemaining))) { + static if(is(E : const(ubyte)) || is(E : const(char))) { + while(numBytesRemaining) { + __traits(getMember, t, memberName) ~= next; + numBytesRemaining--; + } + } else { + while(numBytesRemaining) { + E piece; + auto by = loadFrom(e, r, endianness); + numBytesRemaining -= by; + bytesConsumed += by; + __traits(getMember, t, memberName) ~= piece; + } + } + } else static if(is(typeof(numElementsRemaining))) { + static if(is(E : const(ubyte)) || is(E : const(char))) { + while(numElementsRemaining) { + __traits(getMember, t, memberName) ~= next; + numElementsRemaining--; + } + } else static if(is(E : const(ushort))) { + while(numElementsRemaining) { + ushort n; + n = next << 8; + n |= next; + // FIXME all of this filth + __traits(getMember, t, memberName) ~= n; + numElementsRemaining--; + } + } else { + while(numElementsRemaining) { + E piece; + auto by = loadFrom(piece, r, endianness); + numElementsRemaining--; + bytesConsumed += by; + __traits(getMember, t, memberName) ~= piece; + } + } + } else static assert(0, "no way to identify length... " ~ memberName); + + } else static assert(0, ty.stringof); + } + } + }} + + return bytesConsumed; +} From 24a44cee1a94c1cfcfdc0d400cc705e2bcd9932a Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Mon, 23 Dec 2019 23:34:03 -0500 Subject: [PATCH 5/5] remove dependency for now, will fix later --- jni.d | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jni.d b/jni.d index d905686..912f843 100644 --- a/jni.d +++ b/jni.d @@ -194,6 +194,7 @@ private string getJavaName(alias a)() { /+ Java class file definitions { +/ // see: https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6 +version(WithClassLoadSupport) { import arsd.declarativeloader; struct cp_info { @@ -410,6 +411,8 @@ struct ClassFile { @NumElements!attributes_count attribute_info[] attributes; } +} + /+ } end java class file definitions +/ // semi-FIXME: java.lang.CharSequence is the interface for String. We should support that just as well.