The documentation provided for Canon EOS Digital SDK is not very complete and it is difficult to find good examples on the internet. This is a tutorial of some of the most important things I have compiled.
New Version Download
Old Version Download
Introduction
The Canon EOS Digital SDK is quite a powerful SDK to remote control Canon DSLRs. Unfortunately, it is quite difficult to find some good examples for it on the internet and the provided documentation is not very complete. Since I have found out many things already and want to make it easier for others, I thought I could compile some of the most important things together and do a tutorial.
The tutorial and the library is written in C# but it is CLS compliant and therefore can be used from any .NET language.
This tutorial includes:
Note: I'm not affiliated with or funded by Canon Inc. in any way.
I do not guarantee for this software in any way. Use it at your own risk!
Background
You have to have a copy of the Canon EDSDK to get this working. I am not allowed to include the DLLs within the project so you'll have to apply to get them, here:
Once you have the DLLs, put them beside your executable. Other places will cause problems if the main DLL makes a call to a sub-DLL.
It is also important that you use your camera in fully manual or at least half-automated mode for some methods to work.
Using the Code
The solution consists of four projects:
EDSDKLib: The main project where all the SDK and camera handling happens WinFormsExample: An example project that consumes the EDSDKLib and uses it in a Windows Forms UI application WpfExample: An example project that consumes the EDSDKLib and uses it in a WPF UI application ConsoleExample: An example project that consumes the EDSDKLib and uses it in a console application
I will only concentrate on the EDSDKLib project here since this is what this whole article is about: Using the EDSDK with C#.
First, let us look at the main classes:
CanonAPI: The class that mainly handles the SDK lifetime, the connected Cameras and the SDK events Camera: This is used to communicate with a physical camera. Set/Get properties, take photos, download data and more. CanonSDK: This class contains all the native calls to the Canon SDK DLLs and some helper methods to set/get values. STAThread: A helper class to create an STA thread or execute code on an STA thread ErrorHandler: A static class that provides methods to check the SDK return values/error codes
Let's have a closer look into the inner workings of those classes.
Initializing and terminating are the easiest things to do. When you start your program, create a new instance of the CanonAPI class:
public CanonAPI(bool useCallingThread)
{
try
{
lock (InitLock)
{
if (RefCount == 0)
{
if (useCallingThread)
{
if (Thread.CurrentThread.GetApartmentState() != ApartmentState.STA)
throw new ThreadStateException("Calling thread must be in STA");
ErrorHandler.CheckError(this, CanonSDK.EdsInitializeSDK());
}
else
{
CanonSDK.EdsRelease(IntPtr.Zero);
MainThread = new ApiThread();
MainThread.Start();
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsInitializeSDK()));
}
CanonSDK.InitializeVersion();
CameraAddedEvent = new SDKCameraAddedHandler(CanonAPI_CameraAddedEvent);
ErrorHandler.CheckError(this,
CanonSDK.EdsSetCameraAddedHandler(CameraAddedEvent, IntPtr.Zero));
_IsSDKInitialized = true;
}
RefCount++;
}
}
catch
{
IsDisposed = true;
if (MainThread?.IsRunning == true) MainThread.Shutdown();
throw;
}
}
Ensure that you keep around an instance of this class, otherwise the GC might collect it and the SDK gets terminated with it (because of the destructor of the CanonAPI class).
And when you close your program, call the Dispose method. The public Dispose method in turn calls this overload:
protected virtual void Dispose(bool managed)
{
lock (InitLock)
{
if (!IsDisposed)
{
if (RefCount == 1)
{
_IsSDKInitialized = false;
ErrorCode err = CanonSDK.EdsSetCameraAddedHandler(null, IntPtr.Zero);
if (managed)
{
ErrorHandler.CheckError(this, err);
CurrentCameras.ForEach(t => t.Dispose());
}
if (MainThread?.IsRunning == true)
err = MainThread.Invoke(() => { return CanonSDK.EdsTerminateSDK(); });
if (MainThread?.IsRunning == true) MainThread.Shutdown();
if (managed) ErrorHandler.CheckError(this, err);
}
RefCount--;
IsDisposed = true;
}
}
}
Now that the SDK is initialized, we can get a list of currently connected cameras:
public List<Camera> GetCameraList()
{
if (IsDisposed) throw new ObjectDisposedException(nameof(CanonAPI));
lock (CameraLock)
{
IEnumerable<IntPtr> ptrList = GetCameraPointerList();
List<Camera> camList = new List<Camera>();
foreach (var ptr in ptrList)
{
var oldCam = CurrentCameras.FirstOrDefault(t => t.Reference == ptr);
if (oldCam != null && !oldCam.IsDisposed)
camList.Add(oldCam);
else camList.Add(new Camera(ptr));
}
var oldCameras = CurrentCameras.Where(t => !ptrList.Any(u => u == t.Reference));
foreach (var cam in oldCameras) { if (!cam.IsDisposed) cam.Dispose(); }
CurrentCameras.Clear();
CurrentCameras.AddRange(camList);
return camList;
}
}
The CanonAPI class keeps a list of connected cameras to ensure that each SDK camera pointer is associated with only one Camera class instance. Having two or more Camera class instances that use the same SDK pointer could potentially cause problems if they access the camera at the same time.
The subroutine GetCameraPointerList that is used above gets a list of pointers that point to the SDKs camera objects:
protected IEnumerable<IntPtr> GetCameraPointerList()
{
if (IsDisposed) throw new ObjectDisposedException(nameof(CanonAPI));
IntPtr camlist;
ErrorHandler.CheckError(this, CanonSDK.EdsGetCameraList(out camlist));
int camCount;
ErrorHandler.CheckError(this, CanonSDK.EdsGetChildCount(camlist, out camCount));
List<IntPtr> ptrList = new List<IntPtr>();
for (int i = 0; i < camCount; i++)
{
IntPtr cptr;
ErrorHandler.CheckError(this, CanonSDK.EdsGetChildAtIndex(camlist, i, out cptr));
ptrList.Add(cptr);
}
ErrorHandler.CheckError(this, CanonSDK.EdsRelease(camlist));
return ptrList;
}
For now, we are done with the CanonAPI class and can start using the Camera and open a session with it:
public void OpenSession()
{
CheckState(false);
if (!SessionOpen)
{
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsOpenSession(CamRef));
uint property;
_IsRecordAvailable = CanonSDK.GetPropertyData
(CamRef, PropertyID.Record, 0, out property) == ErrorCode.OK;
SDKStateEvent += new SDKStateEventHandler(Camera_SDKStateEvent);
SDKPropertyEvent += new SDKPropertyEventHandler(Camera_SDKPropertyEvent);
SDKProgressCallbackEvent += new SDKProgressCallback
(Camera_SDKProgressCallbackEvent);
SDKObjectEvent += new SDKObjectEventHandler(Camera_SDKObjectEvent);
ErrorHandler.CheckError(this,
CanonSDK.EdsSetCameraStateEventHandler
(CamRef, StateEventID.All, SDKStateEvent, CamRef));
ErrorHandler.CheckError(this,
CanonSDK.EdsSetObjectEventHandler
(CamRef, ObjectEventID.All, SDKObjectEvent, CamRef));
ErrorHandler.CheckError(this,
CanonSDK.EdsSetPropertyEventHandler
(CamRef, PropertyEventID.All, SDKPropertyEvent, CamRef));
SessionOpen = true;
});
}
}
If you are done working with this camera, close the session this way:
public void CloseSession()
{
CheckState(false);
if (SessionOpen)
{
UnsubscribeEvents();
if (IsLiveViewOn)
{
KeepLVAlive = false;
LVThread.Join(5000);
}
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsCloseSession(CamRef));
SessionOpen = false;
});
}
}
You can safely open and close the session multiple times while the camera is connected to the computer.
If you are completely done with this camera, you can dispose it by calling the Dispose method (which, as before, internally calls this overload):
protected virtual void Dispose(bool managed)
{
if (!IsDisposed)
{
UnsubscribeEvents();
if (IsLiveViewOn)
{
KeepLVAlive = false;
LVThread.Join();
}
IsLiveViewOn = false;
MainThread.Invoke(() =>
{
if (CanonAPI.IsSDKInitialized)
{
if (SessionOpen) CanonSDK.EdsCloseSession(CamRef);
CanonSDK.EdsRelease(CamRef);
}
_IsDisposed = true;
});
MainThread.Shutdown();
}
}
UnsubscribeEvents is just a little helper method:
private void UnsubscribeEvents()
{
SDKStateEvent -= Camera_SDKStateEvent;
SDKPropertyEvent -= Camera_SDKPropertyEvent;
SDKProgressCallbackEvent -= Camera_SDKProgressCallbackEvent;
SDKObjectEvent -= Camera_SDKObjectEvent;
if (CanonAPI.IsSDKInitialized)
{
MainThread.Invoke(() =>
{
CanonSDK.EdsSetCameraStateEventHandler(CamRef, StateEventID.All, null, CamRef);
CanonSDK.EdsSetObjectEventHandler(CamRef, ObjectEventID.All, null, CamRef);
CanonSDK.EdsSetPropertyEventHandler(CamRef, PropertyEventID.All, null, CamRef);
});
}
}
Setting and getting camera settings can be very easy for values with an ID, but more difficult for struct values.
Here is the getting method for normal int values (like Tv, Av or ISO):
public int GetInt32Setting(PropertyID propID, int inParam = 0)
{
CheckState();
return MainThread.Invoke(() =>
{
int property;
ErrorHandler.CheckError(this, CanonSDK.GetPropertyData
(CamRef, propID, inParam, out property));
return property;
});
}
There are multiple methods for getting values of different types but they all look basically the same. The important part is that all of them call the CanonSDK.GetPropertyData method that handles all the details. If you are interested in how to get values from managed to unmanaged, have a look at the mentioned method and its overloads in the CanonSDK class.
Setting a camera setting is similar to getting it but there are only two methods because we can handle it in a more generic way with the object type.
Any value other than a string can be set with this method (e.g., integers for Tv, Av or ISO):
public void SetSetting(PropertyID propID, object value, int inParam = 0)
{
CheckState();
MainThread.Invoke(() =>
{
int propsize;
DataType proptype;
ErrorHandler.CheckError(this,
CanonSDK.EdsGetPropertySize(CamRef, propID, inParam, out proptype, out propsize));
ErrorHandler.CheckError(this,
CanonSDK.EdsSetPropertyData(CamRef, propID, inParam, propsize, value));
});
}
Setting a string value:
public void SetSetting(PropertyID propID, string value, int inParam = 0, int MAX = 32)
{
CheckState();
if (value == null) value = string.Empty;
if (value.Length > MAX - 1) value = value.Substring(0, MAX - 1);
byte[] propBytes = System.Text.Encoding.ASCII.GetBytes(value + '\0');
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsSetPropertyData(CamRef,
propID, inParam, propBytes.Length, propBytes));
});
}
String length can be limited with the MAX parameter because some camera properties can only have a specific length. Most of them have a length of 32 characters though.
Different cameras have different settings available and lenses only have a certain range of Av values. That's why you need to get a list of all supported settings. This only works with "AEModeSelect", "ISO", "Av", "Tv", "MeteringMode" and "ExposureCompensation". This method returns an array of CameraValues. Each CameraValue has an integer value (that's the ID used by the SDK), a string value (that can be used as a label, e.g., "ISO 100" or "1/100") and, if applicable, a double value (that can be used for calculations, e.g., ISO 100 has a value of 100 and a Tv value of 1/100 has the value 0.01):
public CameraValue[] GetSettingsList(PropertyID propId)
{
CheckState();
if (propId == PropertyID.AEModeSelect || propId == PropertyID.ISO ||
propId == PropertyID.Av || propId == PropertyID.Tv
|| propId == PropertyID.MeteringMode ||
propId == PropertyID.ExposureCompensation)
{
CameraValue[] vals = null;
PropertyDesc des = default(PropertyDesc);
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsGetPropertyDesc(CamRef, propId, out des)));
vals = new CameraValue[des.NumElements];
for (int i = 0; i < vals.Length; i++)
vals[i] = new CameraValue(des.PropDesc[i], propId);
return vals;
}
else throw new ArgumentException($"Method cannot be used with Property ID {propId}");
}
In the library, there are also five corresponding classes for each type that supports this method. These classes contain a list of all possible values and some regularly used fields (like Auto or Bulb). These classes are called AvValues, TvValues, ISOValues, ExpCompValues, AEModeValues and MeteringModeValues. All methods, properties and fields of those classes are static.
Send a Command to the Camera
With camera commands, you can control several things, for example, take a picture, press the shutter button or drive the focus motor of your lens.
public void SendCommand(CameraCommand command, int inParam = 0)
{
CheckState();
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsSendCommand(CamRef, command, inParam)));
}
Some of the commands are used in the following topics.
To change the lock status of your camera or to enter/exit direct transfer mode, you can use the camera status commands:
public void SendCommand(CameraCommand command, int inParam = 0)
{
CheckState();
MainThread.Invoke(() => ErrorHandler.CheckError
(this, CanonSDK.EdsSendCommand(CamRef, command, inParam)));
}
To take a photo with the current settings, call the TakePhoto method:
public void TakePhoto()
{
CheckState();
SendCommand(CameraCommand.TakePicture);
}
Alternatively, you can use the TakePhotoAsync method that executes the command asynchronously (on a Threadpool Thread, not on a .NET 4.5 Task).
Or if your camera supports it, you can use the TakePhotoShutter method that uses the PressShutterButton command instead of the TakePicture command:
public void TakePhotoShutter()
{
CheckState();
SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.Completely);
SendCommand(CameraCommand.PressShutterButton, (int)ShutterButton.OFF);
}
Same as before, you can also use the TakePhotoShutterAsync method for asynchronous execution.
To take a photo in bulb mode, call the TakePhotoBulb method:
public void TakePhotoBulb(int bulbTime)
{
CheckState();
SendCommand(CameraCommand.BulbStart);
Thread.Sleep(bulbTime);
SendCommand(CameraCommand.BulbEnd);
}
Note that you have to have the Tv value on Bulb before calling this method, otherwise you'll get an error. The opposite applies to the TakePhoto methods, there, Tv must not be set to Bulb.
If you would like to download the taken image directly to your computer, have a look at the next topic.
To save taken photos directly onto the computer instead of the camera-memory, set it by calling the SetSetting method:
SetSetting(PropertyID.SaveTo, (int)SaveTo.Host);
Most cameras require that you set the remaining disk space on your computer before taking a picture. If you do not do this, most cameras assume that there is no disk space left and will return an error.
You can call the SetCapacity method to set the remaining disk space (if you want, you can just set an arbitrarily high value):
public void SetCapacity(int bytesPerSector, int numberOfFreeClusters)
{
CheckState();
MainThread.Invoke(() =>
{
Capacity capacity = new Capacity(numberOfFreeClusters, bytesPerSector, true);
ErrorHandler.CheckError(this, CanonSDK.EdsSetCapacity(CamRef, capacity));
});
}
Once you have taken a photo, the SDKObjectEvent will fire with the inEvent variable being ObjectEventID.DirItemRequestTransfer. For convenience, the Camera class has a DownloadReady event that fires with all the info you need.
The DownloadReady event has two parameters, one is the Camera object from which the event was fired and the second being a DownloadInfo object. The DownloadInfo object provides some information about the file and can be passed to three methods:
The DownloadFile method downloads the image into the provided directory. To get or set the file name, you can use the FileName property of the DownloadInfo object.
public void DownloadFile(DownloadInfo Info, string directory)
{
CheckState();
if (Info == null) throw new ArgumentNullException(nameof(Info));
if (directory == null || string.IsNullOrEmpty(directory.Trim())) directory = ".";
string currentFile = Path.Combine(directory, Info.FileName);
if (!Directory.Exists(directory)) Directory.CreateDirectory(directory);
DownloadToFile(Info, currentFile);
}
The DownloadFile method without the directory parameter downloads the image into a System.IO.Stream instead of saving it to the file system.
public Stream DownloadFile(DownloadInfo Info)
{
CheckState();
if (Info == null) throw new ArgumentNullException(nameof(Info));
return DownloadToStream(Info);
}
With the CancelDownload method, you can cancel and discard the taken image:
public void CancelDownload(DownloadInfo Info)
{
CheckState();
if (Info == null) throw new ArgumentNullException(nameof(Info));
MainThread.Invoke(() =>
{
ErrorHandler.CheckError(this, CanonSDK.EdsDownloadCancel(Info.Reference));
ErrorHandler.CheckError(this, CanonSDK.EdsRelease(Info.Reference));
});
}
The two DownloadFile methods used these subroutines to do the actual work:
protected void DownloadToFile(DownloadInfo Info, string filepath)
{
using (var stream = new SDKStream
(filepath, FileCreateDisposition.CreateAlways, FileAccess.ReadWrite))
{
DownloadData(Info, stream.Reference);
}
}
and:
protected Stream DownloadToStream(DownloadInfo Info)
{
SDKStream stream = new SDKStream(Info.Size64);
DownloadData(Info, stream.Reference);
stream.Position = 0;
return stream;
}
The SDKStream class is a wrapper around SDK streams so it can be used like a normal System.IO.Stream.
And this is the real download:
protected void DownloadData(DownloadInfo Info, IntPtr stream)
{
MainThread.Invoke(() =>
{
try
{
ErrorHandler.CheckError(this, CanonSDK.EdsSetProgressCallback
(stream, SDKProgressCallbackEvent, ProgressOption.Periodically, Info.Reference));
if (CanonSDK.IsVerGE34) ErrorHandler.CheckError
(this, CanonSDK.EdsDownload(Info.Reference, Info.Size64, stream));
else ErrorHandler.CheckError
(this, CanonSDK.EdsDownload(Info.Reference, Info.Size, stream));
}
finally
{
ErrorHandler.CheckError(this, CanonSDK.EdsDownloadComplete(Info.Reference));
ErrorHandler.CheckError(this, CanonSDK.EdsRelease(Info.Reference));
}
});
}
The live view is one of the more difficult things to do, especially if it should be high-performance. First, we start the live view like this:
public void StartLiveView()
{
CheckState();
if (!IsLiveViewOn) SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.PC);
}
Once this is done, the SDKPropertyEvent will fire with the inPropertyID variable being PropertyID.Evf_OutputDevice:
private ErrorCode Camera_SDKPropertyEvent
(PropertyEventID inEvent, PropertyID inPropertyID, int inParameter, IntPtr inContext)
{
ThreadPool.QueueUserWorkItem((state) =>
{
try
{
if (inPropertyID == PropertyID.Evf_OutputDevice ||
inPropertyID == PropertyID.Record)
{
lock (lvThreadLockObj)
{
EvfOutputDevice outDevice = GetEvf_OutputDevice();
Recording recordState = IsRecordAvailable ?
((Recording)GetInt32Setting(PropertyID.Record)) : Recording.Off;
if (outDevice == EvfOutputDevice.PC ||
(recordState == Recording.Ready &&
outDevice == EvfOutputDevice.Filming) ||
(useFilmingPcLv && recordState == Recording.On &&
(outDevice == EvfOutputDevice.Filming ||
outDevice == EvfOutputDevice.Camera)))
{
if (!KeepLVAlive)
{
KeepLVAlive = true;
LVThread = STAThread.CreateThread(DownloadEvf);
LVThread.Start();
}
}
else if (KeepLVAlive) { KeepLVAlive = false; }
}
}
}
catch (Exception ex)
{ if (!IsDisposed && !ErrorHandler.ReportError(this, ex)) throw; }
PropertyChanged?.Invoke(this, inEvent, inPropertyID, inParameter);
});
return ErrorCode.OK;
}
There are a few more checks in there to ensure that the live view also works while filming.
And the DownloadEvf method being:
private void DownloadEvf()
{
if (IsLiveViewOn) return;
try
{
IntPtr evfImageRef = IntPtr.Zero;
ErrorCode err;
using (var stream = new SDKStream(0))
{
IsLiveViewOn = true;
while (KeepLVAlive)
{
lock (STAThread.ExecLock)
{
err = CanonSDK.EdsCreateEvfImageRef(stream.Reference, out evfImageRef);
if (err == ErrorCode.OK) err =
CanonSDK.EdsDownloadEvfImage(CamRef, evfImageRef);
}
if (err == ErrorCode.OBJECT_NOTREADY) { continue; }
else if (err != ErrorCode.OK) { ErrorHandler.CheckError(err); continue; }
CanonSDK.EdsRelease(evfImageRef);
stream.Position = 0;
LiveViewUpdated?.Invoke(this, stream);
}
}
}
catch (Exception ex) { if (ex is ThreadAbortException ||
!ErrorHandler.ReportError(this, ex)) throw; }
finally
{
IsLiveViewOn = false;
ThreadPool.QueueUserWorkItem((state) => LiveViewStopped?.Invoke(this));
}
}
To stop the live view, simply call the StopLiveView method. This will trigger the SDKPropertyEvent again where the KeepLVAlive variable is set to false and the live view loop will stop. With the LVOff parameter, you can set if the live view should be shut down completely or should be shown on the camera. (Some older cameras might always shut down.)
public void StopLiveView(bool LVOff = true)
{
CheckState();
if (LVOff) SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.Off);
else SetSetting(PropertyID.Evf_OutputDevice, (int)EvfOutputDevice.Camera);
}
Newer cameras have the possibility to record videos built in. To use this, you have to set your camera to video recording mode first (it will not work otherwise!). Usually, there is some button, knob or dial you have to switch. If that is done, you can call the starting method:
public void StartFilming(bool PCLiveview)
{
CheckState();
Recording state = (Recording)GetInt32Setting(PropertyID.Record);
if (state != Recording.On)
{
if (state != Recording.Ready) throw new InvalidOperationException
("The camera is not ready to film. The Record property has to be Recording.Ready");
useFilmingPcLv = PCLiveview;
SetSetting(PropertyID.SaveTo, (int)SaveTo.Camera);
SetSetting(PropertyID.Record, (int)Recording.On);
}
}
Note that the SaveTo property is set to Camera. This is a requirement of the SDK, presumably because the data transfer to the computer would be too slow.
Still, you can download the finished film after you stopped filming just by setting the saveFilm parameter of the StopFilming method to true:
public void StopFilming(bool saveFilm, bool stopLiveView)
{
CheckState();
Recording state = (Recording)GetInt32Setting(PropertyID.Record);
if (state == Recording.On)
{
this.saveFilm = saveFilm;
SetSetting(PropertyID.Record, (int)Recording.Off);
useFilmingPcLv = false;
if (IsLiveViewOn && stopLiveView) StopLiveView(false);
}
}
Internally, it checks the SDKObjectEvent that will fire with inEvent being ObjectEventID.DirItemCreated. We can safely assume that this created item is the film. Same as with downloading a photo, the DownloadReady event will fire and you can download the film with one of the DownloadFile methods.
To keep the user or from changing settings on the physical camera, or allowing to do so, you can lock or unlock the camera UI like this:
public void UILock(bool lockState)
{
if (lockState) SendStatusCommand(CameraStatusCommand.UILock);
else SendStatusCommand(CameraStatusCommand.UIUnLock);
}
A question often asked all over the internet is how to control the focus of the camera. It's actually quite easy if you know how. Most importantly, the camera has to be in live view and the lens has to be on AF. Then, you can simply call SendCommand with DriveLensEvf as the command type:
SendCommand(CameraCommand.DriveLensEvf, (int)DriveLens.Near2);
To control distance and direction, you have to set the DriveLens enum appropriately. Near is to focus closer, Far is to focus further away and 1 is a small, 2 is a medium and 3 is a big step.
There are several methods for handling the file system of the camera:
- Use
GetAllVolumes to get all camera volumes (e.g., CF or SD cards) - Use
GetAllEntries to get all volumes, files and folders on the camera - Use
GetAllImages to get all images on the camera - Use
FormatVolume to format a volume - Use
DownloadFiles to download one or more files into a directory - Use
DeleteFiles to delete one or more files on the camera
I won't go into detail here but feel free to look at the source code of these methods to find out how exactly they work.
Sometimes, it is useful to get a thumbnail from an image, regardless if RAW or jpg. To do that, you can call the GetFileThumb method of the CanonAPI class.
public Bitmap GetFileThumb(string filepath)
{
using (var stream = new SDKStream
(filepath, FileCreateDisposition.OpenExisting, FileAccess.Read))
{
return GetImage(stream.Reference, ImageSource.Thumbnail);
}
}
whereas the more generic method GetImage is defined like this:
protected Bitmap GetImage(IntPtr imgStream, ImageSource imageSource)
{
IntPtr imgRef = IntPtr.Zero;
IntPtr streamPointer = IntPtr.Zero;
ImageInfo imageInfo;
try
{
ErrorHandler.CheckError(this,
CanonSDK.EdsCreateImageRef(imgStream, out imgRef));
ErrorHandler.CheckError(this,
CanonSDK.EdsGetImageInfo(imgRef, imageSource, out imageInfo));
Size outputSize = new Size();
outputSize.Width = imageInfo.EffectiveRect.Width;
outputSize.Height = imageInfo.EffectiveRect.Height;
int datalength = outputSize.Height * outputSize.Width * 3;
byte[] buffer = new byte[datalength];
using (var stream = new SDKStream(buffer))
{
ErrorHandler.CheckError(this, CanonSDK.EdsGetImage
(imgRef, imageSource, TargetImageType.RGB,
imageInfo.EffectiveRect, outputSize, stream.Reference));
unsafe
{
byte tmp;
fixed (byte* pix = buffer)
{
for (long i = 0; i < datalength; i += 3)
{
tmp = pix[i];
pix[i] = pix[i + 2];
pix[i + 2] = tmp;
}
}
}
ErrorHandler.CheckError(this,
CanonSDK.EdsGetPointer(stream.Reference, out streamPointer));
return new Bitmap(outputSize.Width, outputSize.Height,
datalength, PixelFormat.Format24bppRgb, streamPointer);
}
}
finally
{
if (imgStream != IntPtr.Zero) ErrorHandler.CheckError
(this, CanonSDK.EdsRelease(imgStream));
if (imgRef != IntPtr.Zero) ErrorHandler.CheckError
(this, CanonSDK.EdsRelease(imgRef));
}
}
A Note to the Methods
I did not add every single method that is used in the code here. Just download the source if something is not completely clear. And if you still have a question after that, simply ask me. :)
Note to Threading
The Canon SDK requires the use of threads in a single threaded apartment (=STA) which makes things a bit difficult. Lucky for you, I added the STAThread class that handles the setup and issues that arise with this threading model. The CanonAPI class has one thread that is used for initializing and terminating the SDK and to get all SDK events. Each Camera instance has one thread that is used to execute all commands on. To ensure that nothing executes at the same time, all commands are surrounded by a lock (with the ExecLock field as lock object). Should two commands execute at the same time, the SDK will hang completely in most cases. All methods provided by the CanonAPI and the Camera are safe to execute but if you want to call methods from the CanonSDK class, you have to make sure to execute it on the right thread (by calling the Invoke method of the STAThread class). If you have any issues with the SDK hanging, please let me know.
Using the GUI
This project includes a Window Forms UI and a WPF UI. They both work the same way and are here to show you how to use the above code in an actual program.
Note: These GUIs are not meant for production use and are merely here as an example to get you started!
Plug in your camera and select in the list. Click on "Open Session" and start to use the camera.
Select the values in the dropdown menus to set them in the camera.
Use the "StartLV" button to start the live view and the buttons with the arrows on it can be used to control the focus (only works if the lens is in AF mode and live view is running).


