0

I'm writting some logic to do some logging, there's a bunch of C++ mixed with C as most of this library is meant to be p/Invoked. I've managed to write a function that logs a message along with an optional parameter:

void writeToLog(char* message, char* arg) {
    std::ofstream file;
    file.open(fullpath, std::ios::in | std::ios::app);
    if (file.is_open()) {
        std::string fullMessage = getCurrentDateTime();
        fullMessage.append(message);
        if (arg != 0)
            fullMessage.append(arg);
        fullMessage.append("\n");
        const char* pcMessage = fullMessage.c_str();
        file << pcMessage;
        std::cout << pcMessage;
    }
    file.close();
}

However it only takes char* as args, but I'd like to use them with int and long as well... I have tried:

void writeToLog(char* message, void* arg) {
    std::ofstream file;
    file.open(fullpath, std::ios::in | std::ios::app);
    if (file.is_open()) {
        std::string fullMessage = getCurrentDateTime();
        fullMessage.append(message);
        if (arg != 0)
            fullMessage.append((char*)&arg);
        fullMessage.append("\n");
        const char* pcMessage = fullMessage.c_str();
        file << pcMessage;
        std::cout << pcMessage;
    }
    file.close();
}

But it prints/writes gibberish, regardless of data type. Please point to any other errors as you see fit, I'm a bit of a noob when it comes to C/C++.

makoshichi
  • 2,310
  • 6
  • 25
  • 52
  • 2
    Use a template. That's what they are for. – NathanOliver Apr 27 '17 at 20:04
  • Like I said, I'm a C++ noob, I don't know how templates work but I'll take a look – makoshichi Apr 27 '17 at 20:06
  • 2
    Sounds like you could use a [good C++ book](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) then – NathanOliver Apr 27 '17 at 20:06
  • Possible duplicate of [How to write a template?](http://stackoverflow.com/questions/3745412/how-to-write-a-template) – AndyG Apr 27 '17 at 20:10
  • @NathanOliver Templates aren't actually a good answer if the function is meant to be P/Invoke'd. They can only be instantiated by C++ code, and there's no way to call them from C# without a wrapper. – ephemient Apr 27 '17 at 20:12
  • This might be a better dupe: http://stackoverflow.com/questions/8627625/is-it-possible-to-make-function-that-will-accept-multiple-data-types-for-given-a – NathanOliver Apr 27 '17 at 20:13
  • @ephemient I've never dealt with that. That is a good point though – NathanOliver Apr 27 '17 at 20:14
  • Take a look at this [Overloads and templates](http://www.cplusplus.com/doc/tutorial/functions2/), being new to C++ I recommend going for the overload, but see what fits you better. – DIEGO CARRASCAL Apr 27 '17 at 20:58
  • I'm taking a look at templates, I have comprehensive experience with C# and I would not like to override the methods manually. @ephemient In this case, a template wouldn't be a problem 'cause this method is meant to be used within C++ only. I just mentioned P/Invoke to justify that I'm mixing `char*` with `std::string`. And I need those `char*` a lot... – makoshichi Apr 28 '17 at 16:39

4 Answers4

2

Two are two ways you could go about solving this problem. The first way is to overload the function. This means that you are going to write the same function over and over again except with different types as the parameters. Example

#include <iostream>
using namespace std;

class printData {
public:
  void print(int i) {
     cout << "Printing int: " << i << endl;
  }

  void print(double  f) {
     cout << "Printing float: " << f << endl;
  }

  void print(char* c) {
     cout << "Printing character: " << c << endl;
  }
};

int main(void) {
 printData pd;

 // Call print to print integer
 pd.print(5);

 // Call print to print float
 pd.print(500.263);

 // Call print to print character
 pd.print("Hello C++");

 return 0;
}

This code prints:

Printing int: 5
Printing float: 500.263
Printing character: Hello C++

The second option is using templates. Basically, you would do

template<class T>
void writeToLog(char* message, T arg){
   //continue to write code here without assuming anything about the type of arg
}

The link for overloading functions: https://www.tutorialspoint.com/cplusplus/cpp_overloading.htm

The link for templates: http://www.cplusplus.com/doc/oldtutorial/templates/

Suyog Soti
  • 96
  • 4
1

My suggestion:

  1. Change your function to a function template.
  2. Use the standard library function std::to_string to convert numbers to a std::string. Add some wrapper code so you can use the same function call to deal with char* and char const*.
  3. Use the return value of the wrapper function to append to the message being written.
  4. Remove the call to std::string::c_str(). A std::string can be written to a file and std::cout.
  5. There is no need to call file.close();. It will be closed when the function returns.

namespace my_app
{
   template <typename T>
   std::string toString(T in)
   {
      return std::to_string(in);
   }

   std::string toString(char const* in)
   {
      return std::string(in);
   }

   std::string toString(std::string const& in)
   {
      return in;
   }
}

template <typename T>
void writeToLog(char* message, T arg) {
   std::ofstream file;
   file.open(fullpath, std::ios::in | std::ios::app);
   if (file.is_open()) {
      std::string fullMessage = getCurrentDateTime();
      fullMessage.append(message);
      fullMessage.append(my_app::toString(arg));
      fullMessage.append("\n");
      file << fullMessage;
      std::cout << fullMessage;
   }
}

// Overload to deal with the optional parameter
void writeToLog(char* message) {
   writeToLog(message, "");
}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Weirdly enough, I receive a compiler error if I remove `.c_str()` from `fullMessage`: `Error C2679 binary '<<' : no operator found which takes a right-hand operand of type 'std::string' (or there is no acceptable conversion)` – makoshichi Apr 28 '17 at 17:13
  • @S.O., that's strange. What compiler are you using? – R Sahu Apr 28 '17 at 18:29
1

You can use templates.

This is a simple example shows you the basic idea. (not tested though)

template <typename T, typename U>
void func(T param1, U param2)
{
    std::cout << param1 << param2;
}

In main for example:

func("A", 5);

func (2, 2.5);

You will need to read a bit to get it done for your needs.

Shadi
  • 1,701
  • 2
  • 14
  • 27
1

Any managed object can be marshaled as a VARIANT. Of course, you won't have any type checking from the compiler if you do this.

// C# P/Invoke
static extern void writeToLog(string message, ref object arg);
// C++
void writeToLog(const char* message, const VARIANT* arg) {
    std::ofstream file;
    file.open(fullpath, std::ios::in | std::ios::app);
    if (file.is_open()) {
        std::string fullMessage = getCurrentDateTime();
        fullMessage.append(message);
        if (arg) {
            switch (V_VT(arg)) {
                case VT_I4:
                    message.append(V_I4(arg));
                    break;
                case VT_I8:
                    message.append(V_I8(arg));
                    break;
                case VT_BSTR:
                    message.append(V_BSTR(arg));
                    break;
            }
        }
        fullMessage.append("\n");
        const char* pcMessage = fullMessage.c_str();
        file << pcMessage;
        std::cout << pcMessage;
    }
    file.close();
}
ephemient
  • 198,619
  • 38
  • 280
  • 391
  • Yours looks the best one so far. I was planning on going with templates but let me try yours out first. – makoshichi Apr 28 '17 at 16:41
  • @S.O. If you're primarily calling through C++ (not P/Invoke) then mine is going to be more difficult, actually. – ephemient Apr 28 '17 at 16:42
  • Yeah, I just realised that, I hadn't realised you prepared it to be P/Invoked. I'm gonna try templates, but great job! – makoshichi Apr 28 '17 at 16:43