-1

I'm calling APIs in a specific way with the help of templates and I have left one problem with passing a constant parameter.

My try with int bound:

    template <typename F, typename ...Ts> 
          static int f3(int bound, CString file, int line, CString Caller,
                        CString f_name, F f, Ts&& ...ts) {
                 int err = fn(bound, file, line, Caller, f_name,
                 f, std::tuple<Ts...>(ts...), seq3<bound>{},  // error C2975 
                 seq1<sizeof...(Ts)>{});                  
                 return err;
    }

In main:

int const bound; 
bound = 4; 

err = fn(bound, api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path");

compiler error C2975: 'N': invalid template argument for 'seq3', expected compile-time constant expression

How to fix this?

My workaround by now:

err = f3(api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path");

f3 is a specialisation for an API with 3 arguments, because I'm up to now not able to pass in the upper bound - 4 in this case - for generating a sequence: <1,2,3>. This sequence is needed to call an API with 3 arguments, where the tupel starts at the parameter rval in f3().

Background:

api is a #define

f3 calls the API.

f3 handles the return value of the API at the 0 position of the sequence/tupel.

f3 calls with all parameters another variadic function for logging debug informations.

One tupel and two sequences for two function calls.

PROBLEM:

I want to pass a parameter to control the upper bound of a sequence not given by the tupel-size but by the API function signature.

I want only one fn() for all APIs and not f0(), f1(), f2(), f3() ..... for APIs with 0, 1, 2, 3 ... arguments.

I want something like this:

err = fn(seq3<4>, api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path")

Here's my working code:

#include <windows.h>
#include <atlstr.h>
#include <tuple>
#include <utility>

template <int ... Ns> struct seq_3 {};                                  
template <int ... Ns> struct seq3_n {};  

template <int I, int ... Ns> struct seq3_n<I, Ns...>{
   using type = typename seq3_n<I - 1, I - 1, Ns...>::type;};

template <int ... Ns> struct seq3_n<1, Ns...>{
// skip first argument : rval, because it doesn't fit to API,
// but needed for calling other function    
   using type = seq_3<Ns...>;                };

template <int N>
   using seq3 = typename seq3_n<N>::type;   

template <int ... Ms> struct seq_1 {};                          
template <int ... Ms> struct seq1_n {};    
template <int J, int ... Ms> struct seq1_n<J, Ms...>{
   using type = typename seq1_n<J - 1, J - 1, Ms...>::type; };    
template <int ... Ms> struct seq1_n<0, Ms...> {
   using type = seq_1<Ms...>;                };
template <int M>
   using seq1 = typename seq1_n<M>::type;       

template <typename F, typename TUP, int ... INDICES3, int ... INDICES1>                        
   static int fn(CString file,  int line, CString Caller, CString f_name,
              F f, TUP tup, seq_3<INDICES3...>, seq_1<INDICES1...>) {   
                int err = 0;
                // handling of rval = first element of tuple 
                std::get<0>(tup) = f(std::get<INDICES3>(tup) ...);  // calling API  
                err = GetLastError();   
                /* calling next function (variadic too) with same tupel, but other sequence 
                 myOpenDebugOutputString(project, file, line, Caller, f_name, std::get<INDICES1>(tup) ..., "stop");
                */ 
                return err; }

template <typename F, typename ...Ts> 
   static int f3(CString file, int line, CString Caller, CString f_name,
              F f, Ts&& ...ts)  {
                int err = fn(file, line, Caller, f_name,
                f, std::tuple<Ts...>(ts...), seq3<4>{},  // sequence fixed by f3 
                seq1<sizeof...(Ts)>{});                  // 3 arguments api  + skip 1 rval = 4 
                return err;                              // given by signature of API 
}


int main() {    
    // for calling simple API GetModulFileName with 3 arguments     
    //                                      returns len(path)   
    wchar_t     path[MAX_PATH];     
    DWORD           rval = 0;   
    int         err = 0;
    rval = GetModuleFileName( nullptr, path, MAX_PATH);     
    err  = GetLastError(); 

#define api(a)  __FILE__, __LINE__, __func__, L#a, a   
// L#a becomes L"GetModuleFileName" 

    err = f3(api(GetModuleFileName), rval, nullptr, path, MAX_PATH, L"EXE-path");   

    return 0; }

Thanks in advance.

P.S. I'm using Microsoft Visual Studio 2015

Update:

I tried following in template api_call from Richard Hodges solution.

std::tuple<GivenArgs...> tup(args...);   

// OK, but only for an api with 3 arguments 
callsite.function(std::get<0>(tup), std::get<1>(tup), std::get<2>(tup));

// compiler error too many arguments  
callsite.function(std::forward<GivenArgs>(args)..., seq1<callsite.nofArgs()>{}); 

// compiler error too few arguments
callsite.function(tup, seq1<callsite.nofArgs()>{}); 

Remarks:

seq1<3> = seq_1<0,1,2>

callsite.nofArg() = 3

How to get the correct number of arguments?

CarpeDiemKopi
  • 316
  • 3
  • 13

2 Answers2

1

It's not entirely clear how you want to handle errors etc. I have assumed returning a tuple of error code and value.

Here is a general pattern which I think will do what you want. You'll need to be careful around specialisations and overloads of emit_log, particularly with byte arrays that may not be null terminated or contain non-printing characters.

I have used narrow chars for convenience, but this idea will work with wide chars with a few edits.

Note: edited on linux gcc so I have simulated the windows API.

#include <cstdint>
#include <utility>
#include <iostream>
#include <variant>

#define WINAPI
#define _In_opt_
#define _Out_
#define _In_

struct _hmodule {};
using HMODULE = _hmodule*;
using LPTSTR = char*;
using LPCTSTR = const char*;
using DWORD = std::uint32_t;

extern DWORD WINAPI GetModuleFileName(
  _In_opt_ HMODULE hModule,
  _Out_    LPTSTR  lpFilename,
  _In_     DWORD   nSize
);

extern WINAPI DWORD GetLastError();

template<class Ret, class...Args>
struct api_call_site
{
    const char* file;
    int line;
    const char* current_function;
    const char* called_function;
    Ret (* function)(Args...);
};

template<class Ret, class...Args>
auto make_api_call_site(const char* file, int line, const char* callername, const char* calleename, Ret (* WINAPI callee)(Args...))
{
    return api_call_site<Ret, Args...>
    {
        file, 
        line,
        callername,
        calleename,
        callee
    };
}

template<class T>
void emit_log(LPCTSTR& sep, std::ostream& os, T&& x)
{
    os << sep << x;
    sep = ",";
}

template<class Ret>
struct error_with_value
{
    DWORD error;
    Ret value;

    bool has_error() const { return error != 0; }
    friend std::ostream& operator<<(std::ostream& os, const error_with_value& ewv)
    {
        os << "{ error: " << ewv.error << ", value: ";
        LPCTSTR sep = "";
        emit_log(sep, os, ewv.value);
        os << " }";
        return os;
    }
};


#define api(a) make_api_call_site(__FILE__, __LINE__, __func__, #a, a)


// this will need some specialisations...
void emit_log(LPCTSTR& sep, std::ostream& os, std::nullptr_t)
{
    os << sep << "nullptr";
    sep = ",";
}

template<class Ret, class...Args, class...GivenArgs>
auto api_call(api_call_site<Ret, Args...> const& callsite, GivenArgs&&...args) -> error_with_value<Ret>
{
    // log call here
    std::clog << callsite.file << ":" << callsite.line << "@" << callsite.current_function << " - ";
    std::clog << "calling " << callsite.called_function << "(";
    // appropriate code to print arguments in a safe way here...
    LPCTSTR sep = "";
    using expand = int[];
    void(expand{0,
        (emit_log(sep, std::clog, args),0)...
    });
    std::clog << ")";
    error_with_value<Ret> result
    {
        0,
        callsite.function(std::forward<GivenArgs>(args)...)
    };
    result.error = GetLastError();

    std::clog << " -> returns: " << result;
    return result;
}

int main()
{
    char buffer[255];
    DWORD bufsize = 255;

    auto result = api_call(api(GetModuleFileName), nullptr, buffer, bufsize);
    if (! result.has_error())
    {
        //
    }

}

example output:

main.cpp:120@main - calling GetModuleFileName(nullptr,,255) -> returns: { error: 0, value: 14 }

http://coliru.stacked-crooked.com/a/e5da55af212d5500

How do I get the number of arguments in the API call?

template<class Ret, class...Args>
struct api_call_site
{
    const char* file;
    int line;
    const char* current_function;
    const char* called_function;
    Ret (* function)(Args...);

    // like this
    static constexpr std::size_t nofArgs()
    {
        return sizeof...(Args);
    } 
};
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • Thanks a lot. I have changed to wide chars, tried to use the original windows API GetModuleFilename and got compiler errors: error C2672: 'make_api_call_site': no matching overloaded function found error C2784: 'auto make_api_call_site(const char *,int,const char *,const char *,Ret (__cdecl *)(Args...))': could not deduce template argument for 'Ret (__cdecl *)(Args...)' from 'DWORD (HMODULE,LPWSTR,DWORD)'! How to fix this? – CarpeDiemKopi Oct 01 '17 at 11:30
  • @CarpeDiemKopi you'll need to check the width of strings on the logging functions etc. – Richard Hodges Oct 01 '17 at 15:15
  • I got rid of the compiler errors. Problem was the use of WINAPI: I changed Ret (* function)(Args...); to Ret(WINAPI* function)(Args...); and Ret (* WINAPI callee)(Args...)) to Ret(WINAPI* callee)(Args...)). This hast worked before because of defining WINAPI to nothing for your windows api simulation. – CarpeDiemKopi Oct 02 '17 at 10:33
  • For my intended use I have a next question: How is it possible to get the number of arguments for the api as a constant expression in the template api_call? – CarpeDiemKopi Oct 02 '17 at 10:38
  • @CarpeDiemKopi `sizeof...(GivenArgs)` – Richard Hodges Oct 02 '17 at 11:18
  • I have added an update for my next question: Back to my original question I want to use this tuple two times. First time for calling an api function with the appropriate number of arguments and second time for calling another variadic function with the complete tuple. This means, the tuple holds a lot more arguments than needed for the api. My tries - see in update - give compiler errors. How to achieve that? – CarpeDiemKopi Oct 02 '17 at 16:44
  • @CarpeDiemKopi it might be simpler if you break down the questions and ask them one by one. It sounds like you're looking to "chop" elements out of a tuple. I'd so that by building an index_sequence of the indecies I want to chop and then delegate to a function that returns a `std::tie(std::get(tuple)...)` where `Is` is the sequence of indexes. – Richard Hodges Oct 02 '17 at 17:38
0

Thanks to Richard Hodges I could solve my problem:

Now there's one api_call for every api and the templates behind that api_call determine the signature of the called api. Much better than I asked for.

#include <windows.h>
#include <atlstr.h>
#include <tuple>

template <int ... Ns> struct seq_3 {};
template <int ... Ns> struct seq3_n {};
template <int I, int ... Ns> struct seq3_n<I, Ns...> {
    using type = typename seq3_n<I - 1, I - 1, Ns...>::type;
};
template <int ... Ns> struct seq3_n<1, Ns...> {
    // this sequence is more complicated in my real code, because
    // there are more variables for logging, but not for api calling     
    using type = seq_3<Ns...>;
};
template <int N>
using seq3 = typename seq3_n<N>::type;

template <int ... Ms> struct seq_1 {};
template <int ... Ms> struct seq1_n {};
template <int J, int ... Ms> struct seq1_n<J, Ms...> {
    using type = typename seq1_n<J - 1, J - 1, Ms...>::type;
};
template <int ... Ms> struct seq1_n<0, Ms...> {
    using type = seq_1<Ms...>;
};
template <int M>
using seq1 = typename seq1_n<M>::type;

// according to the solution from Richard Hodges
// *********************************************
template<typename Ret, typename...Args>   
struct api_call_site
{
    const CString file;
    int line;
    const CString Caller;
    const CString f_name;
    Ret(WINAPI* function)(Args...);     

    static constexpr std::size_t nofArgs() {
        return sizeof...(Args);
    }
};

template<typename Ret, typename...Args>
auto make_api_call_site(const CString file, int line, const CString Caller, const CString f_name, Ret(WINAPI* callee)(Args...))
// WINAPI see also here  https://stackoverflow.com/questions/18912931/why-need-to-use-winapi-for-the-syntax-for-declaring-function-pointers-for-fun
{
    return api_call_site<Ret, Args...>
    {
        file,
            line,
            Caller,
            f_name,
            callee
    };
}

template <typename Ret, typename...Args, typename TUP, int...INDICES3, int...INDICES1>
int fn(api_call_site<Ret, Args...> const& callsite, TUP tup, seq_3<INDICES3...>, seq_1<INDICES1...>) {
    int err = 0;
    // handling of return value from api call goes always in position 0 from tuple 
    std::get<0>(tup) = callsite.function(std::get<INDICES3>(tup) ...);
    err = GetLastError();
    /* calling next function (variadic too) with same tupel, but other sequence
    myOpenDebugOutputString(project, file, line, Caller, f_name, std::get<INDICES1>(tup) ..., "stop");
    */
    return err;
}

template<typename Ret, typename...Args, typename...GivenArgs>
int api_call(api_call_site<Ret, Args...> const& callsite, GivenArgs&&...args) 
{
    int err;
    err = fn(callsite, std::tuple<GivenArgs...>(args...), seq3 <callsite.nofArgs()+1> {}, seq1 <sizeof...(GivenArgs)> {});
    return err;
}

int main() {
    DWORD       size_path = 20;   // make it small and get error 122 
    wchar_t     path[MAX_PATH];   // ERROR_INSUFFICIENT_BUFFER
    DWORD       rval = 0;
    int         err = 0;
    CString     tolog1(L"EXE-Path determined"); 
    int         tolog2 = 25; 
    // old way without logging information 
    rval = GetModuleFileName(nullptr, path, MAX_PATH);
    err = GetLastError();

    // new way with logging any variables ... behind the must variables for the api  
    // ****************************************************************************
#define api(a) make_api_call_site(__FILE__, __LINE__, __func__, L#a, a)
    err = api_call(api(GetModuleFileName), rval, nullptr, path, size_path, tolog1, tolog2); 

    return 0;
}
CarpeDiemKopi
  • 316
  • 3
  • 13