I fixed all the problems described here (and one additional), and posted the modified
csharp-mode.el (v0.7.1) at emacswiki
The csharp-mode I use is almost really good.
It works for most things, but has a few problems:
#if / #endif tags break indentation, but only within the scope of a method.
attributes applied to fields within a struct, break indentation. (sometimes, see example)
within classes that implement interfaces, the indenting is broken. from that point forward.
Literal strings (prefixed with #) do not fontify correctly, and in fact break fontification from that point forward in the source file, if the last character in the literal string is a slash.
I think there are some other problems, too.
I'm not a mode writer.
Has anyone got improvements on that mode?
anyone want to volunteer to fix these few things?
example code
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Xml.Serialization;
namespace Cheeso.Says.TreyIsTheBest
{
public class Class1
{
private void Method1()
{
// Problem 1: the following if / endif pair causes indenting to break.
// This occurs only within the scope of a method. If the #if/#endif is
// outside of a method, then the indenting does not break.
#if DIAGS
// this first line of code within the conditional scope
// is indented
String StringNumber1;
// the second line of code within the conditional scope
// is un-indented
public String StringNumber2;
#endif
// The endif is where I expect it to be, I guess.
// It's in-line with the matched #if. But the comments here
// are weirdly indented still further. ??
}
// The prior close-curly is indented 2 units more than I would expect.
}
// the close-curly for the class is indented correctly.
// ==================================================================
// ------------------------------------------------------------------
[StructLayout(LayoutKind.Sequential,
CharSet = CharSet.Unicode)]
public struct Class2
{
// Problem 2: when there is an attribute applied to a field
// within a struct, and the attribute include a newline, then
// the field indents strangely. See also "Note 1", and "Note 2"
// below.
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 128)]
public int Value1;
// Note 1: this indents fine.
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)]
public int Value2;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public class Class3
{
public short PrintNameLength;
[MarshalAs(UnmanagedType.ByValArray,
SizeConst = 128)]
// Note 2: this indents just fine
public int Value1;
}
// ==================================================================
// ------------------------------------------------------------------
// Linq Syntax is not a problem as I had originally thought
public class Class4
{
#region eee
#endregion
private void Method1()
{
var files = Directory.GetFiles("Something");
var selection = from f in files
where System.IO.Path.GetFileName(f).StartsWith("C")
select f;
foreach (var e in selection)
Console.WriteLine(e);
}
}
// ==================================================================
// ------------------------------------------------------------------
public interface IGuess { }
public class Class5 : IGuess
{
// Problem 3: When a #region tag is used inside a class that
// implements an interface (or derives from a class) the line
// following the region tag gets indented one extra unit.
#region utility
private static System.Random rnd= new System.Random();
private string FigureCategory()
{
return "unknown";
}
#endregion
// You can also see artifacts of the same confusion in
// methods that have multiple attributes. Again, this only
// occurs when the containing class implements a particular
// interface or derives from a parent class.
[System.Web.Services.WebMethodAttribute()]
[return: System.Xml.Serialization.XmlElementAttribute("getContainerObjectsReturn")]
public String Method1()
{
return "Hello.";
}
}
// ==================================================================
// ------------------------------------------------------------------
public class Pippo
{
// Problem 4: when the final char in an "escaped" string literal is a
// slash, indenting and fontification breaks.
List<string> directories = new List<string> {
#"C:\Temp\sub1\",
// The emacs parser thinks the string has not ended.
// This comment is fontified and indented as if it is in
// the middle of a string literal.
#"C:\Temp\sub2\",
// Because we have another \" at the end of a string,
// emacs now thinks we're "out" of the string literal.
// This comment is now indented and fontified correctly.
#"C:\Home\"
// A third \", and now emacs thinks we're back inside the string literal.
// The rest of the code will be treated as if it were inside the string literal.
};
protected void Page_Load(object sender, EventArgs e)
{
Console.WriteLine("Hello {0}", "world");
}
}
}
This snippet of advice appears to fixes the first half of the indentation problem #1. I'm hoping it won't cause problems elsewhere. It just looks for the condition that is causing the bad indentation (syntax statement-cont and #if or #endif (well, just #)) at the beginning of that) and returns the syntax deduced from before that point. Looks good to me, but I'm not the judge here.
(defvar csharp-mode-syntax-table-no-special-slash
(let ((res (copy-syntax-table csharp-mode-syntax-table)))
(modify-syntax-entry ?\\ "w" res)
res)
"same as regular csharp-mode-syntax-table, only \\ is not an escape char")
(defadvice c-guess-basic-syntax (after c-guess-basic-syntax-csharp-hack activate)
"following an #if/#endif, indentation gets screwey, fix it"
(let ((res ad-return-value))
(save-excursion
(save-match-data
(cond ((and (eq major-mode 'csharp-mode)
(eq 'statement-cont (caar res))
(progn
(goto-char (cadar res))
(looking-at "#")))
;; when following a #if, try for a redo
(goto-char (cadar res))
(setq ad-return-value (c-guess-basic-syntax)))
((and (eq major-mode 'csharp-mode)
(eq 'string (caar res)))
;; inside a string
;; check to see if it is a literal
;; and if so, redo with modified syntax table
(let ((p (point))
(extent (c-literal-limits)))
(when extent
(goto-char (- (car extent) 1))
(when (looking-at "#\"")
;; yup, a string literal
(with-syntax-table csharp-mode-syntax-table-no-special-slash
(goto-char p)
(setq ad-return-value (c-guess-basic-syntax))))))))))))
The close curly brace is still indented improperly. I think folks who maintain the indentation code would know how to make changes. It's beyond the scope of my understanding. There are 104 different "cases" of indentation managed by the cc-engine...
(Same with indentation for Problems 2 & 3, they all are deemed to be a part of 'statement-cont syntax). To see the what the cc-engine thinks the syntax under the current point is, do: C-c C-s.
And regarding Problem 4, the literal string, the above seems to properly indent the literal string, but fontification is still broken.
Related
Hi all you c# wizards!
I need to store all the memory offset values of (packed) nested structs within these respective structs.
Recusively looping through all the members works fine so far. Also, i get the appropriate memory offset values.
This struct contraption might contain several dozends of structs, and several hundreds of other members in the end.
But i do this whole thing at initialization time, so CPU performance won't be an issue here.
But:
In this iteration process, it seems i have trouble accessing the actual instances of those structs. As it turns out, when i try to store these offset values, they don't end up where i need them (of course, i need them in the instance "SomeStruct1" and its containing other struct instances, but the debugger clearly shows me the init values (-1)).
I suspect "field_info.GetValue" or "obj_type.InvokeMember" is not the proper thing to get the object reference? Is there any other way to loop through nested struct instances?
Please help! I've desperately debugged and googled for three days, but i'm so out of ideas now...
Thanks for your efforts!
-Albert
PS - the reason i do this unusual stuff:
I communicate between two embedded CPU cores via the mentioned nested struct (both are mixed c/c++ projects). This works like a charm, as both cores share the same memory, where the struct resides.
Additionally, i have to communicate between a c# host application and theses embedded cores, so i thought it could be a neat thing, if i implement a third instance of this struct. Only this time, i oviously can't use shared RAM. Instead, i implement value setters and getters for the data-holding members, find out the memory offset as well as the lenght of the data-holding members, and feed this information (along with the value itself) via USB or Ethernet down to the embedded system - so the "API" to my embedded system will simply be a struct. The only maintenance i have to do every thime i change the struct: i have to copy the holding .h file (of the embedded project) to a .cs file (host project).
I know it's crazy - but it works now.
Thanks for your interest. -Albert
This is a simplified (buggy, see below) example that should compile and execute (WinForms, c#7.3):
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace CodingExample
{
public interface Interf
{
Int32 Offset {get; set; }
}
[StructLayout (LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct sSomeStruct2 : Interf
{
public sSomeStruct2 (bool dummy)
{
Offset = -1;
SomeMember3 = 0;
}
public Int32 Offset {get; set; }
public Int32 SomeMember3;
// much more various-typed members (e. g. nested structs)...
}
[StructLayout (LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct sSomeStruct1 : Interf
{
public sSomeStruct1 (bool dummy)
{
Offset = -1;
SomeMember1 = 0;
SomeStruct2 = new sSomeStruct2 (true);
SomeMember2 = 0;
}
public Int32 Offset {get; set; }
public Int32 SomeMember1;
public sSomeStruct2 SomeStruct2;
public Int16 SomeMember2;
// much more various-typed members...
}
public partial class Form1 : Form
{
void InitializeOffsets (object obj)
{
Console.WriteLine ("obj: {0}", obj);
Type obj_type = obj.GetType ();
foreach (FieldInfo field_info in obj_type.GetFields ())
{
string field_name = field_info.Name;
Int32 offset = (Int32) Marshal.OffsetOf (obj_type, field_name);
Type field_type = field_info.FieldType;
bool is_leafe = field_type.IsPrimitive;
// none of theses three options seem to give me the right reference:
// object node_obj = field_info.GetValue (obj);
// object node_obj = field_info.GetValue (null);
object node_obj = obj_type.InvokeMember (field_name, BindingFlags.GetField, null, obj, null);
Console.WriteLine ("field: {0}; field_type: {1}; is_leafe: {2}; offset: {3}", field_name, field_type, is_leafe, offset);
if (! is_leafe)
{
// this writes not as expected:
(node_obj as Interf).Offset = offset;
InitializeOffsets (node_obj);
}
}
}
sSomeStruct1 SomeStruct1;
public Form1 ()
{
InitializeComponent ();
SomeStruct1 = new sSomeStruct1 (true);
InitializeOffsets (SomeStruct1);
}
}
}
Meanwhile i found out, what i did wrong:
i have to do boxing, so i can use "ref" when i call my initialize function:
// instead of this:
SomeStruct1 = new sSomeStruct1 (true);
// i have to do it this way:
object boxed_SomeStruct1 = new sSomeStruct1 (true);
InitializeOffsets (ref boxed_SomeStruct1);
SomeStruct1 = (sSomeStruct1) boxed_SomeStruct1;
Within the "InitializeOffsets" function, "field_info.GetValue (obj)" delivers a copy of my member object. That's why i have to copy the modified copy back at the very end of the foreach loop:
field_info.SetValue (obj, node_obj);
After these changes, the code works as intended.
Thanks for your interest. -Albert
I'm trying to build a PostScript to PDF Converter using Ghostscript.Net.
The Args that GetArgs return, are the ones I usually use to call gswin32c.exe and they work fine.
But every time i call Process, i get an error Saying "An error occured when call to 'gsapi_init_with_args' is made: -100". Googling that error didn't bring anything up so I thought I might ask here.
Are there differnet arguments to consider when calling the .dll directly with Ghostscript.net? Or did I made a mistake somewhere else?
Here's my class:
public class PdfConverter
{
#region Private Fields
private List<GhostscriptVersionInfo> _Versions = GhostscriptVersionInfo.GetInstalledVersions(GhostscriptLicense.GPL | GhostscriptLicense.AFPL | GhostscriptLicense.Artifex);
#endregion
#region Private Properties
private GhostscriptVersionInfo Version { get; set; }
#endregion
#region Construction
public PdfConverter()
{
Version = GhostscriptVersionInfo.GetLastInstalledVersion();
}
#endregion
#region Public Members
public bool ConvertToPdf(DirectoryInfo dir)
{
var d = dir;
if(!d.Exists)
return false;
var postScriptFiles = d.GetFiles("*.ps");
var pdfFiles = postScriptFiles.Select(psf => new FileInfo(Path.ChangeExtension(psf.FullName, ".pdf")));
foreach(var file in postScriptFiles) {
//ThreadPool.QueueUserWorkItem(new WaitCallback((o) => {
Process(file, new FileInfo(Path.ChangeExtension(file.FullName, ".pdf")));
//}));
}
pdfFiles.ForEach(pdf => pdf?.Refresh());
return pdfFiles.All(pdf => pdf.Exists);
}
#endregion
#region Private Helpers
private void Process(FileInfo inputFile, FileInfo outputFile)
{
Console.WriteLine($"Converting {inputFile} to {outputFile}");
var proc = new GhostscriptProcessor(Version, true);
proc.Process(GetArgs(inputFile, outputFile).ToArray(), new ConsoleStdIO(true, true, true));
}
private IEnumerable<string> GetArgs(FileInfo inputFile, FileInfo outputFile)
{
return new [] {
$"-q ",
$"-sDEVICE=pdfwrite",
$"-dSAFER",
$"-dNOPAUSE",
$"-dBATCH",
$"-sPAPERSIZE=a4",
$"-dEmbedAllFonts=true",
$"-dAutoRotatePages=/None",
$"-sOutputFile=\"{outputFile.FullName}\"",
$"-dCompatibilityLevel#1.4",
$"-c .setpdfwrite",
$"-f \"{inputFile.FullName}\""
};
}
#endregion
}
Edit:
I forgot to mention: To implement it i had to make my own GhostsdcriptStdIO class. I admit that I'm not entirely sure if I did this right. Although it does get instanciated without exceptions, and override StdOut(...) get's called, and the output is written to the console as expected. override void StdError(...) get's called as well. And also written to the console as expeted.
The Output of the error btw is:
"**** Could not open the file "c:\temp\test.pdf""
"**** Unable to open the initial device, quitting."
Here's my ConsoleStdIO class:
public class ConsoleStdIO : Ghostscript.NET.GhostscriptStdIO
{
#region Construction
public ConsoleStdIO(bool handleStdIn, bool handleStdOut, bool handleStdError) : base(handleStdIn, handleStdOut, handleStdError) { }
#endregion
#region Overrides
public override void StdError(string error)
{
var foo = Encoding.Default.GetBytes(error);
var lenght = foo.Length;
using (var err = Console.OpenStandardError()) {
if(err.CanWrite)
err.Write(foo, 0, lenght);
}
}
public override void StdIn(out string input, int count)
{
byte[] bytes = new byte[0];
using(var stdInput = Console.OpenStandardInput()) {
stdInput.Read(bytes, 0, count);
}
input = Encoding.Default.GetString(bytes);
}
public override void StdOut(string output)
{
var foo = Encoding.Default.GetBytes(output);
var lenght = foo.Length;
using (var err = Console.OpenStandardError()) {
if(err.CanWrite)
err.Write(foo, 0, lenght);
}
}
#endregion
}
Again: doing the same operation with the exact same files and arguments using gswin32c.exe works fine.
Happy Hacking
Error -100 is gs_error_Fatal, which means 'something catastrophic went wrong'. Its an indication that the program failed to start up properly and we can't tell why. The back channel may contain more information.
And indeed, the back channel tells you what's wrong:
**** Could not open the file "c:\temp\test.pdf
**** Unable to open the initial device, quitting.
Ghostscript is unable to open the output file, which means it can't open the pdfwrite device (because that requires an output file) so it aborts the operation.
There could be a number of reasons why Ghostscript can't open the output file. The first thing I'd do is trim down the number of arguments;
You don't want -q (quiet) when you are trying to debug a problem, you want all the information you can get.
I'd remove -dSAFER at least to start with, because that prevents Ghostscript accessing directories outside the current working directory and certain 'special' ones. It may well prevent you accessing the temp directory.
You don't need to set EmbedAllFonts when its the same value as the default.
You could drop the CompatibilityLevel (and note that you've used a # there instead of an =) switch, and the AutoRotatePages while getting this to work.
The "-c .setpdfwrite -f" string has been pointless for years but people still keep using it. All that does these days is slow down the start of processing, ditch it.
Finally you can try changing the backslash ('\') characters to forward slash ('/') in case your string handling is messing that up, or use double backslashes (I'd use the forward slash myself).
You should also check that c:\test\temp.pdf doesn't exist, or if it does exist is not read-only or already open in a different application.
So I solved the problem...
After taking KenS' advice I could run the application without Ghostscript (not Ghostscript.NET) giving me any errors. But it did not produce an actual PDF File.
So KenS's answer did not quite solve the problem, but since 'less is more' and since he took the time to talk to me on IRC to verify that my args in itself were correct, I'll give the answer points nonetheless.
What actually solved my was the following:
Here my original GetArgs(...)
private IEnumerable<string> GetArgs(FileInfo inputFile, FileInfo outputFile)
{
return new [] {
$"-sDEVICE=pdfwrite",
$"-dNOPAUSE",
$"-dBATCH",
$"-sPAPERSIZE=a4",
#"-sFONTPATH=" + System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts),
$"-sOutputFile={outputFile.FullName}",
$"{inputFile.FullName}",
};
}
Someone in #csharp pointed out to me, that in c, the first argument is always the name of the command. So he suggested to just put "gs" as the first argument (as a dummy) and try... And that's what actually solved my problem.
So this is how the working GetArgs(...) looks:
private IEnumerable<string> GetArgs(FileInfo inputFile, FileInfo outputFile)
{
return new [] {
$"gs",
$"-sDEVICE=pdfwrite",
$"-dNOPAUSE",
$"-dBATCH",
$"-sPAPERSIZE=a4",
#"-sFONTPATH=" + System.Environment.GetFolderPath(System.Environment.SpecialFolder.Fonts),
$"-sOutputFile={outputFile.FullName}",
$"{inputFile.FullName}",
};
}
I'm having a bit of trouble getting an expression to work to replace the entire contents of a function. For example
void function1 (void)
{
Some junk here();
Other Junk here
{
Blah blah blah
}
}
I'd Like to replace the contents of this function with some predefined value ie
void function1 (void)
{
Something else here
}
This is what I have currently however it doesn't seem to work. I was trying to capture the first part of the function and then the ending curly brace which is on a new line by itself. I'm pretty new to regular expressions so forgive me if it makes no sense
text = Regex.Replace(text, #"(function1)*?(^}$))", Replace, RegexOptions.Multiline);
Any ideas what I am doing wrong or how I should go about this differently?
This is what I came up with. Let me know if it works for you.
public static string Replace_Function_Contents(string old_function, string new_contents)
{
Regex function_match = new Regex(#"(\s){1,}?([\s\w]{1,})?(\s{1,})?\(.{1,}?\)(\s{1,}){");
var match = function_match.Match(old_function);
return old_function.Remove(match.Index + match.Length) + new_contents + "}";
}
This seems to work:
/function1(?:.|\n)*?^}/m
See http://regexr.com/3geoq.
I think the major issue with your regular expression was (function1)*, which matches the string "function1" zero or more times. Example matching strings are "" and "function1function1function1". You probably meant (function1).*, but unless things work differently in .NET's regular expression engine, the . won't match newlines. I used (?:.|\n) instead to include newlines. I also dropped the captures, since your response to my question about back references didn't seem to indicate you were actually using them.
You also had an extra right parenthesis in your regular expression that I would have expected to cause an error.
Full working C# code:
using System;
using System.Text.RegularExpressions;
namespace regex
{
class Program
{
static void Main(string[] args)
{
var text = #"something up here
void anotherfunc(int x)
{
}
void function1 (void)
{
Some junk here();
Other Junk here
{
Blah blah blah
}
}
int main()
{
}";
var replacement = #"function1 (void)
Something else here
}";
Console.Out.WriteLine(Regex.Replace(text, #"function1(?:.|\n)*?^}", replacement, RegexOptions.Multiline));
}
}
}
Output:
something up here
void anotherfunc(int x)
{
}
void function1 (void)
Something else here
}
int main()
{
}
Ok, I will preface this by saying I did do a look up and read a lot of similar questions and answers on here before posting this.
Background Info
Using Visual Studio Professional 2013
Goal:
This project is for my own amusement and to try to learn as much as I can.
I have a native C++ header file called BinaryTree.h which is just a simple data structure that uses recursive logic to build a binary search tree. It works quite well on its own.
I want to build a GUI in C# that uses this. (Its not really useful or practical, I just choose it, well because I wanted to. Also, while the logic inside the binary tree class is complex(ish), I only need to call 2 methods, a addNode method, and a toString method which return the max depth and number of nodes).
I choose using a c++/cli wrapper to accomplish this. Everything seemed to go well, the build was successful and a .dll file was created in the debug directory of my project.
Now, I started in on the C# side of things. I added the .dll file to references. However, when I typed in " using filename.dll;" I got an error saying "Type or namespace not found...".
To reiterate I did some researching. I found (it seemed in VS2010) that different target frameworks could cause this error. I checked mine, targets for both were net4.5, so that is not the problem.
Here is the code from my c++/cli wrapper. Perhaps it has something to do with using templates? Any help is appreciated.
#pragma once
#include "D:\Schoolwork 2015\Test Projects\CPPtoC#WrapperTest\CPPLogic\CPPLogic\BinaryTree.h"
using namespace System;
namespace BinaryTreeWrapper {
template<class Data>
public ref class BinaryTreeWrapperClass
{
public:
BinaryTreeWrapperClass(){ tree = new BinaryTree(); }
~BinaryTreeWrapperClass(){ this->!BinaryTreeWrapperClass(); }
!BinaryTreeWrapperClass(){ delete tree; }
//methods
void wrapperAddNode(Data)
{
tree->addNode(Data);
}
std::string wrapperToString()
{
return tree->toString();
}
private:
BinaryTree* tree;
};
}
Screenshot of the error:
EDIT
Ok, so here is a weird thing... my original file built just fine with the new code and produced a .dll file. However, I decided to try a fresh project since the namespace was still not being found. Upon moving the code over and trying to build, I've run into 4 errors:
Error 1 error C2955: 'BinaryTree' : use of class template requires template argument list
Error 2 error C2512: 'BinaryTree' : no appropriate default constructor available
Error 3 error C2662: 'void BinaryTree::addNode(Data)' : cannot convert 'this' pointer from 'BinaryTree' to 'BinaryTree &'
Error 4 error C2662: 'std::string BinaryTree::toString(void) const' : cannot convert 'this' pointer from 'BinaryTree' to 'const BinaryTree &'
I copied the code exactly, only changing the namespace to "TreeWrapper' and the class name to 'TreeWrapperClass'.
To help, I've include a snippet from my BinaryTree.h file. There is a bunch more that defines the 'NODE' class, but I didn't want to clutter it up more than i needed.
After further investigation, it appears the problem lies with using 'generic'. If I switch it all to 'template' it builds just fine, but then it can't be used as a reference in C# (getting the namespace error). I built a test project using very simple methods (no templates) and was able to use the .dll wrapper I made in C#. So the problem lies with templates and generics.
Last Edit
I've found if I change the code to initiate the template as 'int' it works just fine, and I can use it in C#. For example:
...
BinaryTreeWrapperClass(){ tree = new BinaryTree<int>(); }
....
private:
BinaryTree<int>* tree;
BinaryTree.h
template<class Data>
class BinaryTree
{
private:
Node<Data>* root;
unsigned int nNodes;
unsigned int maxDepth;
unsigned int currentDepth;
void traverse(Node<Data>*& node, Data data);
public:
BinaryTree();
~BinaryTree();
void addNode(Data);
std::string toString() const
{
std::stringstream sstrm;
sstrm << "\n\t"
<< "Max Depth: " << maxDepth << "\n"
<< "Number of Nodes: " << nNodes << "\n";
return sstrm.str(); // convert the stringstream to a string
}
};
template<class Data>
BinaryTree<Data>::BinaryTree() //constructor
{
//this->root = NULL;
this->root = new Node<Data>(); //we want root to point to a null node.
maxDepth = 0;
nNodes = 0;
}
template<class Data>
BinaryTree<Data>::~BinaryTree() //destructor
{
}
template<class Data>
void BinaryTree<Data>::addNode(Data data)
{
traverse(root, data); //call traverse to get to the node
//set currentDepth to 0
currentDepth = 0;
}
template<class Data>
void BinaryTree<Data>::traverse(Node<Data>*& node, Data data)
{
//increment current depth
currentDepth++;
if (node == NULL) //adds new node with data
{
node = new Node<Data>(data);
//increment nNode
nNodes++;
//increment maxDepth if current depth is greater
if (maxDepth < currentDepth)
{
maxDepth = currentDepth - 1; //currentDepth counts root as 1, even though its 0;
}
return;
}
else if (node->getData() >= data) //case for left, getData must be bigger. The rule is, if a number is equal to getData or greater, it is added to the left node
{
Node<Data>* temp = node->getLeftNode();
traverse(temp, data); //recursive call, going down left side of tree
node->setLeftNode(temp);
}
else if (node->getData() < data) //case for right, getData must be less
{
Node<Data>* temp = node->getRightNode();
traverse(temp, data);
node->setRightNode(temp);
}
return;
}
You're declaring a template, but are not actually instantiating it. C++/CLI templates are just like C++ templates - if you don't instantiate them, they just don't exist outside of the compilation unit.
You're looking for generics here (yes, C++/CLI has both templates and generics). And here's how you declare a generic in C++/CLI:
generic<class Data>
public ref class BinaryTreeWrapperClass
{
// ...
}
But you'll get stuck at this point for several reasons.
First, I'll include the parts which are OK:
generic<class Data>
public ref class BinaryTreeWrapperClass
{
public:
BinaryTreeWrapperClass(){ tree = new BinaryTree(); }
~BinaryTreeWrapperClass(){ this->!BinaryTreeWrapperClass(); }
!BinaryTreeWrapperClass(){ delete tree; }
private:
BinaryTree* tree;
};
You've got this right.
Next, let's look at:
std::string wrapperToString()
{
return tree->toString();
}
That's no good, since you're returning an std::string - you don't want to use that from C#, so let's return a System::String^ instead (using marshal_as):
#include <msclr/marshal_cppstd.h>
System::String^ wrapperToString()
{
return msclr::interop::marshal_as<System::String^>(tree->toString());
}
Here, that's much better for use in C#.
And finally, there's this:
void wrapperAddNode(Data)
{
tree->addNode(Data);
}
See... here you'll have to do some real interop. You want to pass a managed object to a native one for storage. The GC will get in your way.
The GC is allowed to relocate any managed object (move it to another memory location), but your native code is clueless about this. You'll need to pin the object so that the GC won't move it.
There are several ways to do this, and I don't know what BinaryTree::addNode looks like, but I'll just suppose it's BinaryTree::addNode(void*).
For long-term object pinning, you can use a GCHandle.
The full code looks like this:
generic<class Data>
public ref class BinaryTreeWrapperClass
{
public:
BinaryTreeWrapperClass()
{
tree = new BinaryTree();
nodeHandles = gcnew System::Collections::Generic::List<System::Runtime::InteropServices::GCHandle>();
}
~BinaryTreeWrapperClass(){ this->!BinaryTreeWrapperClass(); }
!BinaryTreeWrapperClass()
{
delete tree;
for each (auto handle in nodeHandles)
handle.Free();
}
void wrapperAddNode(Data data)
{
auto handle = System::Runtime::InteropServices::GCHandle::Alloc(
safe_cast<System::Object^>(data),
System::Runtime::InteropServices::GCHandleType::Pinned);
nodeHandles->Add(handle);
tree->addNode(handle.AddrOfPinnedObject().ToPointer());
}
System::String^ wrapperToString()
{
return msclr::interop::marshal_as<System::String^>(tree->toString());
}
private:
BinaryTree* tree;
System::Collections::Generic::List<System::Runtime::InteropServices::GCHandle>^ nodeHandles;
};
This allocates a GCHandle for each node, and stores it in a list in order to free it later. Freeing a pinned handle releases a reference to the object (so it becomes collectable if nothing else references it) as well as its pinned status.
After digging around I think I found the answer, although not definitively, so if anyone can chime in I would appreciate it. (Apologies in advance for any misuse of vocabulary, but I think I can get the idea across).
It seems the problem lies in the fundamental difference between templates in native C++ and generics. Templates are instantiated at compilation, and are considered a type. They cannot be changed at runtime, whereas generics can. I don't think there is an elegant way to solve that.
At least I accomplished one goal of my project, which was learning as much as I can haha. I was able to get c++/cli wrappers working for things without templates, and if I choose a type for the template before building the .dll (see above)
If anyone else has an idea please let me know.
I am trying to understand how this piece of self-replicating code works (found here), but the problem is I can't get it to run as-is:
class c {
static void Main(){
string s = "class c{{static void Main(){{string s={0}{10};System.Console.Write(s,(char)34,s);}}}}";
System.Console.Write(s,(char)34,s); //<<-- exception on this line
}
}
It's throwing an exception on writeline: Index (zero based) must be greater than or equal to zero and less than the size of the argument list.
Can someone help - in particular about the formatting option {0}{10}?
I got it working like this (see below) but it's longer than the original - I am curious how the original could have worked as-is in the 1st place:
class c {
static void Main(){
string s = "class c{{static void Main(){{string s={0}{1}{2};System.Console.Write(s,(char)34,s,(char)34);}}}}";
System.Console.Write(s,(char)34,s,(char)34);
}
}
I think there is a pair of braces missing - instead of {10} it should read {1}{0}.
class c {
static void Main(){
string s = "class c{{static void Main(){{string s={0}{1}{0};System.Console.Write(s,(char)34,s);}}}}";
System.Console.Write(s,(char)34,s); //<<-- exception on this line
}
}
Could the original work with?
s={0}{1}{0}
I believe that the original was supposed to look like this:
class c {
static void Main() {
string s = "class c{{static void Main(){{string s={0}{1}{0};System.Console.Write(s,(char)34,s);}}}}";
System.Console.Write(s, (char)34, s);
}
}
I.e. the {0}{10} should just be changed to {0}{1}{0}.
The {0} in the format string is used to put the quotation marks before and after the string.