Use the Ceres nonlinear optimization library in the windows system: (2) Call the Ceres library

(1) Install the Ceres library

(2) Call the Ceres library

        1. Write DLL library WrapCeres

                1.1. Write the code to be exported

                1.2. Compile link configuration

                1.3. Compile

        2. Call DLL library WrapCeres with C#

                2.1. Correspondence of types between C# and C/C++

                2.2.Dllimport detailed explanation

                2.3. In-depth discussion of the method of C# calling C++

                2.4. Actually calling WrapCeres.dll in WPF

(3) Release the application

(2) Call the Ceres library

Above we have actually run some of the routines that come with Ceres, such as helloworld, helloworld_static, etc. If you are interested, you can study these official routines in depth, and I won’t say much here.

 It is worth noting that these official Ceres routines are application projects, that is, the exe files compiled by these routines can be run directly. If you want to directly write simple C++ applications to call the Ceres library to implement some simple functions, you can learn from these official routines to write your own programs. However, usually, we call the Ceres library to implement a mathematical calculation task in a very complex Windows desktop application. Windows desktop applications are generally .NET-based C# programs, such as WPF desktop applications. We know that a C++ application can directly call other C++ libraries (both lib static libraries and dll dynamic libraries), while C# applications can only call C++ dll dynamic libraries by exporting. It is more troublesome when the C# application needs to call many different C++ libraries, and these C++ libraries include both dynamic libraries and static libraries. Generally, these C++ libraries we want to use are integrated into a special C++ DLL. If conditions permit, it is best to use static linking to integrate these C++ libraries into this special C++ DLL. Let's take the Ceres library call as an example to introduce how to write this special C++ DLL, and how to call this special C++ DLL in C#.

1. Write DLL library WrapCeres

For convenience, I created a new C++ DLL empty project directly under the above ceres-windows solution, named WrapCeres.

 1.1. Write the code to be exported

Create a new header file wrap.h and source file wrap.cpp in the project, fill in the declaration of the variable/function/class to be exported in wrap.h, and fill in the definition of the variable/function/class to be exported in wrap.cpp .

Write the wrap.h header file:

#pragma once

#ifndef _WRAP_H
#define _WRAP_H
extern "C" _declspec(dllexport) int Add(int a, int b);
extern "C" _declspec(dllexport) int nCeresDll;
extern "C" _declspec(dllexport) int fnCeresDll(void);
extern "C" _declspec(dllexport) double testCeresDll(void);
#endif

Write the wrap.cpp source file:

#include "pch.h"
#include "wrap.h"
#include "ceres\ceres.h"
#include "glog\logging.h"

using ceres::AutoDiffCostFunction;
using ceres::CostFunction;
using ceres::Problem;
using ceres::Solver;

int Add(int a, int b)
{
	return 2*(a + b);
}



struct CostFunctor {
	template <typename T>
	bool operator()(const T* const x, T* residual) const {
		residual[0] = T(10.0) - x[0];
		return true;
	}
};
// 这是导出变量的一个示例
int nCeresDll = 0;

// 这是导出函数的一个示例。
int fnCeresDll(void)
{
	return 0;
}

double testCeresDll(void)
{
	google::InitGoogleLogging(new char(123));
	// The variable to solve for with its initial value.
	double initial_x = 5.0;
	double x = initial_x;
	// Build the problem.
	Problem problem;
	// Set up the only cost function (also known as residual). This uses
	// auto-differentiation to obtain the derivative (jacobian).
	CostFunction* cost_function =
		new AutoDiffCostFunction<CostFunctor, 1, 1>(new CostFunctor);
	problem.AddResidualBlock(cost_function, NULL, &x);

	// Run the solver!
	Solver::Options options;
	options.linear_solver_type = ceres::DENSE_QR;
	options.minimizer_progress_to_stdout = true;
	Solver::Summary summary;
	Solve(options, &problem, &summary);
	return x;
}

1.2. Compile link configuration

(1) Output directory and intermediate directory

Here I set the output directory and intermediate directory of the project WrapCeres the same as the configuration of other projects in the solution, so that it is easy to find other dependent library files later, and it is also easy to find when publishing.

(2) Header file directory

Because WrapCeres.cpp uses ceres and its dependent header files, it is necessary to specify the header file paths of ceres, Eigen, and glog. Here, relative paths are used:

$(ProjectDir)\..\Own

$(ProjectDir)\..\glog\src\windows

$(ProjectDir)\..\ceres-solver\include

$(ProjectDir)\..\ceres-solver\internal

$(ProjectDir)\..\win\include

(3) macro

The following macros need to be added to the preprocessor definition, otherwise an error will be reported when compiling. The macros to add are as follows:

GLOG_NO_ABBREVIATED_SEVERITIES

_CRT_NONSTDC_NO_DEPRECATE

NOMINMAX

(4) Library file directory

Because WrapCeres depends on the ceres library, the compilation process needs to link the ceres library, so it is necessary to specify the path of the ceres library file, and a relative path is used here:

