满堂花醉三千客,一剑霜寒十四洲。这篇文章主要讲述HDevEngine in .NET Applications MultiThreading相关的知识,希望能为你提供帮助。
BasicsTo use HDevEngine in Visual Studio .NET, you must
- add a reference to the HALCON/.NET assembly, either by adding an instance of HSmartWindowControlto the form or by adding the reference directly via the Solution Explorer
- add a reference to the HDevEngine/.NET assembly HDevEngine/.NET DLL via the Solution Explorer
- specify the namespace with the following line
using HalconDotNet;
Executing an HDevelop ProgramIn this section, we explain how to load and execute an HDevelop program with HDevEngine. The code fragments checks the boundary of a plastic part for fins.
文章图片
Step 1: Initialization
First, we create a global instance of the main HDevEngine class HDevEngine.
private HDevEngine MyEngine = new HDevEngine();
Upon loading the form, we store the path to the HDevelop program and set the external procedure pathwith the method SetProcedurePath(Note that the latter is only necessary if the HDevelop program calls external procedures.):
string ProgramPathString;
private void ExecProgramForm_Load(object sender, System.EventArgs e)
{
var halconExamples = HSystem.GetSystem("example_dir");
var ProcedurePath= halconExamples + @"hdevengineprocedures";
ProgramPathString= halconExamples + @"hdevenginehdevelopfin_detection.hdev";
MyEngine.SetProcedurePath(ProcedurePath);
}
Step 2: Load ProgramWhen you load the HDevelop program, an instance of the class HDevProgram is created. Furthermore, an instance of HDevProgramCall is created for later use. Note that the working directory will be changed if a program is loaded.
Exceptions occurring in the constructors, e.g., because the file name was not specified correctly, are handled with the standard C# error handling mechanism:
private void LoadBtn_Click(object sender, System.EventArgs e)
{
try
{
var Program = new HDevProgram(ProgramPathString);
ProgramCall = new HDevProgramCall(Program);
}
catch (HDevEngineException Ex)
{
MessageBox.Show(Ex.Message, "HDevEngine Exception");
return;
}
catch
{
return;
}
}
Step 3: Execute Program
private void ExecuteBtn_Click(object sender, System.EventArgs e)
{try
{
ProgramCall.Execute();
}
catch (HDevEngineException Ex)
{
MessageBox.Show(Ex.Message, "HDevEngine Exception");
}
}
Step 4: Get ResultsNote that program variables can only be accessed when the program has terminated.
double FinArea;
FinArea = ProgramCall.GetCtrlVarTuple("FinArea");
Window.SetTposition(150, 20);
Window.WriteString("Fin Area: ");
Executing HDevelop Procedures Executing an External HDevelop ProcedureHow to load and execute an external HDevelop procedure with HDevEngine. In contrast to the previous example, the result display is programmed explicitly instead of relying on the internal display operators.
文章图片
Step 1: Initialization
As when executing an HDevelop program, we create a global instance of the main HDevEngine class HDevEngine and set the external procedure path with the method SetProcedurePath upon loading the form. If the external procedure is from a procedure library, the external procedure path may include the name of the library file.
private HDevEngine MyEngine = new HDevEngine();
private void ExecExtProcForm_Load(object sender, System.EventArgs e)
{
var ProcedurePath = halconExamples + @"hdevengineprocedures";
...
MyEngine.SetProcedurePath(ProcedurePath);
}
For calling the HALCON operators, we declare a global variable of the class HWindow for the underlying HALCON window;
private HWindow Window;
private void WindowControl_Load(object sender, EventArgs e)
{
Window = WindowControl.HalconWindow;
Window.SetDraw("margin");
Window.SetLineWidth(4);
}
Step 2: Load Procedure
private void LoadBtn_Click(object sender, System.EventArgs e)
{
try
{
var Procedure = new HDevProcedure("detect_fin");
ProcCall= new HDevProcedureCall(Procedure);
}
catch (HDevEngineException Ex)
{
MessageBox.Show(Ex.Message, "HDevEngine Exception");
}
}
Executing a procedure consists of multiple steps. First, we load an example image sequence:
private void ExecuteBtn_Click(object sender, System.EventArgs e)
{
var Framegrabber = new HFramegrabber();
Framegrabber.OpenFramegrabber("File",1,1,0,0,0,0,"default",-1,"default",-1,"default","fin.seq","default",-1,-1);
Step 3: Set Input Parameters Of Procedure
It expects an image as (iconic) input parameter and returns the detected fin region and its area as iconic and control output parameter, respectively:procedure detect_fin (Image: FinRegion: : FinArea)
HImage Image = new HImage();
HRegion FinRegion;
HTuple FinArea;
for (int i=0;
i<
=2;
i++)
{
Image.GrabImage(Framegrabber);
Image.DispObj(Window);
ProcCall.SetInputIconicParamObject("Image", Image);
Step 4: Execute Procedure
ProcCall.Execute();
Step 5: Get Output Parameters Of Procedure
FinRegion = ProcCall.GetOutputIconicParamRegion("FinRegion");
FinArea= ProcCall.GetOutputCtrlParamTuple("FinArea");
Step 6: Display Results Of Procedure
Image.DispObj(Window);
Window.SetColor("red");
Window.DispObj(FinRegion);
Window.SetColor("white");
Window.SetTposition(150, 20);
Window.WriteString("FinArea: " + FinArea.D);
}
Executing Local and External HDevelop Procedures
文章图片
Local and external procedures are created and executed in exactly the same way. The only difference is that in order to use a local procedure, you must load the program it is contained in, whereas to load external procedures you must set the procedure path.
private HDevProcedureCall InitAcqProcCall;
private HDevProcedureCall ProcessImageProcCall;
private HDevProcedureCall VisualizeDetailsProcCall;
private void ExecProceduresForm_Load(object sender, System.EventArgs e)
{
string ProcedurePath = halconExamples + @"hdevengineprocedures";
...
MyEngine.SetProcedurePath(ProcedurePath);
}
private void LoadBtn_Click(object sender, System.EventArgs e)
{
try
{
var Program = new HDevProgram(ProgramPathString);
var InitAcqProc = new HDevProcedure(Program, "init_acquisition");
var ProcessImageProc = new HDevProcedure(Program, "detect_fin");
var VisualizeDetailsProc = new HDevProcedure(Program, "display_zoomed_region");
InitAcqProcCall = new HDevProcedureCall(InitAcqProc);
ProcessImageProcCall = new HDevProcedureCall(ProcessImageProc);
VisualizeDetailsProcCall = new HDevProcedureCall(VisualizeDetailsProc);
...
}
One of the procedures opens the image acquisition device. It returns the corresponding handle, which we store in an instance of the class HFramegrabber.
HFramegrabber Framegrabber;
private void InitAcqBtn_Click(object sender, System.EventArgs e)
{
InitAcqProcCall.Execute();
Framegrabber = new HFramegrabber(InitAcqProcCall.GetOutputCtrlParamTuple("AcqHandle"));
...
}
As in the previous example, the results of image processing are displayed “ manually” by calling HALCON/.NET operators. In contrast, when you click the button Visualize Details, an HDevelop procedure is executed that zooms onto the extracted fin. For this, we pass an implementation of HDevelop‘s internal display operators and remove it again after the procedure has been executed.
private void VisualizeDetailsBtn_Click(object sender, System.EventArgs e)
{
MyEngine.SetHDevOperators(MyHDevOperatorImpl);
VisualizeDetailsProcCall.SetInputIconicParamObject("Image", Image);
VisualizeDetailsProcCall.SetInputIconicParamObject("Region", FinRegion);
VisualizeDetailsProcCall.SetInputCtrlParamTuple("ZoomScale", 2);
VisualizeDetailsProcCall.SetInputCtrlParamTuple("Margin", 5);
VisualizeDetailsProcCall.Execute();
MyEngine.SetHDevOperators(null);
}
The instance of the implementation class is initialized with the HALCON window of the form.
private HDevOpMultiWindowImpl MyHDevOperatorImpl;
private void WindowControl_Load(object sender, EventArgs e)
{
Window = WindowControl.HalconWindow;
...
MyHDevOperatorImpl = new HDevOpMultiWindowImpl(Window);
}
If the class HDevOpMultiWindowImpl is initialized without specifying the window, a new HALCON window will open automatically to emulate the behavior of HDevelop.
DisplayHDevOpFixedWindowImpl directs all display operators to a single graphics window.
HDevOpMultiWindowImpl can handle multiple graphics windows. You can pass an arbitrary number of graphics windows in the constructor; if the HDevelop program or procedure uses more than them, HDevEngine opens additional free-floating windows.
Please note that these classes must not be used in multithreaded applications because they are not reentrant. If you need a reentrant version, please discuss your application requirements with your local distributor.
private void WindowControl_Load(object sender, EventArgs e)
{
Window = WindowControl.HalconWindow;
MyEngine.SetHDevOperators(new HDevOpMultiWindowImpl(Window));
}
If your application has special display requirements that are not satisfied by the two classes, you can provide your own implementation of the display operators. Please note that currently you cannot use any form of display operator implementation with Mono.
Error Handling
文章图片
private void DisplayException(HDevEngineException Ex)
{
string FullMessage = "Message: <
"+ Ex.Message + ">
" +
",
Error in program / procedure: <
" + Ex.ProcedureName + ">
" +
",
program line: <
"+ Ex.LineText + ">
" +
",
line number: <
"+ Ex.LineNumber + ">
" +
",
HALCON Error Number: <
"+ Ex.HalconError + ">
";
string Title = "HDevEngine Exception (Category: " + Ex.Category.ToString() + ")";
MessageBox.Show(FullMessage, Title);
}try
{
var Program = new HDevProgram(ProgramPathString);
new HDevProgramCall(Program);
}
catch (HDevEngineException Ex)
{
DisplayException(Ex);
}
Load a non-existing HDevelop program.
文章图片
Input parameter is not initialized
文章图片
Parameter is not valid
文章图片
Creating Multithreaded ApplicationsIn the following, we briefly list the most important rules to observe when creating multithreaded HDevEngine applications. When multiple threads execute HDevelop programs in parallel, each thread must create its own instance of the corresponding HDevProgramCall.
External procedure path and the implementation of HDevelop‘s display operators are always set globally for all instances of HDevEngine. We recommend to set them via a separate HDevEngine instance to keep the code more readable.
Because the implementation of HDevelop‘s display operators can only be set globally, it must be thread-safe and reentrant.
Executing a Procedure in Parallel by Multiple ThreadsIt consists of four threads: The main thread is in charge of the graphical user interface (GUI).
文章图片
文章图片
The main thread also initializes the application by training the shape model via an HDevelop procedure and by creating and initializing the other three threads: two processing threads and the so-called control thread, whichs controls the two processing threads.
【HDevEngine in .NET Applications MultiThreading】The control thread acquires the images and passes them to the processing threads, which then process the images and pass back the results. The control thread collects the results, but does not display them itself, because all activities in the HALCON window must be performed by the thread that created.
Initializationprivate void InitButton_Click(object sender, System.EventArgs e)
- Switch off automatic operator parallelization
- HOperatorSet.SetSystem("parallelize_operators", "false");
- First, the automatic operator parallelization is switched off, otherwise the two mechanisms would use more than the available number of cores / processors and thus slow down the application instead of speeding it up. If you have a system with more than two cores or processors, you can consider to allocate some of them to the automatic operator parallelization as described in section.
- Set external procedure path
- Then, we create an instance of HDevEngine and set the path for searching the HDevelop procedures. If the external procedure is from a procedure library, the external procedure path may include the name of the library file.
HDevEngine
MyEngine = new HDevEngine();
string ProcedurePath = halconExamples + @"hdevengineprocedures";
...
MyEngine.SetProcedurePath(ProcedurePath);
- Train the shape model
HDevProcedureCall
ProcTrain;
HDevProcedure Procedure = new HDevProcedure("train_shape_model");
ProcTrain = new HDevProcedureCall(Procedure);
ProcTrain.Execute();
- Store the model data
public HTuple
ModelID;
public HXLD
ModelContours;
ModelID = ProcTrain.GetOutputCtrlParamTuple("ModelID");
ModelContours = ProcTrain.GetOutputIconicParamXld("ModelContours");
- Create and initialize the processing engines
public class EngineThread
{
Thread WorkerObject = null;
HDevProcedureCall ProcCall;
HTuple ModelID;
HXLD ModelContours;
public AutoResetEvent EngineIsReady;
public EngineThread(MultiThreadingForm mainForm)
{
ModelID = mainForm.ModelID;
ModelContours = mainForm.ModelContours;
EngineIsReady = new AutoResetEvent(true);
}
EngineThread
WorkerEngine1;
// processing thread
EngineThread
WorkerEngine2;
// processing thread
AutoResetEvent Engine1Ready;
AutoResetEvent Engine2Ready;
WorkerEngine1 = new EngineThread(this);
WorkerEngine1.Init();
Engine1Ready = WorkerEngine1.EngineIsReady;
WorkerEngine2 = new EngineThread(this);
WorkerEngine2.Init();
Engine2Ready = WorkerEngine2.EngineIsReady;
An EngineThread initializes itself by creating the procedure call for detecting the caps in the images. Because the input parameters of the procedure that concern the shape model are the same for each call, they can be set once in advance (file: EngineThread.cs).
public void Init()
{
HDevProcedure Procedure = new HDevProcedure("detect_shape");
ProcCall = new HDevProcedureCall(Procedure);
ProcCall.SetInputCtrlParamTuple("ModelID", ModelID);
ProcCall.SetInputIconicParamObject("ModelContours", ModelContours);
}
- Initialize image acquisition
private HFramegrabber
AcqHandle;
string ImagePath = halconExamples + "/images/cap_illumination";
AcqHandle = new HFramegrabber("File", 1, 1, 0, 0, 0, 0, "default", -1,"default", -1, "default", ImagePath, "default", -1, -1);
Image Processing
When you click the Run button, the application starts to process images in a loop.
- Starting the processing threads and the control thread
private void RunButton_Click(object sender, System.EventArgs e)
{
WorkerEngine1.Run();
WorkerEngine2.Run();
The corresponding method creates and starts their thread and sets the “ ready” signal (file: EngineThread.cs).
public void Run()
{
EngineIsReady.Set();
WorkerObject = new Thread(new ThreadStart(Process));
WorkerObject.Start();
}
Then, the main thread starts the control thread (file: MultiThreadingForm.cs):
ControlThread = new Thread(new ThreadStart(Run));
ControlThread.Start();
- Triggering the processing threads from the control thread
EngineThread WorkerEngine;
// variable to switch between processing threadspublic void Run()
{
HImage Image;
while (!StopEventHandle.WaitOne(0, true))
{
if (Engine1Ready.WaitOne(0, true))
WorkerEngine = WorkerEngine1;
else if (Engine2Ready.WaitOne(0, true))
WorkerEngine = WorkerEngine2;
else
continue;
Image = AcqHandle.GrabImageAsync(-1);
WorkerEngine.SetImage(Image);
Then, it acquires the next image and passes it to the engine, which stores it in a member variable (file: EngineThread.cs).
private HImage
InputImage = null;
public void SetImage(HImage Img)
{
InputImage = Img;
}
Processing the image
In their action method (Process), the processing threads wait for the image to be set (file: EngineThread.cs). The actual image processing is performed by the HDevelop procedure, passing the image as input parameter.
public void Process()
{
while (!DelegatedStopEvent.WaitOne(0, true))
{
if (InputImage == null)
continue;
ProcCall.SetInputIconicParamObject("Image", InputImage);
ProcCall.Execute();
- Passing the results to the control thread
public class ResultContainer
{
public HImage
InputImage;
public HXLD
FoundContours;
public double
Row;
public double
Column;
public double
Angle;
}
After executing the procedure, the processing thread accesses its results and stores them in a new instance of the result class (“ result container” ), together with the processed image.
ResultContainer Result;
HTuple
ResultTuple;
Result = new ResultContainer();
Result.InputImage = InputImage;
Result.FoundContours = ProcCall.GetOutputIconicParamXld("ResultObject");
ResultTuple = ProcCall.GetOutputCtrlParamTuple("ResultData");
Result.Row = ResultTuple[0];
Result.Column = ResultTuple[1];
Result.Angle = ResultTuple[2];
The processing thread then passes the result container to the control thread by appending it to a list.
ResultMutex.WaitOne();
ResultList.Add(Result);
ResultMutex.ReleaseMutex();
This list is a member variable of the main thread (file: MultiThreadingForm.cs). It is protected by a mutex so that the threads can access it safely.
public ArrayList
ResultList;
public Mutex
ResultDataMutex;
public MultiThreadingForm()
{
ResultDataMutex
= new Mutex();
ResultList = new ArrayList();
}
The processing threads store references to the list and to the mutex in own member variables (file: EngineThread.cs).
ArrayList
ResultList;
Mutex
ResultMutex;
public EngineThread(MultiThreadingForm mainForm)
{
ResultList
= mainForm.ResultList;
ResultMutex
= mainForm.ResultDataMutex;
}
“ Ready again”
Finally, the processing thread signals that it is ready for the next image by setting the corresponding event and by setting the input image to null.
InputImage = null;
this.EngineIsReady.Set();
Result Display
- Checking whether new results are available
int
Count = -1;
ResultDataMutex.WaitOne();
Count = ResultList.Count;
ResultDataMutex.ReleaseMutex();
Delegating the display
The control thread does not perform the display of results itself but delegates it to the main thread (running the form) with the method Invoke.
for( ;
Count >
0;
Count--)
Invoke(DelegatedDisplay);
The necessary members are defined by the form.delegate void
FuncDelegate();
FuncDelegate
DelegatedDisplay;
public MultiThreadingForm()
{
DelegatedDisplay = new FuncDelegate(DisplayResults);
}
Note that, since HALCON 12, all HALCON visualization operators are automatically delegated to the correct thread as described in chapter section “ Threading Issues with Graphics” .
- Displaying the results
public void DisplayResults()
{
ResultDataMutex.WaitOne();
Result = (ResultContainer) ResultList[0];
ResultList.Remove(Result);
ResultDataMutex.ReleaseMutex();
Window.ClearWindow();
Window.DispImage(Result.InputImage);
Window.DispObj(Result.FoundContours);
Result.InputImage.Dispose();
Result.FoundContours.Dispose();
}
Executing Multiple Procedures in Parallel by Multiple Threads
In contrast to the previous section, the example application MultiThreadingTwoWindows presented here executes different HDevelop procedures (tasks) in parallel by two threads. One task is to find bottle caps using shape-based matching, the other to read ECC 200 data codes.
Figure shows an overview of the structure of the application. Like the application described in the previous section, it consists of four threads: The main thread (i.e., the form) is in charge of the graphical user interface (GUI), which is depicted in figure. It consists of a HALCON window for the display of results and buttons to initialize, start, and stop the application.
The main thread also initializes the application by creating and initializing the other three threads: two processing threads and the so-called control thread, whichs controls the two processing threads. In contrast to the previous application, here the processing threads initialize the image processing tasks by training the shape model and the data code model, respectively, via HDevelop procedures.
The control thread acquires the images and passes them to the processing threads, which then process the image and pass back the results. The control thread collects the results, but does not display them itself, because all activities in the HALCON window must be performed by the thread that created it, i.e., the main thread. In contrast to the previous application the results of the two tasks are displayed in two separate windows.
Below, we take a closer look at the corresponding code, restricting ourselves, however, to the parts that are diferent to the previous application.
文章图片
文章图片
Initialization
As in the previous example, the application is initialized in the event handler of the Init button (file: MultiThreadingTwoWindowsForm.cs).
- Create and initialize the processing engines
public class EngineThread
{
...
public int
WindowIndex = -1;
...
The control thread sets this variable after creating the engines (file:MultiThreadingTwoWindowsForm.cs).
private void InitButton_Click(object sender, System.EventArgs e)
{
...
WorkerEngine1.WindowIndex = 1;
...
WorkerEngine2.WindowIndex = 2;
- Train the shape and data code model
WorkerEngine1.Init("shape");
...
WorkerEngine2.Init("datacode");
The HDevelop procedures for training the models and for performing the image processing have similar names for the two tasks, so that their names can be generated automatically (file: EngineThread.cs). The task name itself is stored in a variable of the class EngineThread.
public class EngineThread
{
HDevProcedureCall
ProcCall;
string
Task;
HTuple
ModelID;
HXLD
ModelContours;
...public void Init(string Task)
{
string
TrainMethod = "train_" + Task + "_model";
string
ProcessingMethod = "detect_" + Task;
HDevProcedureCall
ProcTrain;
this.Task = Task;
Then, the model of the shape or datacode, respectively, is trained by executing the corresponding HDevelop procedure and the returned model data is stored in variables of the class.
HDevProcedure Procedure = new HDevProcedure(TrainMethod);
ProcTrain = new HDevProcedureCall(Procedure);
ProcTrain.Execute();
ModelID = ProcTrain.GetOutputCtrlParamTuple("ModelID");
if (Task.Equals("shape"))
{
ModelContours = ProcTrain.GetOutputIconicParamXld("ModelContours");
}
- Store the model data
HDevProcedure Procedure = new HDevProcedure(ProcessingMethod);
ProcCall = new HDevProcedureCall(Procedure);
ProcCall.SetInputCtrlParamTuple("ModelID", ModelID);
if (Task.Equals("shape"))
{
ProcCall.SetInputIconicParamObject("ModelContours", ModelContours);
}
- Initialize image acquisition
Image Processing
- Triggering the processing threads
public void Run()
{
HImage
Image;
while (!StopEventHandle.WaitOne(0,true))
{
if (Engine1Ready.WaitOne(0,true))
{
Image = AcqHandle1.GrabImageAsync(-1);
WorkerEngine1.SetImage(Image);
}
if (Engine2Ready.WaitOne(0,true))
{
Image = AcqHandle2.GrabImageAsync(-1);
WorkerEngine2.SetImage(Image);
}
- Passing the results to the control thread
public class ResultContainer
{
public int
WindowIndex;
// 1 ->
shape, 2 ->
datacode
public HImage
InputImage;
public HXLD
FoundContours;
public HTuple
ResultData;
public bool
DetectionSuccessful;
}
After executing the procedure, the processing thread accesses its results and stores them in a new instance of the result container, together with the processed image and the window index.
public void Process()
{
ResultContainer
Result;
Result = new ResultContainer();
...
Result.InputImage
= InputImage;
DetectionSuccessful = ProcCall.GetOutputCtrlParamTuple("DetectionSuccessful").S;
if (DetectionSuccessful.Equals("true"))
{
Result.DetectionSuccessful = true;
Result.FoundContours = ProcCall.GetOutputIconicParamXld("ResultObject");
Result.ResultData
= ProcCall.GetOutputCtrlParamTuple("ResultData");
}
else
{
Result.DetectionSuccessful = false;
}
Result.WindowIndex = WindowIndex;
Result Display
As in the previous example, the display of results is performed by the main thread in the method ResultDisplay (file: MultiThreadingTwoWindowsForm.cs). The main difference is that the display now is switched between the two HALCON windows, based on the variable in the result container.
public void DisplayResults()
{
HWindow
Window;
if (Result.WindowIndex == 1)
{
Window = Window1;
}
else
{
Window = Window2;
}
Furthermore, the display method now checks the success of the image processing to avoid accessing non-existing result elements. For both tasks, the resulting contours, i.e., the found shape or data code region, respectively, are displayed. For the data code task, also the read code is displayed.
Window.ClearWindow();
Window.DispImage(Result.InputImage);
if (Result.DetectionSuccessful)
{
Window.DispObj(Result.FoundContours);
// additional display for data code result: code
if (Result.WindowIndex == 2)
{
Row = (int) Result.ResultData[0].D;
Col = (int) Result.ResultData[1].D;
Window.SetTposition(Row,Col);
Window.WriteString((string) Result.ResultData[2].S);
}
}
else
{
Window.SetColor("red");
Window.SetTposition(20, 20);
Window.WriteString("Detection failed!");
Window.SetColor("green");
}
Executing an HDevelop Program with Vector Variables
The example application UseVectorVariables shows how to load and execute an HDevelop example that contains vector variables in HDevengine/C#. In the example two vectors are used for processing: one containing the input images and one containing scaling factors. When executing the program the gray values of the input images are scaled according to the scaling factors. Please have a look at the source file UseVectorVariablesForm.cs for more details on how to work with vector variables in HDevengine/.NET.
Using the Just-in-time Compiler with HDevEngine/.NETThe just-in-time compilation of procedures needs to be enabled in your instance of the HDevEngine class:
...
Engine = new HDevEngine();
// enable or disable execution of compiled procedures
Engine.SetEngineAttribute("execute_procedures_jit_compiled", "true");
Procedures (and procedures referenced by it) are compiled at the moment a corresponding instance of HDevProcedureCall or HDevProgramCall is created.
You can also explicitly pre-compile all used procedures of a HDevelop program or procedure using the method CompileUsedProcedures of HDevProgram or HDevProcedure, respectively.
In the following example, all used procedures of a procedure call are just-in-time compiled:
Program = new HDevProgram(ProgramPathString);
// Get local procedure
Proc = new HDevProcedure(Program, name);
...
Proc.CompileUsedProcedures();
推荐阅读
- android之android.intent.category.DEFAULT
- App Crawler
- JAVA错误提示:The operation is not applicable to the current selection.Select a field which is not declar
- uni-app获取当前位置
- Android中UI线程与worker线程的通信方法
- 360免费wifi app不限时长登陆cmcc你值得拥有!
- Android binder流程简图
- androidstudio的安装与抓log
- idea~创建maven webapp项目