Points of Interest
This code was tested with:
- EOS 5D Mark III
- EOS 7D
- EOS 40D
- EOS 60D
- EOS 700D
- EOS 600D
- EOS 550D
- EOS 500D
- EOS 450D
- EOS 100D/Rebel SL1
- EOS 1200D/Rebel T5
- EOS 1100D/Rebel T3
- and several others
If you tried it with a different model, please let me know so I can add it to this list.
And if you have found a bug, have an improvement or a new snippet, I'm happy to hear from you!
History
- November 2013 - Initial version
- November 2013 - Bugfix for viewing
LiveView and taking photos at the same time - December 2013 - Added
GetStringProperty and GetStructProperty and added video recording - January 2014 - Added
SetStringProperty and SetStructProperty and minor changes in the UI - January 2014 - Added several new methods and made the code much more secure
- June 2014 - Changed a few methods to be more secure and revised the code and UI a bit
- October 2014 - Fixed a few bugs, revised the Winforms UI and added a WPF UI
- November 2014 - Fixed deadlock issue when the live view is running and camera commands are called
- April 2015 - Fixed a bug where the camera would hang before or after filming. Fixed a broken link
- May 2015 - Camera commands are now properly executed on an STA thread. Other smaller fixes
- August 2015 - UI: fixed
CameraAdded+Shutdown bugs; Added basic error handling. Lib: smaller fixes
- March 2016 (1.0.0) - Complete revision of the project. It now uses a similar architecture to the commercial library.
- March 2016 (1.0.1) - Fixed wrong check in
StopFilming method - April 2016 (1.1.0) - Several bugfixes, improvements and support for Canon SDK 3.4
- April 2016 (1.1.1) - Fixed incorrect lock in
STAThread.Invoke method (thanks Dave) - May 2016 (1.1.2) - Various smaller fixes (thanks elgarf) and added
LiveViewStopped event
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.