$(SolutionDir)$(Platform)\$(Configuration)\

At the same time, specify the specific library file name in the additional dependencies, and fill in the library file name as follows:

ceres.lib

libglog_static.lib

It is worth noting that after I changed the ceres.lib in the above additional dependencies to ceres_static.lib, an error will be reported after compiling, but the problem is not too big. When publishing, you only need to send WrapCeres.dll and ceres.dll together. . If you want to break away from the system-level VC++ runtime dependency, you can set "Copy C++ runtime to the output directory" to "Yes" according to the above method, and then copy a bunch of dlls and our own WrapCeres.dll and ceres .dll sent out together on the line.

1.3. Compile

After the above configurations are completed, the project WrapCeres can be compiled. After the compilation is successful, we can copy WrapCeres.dll and ceres.dll in the output directory for the next step.

2. Call DLL library WrapCeres with C#

We know that a C++ application can directly call other C++ libraries (both lib static libraries and dll dynamic libraries), while C# applications can only call C++ dll dynamic libraries by exporting. This is the reason why we wrote the DLL dynamic library WrapCeres above. The following describes how to call this WrapCeres.dll in C#.

2.1. Correspondence of types between C# and C/C++

 2.2.Dllimport detailed explanation

In C#, DllImport can be used to call variables/functions/classes in the C++ DLL library. The usage of DllImport is as follows:

[DllImport("WrapCeres.dll")]

public static extern int Add(int x,int y);

In fact, there are many optional parameters in DllImport, including dllName, CallingConvention, CharSet, EntryPoint, ExactSpelling, and SetLastError.

  • dllName: dynamic link library name
  • CallingConvention: Calling convention (C language calling convention and standard calling convention)
  • CharSet: Set the string encoding format
  • EntryPoint: Function entry name, the name of the method itself is used by default
  • ExactSpelling: Indicates whether the EntryPoint must exactly match the spelling of the indicated entry point. The default is true. If it is false, the A version or W version of the corresponding entry function will be searched according to the CharSet. Can't find it and then look for the entry function
  • SetLastError: Indicates whether the method preserves the Win32 "last error". With the default value of false, whether the Win32 error is set to the caller thread, the error code can be obtained through Marshal.GetLastWin32Error() in C#, and it can be obtained by setting true

Basic data passing and function return value:

  • return value
  • address reference
  • pointer reference
  • Structure analysis of Marshal.Read
  • Overall reading of structure analysis

See the following article for specific usage routines:

C# calls C++ in detail - Programmer Sought

2.3. In-depth discussion of the method of C# calling C++

Here we introduce the general functions and variables of calling C++, calling C++ classes, and calling callback functions in C++ classes. See this article for specific routines:

The use of the C++ library called by C# (three ways)_The method of referencing the C++ library in c#_Milu_Y's Blog-CSDN Blog

(1) Call general functions and variables of C++

Use the DllImport feature to call functions and variables, for example:

[DllImport("WrapCeres.dll")]

public static extern int Add(int x,int y);

(2) Call the C++ class

C# cannot directly call the classes in the C++ class library. A flexible solution is needed to expose the class member methods to be called by creating another C++ class library.

(3) Call the callback function in the C++ class

The callback function of C++ is an event response mechanism, which is similar to the delegation of C#. For example, the callback function in a C++ class: personal feeling and method are similar.

2.4. Actually calling WrapCeres.dll in WPF

When some methods in WrapCeres.dll need to be called in the WPF program, use DllImport to load the corresponding methods in WrapCeres.dll, and then these programs can be used conveniently in the following places.

Finally, compile WPF to generate an exe executable file, and send the exe to the client together with the called WrapCeres.dll and ceres.dll. Is there any way to integrate exe and these dlls and send them to users? This issue is discussed below.

Another issue is system-level runtime support for C# programs. Similar to the above C++ program that needs to depend on the system-level runtime library Virtual C++, the C# program also needs to depend on the system-level runtime library .NET Framework. However, .NET Framework is a basic component that comes with the Windows system. Different versions of the system come with different versions of .NET Framework, as follows:

-win7 .NET Framework3.5 (including .NET 2.0 and 3.0)

-win10 .NET Framework4.6 and above (.NET Framework3.5 can be turned on or off)

-win11 .NET Framework4.8 (.NET Framework3.5 can be turned on or off)

 If we want our C# program to be compatible on different system versions (usually win7 and win10), then we need to compile multiple exes for users to choose in different .NET Framework version environments. Of course, if you change the .NET Framework version environment, some dependencies in the code, third-party packages, and individual statements in the code may need to be modified. The method for selecting the .NET Framework version in a C# program is as follows:

references

[1] Zhang Hu, Robot SLAM Navigation Core Technology and Practice [M]. Machinery Industry Press, 2022.

Guess you like

Origin blog.csdn.net/m0_68732180/article/details/130234769